2015-06-28 14:22:35 +03:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2015 Jolla Ltd.
|
|
|
|
* Contact: Slava Monich <slava.monich@jolla.com>
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
* * Neither the name of Nemo Mobile nor the names of its contributors
|
|
|
|
* 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 "BooksTask.h"
|
|
|
|
#include "BooksTextView.h"
|
|
|
|
#include "BooksTextStyle.h"
|
|
|
|
#include "BooksPaintContext.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"
|
|
|
|
|
|
|
|
#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>
|
|
|
|
|
2015-06-28 14:22:35 +03:00
|
|
|
#define BOOK_STATE_POSITION "position"
|
|
|
|
#define BOOK_COVER_SUFFIX ".cover."
|
|
|
|
|
|
|
|
// ==========================================================================
|
|
|
|
// BooksBook::CoverContext
|
|
|
|
// ==========================================================================
|
|
|
|
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
|
|
|
|
// ==========================================================================
|
|
|
|
|
|
|
|
class BooksBook::CoverTask : public BooksTask
|
|
|
|
{
|
|
|
|
public:
|
2015-12-12 11:36:22 +03:00
|
|
|
CoverTask(QString aStateDir, shared_ptr<Book> aBook, QString aImagePath) :
|
|
|
|
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:
|
2015-11-08 14:20:25 +03:00
|
|
|
LoadCoverTask(QString aStateDir, shared_ptr<Book> aBook,
|
2015-12-12 11:36:22 +03:00
|
|
|
shared_ptr<FormatPlugin> aFormatPlugin, QString aImagePath) :
|
|
|
|
BooksBook::CoverTask(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:
|
2015-12-12 11:36:22 +03:00
|
|
|
GuessCoverTask(QString aStateDir, shared_ptr<Book> aBook, QString aImagePath) :
|
|
|
|
BooksBook::CoverTask(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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ==========================================================================
|
|
|
|
// 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),
|
2015-12-12 11:36:22 +03:00
|
|
|
iTaskQueue(BooksTaskQueue::defaultQueue())
|
2015-06-28 14:22:35 +03:00
|
|
|
{
|
|
|
|
init();
|
|
|
|
HASSERT(!iBook.isNull());
|
|
|
|
iTitle = QString::fromStdString(iBook->title());
|
2015-07-15 22:59:00 +03:00
|
|
|
iPath = QString::fromStdString(iBook->file().physicalFilePath());
|
2015-07-23 02:30:30 +03:00
|
|
|
iFileName = QFileInfo(iPath).fileName();
|
2015-06-28 14:22:35 +03:00
|
|
|
iFormatPlugin = PluginCollection::Instance().plugin(*iBook);
|
|
|
|
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);
|
|
|
|
iStateFilePath = QDir::cleanPath(iStateDir +
|
|
|
|
QDir::separator() + iFileName + BOOKS_STATE_FILE_SUFFIX);
|
2015-06-28 14:22:35 +03:00
|
|
|
// Load the state
|
|
|
|
QVariantMap state;
|
|
|
|
if (HarbourJson::load(iStateFilePath, state)) {
|
|
|
|
iLastPos = BooksPos::fromVariant(state.value(BOOK_STATE_POSITION));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Refcounted BooksBook objects are managed by C++ code
|
|
|
|
QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
|
|
|
|
}
|
|
|
|
|
|
|
|
void BooksBook::init()
|
|
|
|
{
|
|
|
|
iCoverTask = NULL;
|
|
|
|
iCoverTasksDone = false;
|
|
|
|
iCopyingOut = false;
|
|
|
|
iSaveTimer = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
BooksBook::~BooksBook()
|
|
|
|
{
|
|
|
|
HDEBUG(qPrintable(iPath));
|
|
|
|
HASSERT(!iRef.load());
|
|
|
|
if (iCoverTask) iCoverTask->release(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
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-06-28 14:22:35 +03:00
|
|
|
void BooksBook::setLastPos(const BooksPos& aPos)
|
|
|
|
{
|
|
|
|
if (iLastPos != aPos) {
|
|
|
|
iLastPos = aPos;
|
|
|
|
// 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()));
|
|
|
|
}
|
|
|
|
if (iSaveTimer) iSaveTimer->requestSave();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2015-12-12 11:36:22 +03:00
|
|
|
iCoverTask = new LoadCoverTask(iStateDir, iBook, iFormatPlugin,
|
|
|
|
cachedImagePath());
|
2015-06-28 14:22:35 +03:00
|
|
|
connect(iCoverTask, SIGNAL(done()), SLOT(onLoadCoverTaskDone()));
|
|
|
|
iTaskQueue->submit(iCoverTask);
|
|
|
|
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 {
|
2015-12-12 11:36:22 +03:00
|
|
|
iCoverTask = new GuessCoverTask(iStateDir, iBook, cachedImagePath());
|
2015-06-28 14:22:35 +03:00
|
|
|
connect(iCoverTask, SIGNAL(done()), SLOT(onGuessCoverTaskDone()));
|
|
|
|
iTaskQueue->submit(iCoverTask);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void BooksBook::onGuessCoverTaskDone()
|
|
|
|
{
|
|
|
|
HDEBUG(iTitle);
|
|
|
|
coverTaskDone();
|
|
|
|
iCoverTasksDone = true;
|
|
|
|
Q_EMIT loadingCoverChanged();
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
state.insert(BOOK_STATE_POSITION, iLastPos.toVariant());
|
|
|
|
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()) {
|
|
|
|
BooksBook* book = new BooksBook(aStorage, aRelPath, ref);
|
|
|
|
book->moveToThread(qApp->thread());
|
|
|
|
return book;
|
|
|
|
} 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
|
|
|
}
|
|
|
|
}
|