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/speedmonitor.cpp
|
||||||
bittorrent/statistics.cpp
|
bittorrent/statistics.cpp
|
||||||
bittorrent/torrent.cpp
|
bittorrent/torrent.cpp
|
||||||
|
bittorrent/torrentcontentlayout.cpp
|
||||||
bittorrent/torrentcreatorthread.cpp
|
bittorrent/torrentcreatorthread.cpp
|
||||||
bittorrent/torrentimpl.cpp
|
bittorrent/torrentimpl.cpp
|
||||||
bittorrent/torrentinfo.cpp
|
bittorrent/torrentinfo.cpp
|
||||||
|
@ -116,6 +116,7 @@ SOURCES += \
|
|||||||
$$PWD/bittorrent/speedmonitor.cpp \
|
$$PWD/bittorrent/speedmonitor.cpp \
|
||||||
$$PWD/bittorrent/statistics.cpp \
|
$$PWD/bittorrent/statistics.cpp \
|
||||||
$$PWD/bittorrent/torrent.cpp \
|
$$PWD/bittorrent/torrent.cpp \
|
||||||
|
$$PWD/bittorrent/torrentcontentlayout.cpp \
|
||||||
$$PWD/bittorrent/torrentcreatorthread.cpp \
|
$$PWD/bittorrent/torrentcreatorthread.cpp \
|
||||||
$$PWD/bittorrent/torrentimpl.cpp \
|
$$PWD/bittorrent/torrentimpl.cpp \
|
||||||
$$PWD/bittorrent/torrentinfo.cpp \
|
$$PWD/bittorrent/torrentinfo.cpp \
|
||||||
|
@ -53,6 +53,7 @@ namespace BitTorrent
|
|||||||
bool firstLastPiecePriority = false;
|
bool firstLastPiecePriority = false;
|
||||||
bool addForced = false;
|
bool addForced = false;
|
||||||
std::optional<bool> addPaused;
|
std::optional<bool> addPaused;
|
||||||
|
QStringList filePaths; // used if TorrentInfo is set
|
||||||
QVector<DownloadPriority> filePriorities; // used if TorrentInfo is set
|
QVector<DownloadPriority> filePriorities; // used if TorrentInfo is set
|
||||||
bool skipChecking = false;
|
bool skipChecking = false;
|
||||||
std::optional<BitTorrent::TorrentContentLayout> contentLayout;
|
std::optional<BitTorrent::TorrentContentLayout> contentLayout;
|
||||||
|
@ -1698,11 +1698,14 @@ void Session::handleDownloadFinished(const Net::DownloadResult &result)
|
|||||||
{
|
{
|
||||||
case Net::DownloadStatus::Success:
|
case Net::DownloadStatus::Success:
|
||||||
emit downloadFromUrlFinished(result.url);
|
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;
|
break;
|
||||||
case Net::DownloadStatus::RedirectedToMagnet:
|
case Net::DownloadStatus::RedirectedToMagnet:
|
||||||
emit downloadFromUrlFinished(result.url);
|
emit downloadFromUrlFinished(result.url);
|
||||||
addTorrent(MagnetUri {result.magnet}, m_downloadedTorrents.take(result.url));
|
addTorrent(MagnetUri(result.magnet), m_downloadedTorrents.take(result.url));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
emit downloadFromUrlFailed(result.url, result.errorString);
|
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;
|
lt::add_torrent_params &p = params.ltAddTorrentParams;
|
||||||
|
|
||||||
p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
|
p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
|
||||||
const TorrentInfo torrentInfo {p.ti};
|
const TorrentInfo torrentInfo {*p.ti};
|
||||||
const auto nativeIndexes = torrentInfo.nativeIndexes();
|
const auto nativeIndexes = torrentInfo.nativeIndexes();
|
||||||
for (int i = 0; i < fileNames.size(); ++i)
|
for (int i = 0; i < fileNames.size(); ++i)
|
||||||
p.renamed_files[nativeIndexes[i]] = fileNames[i].toStdString();
|
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);
|
return addTorrent(magnetUri, params);
|
||||||
|
|
||||||
TorrentFileGuard guard {source};
|
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();
|
LogMsg(tr("Couldn't load torrent: %1").arg(loadResult.error()), Log::WARNING);
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
guard.markAsAddedToSession();
|
||||||
|
return addTorrent(loadResult.value(), params);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Session::addTorrent(const MagnetUri &magnetUri, const AddTorrentParams ¶ms)
|
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)
|
bool Session::addTorrent(const TorrentInfo &torrentInfo, const AddTorrentParams ¶ms)
|
||||||
{
|
{
|
||||||
if (!torrentInfo.isValid()) return false;
|
|
||||||
|
|
||||||
return addTorrent_impl(torrentInfo, params);
|
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)
|
bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source, const AddTorrentParams &addTorrentParams)
|
||||||
{
|
{
|
||||||
const bool hasMetadata = std::holds_alternative<TorrentInfo>(source);
|
const bool hasMetadata = std::holds_alternative<TorrentInfo>(source);
|
||||||
TorrentInfo metadata = (hasMetadata ? std::get<TorrentInfo>(source) : TorrentInfo {});
|
const auto id = TorrentID::fromInfoHash(hasMetadata ? std::get<TorrentInfo>(source).infoHash() : std::get<MagnetUri>(source).infoHash());
|
||||||
const MagnetUri &magnetUri = (hasMetadata ? MagnetUri {} : std::get<MagnetUri>(source));
|
|
||||||
const auto id = TorrentID::fromInfoHash(hasMetadata ? metadata.infoHash() : magnetUri.infoHash());
|
|
||||||
|
|
||||||
// It looks illogical that we don't just use an existing handle,
|
// It looks illogical that we don't just use an existing handle,
|
||||||
// but as previous experience has shown, it actually creates unnecessary
|
// 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);
|
TorrentImpl *const torrent = m_torrents.value(id);
|
||||||
if (torrent)
|
if (torrent)
|
||||||
{ // a duplicate torrent is added
|
{ // a duplicate torrent is added
|
||||||
if (torrent->isPrivate() || (hasMetadata && metadata.isPrivate()))
|
if (torrent->isPrivate())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// merge trackers and web seeds
|
if (hasMetadata)
|
||||||
torrent->addTrackers(hasMetadata ? metadata.trackers() : magnetUri.trackers());
|
{
|
||||||
torrent->addUrlSeeds(hasMetadata ? metadata.urlSeeds() : magnetUri.urlSeeds());
|
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;
|
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;
|
const QString actualSavePath = loadTorrentParams.savePath.isEmpty() ? categorySavePath(loadTorrentParams.category) : loadTorrentParams.savePath;
|
||||||
if (hasMetadata)
|
if (hasMetadata)
|
||||||
{
|
{
|
||||||
metadata.setContentLayout(loadTorrentParams.contentLayout);
|
const TorrentInfo &torrentInfo = std::get<TorrentInfo>(source);
|
||||||
|
|
||||||
if (!loadTorrentParams.hasSeedStatus)
|
|
||||||
{
|
|
||||||
findIncompleteFiles(metadata, actualSavePath);
|
|
||||||
isFindingIncompleteFiles = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if torrent name wasn't explicitly set we handle the case of
|
// if torrent name wasn't explicitly set we handle the case of
|
||||||
// initial renaming of torrent content and rename torrent accordingly
|
// initial renaming of torrent content and rename torrent accordingly
|
||||||
if (loadTorrentParams.name.isEmpty())
|
if (loadTorrentParams.name.isEmpty())
|
||||||
{
|
{
|
||||||
QString contentName = metadata.rootFolder();
|
QString contentName = torrentInfo.rootFolder();
|
||||||
if (contentName.isEmpty() && (metadata.filesCount() == 1))
|
if (contentName.isEmpty() && (torrentInfo.filesCount() == 1))
|
||||||
contentName = Utils::Fs::fileName(metadata.filePath(0));
|
contentName = Utils::Fs::fileName(torrentInfo.filePath(0));
|
||||||
|
|
||||||
if (!contentName.isEmpty() && (contentName != metadata.name()))
|
if (!contentName.isEmpty() && (contentName != torrentInfo.name()))
|
||||||
loadTorrentParams.name = contentName;
|
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());
|
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)
|
// Use qBittorrent default priority rather than libtorrent's (4)
|
||||||
p.file_priorities = std::vector(internalFilesCount, LT::toNative(DownloadPriority::Normal));
|
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()));
|
Q_ASSERT(addTorrentParams.filePriorities.isEmpty() || (addTorrentParams.filePriorities.size() == nativeIndexes.size()));
|
||||||
for (int i = 0; i < addTorrentParams.filePriorities.size(); ++i)
|
for (int i = 0; i < addTorrentParams.filePriorities.size(); ++i)
|
||||||
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(addTorrentParams.filePriorities[i]);
|
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(addTorrentParams.filePriorities[i]);
|
||||||
|
|
||||||
p.ti = metadata.nativeInfo();
|
p.ti = torrentInfo.nativeInfo();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
const MagnetUri &magnetUri = std::get<MagnetUri>(source);
|
||||||
p = magnetUri.addTorrentParams();
|
p = magnetUri.addTorrentParams();
|
||||||
|
|
||||||
if (loadTorrentParams.name.isEmpty() && !p.name.empty())
|
if (loadTorrentParams.name.isEmpty() && !p.name.empty())
|
||||||
@ -2232,10 +2264,12 @@ bool Session::loadTorrent(LoadTorrentParams params)
|
|||||||
return true;
|
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 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 completeSavePath = savePath;
|
||||||
const QString incompleteSavePath = (isTempPathEnabled() ? torrentTempPath(torrentInfo) : QString {});
|
const QString incompleteSavePath = (isTempPathEnabled() ? torrentTempPath(torrentInfo) : QString {});
|
||||||
QMetaObject::invokeMethod(m_fileSearcher, [=]()
|
QMetaObject::invokeMethod(m_fileSearcher, [=]()
|
||||||
@ -3899,9 +3933,9 @@ void Session::handleTorrentMetadataReceived(TorrentImpl *const torrent)
|
|||||||
if (!torrentExportDirectory().isEmpty())
|
if (!torrentExportDirectory().isEmpty())
|
||||||
{
|
{
|
||||||
#ifdef QBT_USES_LIBTORRENT2
|
#ifdef QBT_USES_LIBTORRENT2
|
||||||
const TorrentInfo torrentInfo {torrent->nativeHandle().torrent_file_with_hashes()};
|
const TorrentInfo torrentInfo {*torrent->nativeHandle().torrent_file_with_hashes()};
|
||||||
#else
|
#else
|
||||||
const TorrentInfo torrentInfo {torrent->nativeHandle().torrent_file()};
|
const TorrentInfo torrentInfo {*torrent->nativeHandle().torrent_file()};
|
||||||
#endif
|
#endif
|
||||||
exportTorrentFile(torrentInfo, torrentExportDirectory(), torrent->name());
|
exportTorrentFile(torrentInfo, torrentExportDirectory(), torrent->name());
|
||||||
}
|
}
|
||||||
@ -3955,9 +3989,9 @@ void Session::handleTorrentFinished(TorrentImpl *const torrent)
|
|||||||
if (!finishedTorrentExportDirectory().isEmpty())
|
if (!finishedTorrentExportDirectory().isEmpty())
|
||||||
{
|
{
|
||||||
#ifdef QBT_USES_LIBTORRENT2
|
#ifdef QBT_USES_LIBTORRENT2
|
||||||
const TorrentInfo torrentInfo {torrent->nativeHandle().torrent_file_with_hashes()};
|
const TorrentInfo torrentInfo {*torrent->nativeHandle().torrent_file_with_hashes()};
|
||||||
#else
|
#else
|
||||||
const TorrentInfo torrentInfo {torrent->nativeHandle().torrent_file()};
|
const TorrentInfo torrentInfo {*torrent->nativeHandle().torrent_file()};
|
||||||
#endif
|
#endif
|
||||||
exportTorrentFile(torrentInfo, finishedTorrentExportDirectory(), torrent->name());
|
exportTorrentFile(torrentInfo, finishedTorrentExportDirectory(), torrent->name());
|
||||||
}
|
}
|
||||||
@ -4155,7 +4189,7 @@ void Session::recursiveTorrentDownload(const TorrentID &id)
|
|||||||
|
|
||||||
for (const QString &torrentRelpath : asConst(torrent->filePaths()))
|
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'"
|
LogMsg(tr("Recursive download of file '%1' embedded in torrent '%2'"
|
||||||
, "Recursive download of 'test.torrent' embedded in torrent 'test2'")
|
, "Recursive download of 'test.torrent' embedded in torrent 'test2'")
|
||||||
@ -4165,7 +4199,11 @@ void Session::recursiveTorrentDownload(const TorrentID &id)
|
|||||||
AddTorrentParams params;
|
AddTorrentParams params;
|
||||||
// Passing the save path along to the sub torrent file
|
// Passing the save path along to the sub torrent file
|
||||||
params.savePath = torrent->savePath();
|
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
|
// Copy the torrent file to the export folder
|
||||||
if (!torrentExportDirectory().isEmpty())
|
if (!torrentExportDirectory().isEmpty())
|
||||||
{
|
{
|
||||||
const TorrentInfo torrentInfo {params.ltAddTorrentParams.ti};
|
const TorrentInfo torrentInfo {*params.ltAddTorrentParams.ti};
|
||||||
exportTorrentFile(torrentInfo, torrentExportDirectory(), torrent->name());
|
exportTorrentFile(torrentInfo, torrentExportDirectory(), torrent->name());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4597,7 +4635,7 @@ void Session::handleMetadataReceivedAlert(const lt::metadata_received_alert *p)
|
|||||||
|
|
||||||
if (downloadedMetadataIter != m_downloadedMetadata.end())
|
if (downloadedMetadataIter != m_downloadedMetadata.end())
|
||||||
{
|
{
|
||||||
const TorrentInfo metadata {p->handle.torrent_file()};
|
const TorrentInfo metadata {*p->handle.torrent_file()};
|
||||||
|
|
||||||
m_downloadedMetadata.erase(downloadedMetadataIter);
|
m_downloadedMetadata.erase(downloadedMetadataIter);
|
||||||
--m_extraLimit;
|
--m_extraLimit;
|
||||||
|
@ -499,7 +499,7 @@ namespace BitTorrent
|
|||||||
|
|
||||||
bool addMoveTorrentStorageJob(TorrentImpl *torrent, const QString &newPath, MoveStorageMode mode);
|
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:
|
signals:
|
||||||
void allTorrentsFinished();
|
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.
|
* 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
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@ -48,4 +48,7 @@ namespace BitTorrent
|
|||||||
|
|
||||||
Q_ENUM_NS(TorrentContentLayout)
|
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.
|
// Initialize it only if torrent is added with metadata.
|
||||||
// Otherwise it should be initialized in "Metadata received" handler.
|
// 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);
|
initializeStatus(m_nativeStatus, m_ltAddTorrentParams);
|
||||||
@ -743,7 +755,7 @@ int TorrentImpl::seedingTimeLimit() const
|
|||||||
|
|
||||||
QString TorrentImpl::filePath(const int index) 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
|
qlonglong TorrentImpl::fileSize(const int index) const
|
||||||
@ -753,7 +765,7 @@ qlonglong TorrentImpl::fileSize(const int index) const
|
|||||||
|
|
||||||
QStringList TorrentImpl::filePaths() const
|
QStringList TorrentImpl::filePaths() const
|
||||||
{
|
{
|
||||||
return m_torrentInfo.filePaths();
|
return m_filePaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a list of absolute paths corresponding
|
// Return a list of absolute paths corresponding
|
||||||
@ -1461,7 +1473,7 @@ void TorrentImpl::applyFirstLastPiecePriority(const bool enabled, const QVector<
|
|||||||
// Determine the priority to set
|
// Determine the priority to set
|
||||||
const DownloadPriority newPrio = enabled ? DownloadPriority::Maximum : filePrio;
|
const DownloadPriority newPrio = enabled ? DownloadPriority::Maximum : filePrio;
|
||||||
const auto piecePrio = static_cast<lt::download_priority_t>(static_cast<int>(newPrio));
|
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)
|
// 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());
|
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;
|
lt::add_torrent_params &p = m_ltAddTorrentParams;
|
||||||
|
|
||||||
const TorrentInfo torrentInfo {m_nativeHandle.torrent_file()};
|
const std::shared_ptr<lt::torrent_info> metadata = std::const_pointer_cast<lt::torrent_info>(m_nativeHandle.torrent_file());
|
||||||
const auto nativeIndexes = torrentInfo.nativeIndexes();
|
m_torrentInfo = TorrentInfo(*metadata);
|
||||||
|
m_filePaths = fileNames;
|
||||||
|
const auto nativeIndexes = m_torrentInfo.nativeIndexes();
|
||||||
for (int i = 0; i < fileNames.size(); ++i)
|
for (int i = 0; i < fileNames.size(); ++i)
|
||||||
p.renamed_files[nativeIndexes[i]] = fileNames[i].toStdString();
|
p.renamed_files[nativeIndexes[i]] = fileNames[i].toStdString();
|
||||||
p.save_path = Utils::Fs::toNativePath(savePath).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
|
const int internalFilesCount = p.ti->files().num_files(); // including .pad files
|
||||||
// Use qBittorrent default priority rather than libtorrent's (4)
|
// 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 = m_nativeSession->add_torrent(p);
|
||||||
m_nativeHandle.queue_position_set(queuePos);
|
m_nativeHandle.queue_position_set(queuePos);
|
||||||
|
|
||||||
m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentImpl::pause()
|
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)
|
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_renameCount;
|
||||||
m_nativeHandle.rename_file(m_torrentInfo.nativeIndexes().at(index), Utils::Fs::toNativePath(path).toStdString());
|
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.have_pieces.clear();
|
||||||
m_ltAddTorrentParams.verified_pieces.clear();
|
m_ltAddTorrentParams.verified_pieces.clear();
|
||||||
|
|
||||||
TorrentInfo metadata = TorrentInfo {m_nativeHandle.torrent_file()};
|
TorrentInfo metadata = TorrentInfo(*m_nativeHandle.torrent_file());
|
||||||
metadata.setContentLayout(m_contentLayout);
|
|
||||||
|
|
||||||
m_session->findIncompleteFiles(metadata, m_savePath);
|
QStringList filePaths = metadata.filePaths();
|
||||||
|
applyContentLayout(filePaths, m_contentLayout);
|
||||||
|
m_session->findIncompleteFiles(metadata, m_savePath, filePaths);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -1848,15 +1857,11 @@ void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
|
|||||||
// Remove empty leftover folders
|
// Remove empty leftover folders
|
||||||
// For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
|
// For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
|
||||||
// be removed if they are empty
|
// be removed if they are empty
|
||||||
#ifndef QBT_USES_LIBTORRENT2
|
const QString oldFilePath = m_filePaths.at(fileIndex);
|
||||||
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 newFilePath = Utils::Fs::toUniformPath(p->new_name());
|
const QString newFilePath = Utils::Fs::toUniformPath(p->new_name());
|
||||||
|
|
||||||
|
m_filePaths[fileIndex] = newFilePath;
|
||||||
|
|
||||||
QList<QStringView> oldPathParts = QStringView(oldFilePath).split('/', Qt::SkipEmptyParts);
|
QList<QStringView> oldPathParts = QStringView(oldFilePath).split('/', Qt::SkipEmptyParts);
|
||||||
oldPathParts.removeLast(); // drop file name part
|
oldPathParts.removeLast(); // drop file name part
|
||||||
QList<QStringView> newPathParts = QStringView(newFilePath).split('/', Qt::SkipEmptyParts);
|
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\"")
|
LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
|
||||||
.arg(name(), filePath(fileIndex), QString::fromLocal8Bit(p->error.message().c_str())), Log::WARNING);
|
.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;
|
--m_renameCount;
|
||||||
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
|
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
|
||||||
m_moveFinishedTriggers.takeFirst()();
|
m_moveFinishedTriggers.takeFirst()();
|
||||||
@ -2081,7 +2080,7 @@ void TorrentImpl::adjustActualSavePath()
|
|||||||
void TorrentImpl::adjustActualSavePath_impl()
|
void TorrentImpl::adjustActualSavePath_impl()
|
||||||
{
|
{
|
||||||
const bool needUseTempDir = useTempPath();
|
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 currentDir {actualStorageLocation()};
|
||||||
const QDir targetDir {needUseTempDir ? tempDir : QDir {savePath()}};
|
const QDir targetDir {needUseTempDir ? tempDir : QDir {savePath()}};
|
||||||
|
|
||||||
@ -2291,6 +2290,8 @@ void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities)
|
|||||||
|
|
||||||
QVector<qreal> TorrentImpl::availableFileFractions() const
|
QVector<qreal> TorrentImpl::availableFileFractions() const
|
||||||
{
|
{
|
||||||
|
Q_ASSERT(hasMetadata());
|
||||||
|
|
||||||
const int filesCount = this->filesCount();
|
const int filesCount = this->filesCount();
|
||||||
if (filesCount <= 0) return {};
|
if (filesCount <= 0) return {};
|
||||||
|
|
||||||
@ -2300,10 +2301,9 @@ QVector<qreal> TorrentImpl::availableFileFractions() const
|
|||||||
|
|
||||||
QVector<qreal> res;
|
QVector<qreal> res;
|
||||||
res.reserve(filesCount);
|
res.reserve(filesCount);
|
||||||
const TorrentInfo info = this->info();
|
|
||||||
for (int i = 0; i < filesCount; ++i)
|
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;
|
int availablePieces = 0;
|
||||||
for (const int piece : filePieces)
|
for (const int piece : filePieces)
|
||||||
|
@ -289,6 +289,7 @@ namespace BitTorrent
|
|||||||
lt::torrent_status m_nativeStatus;
|
lt::torrent_status m_nativeStatus;
|
||||||
TorrentState m_state = TorrentState::Unknown;
|
TorrentState m_state = TorrentState::Unknown;
|
||||||
TorrentInfo m_torrentInfo;
|
TorrentInfo m_torrentInfo;
|
||||||
|
QStringList m_filePaths;
|
||||||
SpeedMonitor m_speedMonitor;
|
SpeedMonitor m_speedMonitor;
|
||||||
|
|
||||||
InfoHash m_infoHash;
|
InfoHash m_infoHash;
|
||||||
@ -301,12 +302,6 @@ namespace BitTorrent
|
|||||||
|
|
||||||
MaintenanceJob m_maintenanceJob = MaintenanceJob::None;
|
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;
|
QHash<QString, QMap<lt::tcp::endpoint, int>> m_trackerPeerCounts;
|
||||||
FileErrorInfo m_lastFileError;
|
FileErrorInfo m_lastFileError;
|
||||||
|
|
||||||
|
@ -49,38 +49,14 @@
|
|||||||
|
|
||||||
using namespace BitTorrent;
|
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>();
|
const int torrentInfoId = qRegisterMetaType<TorrentInfo>();
|
||||||
|
|
||||||
TorrentInfo::TorrentInfo(std::shared_ptr<const lt::torrent_info> nativeInfo)
|
TorrentInfo::TorrentInfo(const lt::torrent_info &nativeInfo)
|
||||||
: m_nativeInfo {std::const_pointer_cast<lt::torrent_info>(nativeInfo)}
|
: m_nativeInfo {std::make_shared<const lt::torrent_info>(nativeInfo)}
|
||||||
{
|
{
|
||||||
if (!m_nativeInfo)
|
Q_ASSERT(m_nativeInfo->is_valid() && (m_nativeInfo->num_files() > 0));
|
||||||
return;
|
|
||||||
|
|
||||||
const lt::file_storage &fileStorage = m_nativeInfo->files();
|
const lt::file_storage &fileStorage = m_nativeInfo->orig_files();
|
||||||
m_nativeIndexes.reserve(fileStorage.num_files());
|
m_nativeIndexes.reserve(fileStorage.num_files());
|
||||||
for (const lt::file_index_t nativeIndex : fileStorage.file_range())
|
for (const lt::file_index_t nativeIndex : fileStorage.file_range())
|
||||||
{
|
{
|
||||||
@ -105,6 +81,11 @@ TorrentInfo &TorrentInfo::operator=(const TorrentInfo &other)
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TorrentInfo::isValid() const
|
||||||
|
{
|
||||||
|
return (m_nativeInfo != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
nonstd::expected<TorrentInfo, QString> TorrentInfo::load(const QByteArray &data) noexcept
|
nonstd::expected<TorrentInfo, QString> TorrentInfo::load(const QByteArray &data) noexcept
|
||||||
{
|
{
|
||||||
// 2-step construction to overcome default limits of `depth_limit` & `token_limit` which are
|
// 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)
|
if (ec)
|
||||||
return nonstd::make_unexpected(QString::fromStdString(ec.message()));
|
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)
|
if (ec)
|
||||||
return nonstd::make_unexpected(QString::fromStdString(ec.message()));
|
return nonstd::make_unexpected(QString::fromStdString(ec.message()));
|
||||||
|
|
||||||
return info;
|
return TorrentInfo(nativeInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
nonstd::expected<TorrentInfo, QString> TorrentInfo::loadFromFile(const QString &path) noexcept
|
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 {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TorrentInfo::isValid() const
|
|
||||||
{
|
|
||||||
return (m_nativeInfo && m_nativeInfo->is_valid() && (m_nativeInfo->num_files() > 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
InfoHash TorrentInfo::infoHash() const
|
InfoHash TorrentInfo::infoHash() const
|
||||||
{
|
{
|
||||||
if (!isValid()) return {};
|
if (!isValid()) return {};
|
||||||
@ -192,6 +168,7 @@ InfoHash TorrentInfo::infoHash() const
|
|||||||
QString TorrentInfo::name() const
|
QString TorrentInfo::name() const
|
||||||
{
|
{
|
||||||
if (!isValid()) return {};
|
if (!isValid()) return {};
|
||||||
|
|
||||||
return QString::fromStdString(m_nativeInfo->orig_files().name());
|
return QString::fromStdString(m_nativeInfo->orig_files().name());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,56 +183,65 @@ QDateTime TorrentInfo::creationDate() const
|
|||||||
QString TorrentInfo::creator() const
|
QString TorrentInfo::creator() const
|
||||||
{
|
{
|
||||||
if (!isValid()) return {};
|
if (!isValid()) return {};
|
||||||
|
|
||||||
return QString::fromStdString(m_nativeInfo->creator());
|
return QString::fromStdString(m_nativeInfo->creator());
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TorrentInfo::comment() const
|
QString TorrentInfo::comment() const
|
||||||
{
|
{
|
||||||
if (!isValid()) return {};
|
if (!isValid()) return {};
|
||||||
|
|
||||||
return QString::fromStdString(m_nativeInfo->comment());
|
return QString::fromStdString(m_nativeInfo->comment());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TorrentInfo::isPrivate() const
|
bool TorrentInfo::isPrivate() const
|
||||||
{
|
{
|
||||||
if (!isValid()) return false;
|
if (!isValid()) return false;
|
||||||
|
|
||||||
return m_nativeInfo->priv();
|
return m_nativeInfo->priv();
|
||||||
}
|
}
|
||||||
|
|
||||||
qlonglong TorrentInfo::totalSize() const
|
qlonglong TorrentInfo::totalSize() const
|
||||||
{
|
{
|
||||||
if (!isValid()) return -1;
|
if (!isValid()) return -1;
|
||||||
|
|
||||||
return m_nativeInfo->total_size();
|
return m_nativeInfo->total_size();
|
||||||
}
|
}
|
||||||
|
|
||||||
int TorrentInfo::filesCount() const
|
int TorrentInfo::filesCount() const
|
||||||
{
|
{
|
||||||
if (!isValid()) return -1;
|
if (!isValid()) return -1;
|
||||||
|
|
||||||
return m_nativeIndexes.size();
|
return m_nativeIndexes.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
int TorrentInfo::pieceLength() const
|
int TorrentInfo::pieceLength() const
|
||||||
{
|
{
|
||||||
if (!isValid()) return -1;
|
if (!isValid()) return -1;
|
||||||
|
|
||||||
return m_nativeInfo->piece_length();
|
return m_nativeInfo->piece_length();
|
||||||
}
|
}
|
||||||
|
|
||||||
int TorrentInfo::pieceLength(const int index) const
|
int TorrentInfo::pieceLength(const int index) const
|
||||||
{
|
{
|
||||||
if (!isValid()) return -1;
|
if (!isValid()) return -1;
|
||||||
|
|
||||||
return m_nativeInfo->piece_size(lt::piece_index_t {index});
|
return m_nativeInfo->piece_size(lt::piece_index_t {index});
|
||||||
}
|
}
|
||||||
|
|
||||||
int TorrentInfo::piecesCount() const
|
int TorrentInfo::piecesCount() const
|
||||||
{
|
{
|
||||||
if (!isValid()) return -1;
|
if (!isValid()) return -1;
|
||||||
|
|
||||||
return m_nativeInfo->num_pieces();
|
return m_nativeInfo->num_pieces();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TorrentInfo::filePath(const int index) const
|
QString TorrentInfo::filePath(const int index) const
|
||||||
{
|
{
|
||||||
if (!isValid()) return {};
|
if (!isValid()) return {};
|
||||||
|
|
||||||
return Utils::Fs::toUniformPath(
|
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
|
QStringList TorrentInfo::filePaths() const
|
||||||
@ -268,23 +254,18 @@ QStringList TorrentInfo::filePaths() const
|
|||||||
return list;
|
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
|
qlonglong TorrentInfo::fileSize(const int index) const
|
||||||
{
|
{
|
||||||
if (!isValid()) return -1;
|
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
|
qlonglong TorrentInfo::fileOffset(const int index) const
|
||||||
{
|
{
|
||||||
if (!isValid()) return -1;
|
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
|
QVector<TrackerEntry> TorrentInfo::trackers() const
|
||||||
@ -349,8 +330,8 @@ QVector<int> TorrentInfo::fileIndicesForPiece(const int pieceIndex) const
|
|||||||
if (!isValid() || (pieceIndex < 0) || (pieceIndex >= piecesCount()))
|
if (!isValid() || (pieceIndex < 0) || (pieceIndex >= piecesCount()))
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
const std::vector<lt::file_slice> files = nativeInfo()->map_block(
|
const std::vector<lt::file_slice> files = m_nativeInfo->map_block(
|
||||||
lt::piece_index_t {pieceIndex}, 0, nativeInfo()->piece_size(lt::piece_index_t {pieceIndex}));
|
lt::piece_index_t {pieceIndex}, 0, m_nativeInfo->piece_size(lt::piece_index_t {pieceIndex}));
|
||||||
QVector<int> res;
|
QVector<int> res;
|
||||||
res.reserve(static_cast<decltype(res)::size_type>(files.size()));
|
res.reserve(static_cast<decltype(res)::size_type>(files.size()));
|
||||||
for (const lt::file_slice &fileSlice : files)
|
for (const lt::file_slice &fileSlice : files)
|
||||||
@ -403,7 +384,7 @@ TorrentInfo::PieceRange TorrentInfo::filePieces(const int fileIndex) const
|
|||||||
return {};
|
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 fileSize = files.file_size(m_nativeIndexes[fileIndex]);
|
||||||
const auto fileOffset = files.file_offset(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);
|
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
|
int TorrentInfo::fileIndex(const QString &fileName) const
|
||||||
{
|
{
|
||||||
// the check whether the object is valid is not needed here
|
// the check whether the object is valid is not needed here
|
||||||
// because if filesCount() returns -1 the loop exits immediately
|
// because if filesCount() returns -1 the loop exits immediately
|
||||||
for (int i = 0; i < filesCount(); ++i)
|
for (int i = 0; i < filesCount(); ++i)
|
||||||
|
{
|
||||||
if (fileName == filePath(i))
|
if (fileName == filePath(i))
|
||||||
return i;
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TorrentInfo::rootFolder() const
|
QString TorrentInfo::rootFolder() const
|
||||||
{
|
{
|
||||||
return getRootFolder(filePaths());
|
if (!isValid())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return Utils::Fs::findRootFolder(filePaths());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TorrentInfo::hasRootFolder() const
|
bool TorrentInfo::hasRootFolder() const
|
||||||
@ -442,75 +422,20 @@ bool TorrentInfo::hasRootFolder() const
|
|||||||
return !rootFolder().isEmpty();
|
return !rootFolder().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentInfo::setContentLayout(const TorrentContentLayout layout)
|
TorrentContentLayout TorrentInfo::contentLayout() const
|
||||||
{
|
{
|
||||||
switch (layout)
|
if (!isValid())
|
||||||
{
|
return TorrentContentLayout::Original;
|
||||||
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()
|
return detectContentLayout(filePaths());
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<lt::torrent_info> TorrentInfo::nativeInfo() const
|
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
|
QVector<lt::file_index_t> TorrentInfo::nativeIndexes() const
|
||||||
|
@ -35,7 +35,6 @@
|
|||||||
|
|
||||||
#include "base/3rdparty/expected.hpp"
|
#include "base/3rdparty/expected.hpp"
|
||||||
#include "base/indexrange.h"
|
#include "base/indexrange.h"
|
||||||
#include "abstractfilestorage.h"
|
|
||||||
#include "torrentcontentlayout.h"
|
#include "torrentcontentlayout.h"
|
||||||
|
|
||||||
class QByteArray;
|
class QByteArray;
|
||||||
@ -48,14 +47,16 @@ namespace BitTorrent
|
|||||||
class InfoHash;
|
class InfoHash;
|
||||||
struct TrackerEntry;
|
struct TrackerEntry;
|
||||||
|
|
||||||
class TorrentInfo final : public AbstractFileStorage
|
class TorrentInfo
|
||||||
{
|
{
|
||||||
Q_DECLARE_TR_FUNCTIONS(TorrentInfo)
|
Q_DECLARE_TR_FUNCTIONS(TorrentInfo)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit TorrentInfo(std::shared_ptr<const lt::torrent_info> nativeInfo = {});
|
TorrentInfo() = default;
|
||||||
TorrentInfo(const TorrentInfo &other);
|
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> load(const QByteArray &data) noexcept;
|
||||||
static nonstd::expected<TorrentInfo, QString> loadFromFile(const QString &path) noexcept;
|
static nonstd::expected<TorrentInfo, QString> loadFromFile(const QString &path) noexcept;
|
||||||
nonstd::expected<void, QString> saveToFile(const QString &path) const;
|
nonstd::expected<void, QString> saveToFile(const QString &path) const;
|
||||||
@ -70,14 +71,13 @@ namespace BitTorrent
|
|||||||
QString comment() const;
|
QString comment() const;
|
||||||
bool isPrivate() const;
|
bool isPrivate() const;
|
||||||
qlonglong totalSize() const;
|
qlonglong totalSize() const;
|
||||||
int filesCount() const override;
|
int filesCount() const;
|
||||||
int pieceLength() const;
|
int pieceLength() const;
|
||||||
int pieceLength(int index) const;
|
int pieceLength(int index) const;
|
||||||
int piecesCount() const;
|
int piecesCount() const;
|
||||||
QString filePath(int index) const override;
|
QString filePath(int index) const;
|
||||||
QStringList filePaths() const;
|
QStringList filePaths() const;
|
||||||
QString origFilePath(int index) const;
|
qlonglong fileSize(int index) const;
|
||||||
qlonglong fileSize(int index) const override;
|
|
||||||
qlonglong fileOffset(int index) const;
|
qlonglong fileOffset(int index) const;
|
||||||
QVector<TrackerEntry> trackers() const;
|
QVector<TrackerEntry> trackers() const;
|
||||||
QVector<QUrl> urlSeeds() const;
|
QVector<QUrl> urlSeeds() const;
|
||||||
@ -92,11 +92,8 @@ namespace BitTorrent
|
|||||||
PieceRange filePieces(const QString &file) const;
|
PieceRange filePieces(const QString &file) const;
|
||||||
PieceRange filePieces(int fileIndex) const;
|
PieceRange filePieces(int fileIndex) const;
|
||||||
|
|
||||||
void renameFile(int index, const QString &newPath) override;
|
|
||||||
|
|
||||||
QString rootFolder() const;
|
QString rootFolder() const;
|
||||||
bool hasRootFolder() const;
|
bool hasRootFolder() const;
|
||||||
void setContentLayout(TorrentContentLayout layout);
|
|
||||||
|
|
||||||
std::shared_ptr<lt::torrent_info> nativeInfo() const;
|
std::shared_ptr<lt::torrent_info> nativeInfo() const;
|
||||||
QVector<lt::file_index_t> nativeIndexes() const;
|
QVector<lt::file_index_t> nativeIndexes() const;
|
||||||
@ -104,11 +101,9 @@ namespace BitTorrent
|
|||||||
private:
|
private:
|
||||||
// returns file index or -1 if fileName is not found
|
// returns file index or -1 if fileName is not found
|
||||||
int fileIndex(const QString &fileName) const;
|
int fileIndex(const QString &fileName) const;
|
||||||
void stripRootFolder();
|
TorrentContentLayout contentLayout() const;
|
||||||
void addRootFolder();
|
|
||||||
TorrentContentLayout defaultContentLayout() 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)
|
// internal indexes of files (payload only, excluding any .pad files)
|
||||||
// by which they are addressed in libtorrent
|
// by which they are addressed in libtorrent
|
||||||
|
@ -398,3 +398,40 @@ bool Utils::Fs::isNetworkFileSystem(const QString &path)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#endif // Q_OS_HAIKU
|
#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 tempPath();
|
||||||
|
|
||||||
|
QString findRootFolder(const QStringList &filePaths);
|
||||||
|
void stripRootFolder(QStringList &filePaths);
|
||||||
|
void addRootFolder(QStringList &filePaths, const QString &name);
|
||||||
|
|
||||||
#if !defined Q_OS_HAIKU
|
#if !defined Q_OS_HAIKU
|
||||||
bool isNetworkFileSystem(const QString &path);
|
bool isNetworkFileSystem(const QString &path);
|
||||||
#endif
|
#endif
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
#include "base/bittorrent/magneturi.h"
|
#include "base/bittorrent/magneturi.h"
|
||||||
#include "base/bittorrent/session.h"
|
#include "base/bittorrent/session.h"
|
||||||
#include "base/bittorrent/torrent.h"
|
#include "base/bittorrent/torrent.h"
|
||||||
|
#include "base/bittorrent/torrentcontentlayout.h"
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "base/net/downloadmanager.h"
|
#include "base/net/downloadmanager.h"
|
||||||
#include "base/settingsstorage.h"
|
#include "base/settingsstorage.h"
|
||||||
@ -72,6 +73,51 @@ namespace
|
|||||||
{
|
{
|
||||||
return SettingsStorage::instance();
|
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;
|
const int AddNewTorrentDialog::minPathHistoryLength;
|
||||||
@ -119,6 +165,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP
|
|||||||
|
|
||||||
m_ui->contentLayoutComboBox->setCurrentIndex(
|
m_ui->contentLayoutComboBox->setCurrentIndex(
|
||||||
static_cast<int>(m_torrentParams.contentLayout.value_or(session->torrentContentLayout())));
|
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->sequentialCheckBox->setChecked(m_torrentParams.sequential);
|
||||||
m_ui->firstLastCheckBox->setChecked(m_torrentParams.firstLastPiecePriority);
|
m_ui->firstLastCheckBox->setChecked(m_torrentParams.firstLastPiecePriority);
|
||||||
@ -146,10 +193,8 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP
|
|||||||
// Signal / slots
|
// Signal / slots
|
||||||
connect(m_ui->doNotDeleteTorrentCheckBox, &QCheckBox::clicked, this, &AddNewTorrentDialog::doNotDeleteTorrentClicked);
|
connect(m_ui->doNotDeleteTorrentCheckBox, &QCheckBox::clicked, this, &AddNewTorrentDialog::doNotDeleteTorrentClicked);
|
||||||
QShortcut *editHotkey = new QShortcut(Qt::Key_F2, m_ui->contentTreeView, nullptr, nullptr, Qt::WidgetShortcut);
|
QShortcut *editHotkey = new QShortcut(Qt::Key_F2, m_ui->contentTreeView, nullptr, nullptr, Qt::WidgetShortcut);
|
||||||
connect(editHotkey, &QShortcut::activated
|
connect(editHotkey, &QShortcut::activated, this, &AddNewTorrentDialog::renameSelectedFile);
|
||||||
, this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
|
connect(m_ui->contentTreeView, &QAbstractItemView::doubleClicked, this, &AddNewTorrentDialog::renameSelectedFile);
|
||||||
connect(m_ui->contentTreeView, &QAbstractItemView::doubleClicked
|
|
||||||
, this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
|
|
||||||
|
|
||||||
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus();
|
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus();
|
||||||
}
|
}
|
||||||
@ -251,7 +296,6 @@ bool AddNewTorrentDialog::loadTorrentFile(const QString &torrentPath)
|
|||||||
: torrentPath;
|
: torrentPath;
|
||||||
|
|
||||||
const nonstd::expected<BitTorrent::TorrentInfo, QString> result = BitTorrent::TorrentInfo::loadFromFile(decodedPath);
|
const nonstd::expected<BitTorrent::TorrentInfo, QString> result = BitTorrent::TorrentInfo::loadFromFile(decodedPath);
|
||||||
m_torrentInfo = result.value_or(BitTorrent::TorrentInfo());
|
|
||||||
if (!result)
|
if (!result)
|
||||||
{
|
{
|
||||||
RaisedMessageBox::critical(this, tr("Invalid torrent")
|
RaisedMessageBox::critical(this, tr("Invalid torrent")
|
||||||
@ -260,6 +304,7 @@ bool AddNewTorrentDialog::loadTorrentFile(const QString &torrentPath)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_torrentInfo = result.value();
|
||||||
m_torrentGuard = std::make_unique<TorrentFileGuard>(decodedPath);
|
m_torrentGuard = std::make_unique<TorrentFileGuard>(decodedPath);
|
||||||
|
|
||||||
return loadTorrentImpl();
|
return loadTorrentImpl();
|
||||||
@ -267,7 +312,6 @@ bool AddNewTorrentDialog::loadTorrentFile(const QString &torrentPath)
|
|||||||
|
|
||||||
bool AddNewTorrentDialog::loadTorrentImpl()
|
bool AddNewTorrentDialog::loadTorrentImpl()
|
||||||
{
|
{
|
||||||
m_hasMetadata = true;
|
|
||||||
const auto torrentID = BitTorrent::TorrentID::fromInfoHash(m_torrentInfo.infoHash());
|
const auto torrentID = BitTorrent::TorrentID::fromInfoHash(m_torrentInfo.infoHash());
|
||||||
|
|
||||||
// Prevent showing the dialog if download is already present
|
// Prevent showing the dialog if download is already present
|
||||||
@ -398,7 +442,7 @@ void AddNewTorrentDialog::updateDiskSpaceLabel()
|
|||||||
// Determine torrent size
|
// Determine torrent size
|
||||||
qlonglong torrentSize = 0;
|
qlonglong torrentSize = 0;
|
||||||
|
|
||||||
if (m_hasMetadata)
|
if (hasMetadata())
|
||||||
{
|
{
|
||||||
if (m_contentModel)
|
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)
|
void AddNewTorrentDialog::setSavePath(const QString &newPath)
|
||||||
{
|
{
|
||||||
int existingIndex = indexOfSavePath(newPath);
|
int existingIndex = indexOfSavePath(newPath);
|
||||||
@ -457,7 +526,7 @@ void AddNewTorrentDialog::setSavePath(const QString &newPath)
|
|||||||
|
|
||||||
void AddNewTorrentDialog::saveTorrentFile()
|
void AddNewTorrentDialog::saveTorrentFile()
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_hasMetadata);
|
Q_ASSERT(hasMetadata());
|
||||||
|
|
||||||
const QString torrentFileExtension {C_TORRENT_FILE_EXTENSION};
|
const QString torrentFileExtension {C_TORRENT_FILE_EXTENSION};
|
||||||
const QString filter {tr("Torrent file (*%1)").arg(torrentFileExtension)};
|
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()
|
void AddNewTorrentDialog::populateSavePathComboBox()
|
||||||
{
|
{
|
||||||
m_ui->savePath->clear();
|
m_ui->savePath->clear();
|
||||||
@ -548,8 +622,7 @@ void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &)
|
|||||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
if (selectedRows.size() == 1)
|
if (selectedRows.size() == 1)
|
||||||
{
|
{
|
||||||
menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename...")
|
menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename..."), this, &AddNewTorrentDialog::renameSelectedFile);
|
||||||
, this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
|
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
|
|
||||||
QMenu *priorityMenu = menu->addMenu(tr("Priority"));
|
QMenu *priorityMenu = menu->addMenu(tr("Priority"));
|
||||||
@ -634,7 +707,7 @@ void AddNewTorrentDialog::accept()
|
|||||||
setEnabled(!m_ui->checkBoxNeverShow->isChecked());
|
setEnabled(!m_ui->checkBoxNeverShow->isChecked());
|
||||||
|
|
||||||
// Add torrent
|
// Add torrent
|
||||||
if (!m_hasMetadata)
|
if (!hasMetadata())
|
||||||
BitTorrent::Session::instance()->addTorrent(m_magnetURI, m_torrentParams);
|
BitTorrent::Session::instance()->addTorrent(m_magnetURI, m_torrentParams);
|
||||||
else
|
else
|
||||||
BitTorrent::Session::instance()->addTorrent(m_torrentInfo, m_torrentParams);
|
BitTorrent::Session::instance()->addTorrent(m_torrentInfo, m_torrentParams);
|
||||||
@ -645,7 +718,7 @@ void AddNewTorrentDialog::accept()
|
|||||||
|
|
||||||
void AddNewTorrentDialog::reject()
|
void AddNewTorrentDialog::reject()
|
||||||
{
|
{
|
||||||
if (!m_hasMetadata)
|
if (!hasMetadata())
|
||||||
{
|
{
|
||||||
setMetadataProgressIndicator(false);
|
setMetadataProgressIndicator(false);
|
||||||
BitTorrent::Session::instance()->cancelDownloadMetadata(m_magnetURI.infoHash().toTorrentID());
|
BitTorrent::Session::instance()->cancelDownloadMetadata(m_magnetURI.infoHash().toTorrentID());
|
||||||
@ -656,20 +729,14 @@ void AddNewTorrentDialog::reject()
|
|||||||
|
|
||||||
void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata)
|
void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata)
|
||||||
{
|
{
|
||||||
|
Q_ASSERT(metadata.isValid());
|
||||||
|
|
||||||
if (metadata.infoHash() != m_magnetURI.infoHash()) return;
|
if (metadata.infoHash() != m_magnetURI.infoHash()) return;
|
||||||
|
|
||||||
disconnect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataDownloaded, this, &AddNewTorrentDialog::updateMetadata);
|
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
|
// Good to go
|
||||||
m_torrentInfo = metadata;
|
m_torrentInfo = metadata;
|
||||||
m_hasMetadata = true;
|
|
||||||
setMetadataProgressIndicator(true, tr("Parsing metadata..."));
|
setMetadataProgressIndicator(true, tr("Parsing metadata..."));
|
||||||
|
|
||||||
// Update UI
|
// Update UI
|
||||||
@ -694,7 +761,7 @@ void AddNewTorrentDialog::setMetadataProgressIndicator(bool visibleIndicator, co
|
|||||||
|
|
||||||
void AddNewTorrentDialog::setupTreeview()
|
void AddNewTorrentDialog::setupTreeview()
|
||||||
{
|
{
|
||||||
if (!m_hasMetadata)
|
if (!hasMetadata())
|
||||||
{
|
{
|
||||||
m_ui->labelCommentData->setText(tr("Not Available", "This comment is unavailable"));
|
m_ui->labelCommentData->setText(tr("Not Available", "This comment is unavailable"));
|
||||||
m_ui->labelDateData->setText(tr("Not Available", "This date 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));
|
, qOverload<const QModelIndex &>(&QAbstractItemView::edit));
|
||||||
connect(m_ui->contentTreeView, &QWidget::customContextMenuRequested, this, &AddNewTorrentDialog::displayContentTreeMenu);
|
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
|
// 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())
|
if (const QByteArray state = m_storeTreeHeaderState; !state.isEmpty())
|
||||||
m_ui->contentTreeView->header()->restoreState(state);
|
m_ui->contentTreeView->header()->restoreState(state);
|
||||||
|
|
||||||
@ -747,7 +820,6 @@ void AddNewTorrentDialog::handleDownloadFinished(const Net::DownloadResult &down
|
|||||||
case Net::DownloadStatus::Success:
|
case Net::DownloadStatus::Success:
|
||||||
{
|
{
|
||||||
const nonstd::expected<BitTorrent::TorrentInfo, QString> result = BitTorrent::TorrentInfo::load(downloadResult.data);
|
const nonstd::expected<BitTorrent::TorrentInfo, QString> result = BitTorrent::TorrentInfo::load(downloadResult.data);
|
||||||
m_torrentInfo = result.value_or(BitTorrent::TorrentInfo());
|
|
||||||
if (!result)
|
if (!result)
|
||||||
{
|
{
|
||||||
RaisedMessageBox::critical(this, tr("Invalid torrent"), tr("Failed to load from URL: %1.\nError: %2")
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_torrentInfo = result.value();
|
||||||
m_torrentGuard = std::make_unique<TorrentFileGuard>();
|
m_torrentGuard = std::make_unique<TorrentFileGuard>();
|
||||||
|
|
||||||
if (loadTorrentImpl())
|
if (loadTorrentImpl())
|
||||||
@ -800,3 +873,12 @@ void AddNewTorrentDialog::doNotDeleteTorrentClicked(bool checked)
|
|||||||
{
|
{
|
||||||
m_torrentGuard->setAutoRemove(!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 handleDownloadFinished(const Net::DownloadResult &downloadResult);
|
||||||
void TMMChanged(int index);
|
void TMMChanged(int index);
|
||||||
void categoryChanged(int index);
|
void categoryChanged(int index);
|
||||||
|
void contentLayoutChanged(int index);
|
||||||
void doNotDeleteTorrentClicked(bool checked);
|
void doNotDeleteTorrentClicked(bool checked);
|
||||||
|
void renameSelectedFile();
|
||||||
|
|
||||||
void accept() override;
|
void accept() override;
|
||||||
void reject() override;
|
void reject() override;
|
||||||
@ -104,13 +106,13 @@ private:
|
|||||||
void setupTreeview();
|
void setupTreeview();
|
||||||
void setSavePath(const QString &newPath);
|
void setSavePath(const QString &newPath);
|
||||||
void saveTorrentFile();
|
void saveTorrentFile();
|
||||||
|
bool hasMetadata() const;
|
||||||
|
|
||||||
void showEvent(QShowEvent *event) override;
|
void showEvent(QShowEvent *event) override;
|
||||||
|
|
||||||
Ui::AddNewTorrentDialog *m_ui;
|
Ui::AddNewTorrentDialog *m_ui;
|
||||||
TorrentContentFilterModel *m_contentModel = nullptr;
|
TorrentContentFilterModel *m_contentModel = nullptr;
|
||||||
PropListDelegate *m_contentDelegate = nullptr;
|
PropListDelegate *m_contentDelegate = nullptr;
|
||||||
bool m_hasMetadata = false;
|
|
||||||
BitTorrent::MagnetUri m_magnetURI;
|
BitTorrent::MagnetUri m_magnetURI;
|
||||||
BitTorrent::TorrentInfo m_torrentInfo;
|
BitTorrent::TorrentInfo m_torrentInfo;
|
||||||
int m_oldIndex = 0;
|
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::TOT_UP, totalUp, peer.totalUpload(), intDataTextAlignment);
|
||||||
setModelData(row, PeerListColumns::RELEVANCE, (Utils::String::fromDouble(peer.relevance() * 100, 1) + '%'), peer.relevance(), 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(';');
|
const QString downloadingFilesDisplayValue = downloadingFiles.join(';');
|
||||||
setModelData(row, PeerListColumns::DOWNLOADING_PIECE, downloadingFilesDisplayValue, downloadingFilesDisplayValue, {}, downloadingFiles.join(QLatin1Char('\n')));
|
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);
|
const bool showDetailedInformation = QApplication::keyboardModifiers().testFlag(Qt::ShiftModifier);
|
||||||
if (showDetailedInformation && m_torrent->hasMetadata())
|
if (showDetailedInformation && m_torrent->hasMetadata())
|
||||||
{
|
{
|
||||||
|
const BitTorrent::TorrentInfo torrentInfo = m_torrent->info();
|
||||||
const int imagePos = e->pos().x() - borderWidth;
|
const int imagePos = e->pos().x() - borderWidth;
|
||||||
if ((imagePos >=0) && (imagePos < m_image.width()))
|
if ((imagePos >=0) && (imagePos < m_image.width()))
|
||||||
{
|
{
|
||||||
stream << "<html><body>";
|
stream << "<html><body>";
|
||||||
PieceIndexToImagePos transform {m_torrent->info(), m_image};
|
PieceIndexToImagePos transform {torrentInfo, m_image};
|
||||||
int pieceIndex = transform.pieceIndex(imagePos);
|
int pieceIndex = transform.pieceIndex(imagePos);
|
||||||
const QVector<int> files {m_torrent->info().fileIndicesForPiece(pieceIndex)};
|
const QVector<int> files {torrentInfo.fileIndicesForPiece(pieceIndex)};
|
||||||
|
|
||||||
QString tooltipTitle;
|
QString tooltipTitle;
|
||||||
if (files.count() > 1)
|
if (files.count() > 1)
|
||||||
@ -271,7 +272,7 @@ void PiecesBar::showToolTip(const QHelpEvent *e)
|
|||||||
}
|
}
|
||||||
else
|
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");
|
tooltipTitle = tr("File in this piece");
|
||||||
else
|
else
|
||||||
tooltipTitle = tr("File in these pieces");
|
tooltipTitle = tr("File in these pieces");
|
||||||
@ -281,8 +282,8 @@ void PiecesBar::showToolTip(const QHelpEvent *e)
|
|||||||
|
|
||||||
for (int f : files)
|
for (int f : files)
|
||||||
{
|
{
|
||||||
const QString filePath {m_torrent->info().filePath(f)};
|
const QString filePath {torrentInfo.filePath(f)};
|
||||||
renderer(Utils::Misc::friendlyUnit(m_torrent->info().fileSize(f)), filePath);
|
renderer(Utils::Misc::friendlyUnit(torrentInfo.fileSize(f)), filePath);
|
||||||
}
|
}
|
||||||
stream << "</body></html>";
|
stream << "</body></html>";
|
||||||
}
|
}
|
||||||
@ -306,13 +307,14 @@ void PiecesBar::highlightFile(int imagePos)
|
|||||||
if (!m_torrent || !m_torrent->hasMetadata() || (imagePos < 0) || (imagePos >= m_image.width()))
|
if (!m_torrent || !m_torrent->hasMetadata() || (imagePos < 0) || (imagePos >= m_image.width()))
|
||||||
return;
|
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);
|
int pieceIndex = transform.pieceIndex(imagePos);
|
||||||
QVector<int> fileIndices {m_torrent->info().fileIndicesForPiece(pieceIndex)};
|
QVector<int> fileIndices {torrentInfo.fileIndicesForPiece(pieceIndex)};
|
||||||
if (fileIndices.count() == 1)
|
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);
|
ImageRange imageRange = transform.imagePos(filePieces);
|
||||||
QRect newHighlightedRegion {imageRange.first(), 0, imageRange.size(), m_image.height()};
|
QRect newHighlightedRegion {imageRange.first(), 0, imageRange.size(), m_image.height()};
|
||||||
|
@ -528,7 +528,7 @@ void PropertiesWidget::loadDynamicData()
|
|||||||
if (!isContentInitialized)
|
if (!isContentInitialized)
|
||||||
{
|
{
|
||||||
// List files in torrent
|
// List files in torrent
|
||||||
m_propListModel->model()->setupModelData(m_torrent->info());
|
m_propListModel->model()->setupModelData(*m_torrent);
|
||||||
// Load file priorities
|
// Load file priorities
|
||||||
m_propListModel->model()->updateFilesPriorities(m_torrent->filePriorities());
|
m_propListModel->model()->updateFilesPriorities(m_torrent->filePriorities());
|
||||||
// Update file progress/availability
|
// Update file progress/availability
|
||||||
|
@ -50,8 +50,8 @@
|
|||||||
#include <QPixmapCache>
|
#include <QPixmapCache>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "base/bittorrent/abstractfilestorage.h"
|
||||||
#include "base/bittorrent/downloadpriority.h"
|
#include "base/bittorrent/downloadpriority.h"
|
||||||
#include "base/bittorrent/torrentinfo.h"
|
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "base/utils/fs.h"
|
#include "base/utils/fs.h"
|
||||||
#include "torrentcontentmodelfile.h"
|
#include "torrentcontentmodelfile.h"
|
||||||
@ -485,7 +485,7 @@ void TorrentContentModel::clear()
|
|||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentContentModel::setupModelData(const BitTorrent::TorrentInfo &info)
|
void TorrentContentModel::setupModelData(const BitTorrent::AbstractFileStorage &info)
|
||||||
{
|
{
|
||||||
qDebug("setup model data called");
|
qDebug("setup model data called");
|
||||||
const int filesCount = info.filesCount();
|
const int filesCount = info.filesCount();
|
||||||
|
@ -41,7 +41,7 @@ class TorrentContentModelFile;
|
|||||||
|
|
||||||
namespace BitTorrent
|
namespace BitTorrent
|
||||||
{
|
{
|
||||||
class TorrentInfo;
|
class AbstractFileStorage;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TorrentContentModel final : public QAbstractItemModel
|
class TorrentContentModel final : public QAbstractItemModel
|
||||||
@ -74,7 +74,7 @@ public:
|
|||||||
QModelIndex parent(const QModelIndex &index) const override;
|
QModelIndex parent(const QModelIndex &index) const override;
|
||||||
int rowCount(const QModelIndex &parent = {}) const override;
|
int rowCount(const QModelIndex &parent = {}) const override;
|
||||||
void clear();
|
void clear();
|
||||||
void setupModelData(const BitTorrent::TorrentInfo &info);
|
void setupModelData(const BitTorrent::AbstractFileStorage &info);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void filteredFilesChanged();
|
void filteredFilesChanged();
|
||||||
|
@ -573,10 +573,12 @@ void SyncController::torrentPeersAction()
|
|||||||
{KEY_PEER_CONNECTION_TYPE, pi.connectionType()},
|
{KEY_PEER_CONNECTION_TYPE, pi.connectionType()},
|
||||||
{KEY_PEER_FLAGS, pi.flags()},
|
{KEY_PEER_FLAGS, pi.flags()},
|
||||||
{KEY_PEER_FLAGS_DESCRIPTION, pi.flagsDescription()},
|
{KEY_PEER_FLAGS_DESCRIPTION, pi.flagsDescription()},
|
||||||
{KEY_PEER_RELEVANCE, pi.relevance()},
|
{KEY_PEER_RELEVANCE, pi.relevance()}
|
||||||
{KEY_PEER_FILES, torrent->info().filesForPiece(pi.downloadingPieceIndex()).join('\n')}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (torrent->hasMetadata())
|
||||||
|
peer.insert(KEY_PEER_FILES, torrent->info().filesForPiece(pi.downloadingPieceIndex()).join('\n'));
|
||||||
|
|
||||||
if (resolvePeerCountries)
|
if (resolvePeerCountries)
|
||||||
{
|
{
|
||||||
peer[KEY_PEER_COUNTRY_CODE] = pi.country().toLower();
|
peer[KEY_PEER_COUNTRY_CODE] = pi.country().toLower();
|
||||||
|
@ -594,9 +594,12 @@ void TorrentsController::pieceHashesAction()
|
|||||||
throw APIError(APIErrorType::NotFound);
|
throw APIError(APIErrorType::NotFound);
|
||||||
|
|
||||||
QJsonArray pieceHashes;
|
QJsonArray pieceHashes;
|
||||||
const QVector<QByteArray> hashes = torrent->info().pieceHashes();
|
if (torrent->hasMetadata())
|
||||||
for (const QByteArray &hash : hashes)
|
{
|
||||||
pieceHashes.append(QString(hash.toHex()));
|
const QVector<QByteArray> hashes = torrent->info().pieceHashes();
|
||||||
|
for (const QByteArray &hash : hashes)
|
||||||
|
pieceHashes.append(QString(hash.toHex()));
|
||||||
|
}
|
||||||
|
|
||||||
setResult(pieceHashes);
|
setResult(pieceHashes);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user