[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 "BooksTextStyle.h"
#include "BooksUtil.h"
#include "HarbourDebug.h"
@ -51,6 +52,7 @@ public:
int iHeight;
shared_ptr<BookModel> iBookModel;
BooksPos::List iPageMarks;
QByteArray iHash;
};
// ==========================================================================
@ -82,8 +84,9 @@ public:
void performTask();
static QString pageMarksFile(BooksBookModel* aModel);
BooksPos::List loadPageMarks();
void savePageMarks();
BooksPos::List loadPageMarks() const;
bool acceptHash(const MarksHeader* aHeader) const;
void savePageMarks() const;
Q_SIGNALS:
void progress(int aProgress);
@ -95,7 +98,8 @@ public:
const BooksMargins iMargins;
const QString iPageMarksFile;
const QByteArray iHash;
BooksBookModel::Data* iData;
const QString iPath;
Data* iData;
};
const char BooksBookModel::PagingTask::MarksFileMagic[] = "MARK";
@ -108,6 +112,7 @@ BooksBookModel::PagingTask::PagingTask(BooksBookModel* aModel,
iMargins(aModel->margins()),
iPageMarksFile(pageMarksFile(aModel)),
iHash(aModel->book()->hash()),
iPath(aModel->book()->path()),
iData(NULL)
{
aModel->connect(this, SIGNAL(done()), SLOT(onResetDone()));
@ -126,11 +131,20 @@ QString BooksBookModel::PagingTask::pageMarksFile(BooksBookModel* aModel)
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;
QFile file(iPageMarksFile);
if (!iHash.isEmpty() && file.open(QIODevice::ReadOnly)) {
if (file.open(QIODevice::ReadOnly)) {
const qint64 size = file.size();
uchar* map = file.map(0, size);
if (map) {
@ -140,8 +154,7 @@ BooksPos::List BooksBookModel::PagingTask::loadPageMarks()
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)) &&
acceptHash(hdr) &&
hdr->fontSize == iTextStyle->fontSize() &&
hdr->leftMargin == iMargins.iLeft &&
hdr->rightMargin == iMargins.iRight &&
@ -184,20 +197,30 @@ BooksPos::List BooksBookModel::PagingTask::loadPageMarks()
return list;
}
void BooksBookModel::PagingTask::savePageMarks()
void BooksBookModel::PagingTask::savePageMarks() const
{
MarksHeader hdr;
HASSERT(iHash.size() == sizeof(hdr.hash));
if (iHash.size() == sizeof(hdr.hash) &&
!iData->iPageMarks.isEmpty()) {
QByteArray hash(iData->iHash);
if (hash.size() == sizeof(hdr.hash) &&
!iData->iPageMarks.isEmpty() &&
!isCanceled()) {
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));
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));
memcpy(hdr.hash, hash.constData(), sizeof(hdr.hash));
hdr.fontSize = iTextStyle->fontSize();
hdr.leftMargin = iMargins.iLeft;
hdr.rightMargin = iMargins.iRight;
@ -223,11 +246,17 @@ void BooksBookModel::PagingTask::savePageMarks()
void BooksBookModel::PagingTask::performTask()
{
if (!isCanceled()) {
iData = new BooksBookModel::Data(iPaint.width(), iPaint.height());
iData = new Data(iPaint.width(), iPaint.height());
iData->iBookModel = new BookModel(iBook);
iData->iHash = iHash;
shared_ptr<ZLTextModel> model(iData->iBookModel->bookTextModel());
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
iData->iPageMarks = loadPageMarks();
if (iData->iPageMarks.isEmpty() && !isCanceled()) {
@ -321,6 +350,7 @@ void BooksBookModel::setBook(BooksBook* aBook)
iTextStyle = iSettings->textStyle(fontSizeAdjust());
iPageStack->setStack(aBook->pageStack(), aBook->pageStackPos());
connect(aBook, SIGNAL(fontSizeAdjustChanged()), SLOT(onTextStyleChanged()));
connect(aBook, SIGNAL(hashChanged()), SLOT(onHashChanged()));
HDEBUG(iTitle);
} else {
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
{
return iData ? iData->iPageMarks.count() : 0;
@ -600,6 +658,8 @@ void BooksBookModel::onResetDone()
HASSERT(iPagingTask->iData);
HASSERT(!iData);
const QByteArray hash(iBook->hash());
if (hash.isEmpty() || iPagingTask->iData->iHash == hash) {
const int oldPageCount(pageCount());
shared_ptr<BookModel> oldBookModel(bookModel());
BooksLoadingSignalBlocker block(this);
@ -620,6 +680,11 @@ void BooksBookModel::onResetDone()
iResetReason = ReasonUnknown;
Q_EMIT resetReasonChanged();
}
} else {
HDEBUG("oops");
iPagingTask->release(this);
iPagingTask = NULL;
}
}
QHash<int,QByteArray> BooksBookModel::roleNames() const

View file

@ -1,6 +1,6 @@
/*
* Copyright (C) 2015-2017 Jolla Ltd.
* Contact: Slava Monich <slava.monich@jolla.com>
* Copyright (C) 2015-2018 Jolla Ltd.
* Copyright (C) 2015-2018 Slava Monich <slava.monich@jolla.com>
*
* You may use this file under the terms of the BSD license as follows:
*
@ -139,6 +139,7 @@ private Q_SLOTS:
void onResetDone();
void onTextStyleChanged();
void onPageStackChanged();
void onHashChanged();
Q_SIGNALS:
void sizeChanged();