Merge pull request #15852 from glassez/torrent-info

Improve torrent content handling
This commit is contained in:
Vladimir Golovnev
2021-12-20 08:54:46 +03:00
committed by GitHub
22 changed files with 418 additions and 255 deletions

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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')));

View File

@@ -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()};

View File

@@ -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

View File

@@ -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();

View File

@@ -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();