[app] Handle the case if hash can't be stored as fs attr (e.g. SD-card)

Assume that the hash from page marks file is correct and verify it
later when the file hash gets recalculated.
This commit is contained in:
Slava Monich 2018-02-02 11:50:45 +03:00
parent 01987a8fa5
commit 28a814dd26
2 changed files with 100 additions and 34 deletions

View file

@ -33,6 +33,7 @@
#include "BooksBookModel.h" #include "BooksBookModel.h"
#include "BooksTextStyle.h" #include "BooksTextStyle.h"
#include "BooksUtil.h"
#include "HarbourDebug.h" #include "HarbourDebug.h"
@ -51,6 +52,7 @@ public:
int iHeight; int iHeight;
shared_ptr<BookModel> iBookModel; shared_ptr<BookModel> iBookModel;
BooksPos::List iPageMarks; BooksPos::List iPageMarks;
QByteArray iHash;
}; };
// ========================================================================== // ==========================================================================
@ -82,8 +84,9 @@ public:
void performTask(); void performTask();
static QString pageMarksFile(BooksBookModel* aModel); static QString pageMarksFile(BooksBookModel* aModel);
BooksPos::List loadPageMarks(); BooksPos::List loadPageMarks() const;
void savePageMarks(); bool acceptHash(const MarksHeader* aHeader) const;
void savePageMarks() const;
Q_SIGNALS: Q_SIGNALS:
void progress(int aProgress); void progress(int aProgress);
@ -95,7 +98,8 @@ public:
const BooksMargins iMargins; const BooksMargins iMargins;
const QString iPageMarksFile; const QString iPageMarksFile;
const QByteArray iHash; const QByteArray iHash;
BooksBookModel::Data* iData; const QString iPath;
Data* iData;
}; };
const char BooksBookModel::PagingTask::MarksFileMagic[] = "MARK"; const char BooksBookModel::PagingTask::MarksFileMagic[] = "MARK";
@ -108,6 +112,7 @@ BooksBookModel::PagingTask::PagingTask(BooksBookModel* aModel,
iMargins(aModel->margins()), iMargins(aModel->margins()),
iPageMarksFile(pageMarksFile(aModel)), iPageMarksFile(pageMarksFile(aModel)),
iHash(aModel->book()->hash()), iHash(aModel->book()->hash()),
iPath(aModel->book()->path()),
iData(NULL) iData(NULL)
{ {
aModel->connect(this, SIGNAL(done()), SLOT(onResetDone())); aModel->connect(this, SIGNAL(done()), SLOT(onResetDone()));
@ -126,11 +131,20 @@ QString BooksBookModel::PagingTask::pageMarksFile(BooksBookModel* aModel)
arg(aModel->width()).arg(aModel->height())); arg(aModel->width()).arg(aModel->height()));
} }
BooksPos::List BooksBookModel::PagingTask::loadPageMarks() bool BooksBookModel::PagingTask::acceptHash(const MarksHeader* aHeader) const
{
// If the real hash is unknown, we accept any. The real one will
// be later compared with the one we fetch from the .marks file
return iHash.isEmpty() ||
(iHash.size() == sizeof(aHeader->hash) &&
!memcmp(iHash.constData(), aHeader->hash, sizeof(aHeader->hash)));
}
BooksPos::List BooksBookModel::PagingTask::loadPageMarks() const
{ {
BooksPos::List list; BooksPos::List list;
QFile file(iPageMarksFile); QFile file(iPageMarksFile);
if (!iHash.isEmpty() && file.open(QIODevice::ReadOnly)) { if (file.open(QIODevice::ReadOnly)) {
const qint64 size = file.size(); const qint64 size = file.size();
uchar* map = file.map(0, size); uchar* map = file.map(0, size);
if (map) { if (map) {
@ -140,8 +154,7 @@ BooksPos::List BooksBookModel::PagingTask::loadPageMarks()
const MarksHeader* hdr = (MarksHeader*)map; const MarksHeader* hdr = (MarksHeader*)map;
if (!memcmp(hdr->magic, MarksFileMagic, sizeof(hdr->magic)) && if (!memcmp(hdr->magic, MarksFileMagic, sizeof(hdr->magic)) &&
hdr->version == MarksFileVersion && hdr->version == MarksFileVersion &&
iHash.size() == sizeof(hdr->hash) && acceptHash(hdr) &&
!memcmp(iHash.constData(), hdr->hash, sizeof(hdr->hash)) &&
hdr->fontSize == iTextStyle->fontSize() && hdr->fontSize == iTextStyle->fontSize() &&
hdr->leftMargin == iMargins.iLeft && hdr->leftMargin == iMargins.iLeft &&
hdr->rightMargin == iMargins.iRight && hdr->rightMargin == iMargins.iRight &&
@ -184,20 +197,30 @@ BooksPos::List BooksBookModel::PagingTask::loadPageMarks()
return list; return list;
} }
void BooksBookModel::PagingTask::savePageMarks() void BooksBookModel::PagingTask::savePageMarks() const
{ {
MarksHeader hdr; MarksHeader hdr;
HASSERT(iHash.size() == sizeof(hdr.hash)); QByteArray hash(iData->iHash);
if (iHash.size() == sizeof(hdr.hash) && if (hash.size() == sizeof(hdr.hash) &&
!iData->iPageMarks.isEmpty()) { !iData->iPageMarks.isEmpty() &&
!isCanceled()) {
QFile file(iPageMarksFile); QFile file(iPageMarksFile);
if (file.open(QIODevice::ReadWrite)) { bool opened = file.open(QIODevice::ReadWrite);
if (!opened) {
// Most likely, the directory doesn't exist
QDir dir = QFileInfo(iPageMarksFile).dir();
if (dir.mkpath(dir.path())) {
HDEBUG("created" << qPrintable(dir.path()));
opened = file.open(QIODevice::ReadWrite);
}
}
if (opened) {
HDEBUG("writing" << qPrintable(iPageMarksFile)); HDEBUG("writing" << qPrintable(iPageMarksFile));
const int n = iData->iPageMarks.count(); const int n = iData->iPageMarks.count();
memset(&hdr, 0, sizeof(hdr)); memset(&hdr, 0, sizeof(hdr));
memcpy(hdr.magic, MarksFileMagic, sizeof(hdr.magic)); memcpy(hdr.magic, MarksFileMagic, sizeof(hdr.magic));
hdr.version = MarksFileVersion; hdr.version = MarksFileVersion;
memcpy(hdr.hash, iHash.constData(), sizeof(hdr.hash)); memcpy(hdr.hash, hash.constData(), sizeof(hdr.hash));
hdr.fontSize = iTextStyle->fontSize(); hdr.fontSize = iTextStyle->fontSize();
hdr.leftMargin = iMargins.iLeft; hdr.leftMargin = iMargins.iLeft;
hdr.rightMargin = iMargins.iRight; hdr.rightMargin = iMargins.iRight;
@ -223,11 +246,17 @@ void BooksBookModel::PagingTask::savePageMarks()
void BooksBookModel::PagingTask::performTask() void BooksBookModel::PagingTask::performTask()
{ {
if (!isCanceled()) { if (!isCanceled()) {
iData = new BooksBookModel::Data(iPaint.width(), iPaint.height()); iData = new Data(iPaint.width(), iPaint.height());
iData->iBookModel = new BookModel(iBook); iData->iBookModel = new BookModel(iBook);
iData->iHash = iHash;
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 (iData->iHash.isEmpty() && !isCanceled()) {
// If hash is unknown then we need to compute it here and now.
// It's a very rare occasion though.
iData->iHash = BooksUtil::computeFileHashAndSetAttr(iPath, this);
}
if (!iData->iHash.isEmpty() && !isCanceled()) {
// Load the cached marks // Load the cached marks
iData->iPageMarks = loadPageMarks(); iData->iPageMarks = loadPageMarks();
if (iData->iPageMarks.isEmpty() && !isCanceled()) { if (iData->iPageMarks.isEmpty() && !isCanceled()) {
@ -321,6 +350,7 @@ void BooksBookModel::setBook(BooksBook* aBook)
iTextStyle = iSettings->textStyle(fontSizeAdjust()); iTextStyle = iSettings->textStyle(fontSizeAdjust());
iPageStack->setStack(aBook->pageStack(), aBook->pageStackPos()); iPageStack->setStack(aBook->pageStack(), aBook->pageStackPos());
connect(aBook, SIGNAL(fontSizeAdjustChanged()), SLOT(onTextStyleChanged())); connect(aBook, SIGNAL(fontSizeAdjustChanged()), SLOT(onTextStyleChanged()));
connect(aBook, SIGNAL(hashChanged()), SLOT(onHashChanged()));
HDEBUG(iTitle); HDEBUG(iTitle);
} else { } else {
iBook = NULL; iBook = NULL;
@ -363,6 +393,34 @@ void BooksBookModel::onPageStackChanged()
} }
} }
void BooksBookModel::onHashChanged()
{
const QByteArray hash(iBook->hash());
HDEBUG(QString(hash.toHex()));
if (!hash.isEmpty()) {
if (iData2 && iData2->iHash != hash) {
// There is no need to delete the stale file - it will be deleted
// by the paging task. Deleting files on the UI thread is not a
// very bright idea - the call may block for quite some time.
delete iData2;
iData2 = NULL;
}
if (iPagingTask &&
!iPagingTask->iHash.isEmpty() &&
iPagingTask->iHash != hash) {
iPagingTask->release(this);
iPagingTask = NULL;
startReset(iResetReason);
} else if (iData && iData->iHash != hash) {
delete iData;
iData = NULL;
startReset(ReasonLoading);
} else {
HDEBUG("we are all set!");
}
}
}
int BooksBookModel::pageCount() const int BooksBookModel::pageCount() const
{ {
return iData ? iData->iPageMarks.count() : 0; return iData ? iData->iPageMarks.count() : 0;
@ -600,25 +658,32 @@ void BooksBookModel::onResetDone()
HASSERT(iPagingTask->iData); HASSERT(iPagingTask->iData);
HASSERT(!iData); HASSERT(!iData);
const int oldPageCount(pageCount()); const QByteArray hash(iBook->hash());
shared_ptr<BookModel> oldBookModel(bookModel()); if (hash.isEmpty() || iPagingTask->iData->iHash == hash) {
BooksLoadingSignalBlocker block(this); const int oldPageCount(pageCount());
shared_ptr<BookModel> oldBookModel(bookModel());
BooksLoadingSignalBlocker block(this);
iData = iPagingTask->iData; iData = iPagingTask->iData;
iPagingTask->iData = NULL; iPagingTask->iData = NULL;
iPagingTask->release(this); iPagingTask->release(this);
iPagingTask = NULL; iPagingTask = NULL;
updateModel(oldPageCount); updateModel(oldPageCount);
iPageStack->setPageMarks(iData->iPageMarks); iPageStack->setPageMarks(iData->iPageMarks);
Q_EMIT jumpToPage(iPageStack->currentPage()); Q_EMIT jumpToPage(iPageStack->currentPage());
Q_EMIT pageMarksChanged(); Q_EMIT pageMarksChanged();
if (oldBookModel != bookModel()) { if (oldBookModel != bookModel()) {
Q_EMIT bookModelChanged(); Q_EMIT bookModelChanged();
} }
if (iResetReason != ReasonUnknown) { if (iResetReason != ReasonUnknown) {
iResetReason = ReasonUnknown; iResetReason = ReasonUnknown;
Q_EMIT resetReasonChanged(); Q_EMIT resetReasonChanged();
}
} else {
HDEBUG("oops");
iPagingTask->release(this);
iPagingTask = NULL;
} }
} }

View file

@ -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:
* *
@ -139,6 +139,7 @@ private Q_SLOTS:
void onResetDone(); void onResetDone();
void onTextStyleChanged(); void onTextStyleChanged();
void onPageStackChanged(); void onPageStackChanged();
void onHashChanged();
Q_SIGNALS: Q_SIGNALS:
void sizeChanged(); void sizeChanged();