Icon downloader util for all files view

Added an icon downloader to display icons for remote library.
While the icons are being downloaded, a placeholder icon is shown.
This commit is contained in:
Nikhil Tanwar 2023-06-26 00:11:11 +05:30
parent ef0227b08c
commit ff5f022cd6
11 changed files with 260 additions and 23 deletions

View File

@ -40,6 +40,7 @@ SOURCES += \
src/findinpagebar.cpp \
src/node.cpp \
src/suggestionlistworker.cpp \
src/thumbnaildownloader.cpp \
src/translation.cpp \
src/main.cpp \
src/mainwindow.cpp \
@ -77,6 +78,7 @@ HEADERS += \
src/findinpagebar.h \
src/node.h \
src/suggestionlistworker.h \
src/thumbnaildownloader.h \
src/translation.h \
src/mainwindow.h \
src/kiwixapp.h \

View File

@ -27,7 +27,7 @@ ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader,
// mp_view will be passed to the tab who will take ownership,
// so, we don't need to delete it.
mp_view = new ContentManagerView();
auto managerModel = new ContentManagerModel();
managerModel = new ContentManagerModel(this);
const auto booksList = getBooksList();
managerModel->setBooksData(booksList);
auto treeView = mp_view->getView();
@ -54,6 +54,7 @@ ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader,
connect(this, &ContentManager::booksChanged, this, [=]() {
const auto nBookList = getBooksList();
managerModel->setBooksData(nBookList);
managerModel->refreshIcons();
});
connect(&m_remoteLibraryManager, &OpdsRequestManager::requestReceived, this, &ContentManager::updateRemoteLibrary);
connect(mp_view->getView(), SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onCustomContextMenu(const QPoint &)));
@ -63,27 +64,10 @@ QList<QMap<QString, QVariant>> ContentManager::getBooksList()
{
const auto bookIds = getBookIds();
QList<QMap<QString, QVariant>> bookList;
QStringList keys = {"title", "tags", "date", "id", "size", "description"};
auto app = KiwixApp::instance();
std::shared_ptr<zim::Archive> archive;
QStringList keys = {"title", "tags", "date", "id", "size", "description", "faviconUrl"};
QIcon bookIcon;
for (auto bookId : bookIds) {
try {
archive = app->getLibrary()->getArchive(bookId);
std::string favicon, _mimetype;
auto item = archive->getIllustrationItem(48);
favicon = item.getData();
_mimetype = item.getMimetype();
QPixmap pixmap;
pixmap.loadFromData((const uchar*)favicon.data(), favicon.size());
bookIcon = QIcon(pixmap);
} catch (zim::EntryNotFound &e) {
bookIcon = QIcon(":/icons/placeholder-icon.png");
} catch (std::out_of_range &e) {
bookIcon = QIcon(":/icons/placeholder-icon.png");
}
auto mp = getBookInfos(bookId, keys);
mp["icon"] = bookIcon;
bookList.append(mp);
}
return bookList;

View File

@ -8,6 +8,7 @@
#include <kiwix/downloader.h>
#include "opdsrequestmanager.h"
#include "contenttypefilter.h"
#include "contentmanagermodel.h"
class ContentManager : public QObject
{
@ -26,6 +27,7 @@ public:
void setCurrentLanguage(QString language);
void setCurrentCategoryFilter(QString category);
void setCurrentContentTypeFilter(QList<ContentTypeFilter*>& contentTypeFilter);
bool isLocal() const { return m_local; }
private:
Library* mp_library;
@ -44,6 +46,7 @@ private:
QStringList getBookIds();
void eraseBookFilesFromComputer(const QString dirPath, const QString filename);
QList<QMap<QString, QVariant>> getBooksList();
ContentManagerModel *managerModel;
signals:
void filterParamsChanged();

View File

@ -16,6 +16,10 @@ ContentManagerDelegate::ContentManagerDelegate(QObject *parent)
"font-family: Selawik;"
"color: blue;"
"margin: 4px;");
QImage placeholderIconFile(":/icons/placeholder-icon.png");
QBuffer buffer(&placeholderIcon);
buffer.open(QIODevice::WriteOnly);
placeholderIconFile.save(&buffer, "png");
}
@ -54,7 +58,12 @@ void ContentManagerDelegate::paint(QPainter *painter, const QStyleOptionViewItem
return;
}
if (index.column() == 0) {
const auto icon = index.data().value<QIcon>();
auto iconData = index.data().value<QByteArray>();
if (iconData.isNull())
iconData = placeholderIcon;
QPixmap pix;
pix.loadFromData(iconData);
QIcon icon(pix);
icon.paint(painter, QRect(x+10, y+10, 30, 50));
return;
}

