diff --git a/src/qtquick/BookListModel.cpp b/src/qtquick/BookListModel.cpp index 1c8f4cb..9e0a257 100644 --- a/src/qtquick/BookListModel.cpp +++ b/src/qtquick/BookListModel.cpp @@ -1,297 +1,320 @@ /* * Copyright (C) 2015 Dan Leinir Turthra Jensen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ #include "BookListModel.h" #include "BookDatabase.h" #include "CategoryEntriesModel.h" #include #include #include #include #include +#include #include class BookListModel::Private { public: Private() : contentModel(0) , titleCategoryModel(0) , newlyAddedCategoryModel(0) , authorCategoryModel(0) , seriesCategoryModel(0) , folderCategoryModel(0) + , cacheLoaded(false) { db = new BookDatabase(); }; ~Private() { qDeleteAll(entries); db->deleteLater(); } QList entries; QAbstractListModel* contentModel; CategoryEntriesModel* titleCategoryModel; CategoryEntriesModel* newlyAddedCategoryModel; CategoryEntriesModel* authorCategoryModel; CategoryEntriesModel* seriesCategoryModel; CategoryEntriesModel* folderCategoryModel; BookDatabase* db; + bool cacheLoaded; void initializeSubModels(BookListModel* q) { if(!titleCategoryModel) { titleCategoryModel = new CategoryEntriesModel(q); connect(q, SIGNAL(entryDataUpdated(BookEntry*)), titleCategoryModel, SIGNAL(entryDataUpdated(BookEntry*))); connect(q, SIGNAL(entryRemoved(BookEntry*)), titleCategoryModel, SIGNAL(entryRemoved(BookEntry*))); emit q->titleCategoryModelChanged(); } if(!newlyAddedCategoryModel) { newlyAddedCategoryModel = new CategoryEntriesModel(q); connect(q, SIGNAL(entryDataUpdated(BookEntry*)), newlyAddedCategoryModel, SIGNAL(entryDataUpdated(BookEntry*))); connect(q, SIGNAL(entryRemoved(BookEntry*)), newlyAddedCategoryModel, SIGNAL(entryRemoved(BookEntry*))); emit q->newlyAddedCategoryModelChanged(); } if(!authorCategoryModel) { authorCategoryModel = new CategoryEntriesModel(q); connect(q, SIGNAL(entryDataUpdated(BookEntry*)), authorCategoryModel, SIGNAL(entryDataUpdated(BookEntry*))); connect(q, SIGNAL(entryRemoved(BookEntry*)), authorCategoryModel, SIGNAL(entryRemoved(BookEntry*))); emit q->authorCategoryModelChanged(); } if(!seriesCategoryModel) { seriesCategoryModel = new CategoryEntriesModel(q); connect(q, SIGNAL(entryDataUpdated(BookEntry*)), seriesCategoryModel, SIGNAL(entryDataUpdated(BookEntry*))); connect(q, SIGNAL(entryRemoved(BookEntry*)), seriesCategoryModel, SIGNAL(entryRemoved(BookEntry*))); emit q->seriesCategoryModelChanged(); } if(!folderCategoryModel) { folderCategoryModel = new CategoryEntriesModel(q); connect(q, SIGNAL(entryDataUpdated(BookEntry*)), folderCategoryModel, SIGNAL(entryDataUpdated(BookEntry*))); connect(q, SIGNAL(entryRemoved(BookEntry*)), folderCategoryModel, SIGNAL(entryRemoved(BookEntry*))); emit q->folderCategoryModel(); } } void addEntry(BookListModel* q, BookEntry* entry) { entries.append(entry); q->append(entry); titleCategoryModel->addCategoryEntry(entry->title.left(1).toUpper(), entry); authorCategoryModel->addCategoryEntry(entry->author, entry); seriesCategoryModel->addCategoryEntry(entry->series, entry); newlyAddedCategoryModel->append(entry, CreatedRole); QUrl url(entry->filename.left(entry->filename.lastIndexOf("/"))); folderCategoryModel->addCategoryEntry(url.path().mid(1), entry); folderCategoryModel->append(entry); } + + void loadCache(BookListModel* q) { + QList entries = db->loadEntries(); + if(entries.count() > 0) + { + initializeSubModels(q); + } + int i = 0; + foreach(BookEntry* entry, entries) + { + addEntry(q, entry); + if(++i % 100 == 0) { + emit q->countChanged(); + qApp->processEvents(); + } + } + cacheLoaded = true; + emit q->cacheLoadedChanged(); + } }; BookListModel::BookListModel(QObject* parent) : CategoryEntriesModel(parent) , d(new Private) { - QList entries = d->db->loadEntries(); - if(entries.count() > 0) - { - d->initializeSubModels(this); - } - foreach(BookEntry* entry, entries) - { - d->addEntry(this, entry); - } } BookListModel::~BookListModel() { delete d; } +void BookListModel::componentComplete() +{ + QTimer::singleShot(0, this, [this](){ d->loadCache(this); }); +} + +bool BookListModel::cacheLoaded() const +{ + return d->cacheLoaded; +} + void BookListModel::setContentModel(QObject* newModel) { if(d->contentModel) { d->contentModel->disconnect(this); } d->contentModel = qobject_cast(newModel); if(d->contentModel) { connect(d->contentModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(contentModelItemsInserted(QModelIndex,int, int))); } emit contentModelChanged(); } QObject * BookListModel::contentModel() const { return d->contentModel; } void BookListModel::contentModelItemsInserted(QModelIndex index, int first, int last) { d->initializeSubModels(this); int newRow = d->entries.count(); beginInsertRows(QModelIndex(), newRow, newRow + (last - first)); for(int i = first; i < last + 1; ++i) { QVariant filename = d->contentModel->data(d->contentModel->index(first, 0, index), Qt::UserRole + 1); BookEntry* entry = new BookEntry(); entry->filename = filename.toString(); QStringList splitName = entry->filename.split("/"); if (!splitName.isEmpty()) entry->filetitle = splitName.takeLast(); if(!splitName.isEmpty()) entry->series = splitName.takeLast(); // hahahaheuristics (dumb assumptions about filesystems, go!) // just in case we end up without a title... using complete basename here, // as we would rather have "book one. part two" and the odd "book one - part two.tar" QFileInfo fileinfo(entry->filename); entry->title = fileinfo.completeBaseName(); if(entry->filename.toLower().endsWith("cbr")) { entry->thumbnail = QString("image://comiccover/").append(entry->filename); } #ifdef USE_PERUSE_PDFTHUMBNAILER else if(entry->filename.toLower().endsWith("pdf")) { entry->thumbnail = QString("image://pdfcover/").append(entry->filename); } #endif else { entry->thumbnail = QString("image://preview/").append(entry->filename); } QVariantHash metadata = d->contentModel->data(d->contentModel->index(first, 0, index), Qt::UserRole + 2).toHash(); QVariantHash::const_iterator it = metadata.constBegin(); for (; it != metadata.constEnd(); it++) { if(it.key() == QLatin1String("author")) { entry->author = it.value().toString().trimmed(); } else if(it.key() == QLatin1String("title")) { entry->title = it.value().toString().trimmed(); } else if(it.key() == QLatin1String("publisher")) { entry->publisher = it.value().toString().trimmed(); } else if(it.key() == QLatin1String("created")) { entry->created = it.value().toDateTime(); } else if(it.key() == QLatin1String("currentPage")) { entry->currentPage = it.value().toInt(); } else if(it.key() == QLatin1String("totalPages")) { entry->totalPages = it.value().toInt(); } } d->addEntry(this, entry); d->db->addEntry(entry); } endInsertRows(); emit countChanged(); qApp->processEvents(); } QObject * BookListModel::titleCategoryModel() const { return d->titleCategoryModel; } QObject * BookListModel::newlyAddedCategoryModel() const { return d->newlyAddedCategoryModel; } QObject * BookListModel::authorCategoryModel() const { return d->authorCategoryModel; } QObject * BookListModel::seriesCategoryModel() const { return d->seriesCategoryModel; } QObject * BookListModel::seriesModelForEntry(QString fileName) { Q_FOREACH(BookEntry* entry, d->entries) { if(entry->filename == fileName) { return d->seriesCategoryModel->leafModelForEntry(entry); } } return 0; } QObject * BookListModel::folderCategoryModel() const { return d->folderCategoryModel; } int BookListModel::count() const { return d->entries.count(); } void BookListModel::setBookData(QString fileName, QString property, QString value) { Q_FOREACH(BookEntry* entry, d->entries) { if(entry->filename == fileName) { if(property == "totalPages") { entry->totalPages = value.toInt(); } else if(property == "currentPage") { entry->currentPage = value.toInt(); } emit entryDataUpdated(entry); break; } } } void BookListModel::removeBook(QString fileName, bool deleteFile) { if(deleteFile) { KIO::DeleteJob* job = KIO::del(QUrl::fromLocalFile(fileName), KIO::HideProgressInfo); job->start(); } Q_FOREACH(BookEntry* entry, d->entries) { if(entry->filename == fileName) { emit entryRemoved(entry); delete entry; break; } } } QStringList BookListModel::knownBookFiles() const { QStringList files; foreach(BookEntry* entry, d->entries) { files.append(entry->filename); } return files; } diff --git a/src/qtquick/BookListModel.h b/src/qtquick/BookListModel.h index 0c7f236..2c86c4e 100644 --- a/src/qtquick/BookListModel.h +++ b/src/qtquick/BookListModel.h @@ -1,92 +1,101 @@ /* * Copyright (C) 2015 Dan Leinir Turthra Jensen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ #ifndef BOOKLISTMODEL_H #define BOOKLISTMODEL_H #include "CategoryEntriesModel.h" +#include -class BookListModel : public CategoryEntriesModel +class BookListModel : public CategoryEntriesModel, public QQmlParserStatus { Q_OBJECT Q_PROPERTY(int count READ count NOTIFY countChanged) Q_PROPERTY(QObject* contentModel READ contentModel WRITE setContentModel NOTIFY contentModelChanged) Q_PROPERTY(QObject* newlyAddedCategoryModel READ newlyAddedCategoryModel NOTIFY newlyAddedCategoryModelChanged) Q_PROPERTY(QObject* titleCategoryModel READ titleCategoryModel NOTIFY titleCategoryModelChanged) Q_PROPERTY(QObject* authorCategoryModel READ authorCategoryModel NOTIFY authorCategoryModelChanged) Q_PROPERTY(QObject* seriesCategoryModel READ seriesCategoryModel NOTIFY seriesCategoryModelChanged) Q_PROPERTY(QObject* folderCategoryModel READ folderCategoryModel NOTIFY folderCategoryModelChanged) + Q_PROPERTY(bool cacheLoaded READ cacheLoaded NOTIFY cacheLoadedChanged) Q_ENUMS(Grouping) + Q_INTERFACES(QQmlParserStatus) public: explicit BookListModel(QObject* parent = 0); virtual ~BookListModel(); + virtual void classBegin() override {}; + virtual void componentComplete() override; + enum Grouping { GroupByNone = 0, GroupByRecentlyAdded, GroupByRecentlyRead, GroupByTitle, GroupByAuthor, GroupByPublisher }; QObject* contentModel() const; void setContentModel(QObject* newModel); Q_SIGNAL void contentModelChanged(); int count() const; Q_SIGNAL void countChanged(); QObject* titleCategoryModel() const; Q_SIGNAL void titleCategoryModelChanged(); QObject* newlyAddedCategoryModel() const; Q_SIGNAL void newlyAddedCategoryModelChanged(); QObject* authorCategoryModel() const; Q_SIGNAL void authorCategoryModelChanged(); QObject* seriesCategoryModel() const; Q_SIGNAL void seriesCategoryModelChanged(); // Returns the leaf model representing the series the entry with the passed URL is a part of // Base assumption: A book is only part of one series. This is not always true, but not sure how // to sensibly represent that Q_INVOKABLE QObject* seriesModelForEntry(QString fileName); QObject* folderCategoryModel() const; Q_SIGNAL void folderCategoryModelChanged(); + bool cacheLoaded() const; + Q_SIGNAL void cacheLoadedChanged(); + // Update the data of a book at runtime - in particular, we need to update totalPages and currentPage Q_INVOKABLE void setBookData(QString fileName, QString property, QString value); // Delete a book from the model, and optionally delete the entry from file storage Q_INVOKABLE void removeBook(QString fileName, bool deleteFile = false); // A list of the files currently known by the applications Q_INVOKABLE QStringList knownBookFiles() const; private: class Private; Private* d; Q_SLOT void contentModelItemsInserted(QModelIndex index,int first, int last); }; #endif//BOOKLISTMODEL_H