|
5 | 5 | #include <zim/error.h>
|
6 | 6 | #include <zim/item.h>
|
7 | 7 | #include "kiwixapp.h"
|
| 8 | +#include <kiwix/tools.h> |
8 | 9 |
|
9 | 10 | ContentManagerModel::ContentManagerModel(QObject *parent)
|
10 | 11 | : QAbstractItemModel(parent)
|
@@ -101,33 +102,60 @@ QVariant ContentManagerModel::headerData(int section, Qt::Orientation orientatio
|
101 | 102 | }
|
102 | 103 | }
|
103 | 104 |
|
104 |
| -void ContentManagerModel::setBooksData(const QList<QMap<QString, QVariant>>& data) |
| 105 | +void ContentManagerModel::setBooksData(const BookInfoList& data) |
105 | 106 | {
|
106 | 107 | m_data = data;
|
107 | 108 | rootNode = std::shared_ptr<RowNode>(new RowNode({tr("Icon"), tr("Name"), tr("Date"), tr("Size"), tr("Content Type"), tr("Download")}, "", std::weak_ptr<RowNode>()));
|
108 | 109 | setupNodes();
|
109 | 110 | emit dataChanged(QModelIndex(), QModelIndex());
|
110 | 111 | }
|
111 | 112 |
|
112 |
| -QString convertToUnits(QString size) |
| 113 | +std::shared_ptr<RowNode> ContentManagerModel::createNode(BookInfo bookItem, QMap<QString, QByteArray> iconMap) const |
113 | 114 | {
|
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 | + } |
120 | 129 | }
|
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; |
124 | 142 | }
|
125 | 143 |
|
126 | 144 | void ContentManagerModel::setupNodes()
|
127 | 145 | {
|
128 | 146 | beginResetModel();
|
| 147 | + bookIdToRowMap.clear(); |
129 | 148 | 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); |
131 | 159 | }
|
132 | 160 | endResetModel();
|
133 | 161 | }
|
@@ -222,58 +250,68 @@ std::shared_ptr<RowNode> getSharedPointer(RowNode* ptr)
|
222 | 250 | void ContentManagerModel::startDownload(QModelIndex index)
|
223 | 251 | {
|
224 | 252 | 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(); |
228 | 258 | 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); |
242 | 260 | });
|
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 | + } |
245 | 294 | }
|
246 | 295 |
|
247 | 296 | void ContentManagerModel::pauseDownload(QModelIndex index)
|
248 | 297 | {
|
249 | 298 | 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(); |
255 | 300 | emit dataChanged(index, index);
|
256 | 301 | }
|
257 | 302 |
|
258 | 303 | void ContentManagerModel::resumeDownload(QModelIndex index)
|
259 | 304 | {
|
260 | 305 | 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(); |
266 | 307 | emit dataChanged(index, index);
|
267 | 308 | }
|
268 | 309 |
|
269 | 310 | void ContentManagerModel::cancelDownload(QModelIndex index)
|
270 | 311 | {
|
271 | 312 | 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()); |
277 | 315 | emit dataChanged(index, index);
|
278 | 316 | }
|
279 | 317 |
|
0 commit comments