Revamp torrent content widget

PR #18162.
This commit is contained in:
Vladimir Golovnev 2023-02-11 15:22:01 +03:00 committed by GitHub
commit 1be5b3abd8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 1181 additions and 1116 deletions

View file

@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006-2012 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -33,6 +34,8 @@
#include <QFileIconProvider>
#include <QFileInfo>
#include <QIcon>
#include <QPointer>
#include <QScopeGuard>
#if defined(Q_OS_WIN)
#include <Windows.h>
@ -50,8 +53,9 @@
#include <QPixmapCache>
#endif
#include "base/bittorrent/abstractfilestorage.h"
#include "base/bittorrent/downloadpriority.h"
#include "base/bittorrent/torrentcontenthandler.h"
#include "base/exceptions.h"
#include "base/global.h"
#include "base/path.h"
#include "base/utils/fs.h"
@ -208,62 +212,89 @@ TorrentContentModel::~TorrentContentModel()
delete m_rootItem;
}
void TorrentContentModel::updateFilesProgress(const QVector<qreal> &fp)
void TorrentContentModel::updateFilesProgress()
{
Q_ASSERT(m_filesIndex.size() == fp.size());
// XXX: Why is this necessary?
if (m_filesIndex.size() != fp.size()) return;
Q_ASSERT(m_contentHandler && m_contentHandler->hasMetadata());
using HandlerPtr = QPointer<BitTorrent::TorrentContentHandler>;
m_contentHandler->fetchFilesProgress([this, handler = HandlerPtr(m_contentHandler)](const QVector<qreal> &filesProgress)
{
if (handler != m_contentHandler)
return;
Q_ASSERT(m_filesIndex.size() == filesProgress.size());
// XXX: Why is this necessary?
if (Q_UNLIKELY(m_filesIndex.size() != filesProgress.size()))
return;
for (int i = 0; i < filesProgress.size(); ++i)
m_filesIndex[i]->setProgress(filesProgress[i]);
// Update folders progress in the tree
m_rootItem->recalculateProgress();
m_rootItem->recalculateAvailability();
});
}
void TorrentContentModel::updateFilesPriorities()
{
Q_ASSERT(m_contentHandler && m_contentHandler->hasMetadata());
const QVector<BitTorrent::DownloadPriority> fprio = m_contentHandler->filePriorities();
Q_ASSERT(m_filesIndex.size() == fprio.size());
// XXX: Why is this necessary?
if (m_filesIndex.size() != fprio.size())
return;
for (int i = 0; i < fprio.size(); ++i)
m_filesIndex[i]->setPriority(static_cast<BitTorrent::DownloadPriority>(fprio[i]));
}
void TorrentContentModel::updateFilesAvailability()
{
Q_ASSERT(m_contentHandler && m_contentHandler->hasMetadata());
using HandlerPtr = QPointer<BitTorrent::TorrentContentHandler>;
m_contentHandler->fetchAvailableFileFractions([this, handler = HandlerPtr(m_contentHandler)](const QVector<qreal> &availableFileFractions)
{
if (handler != m_contentHandler)
return;
Q_ASSERT(m_filesIndex.size() == availableFileFractions.size());
// XXX: Why is this necessary?
if (Q_UNLIKELY(m_filesIndex.size() != availableFileFractions.size()))
return;
for (int i = 0; i < m_filesIndex.size(); ++i)
m_filesIndex[i]->setAvailability(availableFileFractions[i]);
// Update folders progress in the tree
m_rootItem->recalculateProgress();
});
}
bool TorrentContentModel::setItemPriority(const QModelIndex &index, BitTorrent::DownloadPriority priority)
{
Q_ASSERT(index.isValid());
auto *item = static_cast<TorrentContentModelItem *>(index.internalPointer());
const BitTorrent::DownloadPriority currentPriority = item->priority();
if (currentPriority == priority)
return false;
item->setPriority(priority);
m_contentHandler->prioritizeFiles(getFilePriorities());
emit layoutAboutToBeChanged();
for (int i = 0; i < fp.size(); ++i)
m_filesIndex[i]->setProgress(fp[i]);
// Update folders progress in the tree
m_rootItem->recalculateProgress();
m_rootItem->recalculateAvailability();
const QVector<ColumnInterval> columns =
{
{TorrentContentModelItem::COL_PROGRESS, TorrentContentModelItem::COL_PROGRESS}
};
notifySubtreeUpdated(index(0, 0), columns);
}
void TorrentContentModel::updateFilesPriorities(const QVector<BitTorrent::DownloadPriority> &fprio)
{
Q_ASSERT(m_filesIndex.size() == fprio.size());
// XXX: Why is this necessary?
if (m_filesIndex.size() != fprio.size())
return;
emit layoutAboutToBeChanged();
for (int i = 0; i < fprio.size(); ++i)
m_filesIndex[i]->setPriority(static_cast<BitTorrent::DownloadPriority>(fprio[i]));
const QVector<ColumnInterval> columns =
{
{TorrentContentModelItem::COL_NAME, TorrentContentModelItem::COL_NAME},
{TorrentContentModelItem::COL_PRIO, TorrentContentModelItem::COL_PRIO}
};
notifySubtreeUpdated(index(0, 0), columns);
}
notifySubtreeUpdated(index, columns);
void TorrentContentModel::updateFilesAvailability(const QVector<qreal> &fa)
{
Q_ASSERT(m_filesIndex.size() == fa.size());
// XXX: Why is this necessary?
if (m_filesIndex.size() != fa.size()) return;
emit layoutAboutToBeChanged();
for (int i = 0; i < m_filesIndex.size(); ++i)
m_filesIndex[i]->setAvailability(fa[i]);
// Update folders progress in the tree
m_rootItem->recalculateProgress();
const QVector<ColumnInterval> columns =
{
{TorrentContentModelItem::COL_AVAILABILITY, TorrentContentModelItem::COL_AVAILABILITY}
};
notifySubtreeUpdated(index(0, 0), columns);
return true;
}
QVector<BitTorrent::DownloadPriority> TorrentContentModel::getFilePriorities() const
@ -275,14 +306,6 @@ QVector<BitTorrent::DownloadPriority> TorrentContentModel::getFilePriorities() c
return prio;
}
bool TorrentContentModel::allFiltered() const
{
return std::all_of(m_filesIndex.cbegin(), m_filesIndex.cend(), [](const TorrentContentModelFile *fileItem)
{
return (fileItem->priority() == BitTorrent::DownloadPriority::Ignored);
});
}
int TorrentContentModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
@ -296,9 +319,6 @@ bool TorrentContentModel::setData(const QModelIndex &index, const QVariant &valu
if ((index.column() == TorrentContentModelItem::COL_NAME) && (role == Qt::CheckStateRole))
{
auto *item = static_cast<TorrentContentModelItem *>(index.internalPointer());
const BitTorrent::DownloadPriority currentPrio = item->priority();
const auto checkState = static_cast<Qt::CheckState>(value.toInt());
const BitTorrent::DownloadPriority newPrio = (checkState == Qt::PartiallyChecked)
? BitTorrent::DownloadPriority::Mixed
@ -306,23 +326,7 @@ bool TorrentContentModel::setData(const QModelIndex &index, const QVariant &valu
? BitTorrent::DownloadPriority::Ignored
: BitTorrent::DownloadPriority::Normal);
if (currentPrio != newPrio)
{
item->setPriority(newPrio);
// Update folders progress in the tree
m_rootItem->recalculateProgress();
m_rootItem->recalculateAvailability();
const QVector<ColumnInterval> columns =
{
{TorrentContentModelItem::COL_NAME, TorrentContentModelItem::COL_NAME},
{TorrentContentModelItem::COL_PRIO, TorrentContentModelItem::COL_PRIO}
};
notifySubtreeUpdated(index, columns);
emit filteredFilesChanged();
return true;
}
return setItemPriority(index, newPrio);
}
if (role == Qt::EditRole)
@ -337,6 +341,23 @@ bool TorrentContentModel::setData(const QModelIndex &index, const QVariant &valu
const QString newName = value.toString();
if (currentName != newName)
{
try
{
const Path parentPath = getItemPath(index.parent());
const Path oldPath = parentPath / Path(currentName);
const Path newPath = parentPath / Path(newName);
if (item->itemType() == TorrentContentModelItem::FileType)
m_contentHandler->renameFile(oldPath, newPath);
else
m_contentHandler->renameFolder(oldPath, newPath);
}
catch (const RuntimeError &error)
{
emit renameFailed(error.message());
return false;
}
item->setName(newName);
emit dataChanged(index, index);
return true;
@ -346,27 +367,8 @@ bool TorrentContentModel::setData(const QModelIndex &index, const QVariant &valu
case TorrentContentModelItem::COL_PRIO:
{
const BitTorrent::DownloadPriority currentPrio = item->priority();
const auto newPrio = static_cast<BitTorrent::DownloadPriority>(value.toInt());
if (currentPrio != newPrio)
{
item->setPriority(newPrio);
const QVector<ColumnInterval> columns =
{
{TorrentContentModelItem::COL_NAME, TorrentContentModelItem::COL_NAME},
{TorrentContentModelItem::COL_PRIO, TorrentContentModelItem::COL_PRIO}
};
notifySubtreeUpdated(index, columns);
if ((newPrio == BitTorrent::DownloadPriority::Ignored)
|| (currentPrio == BitTorrent::DownloadPriority::Ignored))
{
emit filteredFilesChanged();
}
return true;
}
return setItemPriority(index, newPrio);
}
break;
@ -383,16 +385,23 @@ TorrentContentModelItem::ItemType TorrentContentModel::itemType(const QModelInde
return static_cast<const TorrentContentModelItem *>(index.internalPointer())->itemType();
}
int TorrentContentModel::getFileIndex(const QModelIndex &index)
int TorrentContentModel::getFileIndex(const QModelIndex &index) const
{
auto *item = static_cast<TorrentContentModelItem *>(index.internalPointer());
if (item->itemType() == TorrentContentModelItem::FileType)
return static_cast<TorrentContentModelFile *>(item)->fileIndex();
Q_ASSERT(item->itemType() == TorrentContentModelItem::FileType);
return -1;
}
Path TorrentContentModel::getItemPath(const QModelIndex &index) const
{
Path path;
for (QModelIndex i = index; i.isValid(); i = i.parent())
path = Path(i.data().toString()) / path;
return path;
}
QVariant TorrentContentModel::data(const QModelIndex &index, const int role) const
{
if (!index.isValid())
@ -403,29 +412,45 @@ QVariant TorrentContentModel::data(const QModelIndex &index, const int role) con
switch (role)
{
case Qt::DecorationRole:
{
if (index.column() != TorrentContentModelItem::COL_NAME)
return {};
if (index.column() != TorrentContentModelItem::COL_NAME)
return {};
if (item->itemType() == TorrentContentModelItem::FolderType)
return m_fileIconProvider->icon(QFileIconProvider::Folder);
return m_fileIconProvider->icon(QFileInfo(item->name()));
if (item->itemType() == TorrentContentModelItem::FolderType)
return m_fileIconProvider->icon(QFileIconProvider::Folder);
return m_fileIconProvider->icon(QFileInfo(item->name()));
}
case Qt::CheckStateRole:
{
if (index.column() != TorrentContentModelItem::COL_NAME)
return {};
if (index.column() != TorrentContentModelItem::COL_NAME)
return {};
if (item->priority() == BitTorrent::DownloadPriority::Ignored)
return Qt::Unchecked;
if (item->priority() == BitTorrent::DownloadPriority::Mixed)
return Qt::PartiallyChecked;
return Qt::Checked;
if (item->priority() == BitTorrent::DownloadPriority::Ignored)
return Qt::Unchecked;
if (item->priority() == BitTorrent::DownloadPriority::Mixed)
{
Q_ASSERT(item->itemType() == TorrentContentModelItem::FolderType);
const auto *folder = static_cast<TorrentContentModelFolder *>(item);
const auto childItems = folder->children();
const bool hasIgnored = std::any_of(childItems.cbegin(), childItems.cend()
, [](const TorrentContentModelItem *childItem)
{
return (childItem->priority() == BitTorrent::DownloadPriority::Ignored);
});
return hasIgnored ? Qt::PartiallyChecked : Qt::Checked;
}
return Qt::Checked;
case Qt::TextAlignmentRole:
if ((index.column() == TorrentContentModelItem::COL_SIZE)
|| (index.column() == TorrentContentModelItem::COL_REMAINING))
{
return QVariant {Qt::AlignRight | Qt::AlignVCenter};
}
return {};
case Qt::DisplayRole:
@ -469,7 +494,10 @@ QVariant TorrentContentModel::headerData(int section, Qt::Orientation orientatio
case Qt::TextAlignmentRole:
if ((section == TorrentContentModelItem::COL_SIZE)
|| (section == TorrentContentModelItem::COL_REMAINING))
{
return QVariant {Qt::AlignRight | Qt::AlignVCenter};
}
return {};
default:
@ -525,26 +553,11 @@ int TorrentContentModel::rowCount(const QModelIndex &parent) const
return parentItem ? parentItem->childCount() : 0;
}
void TorrentContentModel::clear()
void TorrentContentModel::populate()
{
qDebug("clear called");
beginResetModel();
m_filesIndex.clear();
m_rootItem->deleteAllChildren();
endResetModel();
}
Q_ASSERT(m_contentHandler && m_contentHandler->hasMetadata());
void TorrentContentModel::setupModelData(const BitTorrent::AbstractFileStorage &info)
{
qDebug("setup model data called");
const int filesCount = info.filesCount();
if (filesCount <= 0)
return;
beginResetModel();
// Initialize files_index array
qDebug("Torrent contains %d files", filesCount);
const int filesCount = m_contentHandler->filesCount();
m_filesIndex.reserve(filesCount);
QHash<TorrentContentModelFolder *, QHash<QString, TorrentContentModelFolder *>> folderMap;
@ -553,7 +566,7 @@ void TorrentContentModel::setupModelData(const BitTorrent::AbstractFileStorage &
// Iterate over files
for (int i = 0; i < filesCount; ++i)
{
const QString path = info.filePath(i).data();
const QString path = m_contentHandler->filePath(i).data();
// Iterate of parts of the path to create necessary folders
QList<QStringView> pathFolders = QStringView(path).split(u'/', Qt::SkipEmptyParts);
@ -584,13 +597,64 @@ void TorrentContentModel::setupModelData(const BitTorrent::AbstractFileStorage &
}
// Actually create the file
TorrentContentModelFile *fileItem = new TorrentContentModelFile(
fileName, info.fileSize(i), lastParent, i);
auto *fileItem = new TorrentContentModelFile(fileName, m_contentHandler->fileSize(i), lastParent, i);
lastParent->appendChild(fileItem);
m_filesIndex.push_back(fileItem);
}
endResetModel();
updateFilesProgress();
updateFilesPriorities();
updateFilesAvailability();
}
void TorrentContentModel::setContentHandler(BitTorrent::TorrentContentHandler *contentHandler)
{
beginResetModel();
[[maybe_unused]] const auto modelResetGuard = qScopeGuard([this] { endResetModel(); });
if (m_contentHandler)
{
m_filesIndex.clear();
m_rootItem->deleteAllChildren();
}
m_contentHandler = contentHandler;
if (m_contentHandler && m_contentHandler->hasMetadata())
populate();
}
BitTorrent::TorrentContentHandler *TorrentContentModel::contentHandler() const
{
return m_contentHandler;
}
void TorrentContentModel::refresh()
{
if (!m_contentHandler || !m_contentHandler->hasMetadata())
return;
if (!m_filesIndex.isEmpty())
{
updateFilesProgress();
updateFilesPriorities();
updateFilesAvailability();
const QVector<ColumnInterval> columns =
{
{TorrentContentModelItem::COL_NAME, TorrentContentModelItem::COL_NAME},
{TorrentContentModelItem::COL_PROGRESS, TorrentContentModelItem::COL_PROGRESS},
{TorrentContentModelItem::COL_PRIO, TorrentContentModelItem::COL_PRIO},
{TorrentContentModelItem::COL_AVAILABILITY, TorrentContentModelItem::COL_AVAILABILITY}
};
notifySubtreeUpdated(index(0, 0), columns);
}
else
{
beginResetModel();
populate();
endResetModel();
}
}
void TorrentContentModel::notifySubtreeUpdated(const QModelIndex &index, const QVector<ColumnInterval> &columns)