Skip to content

Commit 2d49d79

Browse files
authored
Merge pull request #1024 from kiwix/download_bugfixes
A couple of bugfixes in Download management
2 parents 9924570 + 341c7ed commit 2d49d79

10 files changed

+244
-112
lines changed

resources/i18n/en.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@
137137
"monitor-directory-tooltip":"All ZIM files in this directory will be automatically added to the library.",
138138
"next-tab":"Move to next tab",
139139
"previous-tab":"Move to previous tab",
140-
"cancel-download": "Cancel Download",
140+
"cancel-download": "Cancel download",
141141
"cancel-download-text": "Are you sure you want to cancel the download of <b>{{ZIM}}</b>?",
142142
"delete-book": "Delete book",
143143
"delete-book-text": "Are you sure you want to delete <b>{{ZIM}}</b>?",

src/contentmanager.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,10 @@ ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader,
7777
setLanguages();
7878
}
7979

80-
QList<QMap<QString, QVariant>> ContentManager::getBooksList()
80+
ContentManager::BookInfoList ContentManager::getBooksList()
8181
{
8282
const auto bookIds = getBookIds();
83-
QList<QMap<QString, QVariant>> bookList;
83+
BookInfoList bookList;
8484
QStringList keys = {"title", "tags", "date", "id", "size", "description", "faviconUrl"};
8585
QIcon bookIcon;
8686
for (auto bookId : bookIds) {
@@ -107,8 +107,8 @@ void ContentManager::onCustomContextMenu(const QPoint &point)
107107
QAction menuCancelBook(gt("cancel-download"), this);
108108
QAction menuOpenFolder(gt("open-folder"), this);
109109

110-
if (bookNode->isDownloading()) {
111-
if (bookNode->getDownloadInfo().paused) {
110+
if (const auto download = bookNode->getDownloadState()) {
111+
if (download->getDownloadInfo().paused) {
112112
contextMenu.addAction(&menuResumeBook);
113113
} else {
114114
contextMenu.addAction(&menuPauseBook);
@@ -216,9 +216,9 @@ void ContentManager::setLanguages()
216216
}
217217

218218
#define ADD_V(KEY, METH) {if(key==KEY) values.insert(key, QString::fromStdString((b->METH())));}
219-
QMap<QString, QVariant> ContentManager::getBookInfos(QString id, const QStringList &keys)
219+
ContentManager::BookInfo ContentManager::getBookInfos(QString id, const QStringList &keys)
220220
{
221-
QMap<QString, QVariant> values;
221+
BookInfo values;
222222
const kiwix::Book* b = [=]()->const kiwix::Book* {
223223
try {
224224
return &mp_library->getBookById(id);
@@ -681,13 +681,13 @@ void ContentManager::updateLibrary() {
681681
} catch (std::runtime_error&) {}
682682
}
683683

684-
#define CATALOG_URL "library.kiwix.org"
685684
void ContentManager::updateRemoteLibrary(const QString& content) {
686685
QtConcurrent::run([=]() {
687686
QMutexLocker locker(&remoteLibraryLocker);
688687
mp_remoteLibrary = kiwix::Library::create();
689688
kiwix::Manager manager(mp_remoteLibrary);
690-
manager.readOpds(content.toStdString(), CATALOG_URL);
689+
const auto catalogUrl = m_remoteLibraryManager.getCatalogHost();
690+
manager.readOpds(content.toStdString(), catalogUrl.toStdString());
691691
emit(this->booksChanged());
692692
emit(this->pendingRequest(false));
693693
});

src/contentmanager.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@ class ContentManager : public QObject
1717
Q_PROPERTY(QStringList downloadIds READ getDownloadIds NOTIFY downloadsChanged)
1818
Q_PROPERTY(bool isLocal MEMBER m_local READ isLocal WRITE setLocal NOTIFY localChanged)
1919

20-
public:
20+
public: // types
2121
typedef QList<QPair<QString, QString>> LanguageList;
2222
typedef QList<QPair<QString, QString>> FilterList;
23+
typedef ContentManagerModel::BookInfo BookInfo;
24+
typedef ContentManagerModel::BookInfoList BookInfoList;
25+
26+
public: // functions
2327
explicit ContentManager(Library* library, kiwix::Downloader *downloader, QObject *parent = nullptr);
2428
virtual ~ContentManager() {}
2529

@@ -51,7 +55,7 @@ class ContentManager : public QObject
5155

5256
QStringList getBookIds();
5357
void eraseBookFilesFromComputer(const QString dirPath, const QString filename, const bool moveToTrash);
54-
QList<QMap<QString, QVariant>> getBooksList();
58+
BookInfoList getBooksList();
5559
ContentManagerModel *managerModel;
5660
QMutex remoteLibraryLocker;
5761
void setCategories();
@@ -71,7 +75,7 @@ class ContentManager : public QObject
7175

7276
public slots:
7377
QStringList getTranslations(const QStringList &keys);
74-
QMap<QString, QVariant> getBookInfos(QString id, const QStringList &keys);
78+
BookInfo getBookInfos(QString id, const QStringList &keys);
7579
void openBook(const QString& id);
7680
QMap<QString, QVariant> updateDownloadInfos(QString id, const QStringList& keys);
7781
QString downloadBook(const QString& id);

src/contentmanagerdelegate.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,8 @@ void ContentManagerDelegate::paint(QPainter *painter, const QStyleOptionViewItem
177177
}
178178
QStyleOptionViewItem eOpt = option;
179179
if (index.column() == 5) {
180-
if (node->isDownloading()) {
181-
auto downloadInfo = node->getDownloadInfo();
180+
if (const auto downloadState = node->getDownloadState()) {
181+
auto downloadInfo = downloadState->getDownloadInfo();
182182
showDownloadProgress(painter, r, downloadInfo);
183183
}
184184
else {
@@ -244,8 +244,8 @@ void ContentManagerDelegate::handleLastColumnClicked(const QModelIndex& index, Q
244244
int x = r.left();
245245
int w = r.width();
246246

247-
if (node->isDownloading()) {
248-
if (node->getDownloadInfo().paused) {
247+
if (const auto downloadState = node->getDownloadState()) {
248+
if (downloadState->getDownloadInfo().paused) {
249249
if (clickX < (x + w/2)) {
250250
KiwixApp::instance()->getContentManager()->cancelBook(id, index);
251251
} else {

src/contentmanagermodel.cpp

Lines changed: 83 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <zim/error.h>
66
#include <zim/item.h>
77
#include "kiwixapp.h"
8+
#include <kiwix/tools.h>
89

910
ContentManagerModel::ContentManagerModel(QObject *parent)
1011
: QAbstractItemModel(parent)
@@ -101,33 +102,60 @@ QVariant ContentManagerModel::headerData(int section, Qt::Orientation orientatio
101102
}
102103
}
103104

104-
void ContentManagerModel::setBooksData(const QList<QMap<QString, QVariant>>& data)
105+
void ContentManagerModel::setBooksData(const BookInfoList& data)
105106
{
106107
m_data = data;
107108
rootNode = std::shared_ptr<RowNode>(new RowNode({tr("Icon"), tr("Name"), tr("Date"), tr("Size"), tr("Content Type"), tr("Download")}, "", std::weak_ptr<RowNode>()));
108109
setupNodes();
109110
emit dataChanged(QModelIndex(), QModelIndex());
110111
}
111112

112-
QString convertToUnits(QString size)
113+
std::shared_ptr<RowNode> ContentManagerModel::createNode(BookInfo bookItem, QMap<QString, QByteArray> iconMap) const
113114
{
114-
QStringList units = {"bytes", "KB", "MB", "GB", "TB", "PB", "EB"};
115-
int unitIndex = 0;
116-
auto bytes = size.toDouble();
117-
while (bytes >= 1024 && unitIndex < units.size()) {
118-
bytes /= 1024;
119-
unitIndex++;
115+
auto faviconUrl = "https://" + bookItem["faviconUrl"].toString();
116+
QString id = bookItem["id"].toString();
117+
QByteArray bookIcon;
118+
try {
119+
auto book = KiwixApp::instance()->getLibrary()->getBookById(id);
120+
std::string favicon;
121+
auto item = book.getIllustration(48);
122+
favicon = item->getData();
123+
bookIcon = QByteArray::fromRawData(reinterpret_cast<const char*>(favicon.data()), favicon.size());
124+
bookIcon.detach(); // deep copy
125+
} catch (...) {
126+
if (iconMap.contains(faviconUrl)) {
127+
bookIcon = iconMap[faviconUrl];
128+
}
120129
}
121-
122-
const auto preciseBytes = QString::number(bytes, 'g', 3);
123-
return preciseBytes + " " + units[unitIndex];
130+
std::weak_ptr<RowNode> weakRoot = rootNode;
131+
auto rowNodePtr = std::shared_ptr<RowNode>(new
132+
RowNode({bookIcon, bookItem["title"],
133+
bookItem["date"],
134+
QString::fromStdString(kiwix::beautifyFileSize(bookItem["size"].toULongLong())),
135+
bookItem["tags"]
136+
}, id, weakRoot));
137+
std::weak_ptr<RowNode> weakRowNodePtr = rowNodePtr;
138+
const auto descNodePtr = std::make_shared<DescriptionNode>(DescriptionNode(bookItem["description"].toString(), weakRowNodePtr));
139+
140+
rowNodePtr->appendChild(descNodePtr);
141+
return rowNodePtr;
124142
}
125143

126144
void ContentManagerModel::setupNodes()
127145
{
128146
beginResetModel();
147+
bookIdToRowMap.clear();
129148
for (auto bookItem : m_data) {
130-
rootNode->appendChild(RowNode::createNode(bookItem, iconMap, rootNode));
149+
const auto rowNode = createNode(bookItem, iconMap);
150+
151+
// Restore download state during model updates (filtering, etc)
152+
const auto downloadIter = m_downloads.constFind(rowNode->getBookId());
153+
if ( downloadIter != m_downloads.constEnd() ) {
154+
rowNode->setDownloadState(downloadIter.value());
155+
}
156+
157+
bookIdToRowMap[bookItem["id"].toString()] = rootNode->childCount();
158+
rootNode->appendChild(rowNode);
131159
}
132160
endResetModel();
133161
}
@@ -222,58 +250,68 @@ std::shared_ptr<RowNode> getSharedPointer(RowNode* ptr)
222250
void ContentManagerModel::startDownload(QModelIndex index)
223251
{
224252
auto node = getSharedPointer(static_cast<RowNode*>(index.internalPointer()));
225-
node->setIsDownloading(true);
226-
auto id = node->getBookId();
227-
QTimer *timer = new QTimer(this);
253+
const auto bookId = node->getBookId();
254+
const auto newDownload = std::make_shared<DownloadState>();
255+
m_downloads[bookId] = newDownload;
256+
node->setDownloadState(newDownload);
257+
QTimer *timer = newDownload->getDownloadUpdateTimer();
228258
connect(timer, &QTimer::timeout, this, [=]() {
229-
auto downloadInfos = KiwixApp::instance()->getContentManager()->updateDownloadInfos(id, {"status", "completedLength", "totalLength", "downloadSpeed"});
230-
double percent = (double) downloadInfos["completedLength"].toInt() / downloadInfos["totalLength"].toInt();
231-
percent *= 100;
232-
percent = QString::number(percent, 'g', 3).toDouble();
233-
auto completedLength = convertToUnits(downloadInfos["completedLength"].toString());
234-
auto downloadSpeed = convertToUnits(downloadInfos["downloadSpeed"].toString()) + "/s";
235-
node->setDownloadInfo({percent, completedLength, downloadSpeed});
236-
if (!downloadInfos["status"].isValid()) {
237-
node->setIsDownloading(false);
238-
timer->stop();
239-
timer->deleteLater();
240-
}
241-
emit dataChanged(index, index);
259+
updateDownload(bookId);
242260
});
243-
timer->start(1000);
244-
timers[id] = timer;
261+
}
262+
263+
void ContentManagerModel::updateDownload(QString bookId)
264+
{
265+
const auto download = m_downloads.value(bookId);
266+
267+
if ( ! download )
268+
return;
269+
270+
const bool downloadStillValid = download->update(bookId);
271+
272+
// The download->update() call above may result in
273+
// ContentManagerModel::setBooksData() being called (through a chain
274+
// of signals), which in turn will rebuild bookIdToRowMap. Hence
275+
// bookIdToRowMap access must happen after it.
276+
277+
const auto it = bookIdToRowMap.constFind(bookId);
278+
279+
if ( ! downloadStillValid ) {
280+
m_downloads.remove(bookId);
281+
if ( it != bookIdToRowMap.constEnd() ) {
282+
const size_t row = it.value();
283+
RowNode& rowNode = static_cast<RowNode&>(*rootNode->child(row));
284+
rowNode.setDownloadState(nullptr);
285+
}
286+
}
287+
288+
if ( it != bookIdToRowMap.constEnd() ) {
289+
const size_t row = it.value();
290+
const QModelIndex rootNodeIndex = this->index(0, 0);
291+
const QModelIndex newIndex = this->index(row, 5, rootNodeIndex);
292+
emit dataChanged(newIndex, newIndex);
293+
}
245294
}
246295

247296
void ContentManagerModel::pauseDownload(QModelIndex index)
248297
{
249298
auto node = static_cast<RowNode*>(index.internalPointer());
250-
auto id = node->getBookId();
251-
auto prevDownloadInfo = node->getDownloadInfo();
252-
prevDownloadInfo.paused = true;
253-
node->setDownloadInfo(prevDownloadInfo);
254-
timers[id]->stop();
299+
node->getDownloadState()->pause();
255300
emit dataChanged(index, index);
256301
}
257302

258303
void ContentManagerModel::resumeDownload(QModelIndex index)
259304
{
260305
auto node = static_cast<RowNode*>(index.internalPointer());
261-
auto id = node->getBookId();
262-
auto prevDownloadInfo = node->getDownloadInfo();
263-
prevDownloadInfo.paused = false;
264-
node->setDownloadInfo(prevDownloadInfo);
265-
timers[id]->start(1000);
306+
node->getDownloadState()->resume();
266307
emit dataChanged(index, index);
267308
}
268309

269310
void ContentManagerModel::cancelDownload(QModelIndex index)
270311
{
271312
auto node = static_cast<RowNode*>(index.internalPointer());
272-
auto id = node->getBookId();
273-
node->setIsDownloading(false);
274-
node->setDownloadInfo({0, "", "", false});
275-
timers[id]->stop();
276-
timers[id]->deleteLater();
313+
node->setDownloadState(nullptr);
314+
m_downloads.remove(node->getBookId());
277315
emit dataChanged(index, index);
278316
}
279317

src/contentmanagermodel.h

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <QVariant>
77
#include <QIcon>
88
#include "thumbnaildownloader.h"
9+
#include "rownode.h"
910
#include <memory>
1011

1112
class RowNode;
@@ -16,7 +17,11 @@ class ContentManagerModel : public QAbstractItemModel
1617
{
1718
Q_OBJECT
1819

19-
public:
20+
public: // types
21+
typedef QMap<QString, QVariant> BookInfo;
22+
typedef QList<BookInfo> BookInfoList;
23+
24+
public: // functions
2025
explicit ContentManagerModel(QObject *parent = nullptr);
2126
~ContentManagerModel();
2227

@@ -29,30 +34,36 @@ class ContentManagerModel : public QAbstractItemModel
2934
QModelIndex parent(const QModelIndex &index) const override;
3035
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
3136
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
32-
void setBooksData(const QList<QMap<QString, QVariant>>& data);
37+
void setBooksData(const BookInfoList& data);
3338
void setupNodes();
3439
bool hasChildren(const QModelIndex &parent) const override;
3540
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
3641
void refreshIcons();
3742

43+
std::shared_ptr<RowNode> createNode(BookInfo bookItem, QMap<QString, QByteArray> iconMap) const;
44+
3845
public slots:
3946
void updateImage(QModelIndex index, QString url, QByteArray imageData);
4047
void startDownload(QModelIndex index);
4148
void pauseDownload(QModelIndex index);
4249
void resumeDownload(QModelIndex index);
4350
void cancelDownload(QModelIndex index);
4451

45-
protected:
52+
protected: // functions
4653
bool canFetchMore(const QModelIndex &parent) const override;
4754
void fetchMore(const QModelIndex &parent) override;
4855

49-
private:
50-
QList<QMap<QString, QVariant>> m_data;
56+
private: // functions
57+
void updateDownload(QString bookId);
58+
59+
private: // data
60+
BookInfoList m_data;
5161
std::shared_ptr<RowNode> rootNode;
5262
int zimCount = 0;
5363
ThumbnailDownloader td;
64+
QMap<QString, size_t> bookIdToRowMap;
5465
QMap<QString, QByteArray> iconMap;
55-
QMap<QString, QTimer*> timers;
66+
QMap<QString, std::shared_ptr<DownloadState>> m_downloads;
5667
};
5768

5869
#endif // CONTENTMANAGERMODEL_H

0 commit comments

Comments
 (0)