View File

@ -3,6 +3,7 @@
#include <QStyledItemDelegate>
#include <QPushButton>
#include <QByteArray>
class ContentManagerDelegate : public QStyledItemDelegate
{
@ -16,6 +17,7 @@ public:
private:
QScopedPointer<QPushButton> baseButton;
QByteArray placeholderIcon;
};
#endif // CONTENTMANAGERDELEGATE_H

View File

@ -5,12 +5,15 @@
#include<QDebug>
#include <QStringList>
#include <QSize>
#include <QIcon>
#include <zim/error.h>
#include <zim/item.h>
#include "kiwixapp.h"
ContentManagerModel::ContentManagerModel(QObject *parent)
: QAbstractItemModel(parent)
{
connect(&td, &ThumbnailDownloader::oneThumbnailDownloaded, this, &ContentManagerModel::updateImage);
}
ContentManagerModel::~ContentManagerModel()
@ -126,6 +129,7 @@ QString convertToUnits(QString size)
void ContentManagerModel::setupNodes()
{
QByteArray bookIcon;
beginResetModel();
for (auto bookItem : m_data) {
auto name = bookItem["title"].toString();
@ -134,8 +138,20 @@ void ContentManagerModel::setupNodes()
auto content = bookItem["tags"].toString();
auto id = bookItem["id"].toString();
auto description = bookItem["description"].toString();
auto icon = bookItem["icon"];
const auto temp = new Node({icon, name, date, size, content, id}, rootNode, id);
auto faviconUrl = "https://" + bookItem["faviconUrl"].toString();
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 (std::out_of_range &e) {
if (iconMap.contains(faviconUrl)) {
bookIcon = iconMap[faviconUrl];
}
}
const auto temp = new Node({bookIcon, name, date, size, content, id}, rootNode, id);
const auto tempsTemp = new Node({"", description, "", "", "", ""}, temp, "", true);
temp->appendChild(tempsTemp);
rootNode->appendChild(temp);
@ -143,6 +159,27 @@ void ContentManagerModel::setupNodes()
endResetModel();
}
void ContentManagerModel::refreshIcons()
{
if (KiwixApp::instance()->getContentManager()->isLocal())
return;
td.clearQueue();
for (auto i = 0; i < rowCount() && i < m_data.size(); i++) {
auto bookItem = m_data[i];
auto id = bookItem["id"].toString();
auto faviconUrl = "https://" + bookItem["faviconUrl"].toString();
auto app = KiwixApp::instance();
try {
auto book = app->getLibrary()->getBookById(id);
auto item = book.getIllustration(48);
} catch (std::out_of_range &e) {
if (faviconUrl != "" && !iconMap.contains(faviconUrl)) {
td.addDownload(faviconUrl, index(i, 0));
}
}
}
}
bool ContentManagerModel::hasChildren(const QModelIndex &parent) const
{
Node *item = static_cast<Node*>(parent.internalPointer());
@ -167,6 +204,7 @@ void ContentManagerModel::fetchMore(const QModelIndex &parent)
beginInsertRows(QModelIndex(), zimCount, zimCount + zimsToFetch - 1);
zimCount += zimsToFetch;
endInsertRows();
refreshIcons();
}
void ContentManagerModel::sort(int column, Qt::SortOrder order)
@ -190,3 +228,11 @@ void ContentManagerModel::sort(int column, Qt::SortOrder order)
}
KiwixApp::instance()->getContentManager()->setSortBy(sortBy, order == Qt::AscendingOrder);
}
void ContentManagerModel::updateImage(QModelIndex index, QString url, QByteArray imageData)
{
Node *item = static_cast<Node*>(index.internalPointer());
item->setIconData(imageData);
iconMap[url] = imageData;
emit dataChanged(index, index);
}

View File

@ -4,6 +4,8 @@
#include <QAbstractItemModel>
#include <QModelIndex>
#include <QVariant>
#include <QIcon>
#include "thumbnaildownloader.h"
class Node;
@ -28,6 +30,10 @@ public:
void setupNodes();
bool hasChildren(const QModelIndex &parent) const override;
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
void refreshIcons();
public slots:
void updateImage(QModelIndex index, QString url, QByteArray imageData);
protected:
bool canFetchMore(const QModelIndex &parent) const override;
@ -37,6 +43,8 @@ private:
QList<QMap<QString, QVariant>> m_data;
Node *rootNode;
int zimCount = 0;
ThumbnailDownloader td;
QMap<QString, QByteArray> iconMap;
};
#endif // CONTENTMANAGERMODEL_H

View File

@ -4,6 +4,7 @@
#include <QVariant>
#include <QList>
#include "contentmanagermodel.h"
#include <QIcon>
class Node
{
@ -19,6 +20,7 @@ public:
Node *parentItem();
bool isAdditonal() const { return m_isAdditonal; }
QString getBookId() const { return m_bookId; }
void setIconData(QByteArray iconData) { m_itemData[0] = iconData; }
private:
QList<QVariant> m_itemData;

92
src/rownode.cpp Normal file
View File

@ -0,0 +1,92 @@
#include "rownode.h"
#include <QVariant>
#include "kiwixapp.h"
#include "descriptionnode.h"
#include "kiwix/tools.h"
RowNode::RowNode(QList<QVariant> itemData, QString bookId, std::weak_ptr<RowNode> parent)
: m_itemData(itemData), m_parentItem(parent), m_bookId(bookId)
{
m_downloadInfo = {0, "", "", false};
}
RowNode::~RowNode()
{}
void RowNode::appendChild(std::shared_ptr<Node> item)
{
m_childItems.append(item);
}
std::shared_ptr<Node> RowNode::child(int row)
{
if (row < 0 || row >= m_childItems.size())
return nullptr;
return m_childItems.at(row);
}
int RowNode::childCount() const
{
return m_childItems.count();
}
int RowNode::columnCount() const
{
return 6;
}
std::shared_ptr<Node> RowNode::parentItem()
{
std::shared_ptr<Node> temp = m_parentItem.lock();
return temp;
}
QVariant RowNode::data(int column)
{
if (column < 0 || column >= m_itemData.size())
return QVariant();
return m_itemData.at(column);
}
int RowNode::row() const
{
try {
std::shared_ptr<RowNode> temp = m_parentItem.lock();
return temp->m_childItems.indexOf(std::const_pointer_cast<RowNode>(shared_from_this()));
} catch(...) {
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 (...) {
bookIcon = QByteArray();
if (iconMap.contains(faviconUrl)) {
bookIcon = iconMap[faviconUrl];
}
}
std::weak_ptr<RowNode> weakRoot = rootNode;
auto rowNodePtr = std::make_shared<RowNode>(
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;
}

View File

@ -0,0 +1,52 @@
#include "thumbnaildownloader.h"
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QPixmap>
#include <QIcon>
ThumbnailDownloader::ThumbnailDownloader(QObject *parent)
{
connect(this, &ThumbnailDownloader::oneThumbnailDownloaded, [=]() {
if (m_urlPairList.size() != 0)
downloadOnePair(m_urlPairList.takeFirst());
else
m_isDownloading = false;
});
}
ThumbnailDownloader::~ThumbnailDownloader()
{
}
void ThumbnailDownloader::addDownload(QString url, QModelIndex index)
{
m_urlPairList.append({index, url});
if (!m_isDownloading)
startDownload();
}
void ThumbnailDownloader::startDownload()
{
if (m_urlPairList.size() == 0) {
m_isDownloading = false;
return;
}
m_isDownloading = true;
downloadOnePair(m_urlPairList.takeFirst());
}
void ThumbnailDownloader::downloadOnePair(QPair<QModelIndex, QString> urlPair)
{
QNetworkRequest req(urlPair.second);
auto reply = manager.get(req);
connect(reply, &QNetworkReply::finished, this, [=](){
fileDownloaded(reply, urlPair);
});
}
void ThumbnailDownloader::fileDownloaded(QNetworkReply *pReply, QPair<QModelIndex, QString> urlPair)
{
auto downloadedData = pReply->readAll();
emit oneThumbnailDownloaded(urlPair.first, urlPair.second, downloadedData);
pReply->deleteLater();
}

37
src/thumbnaildownloader.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef THUMBNAILDOWNLOADER_H
#define THUMBNAILDOWNLOADER_H
#include <QObject>
#include <QQueue>
#include <QNetworkAccessManager>
#include <QIcon>
#include <QNetworkReply>
#include <QModelIndex>
class ThumbnailDownloader : public QObject
{
Q_OBJECT
public:
ThumbnailDownloader(QObject *parent = 0);
~ThumbnailDownloader();
void addDownload(QString url, QModelIndex index);
void startDownload();
void downloadOnePair(QPair<QModelIndex, QString> urlPair);
void clearQueue() { m_urlPairList.clear(); }
signals:
void oneThumbnailDownloaded(QModelIndex, QString, QByteArray);
private:
QQueue<QPair<QModelIndex, QString>> m_urlPairList;
QNetworkAccessManager manager;
bool m_isDownloading = false;
private slots:
void fileDownloaded(QNetworkReply *pReply, QPair<QModelIndex, QString> urlPair);
};
#endif // THUMBNAILDOWNLOADER_H