mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-08-19 12:59:56 -07:00
parent
e37661d53a
commit
1be5b3abd8
23 changed files with 1181 additions and 1116 deletions
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue