mirror of
https://github.com/kiwix/kiwix-desktop.git
synced 2025-09-22 19:46:12 -04:00
Merge pull request #1024 from kiwix/download_bugfixes
A couple of bugfixes in Download management
This commit is contained in:
commit
2d49d79fc2
@ -137,7 +137,7 @@
|
|||||||
"monitor-directory-tooltip":"All ZIM files in this directory will be automatically added to the library.",
|
"monitor-directory-tooltip":"All ZIM files in this directory will be automatically added to the library.",
|
||||||
"next-tab":"Move to next tab",
|
"next-tab":"Move to next tab",
|
||||||
"previous-tab":"Move to previous tab",
|
"previous-tab":"Move to previous tab",
|
||||||
"cancel-download": "Cancel Download",
|
"cancel-download": "Cancel download",
|
||||||
"cancel-download-text": "Are you sure you want to cancel the download of <b>{{ZIM}}</b>?",
|
"cancel-download-text": "Are you sure you want to cancel the download of <b>{{ZIM}}</b>?",
|
||||||
"delete-book": "Delete book",
|
"delete-book": "Delete book",
|
||||||
"delete-book-text": "Are you sure you want to delete <b>{{ZIM}}</b>?",
|
"delete-book-text": "Are you sure you want to delete <b>{{ZIM}}</b>?",
|
||||||
|
@ -77,10 +77,10 @@ ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader,
|
|||||||
setLanguages();
|
setLanguages();
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QMap<QString, QVariant>> ContentManager::getBooksList()
|
ContentManager::BookInfoList ContentManager::getBooksList()
|
||||||
{
|
{
|
||||||
const auto bookIds = getBookIds();
|
const auto bookIds = getBookIds();
|
||||||
QList<QMap<QString, QVariant>> bookList;
|
BookInfoList bookList;
|
||||||
QStringList keys = {"title", "tags", "date", "id", "size", "description", "faviconUrl"};
|
QStringList keys = {"title", "tags", "date", "id", "size", "description", "faviconUrl"};
|
||||||
QIcon bookIcon;
|
QIcon bookIcon;
|
||||||
for (auto bookId : bookIds) {
|
for (auto bookId : bookIds) {
|
||||||
@ -107,8 +107,8 @@ void ContentManager::onCustomContextMenu(const QPoint &point)
|
|||||||
QAction menuCancelBook(gt("cancel-download"), this);
|
QAction menuCancelBook(gt("cancel-download"), this);
|
||||||
QAction menuOpenFolder(gt("open-folder"), this);
|
QAction menuOpenFolder(gt("open-folder"), this);
|
||||||
|
|
||||||
if (bookNode->isDownloading()) {
|
if (const auto download = bookNode->getDownloadState()) {
|
||||||
if (bookNode->getDownloadInfo().paused) {
|
if (download->getDownloadInfo().paused) {
|
||||||
contextMenu.addAction(&menuResumeBook);
|
contextMenu.addAction(&menuResumeBook);
|
||||||
} else {
|
} else {
|
||||||
contextMenu.addAction(&menuPauseBook);
|
contextMenu.addAction(&menuPauseBook);
|
||||||
@ -216,9 +216,9 @@ void ContentManager::setLanguages()
|
|||||||
}
|
}
|
||||||
|
|
||||||
#define ADD_V(KEY, METH) {if(key==KEY) values.insert(key, QString::fromStdString((b->METH())));}
|
#define ADD_V(KEY, METH) {if(key==KEY) values.insert(key, QString::fromStdString((b->METH())));}
|
||||||
QMap<QString, QVariant> ContentManager::getBookInfos(QString id, const QStringList &keys)
|
ContentManager::BookInfo ContentManager::getBookInfos(QString id, const QStringList &keys)
|
||||||
{
|
{
|
||||||
QMap<QString, QVariant> values;
|
BookInfo values;
|
||||||
const kiwix::Book* b = [=]()->const kiwix::Book* {
|
const kiwix::Book* b = [=]()->const kiwix::Book* {
|
||||||
try {
|
try {
|
||||||
return &mp_library->getBookById(id);
|
return &mp_library->getBookById(id);
|
||||||
@ -681,13 +681,13 @@ void ContentManager::updateLibrary() {
|
|||||||
} catch (std::runtime_error&) {}
|
} catch (std::runtime_error&) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define CATALOG_URL "library.kiwix.org"
|
|
||||||
void ContentManager::updateRemoteLibrary(const QString& content) {
|
void ContentManager::updateRemoteLibrary(const QString& content) {
|
||||||
QtConcurrent::run([=]() {
|
QtConcurrent::run([=]() {
|
||||||
QMutexLocker locker(&remoteLibraryLocker);
|
QMutexLocker locker(&remoteLibraryLocker);
|
||||||
mp_remoteLibrary = kiwix::Library::create();
|
mp_remoteLibrary = kiwix::Library::create();
|
||||||
kiwix::Manager manager(mp_remoteLibrary);
|
kiwix::Manager manager(mp_remoteLibrary);
|
||||||
manager.readOpds(content.toStdString(), CATALOG_URL);
|
const auto catalogUrl = m_remoteLibraryManager.getCatalogHost();
|
||||||
|
manager.readOpds(content.toStdString(), catalogUrl.toStdString());
|
||||||
emit(this->booksChanged());
|
emit(this->booksChanged());
|
||||||
emit(this->pendingRequest(false));
|
emit(this->pendingRequest(false));
|
||||||
});
|
});
|
||||||
|
@ -17,9 +17,13 @@ class ContentManager : public QObject
|
|||||||
Q_PROPERTY(QStringList downloadIds READ getDownloadIds NOTIFY downloadsChanged)
|
Q_PROPERTY(QStringList downloadIds READ getDownloadIds NOTIFY downloadsChanged)
|
||||||
Q_PROPERTY(bool isLocal MEMBER m_local READ isLocal WRITE setLocal NOTIFY localChanged)
|
Q_PROPERTY(bool isLocal MEMBER m_local READ isLocal WRITE setLocal NOTIFY localChanged)
|
||||||
|
|
||||||
public:
|
public: // types
|
||||||
typedef QList<QPair<QString, QString>> LanguageList;
|
typedef QList<QPair<QString, QString>> LanguageList;
|
||||||
typedef QList<QPair<QString, QString>> FilterList;
|
typedef QList<QPair<QString, QString>> FilterList;
|
||||||
|
typedef ContentManagerModel::BookInfo BookInfo;
|
||||||
|
typedef ContentManagerModel::BookInfoList BookInfoList;
|
||||||
|
|
||||||
|
public: // functions
|
||||||
explicit ContentManager(Library* library, kiwix::Downloader *downloader, QObject *parent = nullptr);
|
explicit ContentManager(Library* library, kiwix::Downloader *downloader, QObject *parent = nullptr);
|
||||||
virtual ~ContentManager() {}
|
virtual ~ContentManager() {}
|
||||||
|
|
||||||
@ -51,7 +55,7 @@ private:
|
|||||||
|
|
||||||
QStringList getBookIds();
|
QStringList getBookIds();
|
||||||
void eraseBookFilesFromComputer(const QString dirPath, const QString filename, const bool moveToTrash);
|
void eraseBookFilesFromComputer(const QString dirPath, const QString filename, const bool moveToTrash);
|
||||||
QList<QMap<QString, QVariant>> getBooksList();
|
BookInfoList getBooksList();
|
||||||
ContentManagerModel *managerModel;
|
ContentManagerModel *managerModel;
|
||||||
QMutex remoteLibraryLocker;
|
QMutex remoteLibraryLocker;
|
||||||
void setCategories();
|
void setCategories();
|
||||||
@ -71,7 +75,7 @@ signals:
|
|||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
QStringList getTranslations(const QStringList &keys);
|
QStringList getTranslations(const QStringList &keys);
|
||||||
QMap<QString, QVariant> getBookInfos(QString id, const QStringList &keys);
|
BookInfo getBookInfos(QString id, const QStringList &keys);
|
||||||
void openBook(const QString& id);
|
void openBook(const QString& id);
|
||||||
QMap<QString, QVariant> updateDownloadInfos(QString id, const QStringList& keys);
|
QMap<QString, QVariant> updateDownloadInfos(QString id, const QStringList& keys);
|
||||||
QString downloadBook(const QString& id);
|
QString downloadBook(const QString& id);
|
||||||
|
@ -177,8 +177,8 @@ void ContentManagerDelegate::paint(QPainter *painter, const QStyleOptionViewItem
|
|||||||
}
|
}
|
||||||
QStyleOptionViewItem eOpt = option;
|
QStyleOptionViewItem eOpt = option;
|
||||||
if (index.column() == 5) {
|
if (index.column() == 5) {
|
||||||
if (node->isDownloading()) {
|
if (const auto downloadState = node->getDownloadState()) {
|
||||||
auto downloadInfo = node->getDownloadInfo();
|
auto downloadInfo = downloadState->getDownloadInfo();
|
||||||
showDownloadProgress(painter, r, downloadInfo);
|
showDownloadProgress(painter, r, downloadInfo);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -244,8 +244,8 @@ void ContentManagerDelegate::handleLastColumnClicked(const QModelIndex& index, Q
|
|||||||
int x = r.left();
|
int x = r.left();
|
||||||
int w = r.width();
|
int w = r.width();
|
||||||
|
|
||||||
if (node->isDownloading()) {
|
if (const auto downloadState = node->getDownloadState()) {
|
||||||
if (node->getDownloadInfo().paused) {
|
if (downloadState->getDownloadInfo().paused) {
|
||||||
if (clickX < (x + w/2)) {
|
if (clickX < (x + w/2)) {
|
||||||
KiwixApp::instance()->getContentManager()->cancelBook(id, index);
|
KiwixApp::instance()->getContentManager()->cancelBook(id, index);
|
||||||
} else {
|
} else {
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include <zim/error.h>
|
#include <zim/error.h>
|
||||||
#include <zim/item.h>
|
#include <zim/item.h>
|
||||||
#include "kiwixapp.h"
|
#include "kiwixapp.h"
|
||||||
|
#include <kiwix/tools.h>
|
||||||
|
|
||||||
ContentManagerModel::ContentManagerModel(QObject *parent)
|
ContentManagerModel::ContentManagerModel(QObject *parent)
|
||||||
: QAbstractItemModel(parent)
|
: QAbstractItemModel(parent)
|
||||||
@ -101,7 +102,7 @@ QVariant ContentManagerModel::headerData(int section, Qt::Orientation orientatio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContentManagerModel::setBooksData(const QList<QMap<QString, QVariant>>& data)
|
void ContentManagerModel::setBooksData(const BookInfoList& data)
|
||||||
{
|
{
|
||||||
m_data = data;
|
m_data = data;
|
||||||
rootNode = std::shared_ptr<RowNode>(new RowNode({tr("Icon"), tr("Name"), tr("Date"), tr("Size"), tr("Content Type"), tr("Download")}, "", std::weak_ptr<RowNode>()));
|
rootNode = std::shared_ptr<RowNode>(new RowNode({tr("Icon"), tr("Name"), tr("Date"), tr("Size"), tr("Content Type"), tr("Download")}, "", std::weak_ptr<RowNode>()));
|
||||||
@ -109,25 +110,52 @@ void ContentManagerModel::setBooksData(const QList<QMap<QString, QVariant>>& dat
|
|||||||
emit dataChanged(QModelIndex(), QModelIndex());
|
emit dataChanged(QModelIndex(), QModelIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
QString convertToUnits(QString size)
|
std::shared_ptr<RowNode> ContentManagerModel::createNode(BookInfo bookItem, QMap<QString, QByteArray> iconMap) const
|
||||||
{
|
{
|
||||||
QStringList units = {"bytes", "KB", "MB", "GB", "TB", "PB", "EB"};
|
auto faviconUrl = "https://" + bookItem["faviconUrl"].toString();
|
||||||
int unitIndex = 0;
|
QString id = bookItem["id"].toString();
|
||||||
auto bytes = size.toDouble();
|
QByteArray bookIcon;
|
||||||
while (bytes >= 1024 && unitIndex < units.size()) {
|
try {
|
||||||
bytes /= 1024;
|
auto book = KiwixApp::instance()->getLibrary()->getBookById(id);
|
||||||
unitIndex++;
|
std::string favicon;
|
||||||
|
auto item = book.getIllustration(48);
|
||||||
|
favicon = item->getData();
|
||||||
|
bookIcon = QByteArray::fromRawData(reinterpret_cast<const char*>(favicon.data()), favicon.size());
|
||||||
|
bookIcon.detach(); // deep copy
|
||||||
|
} catch (...) {
|
||||||
|
if (iconMap.contains(faviconUrl)) {
|
||||||
|
bookIcon = iconMap[faviconUrl];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
std::weak_ptr<RowNode> weakRoot = rootNode;
|
||||||
|
auto rowNodePtr = std::shared_ptr<RowNode>(new
|
||||||
|
RowNode({bookIcon, bookItem["title"],
|
||||||
|
bookItem["date"],
|
||||||
|
QString::fromStdString(kiwix::beautifyFileSize(bookItem["size"].toULongLong())),
|
||||||
|
bookItem["tags"]
|
||||||
|
}, id, weakRoot));
|
||||||
|
std::weak_ptr<RowNode> weakRowNodePtr = rowNodePtr;
|
||||||
|
const auto descNodePtr = std::make_shared<DescriptionNode>(DescriptionNode(bookItem["description"].toString(), weakRowNodePtr));
|
||||||
|
|
||||||
const auto preciseBytes = QString::number(bytes, 'g', 3);
|
rowNodePtr->appendChild(descNodePtr);
|
||||||
return preciseBytes + " " + units[unitIndex];
|
return rowNodePtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContentManagerModel::setupNodes()
|
void ContentManagerModel::setupNodes()
|
||||||
{
|
{
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
|
bookIdToRowMap.clear();
|
||||||
for (auto bookItem : m_data) {
|
for (auto bookItem : m_data) {
|
||||||
rootNode->appendChild(RowNode::createNode(bookItem, iconMap, rootNode));
|
const auto rowNode = createNode(bookItem, iconMap);
|
||||||
|
|
||||||
|
// Restore download state during model updates (filtering, etc)
|
||||||
|
const auto downloadIter = m_downloads.constFind(rowNode->getBookId());
|
||||||
|
if ( downloadIter != m_downloads.constEnd() ) {
|
||||||
|
rowNode->setDownloadState(downloadIter.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
bookIdToRowMap[bookItem["id"].toString()] = rootNode->childCount();
|
||||||
|
rootNode->appendChild(rowNode);
|
||||||
}
|
}
|
||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
@ -222,58 +250,68 @@ std::shared_ptr<RowNode> getSharedPointer(RowNode* ptr)
|
|||||||
void ContentManagerModel::startDownload(QModelIndex index)
|
void ContentManagerModel::startDownload(QModelIndex index)
|
||||||
{
|
{
|
||||||
auto node = getSharedPointer(static_cast<RowNode*>(index.internalPointer()));
|
auto node = getSharedPointer(static_cast<RowNode*>(index.internalPointer()));
|
||||||
node->setIsDownloading(true);
|
const auto bookId = node->getBookId();
|
||||||
auto id = node->getBookId();
|
const auto newDownload = std::make_shared<DownloadState>();
|
||||||
QTimer *timer = new QTimer(this);
|
m_downloads[bookId] = newDownload;
|
||||||
|
node->setDownloadState(newDownload);
|
||||||
|
QTimer *timer = newDownload->getDownloadUpdateTimer();
|
||||||
connect(timer, &QTimer::timeout, this, [=]() {
|
connect(timer, &QTimer::timeout, this, [=]() {
|
||||||
auto downloadInfos = KiwixApp::instance()->getContentManager()->updateDownloadInfos(id, {"status", "completedLength", "totalLength", "downloadSpeed"});
|
updateDownload(bookId);
|
||||||
double percent = (double) downloadInfos["completedLength"].toInt() / downloadInfos["totalLength"].toInt();
|
|
||||||
percent *= 100;
|
|
||||||
percent = QString::number(percent, 'g', 3).toDouble();
|
|
||||||
auto completedLength = convertToUnits(downloadInfos["completedLength"].toString());
|
|
||||||
auto downloadSpeed = convertToUnits(downloadInfos["downloadSpeed"].toString()) + "/s";
|
|
||||||
node->setDownloadInfo({percent, completedLength, downloadSpeed});
|
|
||||||
if (!downloadInfos["status"].isValid()) {
|
|
||||||
node->setIsDownloading(false);
|
|
||||||
timer->stop();
|
|
||||||
timer->deleteLater();
|
|
||||||
}
|
|
||||||
emit dataChanged(index, index);
|
|
||||||
});
|
});
|
||||||
timer->start(1000);
|
}
|
||||||
timers[id] = timer;
|
|
||||||
|
void ContentManagerModel::updateDownload(QString bookId)
|
||||||
|
{
|
||||||
|
const auto download = m_downloads.value(bookId);
|
||||||
|
|
||||||
|
if ( ! download )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const bool downloadStillValid = download->update(bookId);
|
||||||
|
|
||||||
|
// The download->update() call above may result in
|
||||||
|
// ContentManagerModel::setBooksData() being called (through a chain
|
||||||
|
// of signals), which in turn will rebuild bookIdToRowMap. Hence
|
||||||
|
// bookIdToRowMap access must happen after it.
|
||||||
|
|
||||||
|
const auto it = bookIdToRowMap.constFind(bookId);
|
||||||
|
|
||||||
|
if ( ! downloadStillValid ) {
|
||||||
|
m_downloads.remove(bookId);
|
||||||
|
if ( it != bookIdToRowMap.constEnd() ) {
|
||||||
|
const size_t row = it.value();
|
||||||
|
RowNode& rowNode = static_cast<RowNode&>(*rootNode->child(row));
|
||||||
|
rowNode.setDownloadState(nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( it != bookIdToRowMap.constEnd() ) {
|
||||||
|
const size_t row = it.value();
|
||||||
|
const QModelIndex rootNodeIndex = this->index(0, 0);
|
||||||
|
const QModelIndex newIndex = this->index(row, 5, rootNodeIndex);
|
||||||
|
emit dataChanged(newIndex, newIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContentManagerModel::pauseDownload(QModelIndex index)
|
void ContentManagerModel::pauseDownload(QModelIndex index)
|
||||||
{
|
{
|
||||||
auto node = static_cast<RowNode*>(index.internalPointer());
|
auto node = static_cast<RowNode*>(index.internalPointer());
|
||||||
auto id = node->getBookId();
|
node->getDownloadState()->pause();
|
||||||
auto prevDownloadInfo = node->getDownloadInfo();
|
|
||||||
prevDownloadInfo.paused = true;
|
|
||||||
node->setDownloadInfo(prevDownloadInfo);
|
|
||||||
timers[id]->stop();
|
|
||||||
emit dataChanged(index, index);
|
emit dataChanged(index, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContentManagerModel::resumeDownload(QModelIndex index)
|
void ContentManagerModel::resumeDownload(QModelIndex index)
|
||||||
{
|
{
|
||||||
auto node = static_cast<RowNode*>(index.internalPointer());
|
auto node = static_cast<RowNode*>(index.internalPointer());
|
||||||
auto id = node->getBookId();
|
node->getDownloadState()->resume();
|
||||||
auto prevDownloadInfo = node->getDownloadInfo();
|
|
||||||
prevDownloadInfo.paused = false;
|
|
||||||
node->setDownloadInfo(prevDownloadInfo);
|
|
||||||
timers[id]->start(1000);
|
|
||||||
emit dataChanged(index, index);
|
emit dataChanged(index, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContentManagerModel::cancelDownload(QModelIndex index)
|
void ContentManagerModel::cancelDownload(QModelIndex index)
|
||||||
{
|
{
|
||||||
auto node = static_cast<RowNode*>(index.internalPointer());
|
auto node = static_cast<RowNode*>(index.internalPointer());
|
||||||
auto id = node->getBookId();
|
node->setDownloadState(nullptr);
|
||||||
node->setIsDownloading(false);
|
m_downloads.remove(node->getBookId());
|
||||||
node->setDownloadInfo({0, "", "", false});
|
|
||||||
timers[id]->stop();
|
|
||||||
timers[id]->deleteLater();
|
|
||||||
emit dataChanged(index, index);
|
emit dataChanged(index, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include "thumbnaildownloader.h"
|
#include "thumbnaildownloader.h"
|
||||||
|
#include "rownode.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
class RowNode;
|
class RowNode;
|
||||||
@ -16,7 +17,11 @@ class ContentManagerModel : public QAbstractItemModel
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public: // types
|
||||||
|
typedef QMap<QString, QVariant> BookInfo;
|
||||||
|
typedef QList<BookInfo> BookInfoList;
|
||||||
|
|
||||||
|
public: // functions
|
||||||
explicit ContentManagerModel(QObject *parent = nullptr);
|
explicit ContentManagerModel(QObject *parent = nullptr);
|
||||||
~ContentManagerModel();
|
~ContentManagerModel();
|
||||||
|
|
||||||
@ -29,12 +34,14 @@ public:
|
|||||||
QModelIndex parent(const QModelIndex &index) const override;
|
QModelIndex parent(const QModelIndex &index) const override;
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
void setBooksData(const QList<QMap<QString, QVariant>>& data);
|
void setBooksData(const BookInfoList& data);
|
||||||
void setupNodes();
|
void setupNodes();
|
||||||
bool hasChildren(const QModelIndex &parent) const override;
|
bool hasChildren(const QModelIndex &parent) const override;
|
||||||
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
|
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
|
||||||
void refreshIcons();
|
void refreshIcons();
|
||||||
|
|
||||||
|
std::shared_ptr<RowNode> createNode(BookInfo bookItem, QMap<QString, QByteArray> iconMap) const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void updateImage(QModelIndex index, QString url, QByteArray imageData);
|
void updateImage(QModelIndex index, QString url, QByteArray imageData);
|
||||||
void startDownload(QModelIndex index);
|
void startDownload(QModelIndex index);
|
||||||
@ -42,17 +49,21 @@ public slots:
|
|||||||
void resumeDownload(QModelIndex index);
|
void resumeDownload(QModelIndex index);
|
||||||
void cancelDownload(QModelIndex index);
|
void cancelDownload(QModelIndex index);
|
||||||
|
|
||||||
protected:
|
protected: // functions
|
||||||
bool canFetchMore(const QModelIndex &parent) const override;
|
bool canFetchMore(const QModelIndex &parent) const override;
|
||||||
void fetchMore(const QModelIndex &parent) override;
|
void fetchMore(const QModelIndex &parent) override;
|
||||||
|
|
||||||
private:
|
private: // functions
|
||||||
QList<QMap<QString, QVariant>> m_data;
|
void updateDownload(QString bookId);
|
||||||
|
|
||||||
|
private: // data
|
||||||
|
BookInfoList m_data;
|
||||||
std::shared_ptr<RowNode> rootNode;
|
std::shared_ptr<RowNode> rootNode;
|
||||||
int zimCount = 0;
|
int zimCount = 0;
|
||||||
ThumbnailDownloader td;
|
ThumbnailDownloader td;
|
||||||
|
QMap<QString, size_t> bookIdToRowMap;
|
||||||
QMap<QString, QByteArray> iconMap;
|
QMap<QString, QByteArray> iconMap;
|
||||||
QMap<QString, QTimer*> timers;
|
QMap<QString, std::shared_ptr<DownloadState>> m_downloads;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CONTENTMANAGERMODEL_H
|
#endif // CONTENTMANAGERMODEL_H
|
||||||
|
@ -5,8 +5,22 @@ OpdsRequestManager::OpdsRequestManager()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
#define CATALOG_HOST "library.kiwix.org"
|
QString OpdsRequestManager::getCatalogHost()
|
||||||
#define CATALOG_PORT 443
|
{
|
||||||
|
const char* const envVarVal = getenv("KIWIX_CATALOG_HOST");
|
||||||
|
return envVarVal
|
||||||
|
? envVarVal
|
||||||
|
: "library.kiwix.org";
|
||||||
|
}
|
||||||
|
|
||||||
|
int OpdsRequestManager::getCatalogPort()
|
||||||
|
{
|
||||||
|
const char* const envVarVal = getenv("KIWIX_CATALOG_PORT");
|
||||||
|
return envVarVal
|
||||||
|
? atoi(envVarVal)
|
||||||
|
: 443;
|
||||||
|
}
|
||||||
|
|
||||||
void OpdsRequestManager::doUpdate(const QString& currentLanguage, const QString& categoryFilter)
|
void OpdsRequestManager::doUpdate(const QString& currentLanguage, const QString& categoryFilter)
|
||||||
{
|
{
|
||||||
QUrlQuery query;
|
QUrlQuery query;
|
||||||
@ -36,9 +50,10 @@ void OpdsRequestManager::doUpdate(const QString& currentLanguage, const QString&
|
|||||||
QNetworkReply* OpdsRequestManager::opdsResponseFromPath(const QString &path, const QUrlQuery &query)
|
QNetworkReply* OpdsRequestManager::opdsResponseFromPath(const QString &path, const QUrlQuery &query)
|
||||||
{
|
{
|
||||||
QUrl url;
|
QUrl url;
|
||||||
url.setScheme("https");
|
const int port = getCatalogPort();
|
||||||
url.setHost(CATALOG_HOST);
|
url.setScheme(port == 443 ? "https" : "http");
|
||||||
url.setPort(CATALOG_PORT);
|
url.setHost(getCatalogHost());
|
||||||
|
url.setPort(port);
|
||||||
url.setPath(path);
|
url.setPath(path);
|
||||||
url.setQuery(query);
|
url.setQuery(query);
|
||||||
qInfo() << "Downloading" << url.toString(QUrl::FullyEncoded);
|
qInfo() << "Downloading" << url.toString(QUrl::FullyEncoded);
|
||||||
|
@ -32,6 +32,10 @@ public slots:
|
|||||||
void receiveContent(QNetworkReply*);
|
void receiveContent(QNetworkReply*);
|
||||||
void receiveLanguages(QNetworkReply*);
|
void receiveLanguages(QNetworkReply*);
|
||||||
void receiveCategories(QNetworkReply*);
|
void receiveCategories(QNetworkReply*);
|
||||||
|
|
||||||
|
public:
|
||||||
|
static QString getCatalogHost();
|
||||||
|
static int getCatalogPort();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // OPDSREQUESTMANAGER_H
|
#endif // OPDSREQUESTMANAGER_H
|
||||||
|
108
src/rownode.cpp
108
src/rownode.cpp
@ -2,12 +2,81 @@
|
|||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include "kiwixapp.h"
|
#include "kiwixapp.h"
|
||||||
#include "descriptionnode.h"
|
#include "descriptionnode.h"
|
||||||
#include "kiwix/tools.h"
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// DowloadState
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
DownloadState::DownloadState()
|
||||||
|
: m_downloadInfo({0, "", "", false})
|
||||||
|
{
|
||||||
|
m_downloadUpdateTimer.reset(new QTimer);
|
||||||
|
m_downloadUpdateTimer->start(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
QString convertToUnits(QString size)
|
||||||
|
{
|
||||||
|
QStringList units = {"bytes", "KB", "MB", "GB", "TB", "PB", "EB"};
|
||||||
|
int unitIndex = 0;
|
||||||
|
auto bytes = size.toDouble();
|
||||||
|
while (bytes >= 1024 && unitIndex < units.size()) {
|
||||||
|
bytes /= 1024;
|
||||||
|
unitIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto preciseBytes = QString::number(bytes, 'g', 3);
|
||||||
|
return preciseBytes + " " + units[unitIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
} // unnamed namespace
|
||||||
|
|
||||||
|
bool DownloadState::update(QString id)
|
||||||
|
{
|
||||||
|
auto downloadInfos = KiwixApp::instance()->getContentManager()->updateDownloadInfos(id, {"status", "completedLength", "totalLength", "downloadSpeed"});
|
||||||
|
if (!downloadInfos["status"].isValid()) {
|
||||||
|
m_downloadUpdateTimer->stop();
|
||||||
|
|
||||||
|
// Deleting the timer object immediately instead of via
|
||||||
|
// QObject::deleteLater() seems to be safe since it is not a recipient
|
||||||
|
// of any events that may be in the process of being delivered to it
|
||||||
|
// from another thread.
|
||||||
|
m_downloadUpdateTimer.reset();
|
||||||
|
m_downloadInfo = {0, "", "", false};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
double percent = downloadInfos["completedLength"].toDouble() / downloadInfos["totalLength"].toDouble();
|
||||||
|
percent *= 100;
|
||||||
|
percent = QString::number(percent, 'g', 3).toDouble();
|
||||||
|
auto completedLength = convertToUnits(downloadInfos["completedLength"].toString());
|
||||||
|
auto downloadSpeed = convertToUnits(downloadInfos["downloadSpeed"].toString()) + "/s";
|
||||||
|
m_downloadInfo = {percent, completedLength, downloadSpeed, false};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadState::pause()
|
||||||
|
{
|
||||||
|
m_downloadInfo.paused = true;
|
||||||
|
m_downloadUpdateTimer->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadState::resume()
|
||||||
|
{
|
||||||
|
m_downloadInfo.paused = false;
|
||||||
|
m_downloadUpdateTimer->start(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// RowNode
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
RowNode::RowNode(QList<QVariant> itemData, QString bookId, std::weak_ptr<RowNode> parent)
|
RowNode::RowNode(QList<QVariant> itemData, QString bookId, std::weak_ptr<RowNode> parent)
|
||||||
: m_itemData(itemData), m_parentItem(parent), m_bookId(bookId)
|
: m_itemData(itemData), m_parentItem(parent), m_bookId(bookId)
|
||||||
{
|
{
|
||||||
m_downloadInfo = {0, "", "", false};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RowNode::~RowNode()
|
RowNode::~RowNode()
|
||||||
@ -65,36 +134,6 @@ int RowNode::row() const
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<RowNode> RowNode::createNode(QMap<QString, QVariant> bookItem, QMap<QString, QByteArray> iconMap, std::shared_ptr<RowNode> rootNode)
|
|
||||||
{
|
|
||||||
auto faviconUrl = "https://" + bookItem["faviconUrl"].toString();
|
|
||||||
QString id = bookItem["id"].toString();
|
|
||||||
QByteArray bookIcon;
|
|
||||||
try {
|
|
||||||
auto book = KiwixApp::instance()->getLibrary()->getBookById(id);
|
|
||||||
std::string favicon;
|
|
||||||
auto item = book.getIllustration(48);
|
|
||||||
favicon = item->getData();
|
|
||||||
bookIcon = QByteArray::fromRawData(reinterpret_cast<const char*>(favicon.data()), favicon.size());
|
|
||||||
bookIcon.detach(); // deep copy
|
|
||||||
} catch (...) {
|
|
||||||
if (iconMap.contains(faviconUrl)) {
|
|
||||||
bookIcon = iconMap[faviconUrl];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::weak_ptr<RowNode> weakRoot = rootNode;
|
|
||||||
auto rowNodePtr = std::shared_ptr<RowNode>(new
|
|
||||||
RowNode({bookIcon, bookItem["title"],
|
|
||||||
bookItem["date"],
|
|
||||||
QString::fromStdString(kiwix::beautifyFileSize(bookItem["size"].toULongLong())),
|
|
||||||
bookItem["tags"]
|
|
||||||
}, id, weakRoot));
|
|
||||||
std::weak_ptr<RowNode> weakRowNodePtr = rowNodePtr;
|
|
||||||
const auto descNodePtr = std::make_shared<DescriptionNode>(DescriptionNode(bookItem["description"].toString(), weakRowNodePtr));
|
|
||||||
rowNodePtr->appendChild(descNodePtr);
|
|
||||||
return rowNodePtr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RowNode::isChild(Node *candidate)
|
bool RowNode::isChild(Node *candidate)
|
||||||
{
|
{
|
||||||
if (!candidate)
|
if (!candidate)
|
||||||
@ -105,3 +144,8 @@ bool RowNode::isChild(Node *candidate)
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RowNode::setDownloadState(std::shared_ptr<DownloadState> ds)
|
||||||
|
{
|
||||||
|
m_downloadState = ds;
|
||||||
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
#include "node.h"
|
#include "node.h"
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include "contentmanagermodel.h"
|
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include "kiwix/book.h"
|
#include "kiwix/book.h"
|
||||||
|
|
||||||
@ -15,6 +14,25 @@ struct DownloadInfo
|
|||||||
bool paused;
|
bool paused;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class DownloadState
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DownloadState();
|
||||||
|
|
||||||
|
bool isDownloading() const { return m_downloadUpdateTimer.get() != nullptr; }
|
||||||
|
DownloadInfo getDownloadInfo() const { return m_downloadInfo; }
|
||||||
|
QTimer* getDownloadUpdateTimer() const { return m_downloadUpdateTimer.get(); }
|
||||||
|
void pause();
|
||||||
|
void resume();
|
||||||
|
bool update(QString id);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// This is non-NULL only for a pending (even if paused) download
|
||||||
|
std::unique_ptr<QTimer> m_downloadUpdateTimer;
|
||||||
|
|
||||||
|
DownloadInfo m_downloadInfo;
|
||||||
|
};
|
||||||
|
|
||||||
class RowNode : public Node
|
class RowNode : public Node
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -29,20 +47,18 @@ public:
|
|||||||
int row() const override;
|
int row() const override;
|
||||||
QString getBookId() const override { return m_bookId; }
|
QString getBookId() const override { return m_bookId; }
|
||||||
void setIconData(QByteArray iconData) { m_itemData[0] = iconData; }
|
void setIconData(QByteArray iconData) { m_itemData[0] = iconData; }
|
||||||
bool isDownloading() const { return m_isDownloading; }
|
|
||||||
void setDownloadInfo(DownloadInfo downloadInfo) { m_downloadInfo = downloadInfo; }
|
|
||||||
DownloadInfo getDownloadInfo() const { return m_downloadInfo; }
|
|
||||||
void setIsDownloading(bool val) { m_isDownloading = val; }
|
|
||||||
static std::shared_ptr<RowNode> createNode(QMap<QString, QVariant> bookItem, QMap<QString, QByteArray> iconMap, std::shared_ptr<RowNode> rootNode);
|
|
||||||
bool isChild(Node* candidate);
|
bool isChild(Node* candidate);
|
||||||
|
|
||||||
|
|
||||||
|
void setDownloadState(std::shared_ptr<DownloadState> ds);
|
||||||
|
std::shared_ptr<DownloadState> getDownloadState() { return m_downloadState; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QList<QVariant> m_itemData;
|
QList<QVariant> m_itemData;
|
||||||
QList<std::shared_ptr<Node>> m_childItems;
|
QList<std::shared_ptr<Node>> m_childItems;
|
||||||
std::weak_ptr<RowNode> m_parentItem;
|
std::weak_ptr<RowNode> m_parentItem;
|
||||||
QString m_bookId;
|
QString m_bookId;
|
||||||
bool m_isDownloading = false;
|
std::shared_ptr<DownloadState> m_downloadState;
|
||||||
DownloadInfo m_downloadInfo;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user