2015-06-28 14:22:35 +03:00
|
|
|
/*
|
2018-01-27 03:03:08 +03:00
|
|
|
* Copyright (C) 2015-2018 Jolla Ltd.
|
|
|
|
* Copyright (C) 2015-2018 Slava Monich <slava.monich@jolla.com>
|
2015-06-28 14:22:35 +03:00
|
|
|
*
|
|
|
|
* You may use this file under the terms of the BSD license as follows:
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
*
|
|
|
|
* * Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in
|
|
|
|
* the documentation and/or other materials provided with the
|
|
|
|
* distribution.
|
2017-08-03 18:48:17 +03:00
|
|
|
* * Neither the name of Jolla Ltd nor the names of its contributors
|
2015-06-28 14:22:35 +03:00
|
|
|
* may be used to endorse or promote products derived from this
|
|
|
|
* software without specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "BooksBook.h"
|
|
|
|
#include "BooksDefs.h"
|
|
|
|
#include "BooksTextView.h"
|
|
|
|
#include "BooksTextStyle.h"
|
|
|
|
#include "BooksPaintContext.h"
|
2015-12-13 14:46:53 +03:00
|
|
|
#include "BooksSettings.h"
|
2015-12-12 11:36:22 +03:00
|
|
|
#include "BooksUtil.h"
|
2015-06-28 14:22:35 +03:00
|
|
|
|
|
|
|
#include "HarbourJson.h"
|
|
|
|
#include "HarbourDebug.h"
|
2018-05-16 18:27:12 +03:00
|
|
|
#include "HarbourTask.h"
|
2015-06-28 14:22:35 +03:00
|
|
|
|
|
|
|
#include "ZLImage.h"
|
|
|
|
#include "image/ZLQtImageManager.h"
|
|
|
|
#include "bookmodel/BookModel.h"
|
|
|
|
#include "library/Author.h"
|
|
|
|
|
2015-12-12 11:36:22 +03:00
|
|
|
#include <QDir>
|
2015-06-28 14:22:35 +03:00
|
|
|
#include <QFile>
|
2015-07-23 02:30:30 +03:00
|
|
|
#include <QFileInfo>
|
2015-06-28 14:22:35 +03:00
|
|
|
#include <QDirIterator>
|
|
|
|
#include <QGuiApplication>
|
|
|
|
#include <QScreen>
|
|
|
|
|
2015-12-03 00:38:16 +03:00
|
|
|
#include <unistd.h>
|
|
|
|
#include <errno.h>
|
|
|
|
|
2017-08-03 18:48:17 +03:00
|
|
|
#define BOOK_STATE_PAGE_STACK_INDEX "pageStackIndex"
|
2015-12-13 14:46:53 +03:00
|
|
|
#define BOOK_STATE_FONT_SIZE_ADJUST "fontSizeAdjust"
|
|
|
|
#define BOOK_STATE_POSITION "position"
|
|
|
|
#define BOOK_COVER_SUFFIX ".cover."
|
2015-06-28 14:22:35 +03:00
|
|
|
|
|
|
|
// ==========================================================================
|
|
|
|
// BooksBook::CoverContext
|
|
|
|
// ==========================================================================
|
2018-01-30 01:27:40 +03:00
|
|
|
|
2015-06-28 14:22:35 +03:00
|
|
|
class BooksBook::CoverPaintContext : public BooksPaintContext {
|
|
|
|
public:
|
|
|
|
CoverPaintContext();
|
|
|
|
|
|
|
|
void drawImage(int x, int y, const ZLImageData& image);
|
|
|
|
void drawImage(int x, int y, const ZLImageData& image, int width, int height, ScalingType type);
|
|
|
|
void handleImage(const ZLImageData& image);
|
|
|
|
bool gotIt() const;
|
|
|
|
|
|
|
|
public:
|
|
|
|
static QSize gMaxScreenSize;
|
|
|
|
static bool gMaxScreenSizeKnown;
|
|
|
|
|
|
|
|
QImage iImage;
|
|
|
|
};
|
|
|
|
|
|
|
|
QSize BooksBook::CoverPaintContext::gMaxScreenSize(480,640);
|
|
|
|
bool BooksBook::CoverPaintContext::gMaxScreenSizeKnown = false;
|
|
|
|
|
|
|
|
BooksBook::CoverPaintContext::CoverPaintContext()
|
|
|
|
{
|
|
|
|
if (!gMaxScreenSizeKnown) {
|
|
|
|
QList<QScreen*> screens = qGuiApp->screens();
|
|
|
|
const int n = screens.count();
|
|
|
|
for (int i=0; i<n; i++) {
|
|
|
|
QSize s = screens.at(i)->size();
|
|
|
|
gMaxScreenSize.setWidth(qMax(s.width(), gMaxScreenSize.width()));
|
|
|
|
gMaxScreenSize.setHeight(qMax(s.width(), gMaxScreenSize.height()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setWidth(gMaxScreenSize.width());
|
|
|
|
setHeight(gMaxScreenSize.height());
|
|
|
|
}
|
|
|
|
|
|
|
|
void BooksBook::CoverPaintContext::drawImage(int x, int y, const ZLImageData& image)
|
|
|
|
{
|
|
|
|
handleImage(image);
|
|
|
|
}
|
|
|
|
|
|
|
|
void BooksBook::CoverPaintContext::drawImage(int x, int y, const ZLImageData& image,
|
|
|
|
int width, int height, ScalingType type)
|
|
|
|
{
|
|
|
|
handleImage(image);
|
|
|
|
}
|
|
|
|
|
|
|
|
void BooksBook::CoverPaintContext::handleImage(const ZLImageData& image)
|
|
|
|
{
|
|
|
|
const QImage* qImage = ((ZLQtImageData&)image).image();
|
|
|
|
HDEBUG(image.width() << 'x' << image.height());
|
|
|
|
if (qImage->height() > qImage->width() &&
|
|
|
|
qImage->width() > iImage.width() &&
|
|
|
|
qImage->height() > iImage.height()) {
|
|
|
|
iImage = *qImage;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool BooksBook::CoverPaintContext::gotIt() const
|
|
|
|
{
|
|
|
|
return iImage.width() >= 50 &&
|
|
|
|
iImage.height() >= 80 &&
|
|
|
|
iImage.height() > iImage.width();
|
|
|
|
}
|
|
|
|
|
|
|
|
// ==========================================================================
|
|
|
|
// BooksBook::CoverTask
|
|
|
|
// ==========================================================================
|
|
|
|
|
2018-05-16 18:27:12 +03:00
|
|
|
class BooksBook::CoverTask : public HarbourTask
|
2015-06-28 14:22:35 +03:00
|
|
|
{
|
|
|
|
public:
|
2018-05-16 18:27:12 +03:00
|
|
|
CoverTask(QThreadPool* aPool, QString aStateDir, shared_ptr<Book> aBook,
|
|
|
|
QString aImagePath) : HarbourTask(aPool),
|
2015-12-12 11:36:22 +03:00
|
|
|
iStateDir(aStateDir), iBook(aBook), iImagePath(aImagePath),
|
|
|
|
iCoverMissing(false) {}
|
2015-06-28 14:22:35 +03:00
|
|
|
|
|
|
|
bool hasImage() const;
|
|
|
|
|
|
|
|
public:
|
2015-11-08 14:20:25 +03:00
|
|
|
QString iStateDir;
|
2015-06-28 14:22:35 +03:00
|
|
|
shared_ptr<Book> iBook;
|
2015-12-12 11:36:22 +03:00
|
|
|
QString iImagePath;
|
2015-06-28 14:22:35 +03:00
|
|
|
QImage iCoverImage;
|
|
|
|
bool iCoverMissing;
|
|
|
|
};
|
|
|
|
|
|
|
|
inline bool BooksBook::CoverTask::hasImage() const
|
|
|
|
{
|
|
|
|
return iCoverImage.width() > 0 && iCoverImage.height() > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ==========================================================================
|
|
|
|
// BooksBook::LoadCoverTask
|
|
|
|
// ==========================================================================
|
|
|
|
|
|
|
|
class BooksBook::LoadCoverTask : public BooksBook::CoverTask
|
|
|
|
{
|
|
|
|
public:
|
2018-05-16 18:27:12 +03:00
|
|
|
LoadCoverTask(QThreadPool* aPool, QString aStateDir,
|
|
|
|
shared_ptr<Book> aBook, shared_ptr<FormatPlugin> aFormatPlugin,
|
|
|
|
QString aImagePath) :
|
|
|
|
BooksBook::CoverTask(aPool, aStateDir, aBook, aImagePath),
|
2015-06-28 14:22:35 +03:00
|
|
|
iFormatPlugin(aFormatPlugin) {}
|
|
|
|
|
|
|
|
virtual void performTask();
|
|
|
|
|
|
|
|
public:
|
|
|
|
shared_ptr<FormatPlugin> iFormatPlugin;
|
|
|
|
};
|
|
|
|
|
|
|
|
void BooksBook::LoadCoverTask::performTask()
|
|
|
|
{
|
|
|
|
if (!isCanceled()) {
|
|
|
|
// Try to load cached (or custom) cover
|
2015-11-08 14:20:25 +03:00
|
|
|
if (!iStateDir.isEmpty()) {
|
2015-06-28 14:22:35 +03:00
|
|
|
QString coverPrefix(QString::fromStdString(
|
|
|
|
iBook->file().name(false)) + BOOK_COVER_SUFFIX);
|
2015-11-08 14:20:25 +03:00
|
|
|
QDirIterator it(iStateDir);
|
2015-06-28 14:22:35 +03:00
|
|
|
while (it.hasNext()) {
|
|
|
|
QString path(it.next());
|
|
|
|
if (it.fileName().startsWith(coverPrefix)) {
|
|
|
|
if (QFile(path).size() == 0) {
|
|
|
|
HDEBUG("no cover for" << iBook->title().c_str());
|
|
|
|
iCoverMissing = true;
|
|
|
|
return;
|
|
|
|
} else if (iCoverImage.load(path)) {
|
|
|
|
HDEBUG("loaded cover" << iCoverImage.width() << 'x' <<
|
|
|
|
iCoverImage.height() << "for" <<
|
|
|
|
iBook->title().c_str() << "from" <<
|
|
|
|
qPrintable(path));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// OK, fetch one from the book file
|
|
|
|
shared_ptr<ZLImageData> imageData;
|
|
|
|
shared_ptr<ZLImage> image = iFormatPlugin->coverImage(iBook->file());
|
|
|
|
if (!image.isNull()) {
|
|
|
|
imageData = ZLImageManager::Instance().imageData(*image);
|
|
|
|
}
|
|
|
|
if (!imageData.isNull()) {
|
|
|
|
QImage* qImage = (QImage*)((ZLQtImageData&)*imageData).image();
|
|
|
|
if (qImage) iCoverImage = *qImage;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#if HARBOUR_DEBUG
|
|
|
|
if (hasImage()) {
|
|
|
|
HDEBUG("loaded cover" << iCoverImage.width() << 'x' <<
|
|
|
|
iCoverImage.height() << "for" << iBook->title().c_str());
|
|
|
|
} else if (isCanceled()) {
|
|
|
|
HDEBUG("cancelled" << iBook->title().c_str());
|
|
|
|
} else {
|
|
|
|
HDEBUG("no cover found in" << iBook->title().c_str());
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
// ==========================================================================
|
|
|
|
// BooksBook::GuessCoverTask
|
|
|
|
// ==========================================================================
|
|
|
|
|
|
|
|
class BooksBook::GuessCoverTask : public BooksBook::CoverTask
|
|
|
|
{
|
|
|
|
public:
|
2018-05-16 18:27:12 +03:00
|
|
|
GuessCoverTask(QThreadPool* aPool, QString aStateDir,
|
|
|
|
shared_ptr<Book> aBook, QString aImagePath) :
|
|
|
|
BooksBook::CoverTask(aPool, aStateDir, aBook, aImagePath) {}
|
2015-06-28 14:22:35 +03:00
|
|
|
|
|
|
|
virtual void performTask();
|
|
|
|
};
|
|
|
|
|
|
|
|
void BooksBook::GuessCoverTask::performTask()
|
|
|
|
{
|
|
|
|
if (!isCanceled()) {
|
|
|
|
BooksMargins margins;
|
|
|
|
CoverPaintContext context;
|
|
|
|
BookModel bookModel(iBook);
|
|
|
|
shared_ptr<ZLTextModel> model(bookModel.bookTextModel());
|
|
|
|
BooksTextView view(context, BooksTextStyle::defaults(), margins);
|
|
|
|
view.setModel(model);
|
|
|
|
view.rewind();
|
|
|
|
if (!isCanceled()) {
|
|
|
|
view.rewind();
|
|
|
|
if (!isCanceled()) {
|
|
|
|
view.paint();
|
|
|
|
iCoverImage = context.iImage;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasImage()) {
|
|
|
|
HDEBUG("using image" << iCoverImage.width() << 'x' <<
|
|
|
|
iCoverImage.width() << "as cover for" << iBook->title().c_str());
|
|
|
|
|
|
|
|
// Save the extracted image
|
2015-12-12 11:36:22 +03:00
|
|
|
if (!iImagePath.isEmpty()) {
|
|
|
|
QFileInfo file(iImagePath);
|
2015-11-08 14:20:25 +03:00
|
|
|
QDir dir(file.dir());
|
|
|
|
if (!dir.mkpath(dir.absolutePath())) {
|
|
|
|
HWARN("failed to create" << qPrintable(dir.absolutePath()));
|
|
|
|
}
|
2015-12-12 11:36:22 +03:00
|
|
|
if (iCoverImage.save(iImagePath)) {
|
|
|
|
HDEBUG("saved cover to" << qPrintable(iImagePath));
|
2015-11-08 14:20:25 +03:00
|
|
|
} else {
|
2015-12-12 11:36:22 +03:00
|
|
|
HWARN("failed to save" << qPrintable(iImagePath));
|
2015-11-08 14:20:25 +03:00
|
|
|
}
|
2015-06-28 14:22:35 +03:00
|
|
|
}
|
|
|
|
} else if (isCanceled()) {
|
|
|
|
HDEBUG("cancelled" << iBook->title().c_str());
|
|
|
|
} else {
|
|
|
|
HDEBUG("no cover for" << iBook->title().c_str());
|
|
|
|
iCoverMissing = true;
|
|
|
|
|
|
|
|
// Create empty file. Guessing the cover image is an expensive task,
|
|
|
|
// we don't want to do it every time the application is started.
|
2015-12-12 11:36:22 +03:00
|
|
|
if (!iImagePath.isEmpty() &&
|
2015-06-28 14:22:35 +03:00
|
|
|
// Check if the book file still exists - the failure could've
|
|
|
|
// been caused by the SD-card removal.
|
|
|
|
QFile::exists(QString::fromStdString(iBook->file().path()))) {
|
2015-12-12 11:36:22 +03:00
|
|
|
QFile(iImagePath).open(QIODevice::WriteOnly);
|
2015-06-28 14:22:35 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-30 01:27:40 +03:00
|
|
|
// ==========================================================================
|
|
|
|
// BooksBook::HashTask
|
|
|
|
// ==========================================================================
|
|
|
|
|
2018-05-16 18:27:12 +03:00
|
|
|
class BooksBook::HashTask : public HarbourTask
|
2018-01-30 01:27:40 +03:00
|
|
|
{
|
|
|
|
public:
|
2018-05-16 18:27:12 +03:00
|
|
|
HashTask(QThreadPool* aPool, QString aPath);
|
2018-01-30 01:27:40 +03:00
|
|
|
|
|
|
|
virtual void performTask();
|
|
|
|
|
|
|
|
public:
|
|
|
|
QString iPath;
|
|
|
|
QByteArray iHash;
|
|
|
|
};
|
|
|
|
|
2018-05-16 18:27:12 +03:00
|
|
|
BooksBook::HashTask::HashTask(QThreadPool* aPool, QString aPath) :
|
|
|
|
HarbourTask(aPool), iPath(aPath)
|
2018-01-30 01:27:40 +03:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void BooksBook::HashTask::performTask()
|
|
|
|
{
|
|
|
|
iHash = BooksUtil::computeFileHashAndSetAttr(iPath, this);
|
|
|
|
}
|
|
|
|
|
2015-06-28 14:22:35 +03:00
|
|
|
// ==========================================================================
|
|
|
|
// BooksBook
|
|
|
|
// ==========================================================================
|
|
|
|
|
|
|
|
// This constructor isn't really used, but is required by qmlRegisterType
|
|
|
|
BooksBook::BooksBook(QObject* aParent) :
|
|
|
|
QObject(aParent),
|
|
|
|
iRef(-1)
|
|
|
|
{
|
|
|
|
init();
|
|
|
|
}
|
|
|
|
|
2015-11-08 14:20:25 +03:00
|
|
|
BooksBook::BooksBook(const BooksStorage& aStorage, QString aRelativePath,
|
|
|
|
shared_ptr<Book> aBook) :
|
2015-06-28 14:22:35 +03:00
|
|
|
QObject(NULL),
|
|
|
|
iRef(1),
|
|
|
|
iStorage(aStorage),
|
|
|
|
iBook(aBook),
|
2018-01-30 01:27:40 +03:00
|
|
|
iFormatPlugin(PluginCollection::Instance().plugin(*iBook)),
|
|
|
|
iTaskQueue(BooksTaskQueue::defaultQueue()),
|
|
|
|
iTitle(QString::fromStdString(iBook->title())),
|
|
|
|
iPath(QString::fromStdString(iBook->file().physicalFilePath())),
|
|
|
|
iFileName(QFileInfo(iPath).fileName()),
|
|
|
|
iHash(BooksUtil::fileHashAttr(iPath))
|
2015-06-28 14:22:35 +03:00
|
|
|
{
|
|
|
|
init();
|
|
|
|
AuthorList authors(iBook->authors());
|
|
|
|
const int n = authors.size();
|
|
|
|
for (int i=0; i<n; i++) {
|
|
|
|
if (i > 0) iAuthors += ", ";
|
|
|
|
iAuthors += QString::fromStdString(authors[i]->name());
|
|
|
|
}
|
|
|
|
if (iStorage.isValid()) {
|
2015-12-12 11:36:22 +03:00
|
|
|
iStateDir = QDir::cleanPath(iStorage.configDir().path() +
|
|
|
|
QDir::separator() + aRelativePath);
|
2018-01-30 01:27:40 +03:00
|
|
|
iStateFileBase = QDir::cleanPath(iStateDir +
|
|
|
|
QDir::separator() + iFileName);
|
|
|
|
iStateFilePath = storageFile(BOOKS_STATE_FILE_SUFFIX);
|
2015-06-28 14:22:35 +03:00
|
|
|
// Load the state
|
|
|
|
QVariantMap state;
|
|
|
|
if (HarbourJson::load(iStateFilePath, state)) {
|
2015-12-13 14:46:53 +03:00
|
|
|
iFontSizeAdjust = state.value(BOOK_STATE_FONT_SIZE_ADJUST).toInt();
|
2017-08-03 18:48:17 +03:00
|
|
|
#ifdef BOOK_STATE_PAGE_STACK_INDEX
|
|
|
|
iPageStackPos = state.value(BOOK_STATE_PAGE_STACK_INDEX).toInt();
|
|
|
|
#endif
|
|
|
|
// Current position can be stored in two formats - a single
|
|
|
|
// position (older format) or a list of position (newer one).
|
|
|
|
// We have to detect which one we are dealing with
|
|
|
|
QVariant position(state.value(BOOK_STATE_POSITION));
|
|
|
|
BooksPos bookPos(BooksPos::fromVariant(position));
|
|
|
|
if (bookPos.valid()) {
|
|
|
|
// Old format (single position)
|
|
|
|
iPageStack.append(bookPos);
|
|
|
|
} else {
|
|
|
|
// New format (list of positions)
|
2018-01-27 03:03:08 +03:00
|
|
|
iPageStack = BooksPos::List::fromVariant(position);
|
2017-08-03 18:48:17 +03:00
|
|
|
}
|
2015-06-28 14:22:35 +03:00
|
|
|
}
|
|
|
|
}
|
2017-08-03 18:48:17 +03:00
|
|
|
// Validate the state
|
|
|
|
if (iPageStack.isEmpty()) {
|
|
|
|
iPageStack.append(BooksPos(0,0,0));
|
|
|
|
}
|
|
|
|
if (iPageStackPos < 0) {
|
|
|
|
iPageStackPos = 0;
|
|
|
|
} else if (iPageStackPos >= iPageStack.count()) {
|
|
|
|
iPageStackPos = iPageStack.count() - 1;
|
|
|
|
}
|
2018-01-30 01:27:40 +03:00
|
|
|
if (iHash.isEmpty()) {
|
|
|
|
HDEBUG("need to calculate hash for" << qPrintable(iPath));
|
2018-02-04 13:10:31 +03:00
|
|
|
iHashTaskQueue = BooksTaskQueue::hashQueue();
|
2018-05-16 18:27:12 +03:00
|
|
|
iHashTask = new HashTask(iHashTaskQueue->pool(), iPath);
|
|
|
|
iHashTask->moveToThread(thread());
|
|
|
|
iHashTask->submit(this, SLOT(onHashTaskDone()));
|
2018-01-30 01:27:40 +03:00
|
|
|
}
|
2015-06-28 14:22:35 +03:00
|
|
|
// Refcounted BooksBook objects are managed by C++ code
|
|
|
|
QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
|
|
|
|
}
|
|
|
|
|
|
|
|
void BooksBook::init()
|
|
|
|
{
|
2015-12-13 14:46:53 +03:00
|
|
|
iFontSizeAdjust = 0;
|
2017-08-03 18:48:17 +03:00
|
|
|
iPageStackPos = 0;
|
2015-06-28 14:22:35 +03:00
|
|
|
iCoverTask = NULL;
|
2018-01-30 01:27:40 +03:00
|
|
|
iHashTask = NULL;
|
2015-06-28 14:22:35 +03:00
|
|
|
iCoverTasksDone = false;
|
|
|
|
iCopyingOut = false;
|
|
|
|
iSaveTimer = NULL;
|
2018-01-30 01:27:40 +03:00
|
|
|
moveToThread(qApp->thread());
|
2015-06-28 14:22:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
BooksBook::~BooksBook()
|
|
|
|
{
|
|
|
|
HDEBUG(qPrintable(iPath));
|
|
|
|
HASSERT(!iRef.load());
|
|
|
|
if (iCoverTask) iCoverTask->release(this);
|
2018-01-30 01:27:40 +03:00
|
|
|
if (iHashTask) iHashTask->release(this);
|
2015-06-28 14:22:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
BooksItem* BooksBook::retain()
|
|
|
|
{
|
|
|
|
if (iRef.load() >= 0) {
|
|
|
|
iRef.ref();
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
void BooksBook::release()
|
|
|
|
{
|
|
|
|
if (iRef.load() >= 0 && !iRef.deref()) {
|
|
|
|
delete this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QObject* BooksBook::object()
|
|
|
|
{
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
BooksShelf* BooksBook::shelf()
|
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
BooksBook* BooksBook::book()
|
|
|
|
{
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString BooksBook::name() const
|
|
|
|
{
|
|
|
|
return iTitle;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString BooksBook::fileName() const
|
|
|
|
{
|
|
|
|
return iFileName;
|
|
|
|
}
|
|
|
|
|
2015-12-03 00:38:16 +03:00
|
|
|
QString BooksBook::path() const
|
|
|
|
{
|
|
|
|
return iPath;
|
|
|
|
}
|
|
|
|
|
2015-11-08 14:20:25 +03:00
|
|
|
bool BooksBook::accessible() const
|
|
|
|
{
|
|
|
|
return !iCopyingOut;
|
|
|
|
}
|
|
|
|
|
2015-12-13 14:46:53 +03:00
|
|
|
bool BooksBook::setFontSizeAdjust(int aFontSizeAdjust)
|
|
|
|
{
|
|
|
|
if (aFontSizeAdjust > BooksSettings::FontSizeSteps) {
|
|
|
|
aFontSizeAdjust = BooksSettings::FontSizeSteps;
|
|
|
|
} else if (aFontSizeAdjust < -BooksSettings::FontSizeSteps) {
|
|
|
|
aFontSizeAdjust = -BooksSettings::FontSizeSteps;
|
|
|
|
}
|
|
|
|
if (iFontSizeAdjust != aFontSizeAdjust) {
|
|
|
|
iFontSizeAdjust = aFontSizeAdjust;
|
|
|
|
requestSave();
|
|
|
|
Q_EMIT fontSizeAdjustChanged();
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-03 18:48:17 +03:00
|
|
|
void BooksBook::setPageStack(BooksPos::List aStack, int aStackPos)
|
2015-06-28 14:22:35 +03:00
|
|
|
{
|
2017-08-03 18:48:17 +03:00
|
|
|
if (aStackPos < 0) {
|
|
|
|
aStackPos = 0;
|
|
|
|
} else if (aStackPos >= aStack.count()) {
|
|
|
|
aStackPos = aStack.count() - 1;
|
|
|
|
}
|
|
|
|
bool changed = false;
|
|
|
|
if (iPageStack != aStack) {
|
|
|
|
iPageStack = aStack;
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
if (iPageStackPos != aStackPos) {
|
|
|
|
iPageStackPos = aStackPos;
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
if (changed) {
|
2015-12-13 14:46:53 +03:00
|
|
|
requestSave();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void BooksBook::requestSave()
|
|
|
|
{
|
|
|
|
// We only need save timer if we have the state file
|
|
|
|
if (!iSaveTimer && iStorage.isValid()) {
|
|
|
|
iSaveTimer = new BooksSaveTimer(this);
|
|
|
|
connect(iSaveTimer, SIGNAL(save()), SLOT(saveState()));
|
2015-06-28 14:22:35 +03:00
|
|
|
}
|
2015-12-13 14:46:53 +03:00
|
|
|
if (iSaveTimer) iSaveTimer->requestSave();
|
2015-06-28 14:22:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void BooksBook::setCopyingOut(bool aValue)
|
|
|
|
{
|
|
|
|
if (iCopyingOut != aValue) {
|
|
|
|
const bool wasAccessible = accessible();
|
|
|
|
iCopyingOut = aValue;
|
|
|
|
Q_EMIT copyingOutChanged();
|
|
|
|
if (wasAccessible != accessible()) {
|
|
|
|
Q_EMIT accessibleChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool BooksBook::hasCoverImage() const
|
|
|
|
{
|
|
|
|
return iCoverImage.width() > 0 && iCoverImage.height() > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void BooksBook::setCoverImage(QImage aImage)
|
|
|
|
{
|
|
|
|
if (iCoverImage != aImage) {
|
|
|
|
iCoverImage = aImage;
|
|
|
|
Q_EMIT coverImageChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool BooksBook::requestCoverImage()
|
|
|
|
{
|
|
|
|
if (!iBook.isNull() && !iFormatPlugin.isNull() &&
|
|
|
|
!iCoverTasksDone && !iCoverTask) {
|
|
|
|
HDEBUG(iTitle);
|
2018-05-16 18:27:12 +03:00
|
|
|
(iCoverTask = new LoadCoverTask(iTaskQueue->pool(), iStateDir, iBook,
|
|
|
|
iFormatPlugin, cachedImagePath()))->submit(this,
|
|
|
|
SLOT(onLoadCoverTaskDone()));
|
2015-06-28 14:22:35 +03:00
|
|
|
Q_EMIT loadingCoverChanged();
|
|
|
|
}
|
|
|
|
return iCoverTask != NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void BooksBook::cancelCoverRequest()
|
|
|
|
{
|
|
|
|
if (iCoverTask) {
|
|
|
|
iCoverTask->release(this);
|
|
|
|
iCoverTask = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool BooksBook::coverTaskDone()
|
|
|
|
{
|
|
|
|
HASSERT(sender() == iCoverTask);
|
|
|
|
HASSERT(!iCoverTasksDone);
|
|
|
|
HDEBUG(iTitle << iCoverTask->hasImage());
|
|
|
|
const bool gotCover = iCoverTask->hasImage();
|
|
|
|
if (gotCover) {
|
|
|
|
iCoverImage = iCoverTask->iCoverImage;
|
|
|
|
Q_EMIT coverImageChanged();
|
|
|
|
}
|
|
|
|
iCoverTask->release(this);
|
|
|
|
iCoverTask = NULL;
|
|
|
|
return gotCover;
|
|
|
|
}
|
|
|
|
|
|
|
|
void BooksBook::onLoadCoverTaskDone()
|
|
|
|
{
|
|
|
|
HDEBUG(iTitle);
|
|
|
|
const bool coverMissing = iCoverTask->iCoverMissing;
|
|
|
|
if (coverTaskDone() || coverMissing) {
|
|
|
|
iCoverTasksDone = true;
|
|
|
|
Q_EMIT loadingCoverChanged();
|
|
|
|
} else {
|
2018-05-16 18:27:12 +03:00
|
|
|
(iCoverTask = new GuessCoverTask(iTaskQueue->pool(), iStateDir,
|
|
|
|
iBook, cachedImagePath()))->submit(this,
|
|
|
|
SLOT(onGuessCoverTaskDone()));
|
2015-06-28 14:22:35 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void BooksBook::onGuessCoverTaskDone()
|
|
|
|
{
|
|
|
|
HDEBUG(iTitle);
|
|
|
|
coverTaskDone();
|
|
|
|
iCoverTasksDone = true;
|
|
|
|
Q_EMIT loadingCoverChanged();
|
|
|
|
}
|
|
|
|
|
2018-01-30 01:27:40 +03:00
|
|
|
void BooksBook::onHashTaskDone()
|
|
|
|
{
|
|
|
|
iHash = iHashTask->iHash;
|
|
|
|
HDEBUG(QString(iHash.toHex()));
|
|
|
|
iHashTask->release(this);
|
|
|
|
iHashTask = NULL;
|
2018-02-04 13:10:31 +03:00
|
|
|
iHashTaskQueue.reset();
|
2018-01-30 01:27:40 +03:00
|
|
|
Q_EMIT hashChanged();
|
|
|
|
}
|
|
|
|
|
2015-12-12 11:36:22 +03:00
|
|
|
QString BooksBook::cachedImagePath() const
|
|
|
|
{
|
|
|
|
if (!iStateDir.isEmpty() && !iBook.isNull()) {
|
|
|
|
return QDir::cleanPath(iStateDir + QDir::separator() +
|
|
|
|
QString::fromStdString(iBook->file().name(false)) +
|
|
|
|
BOOK_COVER_SUFFIX "jpg");
|
|
|
|
}
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
2015-06-28 14:22:35 +03:00
|
|
|
void BooksBook::saveState()
|
|
|
|
{
|
|
|
|
if (!iStateFilePath.isEmpty()) {
|
|
|
|
QVariantMap state;
|
|
|
|
HarbourJson::load(iStateFilePath, state);
|
2018-01-27 03:03:08 +03:00
|
|
|
state.insert(BOOK_STATE_POSITION, iPageStack.toVariantList());
|
2015-12-13 14:46:53 +03:00
|
|
|
state.insert(BOOK_STATE_FONT_SIZE_ADJUST, iFontSizeAdjust);
|
2017-08-03 18:48:17 +03:00
|
|
|
#ifdef BOOK_STATE_PAGE_STACK_INDEX
|
|
|
|
state.insert(BOOK_STATE_PAGE_STACK_INDEX, iPageStackPos);
|
|
|
|
#endif
|
2015-06-28 14:22:35 +03:00
|
|
|
if (HarbourJson::save(iStateFilePath, state)) {
|
|
|
|
HDEBUG("wrote" << iStateFilePath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void BooksBook::deleteFiles()
|
|
|
|
{
|
|
|
|
if (iStorage.isValid()) {
|
|
|
|
if (QFile::remove(iPath)) {
|
|
|
|
HDEBUG("deleted" << qPrintable(iPath));
|
|
|
|
} else {
|
|
|
|
HWARN("failed to delete" << qPrintable(iPath));
|
|
|
|
}
|
2015-11-08 14:20:25 +03:00
|
|
|
QDirIterator it(iStateDir);
|
2015-06-28 14:22:35 +03:00
|
|
|
while (it.hasNext()) {
|
|
|
|
QString path(it.next());
|
|
|
|
if (it.fileName().startsWith(iFileName)) {
|
|
|
|
if (QFile::remove(path)) {
|
|
|
|
HDEBUG(qPrintable(path));
|
|
|
|
} else {
|
|
|
|
HWARN("failed to delete" << qPrintable(path));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-12-03 00:38:16 +03:00
|
|
|
|
2015-12-12 11:36:22 +03:00
|
|
|
BooksBook* BooksBook::newBook(const BooksStorage& aStorage, QString aRelPath,
|
|
|
|
QString aFileName)
|
|
|
|
{
|
|
|
|
shared_ptr<Book> ref = BooksUtil::bookFromFile(
|
|
|
|
QFileInfo(QDir(aStorage.fullPath(aRelPath)), aFileName).
|
|
|
|
absoluteFilePath());
|
|
|
|
if (!ref.isNull()) {
|
2018-01-30 01:27:40 +03:00
|
|
|
return new BooksBook(aStorage, aRelPath, ref);
|
2015-12-12 11:36:22 +03:00
|
|
|
} else {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-03 00:38:16 +03:00
|
|
|
// NOTE: below methods are invoked on the worker thread
|
|
|
|
bool BooksBook::makeLink(QString aDestPath)
|
|
|
|
{
|
|
|
|
QByteArray oldp(iPath.toLocal8Bit());
|
|
|
|
QByteArray newp(aDestPath.toLocal8Bit());
|
|
|
|
if (!oldp.isEmpty()) {
|
|
|
|
if (!newp.isEmpty()) {
|
|
|
|
int err = link(oldp.data(), newp.data());
|
|
|
|
if (!err) {
|
|
|
|
HDEBUG("linked" << newp << "->" << oldp);
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
HDEBUG(newp << "->" << oldp << "error:" << strerror(errno));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
HDEBUG("failed to convert" << newp << "to locale encoding");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
HDEBUG("failed to convert" << oldp << "to locale encoding");
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-12-12 11:36:22 +03:00
|
|
|
BooksItem* BooksBook::copyTo(const BooksStorage& aStorage, QString aRelPath,
|
|
|
|
CopyOperation* aOperation)
|
2015-12-03 00:38:16 +03:00
|
|
|
{
|
2015-12-12 11:36:22 +03:00
|
|
|
QDir destDir(aStorage.fullPath(aRelPath));
|
|
|
|
destDir.mkpath(destDir.path());
|
|
|
|
const QString absDestPath(QFileInfo(QDir(aStorage.fullPath(aRelPath)),
|
|
|
|
iFileName).absoluteFilePath());
|
|
|
|
if (!isCanceled(aOperation) && makeLink(absDestPath)) {
|
|
|
|
return newBook(aStorage, aRelPath, iFileName);
|
2015-12-03 00:38:16 +03:00
|
|
|
} else if (isCanceled(aOperation)) {
|
2015-12-12 11:36:22 +03:00
|
|
|
return NULL;
|
2015-12-03 00:38:16 +03:00
|
|
|
} else {
|
2015-12-12 11:36:22 +03:00
|
|
|
BooksBook* copy = NULL;
|
2015-12-03 00:38:16 +03:00
|
|
|
QFile src(iPath);
|
|
|
|
const qint64 total = src.size();
|
|
|
|
qint64 copied = 0;
|
|
|
|
if (src.open(QIODevice::ReadOnly)) {
|
2015-12-12 11:36:22 +03:00
|
|
|
QFile dest(absDestPath);
|
2015-12-03 00:38:16 +03:00
|
|
|
if (dest.open(QIODevice::WriteOnly)) {
|
|
|
|
QDateTime lastTime;
|
|
|
|
const qint64 bufsiz = 0x1000;
|
|
|
|
char* buf = new char[bufsiz];
|
|
|
|
int progress = 0;
|
|
|
|
qint64 len;
|
|
|
|
while (!isCanceled(aOperation) &&
|
|
|
|
(len = src.read(buf, bufsiz)) > 0 &&
|
|
|
|
!isCanceled(aOperation) &&
|
|
|
|
dest.write(buf, len) == len) {
|
|
|
|
copied += len;
|
|
|
|
if (aOperation) {
|
|
|
|
int newProg = (int)(copied*PROGRESS_PRECISION/total);
|
|
|
|
if (progress != newProg) {
|
|
|
|
// Don't fire signals too often
|
|
|
|
QDateTime now(QDateTime::currentDateTimeUtc());
|
|
|
|
if (!lastTime.isValid() ||
|
|
|
|
lastTime.msecsTo(now) >= MIN_PROGRESS_DELAY) {
|
|
|
|
lastTime = now;
|
|
|
|
progress = newProg;
|
|
|
|
aOperation->copyProgressChanged(progress);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delete [] buf;
|
|
|
|
dest.close();
|
2015-12-12 11:36:22 +03:00
|
|
|
aOperation->copyProgressChanged(PROGRESS_PRECISION);
|
2015-12-03 00:38:16 +03:00
|
|
|
if (copied == total) {
|
|
|
|
dest.setPermissions(BOOKS_FILE_PERMISSIONS);
|
|
|
|
HDEBUG(total << "bytes copied from"<< qPrintable(iPath) <<
|
2015-12-12 11:36:22 +03:00
|
|
|
"to" << qPrintable(absDestPath));
|
|
|
|
copy = newBook(aStorage, aRelPath, iFileName);
|
|
|
|
|
|
|
|
// Copy cover image too
|
|
|
|
if (copy && !iCoverImage.isNull()) {
|
|
|
|
QString cover(copy->cachedImagePath());
|
|
|
|
if (!cover.isEmpty() && iCoverImage.save(cover)) {
|
|
|
|
HDEBUG("copied cover to" << qPrintable(cover));
|
|
|
|
}
|
|
|
|
}
|
2015-12-03 00:38:16 +03:00
|
|
|
} else {
|
|
|
|
if (isCanceled(aOperation)) {
|
|
|
|
HDEBUG("copy" << qPrintable(iPath) << "to" <<
|
2015-12-12 11:36:22 +03:00
|
|
|
qPrintable(absDestPath) << "cancelled");
|
2015-12-03 00:38:16 +03:00
|
|
|
} else {
|
|
|
|
HWARN(copied << "out of" << total <<
|
|
|
|
"bytes copied from" << qPrintable(iPath) <<
|
2015-12-12 11:36:22 +03:00
|
|
|
"to" << qPrintable(absDestPath));
|
2015-12-03 00:38:16 +03:00
|
|
|
}
|
|
|
|
dest.remove();
|
|
|
|
}
|
|
|
|
} else {
|
2015-12-12 11:36:22 +03:00
|
|
|
HWARN("failed to open" << qPrintable(absDestPath));
|
2015-12-03 00:38:16 +03:00
|
|
|
}
|
|
|
|
src.close();
|
|
|
|
} else {
|
|
|
|
HWARN("failed to open" << qPrintable(iPath));
|
|
|
|
}
|
2015-12-12 11:36:22 +03:00
|
|
|
return copy;
|
2015-12-03 00:38:16 +03:00
|
|
|
}
|
|
|
|
}
|