[app] Speed up book loading by caching page marks
This commit is contained in:
parent
2082fbc09d
commit
ee5a949d41
1 changed files with 149 additions and 14 deletions
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2015-2017 Jolla Ltd.
|
* Copyright (C) 2015-2018 Jolla Ltd.
|
||||||
* Contact: Slava Monich <slava.monich@jolla.com>
|
* Copyright (C) 2015-2018 Slava Monich <slava.monich@jolla.com>
|
||||||
*
|
*
|
||||||
* You may use this file under the terms of the BSD license as follows:
|
* You may use this file under the terms of the BSD license as follows:
|
||||||
*
|
*
|
||||||
|
@ -41,6 +41,7 @@
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// BooksBookModel::Data
|
// BooksBookModel::Data
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
class BooksBookModel::Data {
|
class BooksBookModel::Data {
|
||||||
public:
|
public:
|
||||||
Data(int aWidth, int aHeight) : iWidth(aWidth), iHeight(aHeight) {}
|
Data(int aWidth, int aHeight) : iWidth(aWidth), iHeight(aHeight) {}
|
||||||
|
@ -61,28 +62,52 @@ class BooksBookModel::PagingTask : public BooksTask
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PagingTask(BooksBookModel* aReceiver, shared_ptr<Book> aBook);
|
static const quint32 MarksFileVersion = 1;
|
||||||
|
static const char MarksFileMagic[];
|
||||||
|
struct MarksHeader {
|
||||||
|
char magic[4];
|
||||||
|
quint32 version;
|
||||||
|
char hash[16];
|
||||||
|
qint32 fontSize;
|
||||||
|
qint32 leftMargin;
|
||||||
|
qint32 rightMargin;
|
||||||
|
qint32 topMargin;
|
||||||
|
qint32 bottomMargin;
|
||||||
|
quint32 count;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
PagingTask(BooksBookModel* aModel, shared_ptr<Book> aBook);
|
||||||
~PagingTask();
|
~PagingTask();
|
||||||
|
|
||||||
void performTask();
|
void performTask();
|
||||||
|
|
||||||
|
static QString pageMarksFile(BooksBookModel* aModel);
|
||||||
|
BooksPos::List loadPageMarks();
|
||||||
|
void savePageMarks();
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void progress(int aProgress);
|
void progress(int aProgress);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
shared_ptr<Book> iBook;
|
shared_ptr<Book> iBook;
|
||||||
shared_ptr<ZLTextStyle> iTextStyle;
|
shared_ptr<ZLTextStyle> iTextStyle;
|
||||||
BooksMargins iMargins;
|
|
||||||
BooksPaintContext iPaint;
|
BooksPaintContext iPaint;
|
||||||
|
const BooksMargins iMargins;
|
||||||
|
const QString iPageMarksFile;
|
||||||
|
const QByteArray iHash;
|
||||||
BooksBookModel::Data* iData;
|
BooksBookModel::Data* iData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const char BooksBookModel::PagingTask::MarksFileMagic[] = "MARK";
|
||||||
|
|
||||||
BooksBookModel::PagingTask::PagingTask(BooksBookModel* aModel,
|
BooksBookModel::PagingTask::PagingTask(BooksBookModel* aModel,
|
||||||
shared_ptr<Book> aBook) :
|
shared_ptr<Book> aBook) :
|
||||||
iBook(aBook),
|
iBook(aBook),
|
||||||
iTextStyle(aModel->textStyle()),
|
iTextStyle(aModel->textStyle()),
|
||||||
iMargins(aModel->margins()),
|
|
||||||
iPaint(aModel->width(), aModel->height()),
|
iPaint(aModel->width(), aModel->height()),
|
||||||
|
iMargins(aModel->margins()),
|
||||||
|
iPageMarksFile(pageMarksFile(aModel)),
|
||||||
|
iHash(aModel->book()->hash()),
|
||||||
iData(NULL)
|
iData(NULL)
|
||||||
{
|
{
|
||||||
aModel->connect(this, SIGNAL(done()), SLOT(onResetDone()));
|
aModel->connect(this, SIGNAL(done()), SLOT(onResetDone()));
|
||||||
|
@ -95,6 +120,106 @@ BooksBookModel::PagingTask::~PagingTask()
|
||||||
delete iData;
|
delete iData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString BooksBookModel::PagingTask::pageMarksFile(BooksBookModel* aModel)
|
||||||
|
{
|
||||||
|
return aModel->book()->storageFile(QString(".%1x%2.marks").
|
||||||
|
arg(aModel->width()).arg(aModel->height()));
|
||||||
|
}
|
||||||
|
|
||||||
|
BooksPos::List BooksBookModel::PagingTask::loadPageMarks()
|
||||||
|
{
|
||||||
|
BooksPos::List list;
|
||||||
|
QFile file(iPageMarksFile);
|
||||||
|
if (!iHash.isEmpty() && file.open(QIODevice::ReadOnly)) {
|
||||||
|
const qint64 size = file.size();
|
||||||
|
uchar* map = file.map(0, size);
|
||||||
|
if (map) {
|
||||||
|
HWARN("reading" << qPrintable(iPageMarksFile));
|
||||||
|
if (size > sizeof(MarksHeader)) {
|
||||||
|
const qint64 dataSize = size - sizeof(MarksHeader);
|
||||||
|
const MarksHeader* hdr = (MarksHeader*)map;
|
||||||
|
if (!memcmp(hdr->magic, MarksFileMagic, sizeof(hdr->magic)) &&
|
||||||
|
hdr->version == MarksFileVersion &&
|
||||||
|
iHash.size() == sizeof(hdr->hash) &&
|
||||||
|
!memcmp(iHash.constData(), hdr->hash, sizeof(hdr->hash)) &&
|
||||||
|
hdr->fontSize == iTextStyle->fontSize() &&
|
||||||
|
hdr->leftMargin == iMargins.iLeft &&
|
||||||
|
hdr->rightMargin == iMargins.iRight &&
|
||||||
|
hdr->topMargin == iMargins.iTop &&
|
||||||
|
hdr->bottomMargin == iMargins.iBottom &&
|
||||||
|
hdr->count > 0 && hdr->count * 12 == dataSize) {
|
||||||
|
const quint32* ptr = (quint32*)(hdr + 1);
|
||||||
|
for (quint32 i = 0; i < hdr->count; i++) {
|
||||||
|
quint32 para = *ptr++;
|
||||||
|
quint32 elem = *ptr++;
|
||||||
|
quint32 charIndex = *ptr++;
|
||||||
|
BooksPos pos(para, elem, charIndex);
|
||||||
|
if (!list.isEmpty()) {
|
||||||
|
const BooksPos& last = list.last();
|
||||||
|
if (last >= pos) {
|
||||||
|
HWARN(qPrintable(iPageMarksFile) <<
|
||||||
|
"broken order");
|
||||||
|
list.clear();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list.append(pos);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
HWARN(qPrintable(iPageMarksFile) << "header mismatch");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
HWARN(qPrintable(iPageMarksFile) << "too short");
|
||||||
|
}
|
||||||
|
file.unmap(map);
|
||||||
|
} else {
|
||||||
|
HWARN("error mapping" << qPrintable(iPageMarksFile));
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
HDEBUG("deleting" << qPrintable(iPageMarksFile));
|
||||||
|
QFile::remove(iPageMarksFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BooksBookModel::PagingTask::savePageMarks()
|
||||||
|
{
|
||||||
|
MarksHeader hdr;
|
||||||
|
HASSERT(iHash.size() == sizeof(hdr.hash));
|
||||||
|
if (iHash.size() == sizeof(hdr.hash) &&
|
||||||
|
!iData->iPageMarks.isEmpty()) {
|
||||||
|
QFile file(iPageMarksFile);
|
||||||
|
if (file.open(QIODevice::ReadWrite)) {
|
||||||
|
HWARN("writing" << qPrintable(iPageMarksFile));
|
||||||
|
const int n = iData->iPageMarks.count();
|
||||||
|
memset(&hdr, 0, sizeof(hdr));
|
||||||
|
memcpy(hdr.magic, MarksFileMagic, sizeof(hdr.magic));
|
||||||
|
hdr.version = MarksFileVersion;
|
||||||
|
memcpy(hdr.hash, iHash.constData(), sizeof(hdr.hash));
|
||||||
|
hdr.fontSize = iTextStyle->fontSize();
|
||||||
|
hdr.leftMargin = iMargins.iLeft;
|
||||||
|
hdr.rightMargin = iMargins.iRight;
|
||||||
|
hdr.topMargin = iMargins.iTop;
|
||||||
|
hdr.bottomMargin = iMargins.iBottom;
|
||||||
|
hdr.count = n;
|
||||||
|
file.write((char*)&hdr, sizeof(hdr));
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
const BooksPos& pos = iData->iPageMarks.at(i);
|
||||||
|
quint32 data[3];
|
||||||
|
data[0] = pos.iParagraphIndex;
|
||||||
|
data[1] = pos.iElementIndex;
|
||||||
|
data[2] = pos.iCharIndex;
|
||||||
|
file.write((char*)data, sizeof(data));
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
} else {
|
||||||
|
HWARN("can't open" << qPrintable(iPageMarksFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void BooksBookModel::PagingTask::performTask()
|
void BooksBookModel::PagingTask::performTask()
|
||||||
{
|
{
|
||||||
if (!isCanceled()) {
|
if (!isCanceled()) {
|
||||||
|
@ -103,16 +228,27 @@ void BooksBookModel::PagingTask::performTask()
|
||||||
shared_ptr<ZLTextModel> model(iData->iBookModel->bookTextModel());
|
shared_ptr<ZLTextModel> model(iData->iBookModel->bookTextModel());
|
||||||
ZLTextHyphenator::Instance().load(iBook->language());
|
ZLTextHyphenator::Instance().load(iBook->language());
|
||||||
if (!isCanceled()) {
|
if (!isCanceled()) {
|
||||||
BooksTextView view(iPaint, iTextStyle, iMargins);
|
// Load the cached marks
|
||||||
view.setModel(model);
|
iData->iPageMarks = loadPageMarks();
|
||||||
if (model->paragraphsNumber() > 0) {
|
if (iData->iPageMarks.isEmpty() && !isCanceled()) {
|
||||||
BooksPos mark = view.rewind();
|
// We have to do the hard way. This is going to take
|
||||||
iData->iPageMarks.append(mark);
|
// a bit of time (from tens of seconds to minutes for
|
||||||
Q_EMIT progress(iData->iPageMarks.count());
|
// large books).
|
||||||
while (!isCanceled() && view.nextPage()) {
|
BooksTextView view(iPaint, iTextStyle, iMargins);
|
||||||
mark = view.position();
|
view.setModel(model);
|
||||||
|
if (model->paragraphsNumber() > 0) {
|
||||||
|
BooksPos mark = view.rewind();
|
||||||
iData->iPageMarks.append(mark);
|
iData->iPageMarks.append(mark);
|
||||||
Q_EMIT progress(iData->iPageMarks.count());
|
Q_EMIT progress(iData->iPageMarks.count());
|
||||||
|
while (!isCanceled() && view.nextPage()) {
|
||||||
|
mark = view.position();
|
||||||
|
iData->iPageMarks.append(mark);
|
||||||
|
Q_EMIT progress(iData->iPageMarks.count());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isCanceled()) {
|
||||||
|
// Save it so that next time we won't have to do it again
|
||||||
|
savePageMarks();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -396,7 +532,6 @@ void BooksBookModel::onTextStyleChanged()
|
||||||
|
|
||||||
void BooksBookModel::startReset(ResetReason aResetReason, bool aFullReset)
|
void BooksBookModel::startReset(ResetReason aResetReason, bool aFullReset)
|
||||||
{
|
{
|
||||||
BooksPos dummy;
|
|
||||||
BooksLoadingSignalBlocker block(this);
|
BooksLoadingSignalBlocker block(this);
|
||||||
if (aResetReason == ReasonUnknown) {
|
if (aResetReason == ReasonUnknown) {
|
||||||
if (iResetReason == ReasonUnknown) {
|
if (iResetReason == ReasonUnknown) {
|
||||||
|
|
Loading…
Reference in a new issue