harbour-books/app/src/BooksShelf.cpp

1084 lines
30 KiB
C++
Raw Normal View History

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 "BooksShelf.h"
#include "BooksDefs.h"
#include "BooksBook.h"
#include "HarbourJson.h"
#include "HarbourDebug.h"
#include <unistd.h>
#include <errno.h>
enum BooksItemRole {
BooksItemName = Qt::UserRole,
BooksItemBook,
BooksItemShelf,
BooksItemAccessible,
BooksItemCopyingOut,
BooksItemCopyingIn,
BooksItemCopyPercent,
BooksItemDummy,
BooksItemDeleteRequested
};
#define MIN_SIGNAL_DELAY (100) /* ms */
#define FILE_PERMISSIONS \
(QFile::ReadOwner | QFile::WriteOwner | \
QFile::ReadGroup | QFile::ReadOther)
#define SHELF_STATE_FILE BOOKS_STATE_FILE_SUFFIX
#define SHELF_STATE_ORDER "order"
class BooksShelf::CopyTask : public BooksTask
{
Q_OBJECT
public:
CopyTask(BooksShelf::Data* aData, BooksBook* aBook);
~CopyTask();
void performTask();
bool linkFiles();
Q_SIGNALS:
void copyPercentChanged();
public:
BooksShelf::Data* iData;
int iCopyPercent;
bool iSuccess;
QString iSource;
QString iDest;
};
// ==========================================================================
// BooksShelf::LoadTask
// ==========================================================================
class BooksShelf::LoadTask : public BooksTask
{
Q_OBJECT
public:
LoadTask(BooksStorage aStorage, QString aPath, QString aStateFilePath) :
iStorage(aStorage), iPath(aPath), iStateFilePath(aStateFilePath) {}
~LoadTask();
void performTask();
int findBook(QString aFileName) const;
static int find(QFileInfoList aList, QString aFileName, int aStart);
Q_SIGNALS:
void bookFound(BooksBook* aBook);
public:
BooksStorage iStorage;
QString iPath;
QString iStateFilePath;
QList<BooksBook*> iBooks;
};
BooksShelf::LoadTask::~LoadTask()
{
const int n = iBooks.count();
for (int i=0; i<n; i++) iBooks.at(i)->release();
}
int BooksShelf::LoadTask::find(QFileInfoList aList, QString aName, int aStart)
{
if (!aName.isEmpty()) {
const int n = aList.count();
for (int i=aStart; i<n; i++) {
if (aList.at(i).fileName() == aName) {
return i;
}
}
}
return -1;
}
int BooksShelf::LoadTask::findBook(QString aFileName) const
{
if (!aFileName.isEmpty()) {
const int n = iBooks.count();
for (int i=0; i<n; i++) {
if (iBooks.at(i)->fileName() == aFileName) {
return i;
}
}
}
return -1;
}
void BooksShelf::LoadTask::performTask()
{
if (!isCanceled()) {
QDir dir(iPath);
HDEBUG("checking" << iPath);
QFileInfoList list = dir.entryInfoList(QDir::Files, QDir::Time);
// Restore the order
QVariantMap state;
if (HarbourJson::load(iStateFilePath, state)) {
QVariantList order = state.value(SHELF_STATE_ORDER).toList();
const int n = order.count();
for (int i=0, dest=0; i<n; i++) {
int index = find(list, order.at(i).toString(), dest);
if (index >= 0) {
if (index != dest) {
HDEBUG(order.at(i).toString() << index << "->" << dest);
list.move(index, dest);
} else {
HDEBUG(order.at(i).toString() << index);
}
dest++;
} else {
HDEBUG(order.at(i).toString());
}
}
}
const int n = list.count();
for (int i=0; i<n && !isCanceled(); i++) {
std::string path(list.at(i).filePath().toStdString());
ZLFile file(path);
shared_ptr<Book> book = Book::loadFromFile(file);
if (!book.isNull()) {
BooksBook* newBook = new BooksBook(iStorage, book);
newBook->moveToThread(thread());
iBooks.append(newBook);
HDEBUG("[" << iBooks.size() << "]" <<
qPrintable(newBook->fileName()) <<
newBook->title());
Q_EMIT bookFound(newBook);
} else {
HDEBUG("not a book:" << path.c_str());
}
}
}
// Cleanup the state files
if (!isCanceled()) {
QStringList deleteMe;
const QString suffix(BOOKS_STATE_FILE_SUFFIX);
QDirIterator configIt(iStorage.configDir());
while (configIt.hasNext() && !isCanceled()) {
QString path(configIt.next());
if (path.endsWith(suffix)) {
QString fileName(configIt.fileName());
QString name(fileName.left(fileName.length() - suffix.length()));
if (!name.isEmpty() && findBook(name) < 0) {
deleteMe.append(path);
}
}
}
while (!deleteMe.isEmpty() && !isCanceled()) {
QString path(deleteMe.takeLast());
if (QFile::remove(path)) {
HDEBUG("removed" << qPrintable(path));
} else {
HWARN("failed to remove" << qPrintable(path));
}
}
}
}
// ==========================================================================
// BooksShelf::Data
// ==========================================================================
class BooksShelf::Data {
public:
Data(BooksShelf* aShelf, BooksItem* aItem, bool aExternal);
~Data();
QString name() { return iItem ? iItem->name() : QString(); }
QString fileName() { return iItem ? iItem->fileName() : QString(); }
QObject* object() { return iItem ? iItem->object() : NULL; }
BooksBook* book() { return iItem ? iItem->book() : NULL; }
BooksShelf* shelf() { return iItem ? iItem->shelf() : NULL; }
bool accessible();
bool copyingOut();
bool copyingIn() { return iCopyTask != NULL; }
int copyPercent() { return iCopyTask ? iCopyTask->iCopyPercent : 0; }
void setBook(BooksBook* aBook, bool aExternal);
void connectSignals(BooksBook* aBook);
public:
BooksShelf* iShelf;
BooksItem* iItem;
BooksShelf::CopyTask* iCopyTask;
bool iDeleteRequested;
};
BooksShelf::Data::Data(BooksShelf* aShelf, BooksItem* aItem, bool aExternal) :
iShelf(aShelf),
iItem(aItem),
iCopyTask(NULL),
iDeleteRequested(false)
{
if (iItem && !aExternal) {
connectSignals(iItem->book());
}
}
BooksShelf::Data::~Data()
{
if (iItem) {
iItem->object()->disconnect(iShelf);
iItem->release();
}
if (iCopyTask) {
iCopyTask->release(iShelf);
}
}
void BooksShelf::Data::connectSignals(BooksBook* aBook)
{
if (aBook) {
iShelf->connect(aBook,
SIGNAL(accessibleChanged()),
SLOT(onBookAccessibleChanged()));
iShelf->connect(aBook,
SIGNAL(copyingOutChanged()),
SLOT(onBookCopyingOutChanged()));
iShelf->connect(aBook,
SIGNAL(movedAway()),
SLOT(onBookMovedAway()));
}
}
void BooksShelf::Data::setBook(BooksBook* aBook, bool aExternal)
{
if (iItem != aBook) {
if (iItem) {
iItem->object()->disconnect(iShelf);
iItem->release();
}
iItem = aBook;
if (aBook) {
aBook->retain();
if (!aExternal) connectSignals(aBook);
}
}
}
inline bool BooksShelf::Data::accessible()
{
if (iCopyTask) {
return false;
} else {
BooksBook* bookItem = book();
return bookItem && bookItem->accessible();
}
}
inline bool BooksShelf::Data::copyingOut()
{
BooksBook* bookItem = book();
return bookItem && bookItem->copyingOut();
}
// ==========================================================================
// BooksShelf::CopyTask
// ==========================================================================
BooksShelf::CopyTask::CopyTask(BooksShelf::Data* aData, BooksBook* aBook) :
iData(aData),
iCopyPercent(0),
iSuccess(false),
iSource(aBook->path()),
iDest(QFileInfo(aData->iShelf->path(),
QFileInfo(iSource).fileName()).absoluteFilePath())
{
HDEBUG(qPrintable(iSource) << "->" << qPrintable(iDest));
if (iData->iCopyTask) {
iData->iCopyTask->release(iData->iShelf);
}
iData->iCopyTask = this;
iData->iShelf->connect(this, SIGNAL(done()), SLOT(onCopyTaskDone()));
iData->iShelf->connect(this, SIGNAL(copyPercentChanged()),
SLOT(onCopyTaskPercentChanged()), Qt::QueuedConnection);
}
BooksShelf::CopyTask::~CopyTask()
{
HASSERT(!iData);
}
bool BooksShelf::CopyTask::linkFiles()
{
QByteArray oldp(iSource.toLocal8Bit());
QByteArray newp(iDest.toLocal8Bit());
if (!oldp.isEmpty()) {
if (!newp.isEmpty()) {
int err = link(oldp.data(), newp.data());
if (!err) {
HDEBUG("linked" << newp << "->" << oldp);
iSuccess = 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 iSuccess;
}
void BooksShelf::CopyTask::performTask()
{
if (!isCanceled() && !linkFiles()) {
QFile src(iSource);
const qint64 total = src.size();
qint64 copied = 0;
if (src.open(QIODevice::ReadOnly)) {
QFile dest(iDest);
QDir dir(QFileInfo(dest).dir());
dir.mkpath(dir.path());
if (dest.open(QIODevice::WriteOnly)) {
QDateTime lastSignal;
const qint64 bufsiz = 0x1000;
char* buf = new char[bufsiz];
qint64 len;
while (!isCanceled() && (len = src.read(buf, bufsiz)) > 0 &&
!isCanceled() && dest.write(buf, len) == len) {
copied += len;
int percent = (int)(copied*100/total);
if (iCopyPercent != percent) {
// Don't fire signals too often
QDateTime now(QDateTime::currentDateTimeUtc());
if (!lastSignal.isValid() ||
lastSignal.msecsTo(now) >= MIN_SIGNAL_DELAY) {
lastSignal = now;
iCopyPercent = percent;
Q_EMIT copyPercentChanged();
}
}
}
delete [] buf;
dest.close();
if (copied == total) {
dest.setPermissions(FILE_PERMISSIONS);
iSuccess = true;
HDEBUG(total << "bytes copied from"<< qPrintable(iSource) <<
"to" << qPrintable(iDest));
} else {
if (isCanceled()) {
HDEBUG("copy" << qPrintable(iSource) << "to" <<
qPrintable(iDest) << "cancelled");
} else {
HWARN(copied << "out of" << total <<
"bytes copied from" << qPrintable(iSource) <<
"to" << qPrintable(iDest));
}
dest.remove();
}
} else {
HWARN("failed to open" << qPrintable(iDest));
}
src.close();
} else {
HWARN("failed to open" << qPrintable(iSource));
}
}
}
// ==========================================================================
// BooksShelf::DeleteTask
// ==========================================================================
class BooksShelf::DeleteTask : public BooksTask
{
2015-07-06 18:11:03 +03:00
Q_OBJECT
2015-06-28 14:22:35 +03:00
public:
DeleteTask(BooksBook* aBook);
~DeleteTask();
void performTask();
public:
BooksBook* iBook;
};
BooksShelf::DeleteTask::DeleteTask(BooksBook* aBook) :
iBook(aBook)
{
iBook->retain();
iBook->cancelCoverRequest();
}
BooksShelf::DeleteTask::~DeleteTask()
{
iBook->release();
}
void BooksShelf::DeleteTask::performTask()
{
if (isCanceled()) {
HDEBUG("cancelled" << iBook->title());
} else {
HDEBUG(iBook->title());
iBook->deleteFiles();
}
}
// ==========================================================================
// BooksShelf
// ==========================================================================
BooksShelf::BooksShelf(QObject* aParent) :
QAbstractListModel(aParent),
iLoadTask(NULL),
iDummyItemIndex(-1),
iEditMode(false),
iRef(-1),
iSaveTimer(new BooksSaveTimer(this)),
iTaskQueue(BooksTaskQueue::instance())
{
#if QT_VERSION < 0x050000
setRoleNames(roleNames());
#endif
QQmlEngine::setObjectOwnership(&iStorage, QQmlEngine::CppOwnership);
connect(iSaveTimer, SIGNAL(save()), SLOT(saveState()));
}
BooksShelf::~BooksShelf()
{
const int n = iDeleteTasks.count();
for (int i=0; i<n; i++) iDeleteTasks.at(i)->release(this);
if (iLoadTask) iLoadTask->release(this);
if (iSaveTimer->saveRequested()) saveState();
removeAllBooks();
HDEBUG("destroyed");
}
void BooksShelf::removeAllBooks()
{
while (!iList.isEmpty()) {
Data* data = iList.takeLast();
BooksBook* book = data->book();
if (book) {
Q_EMIT bookRemoved(book);
}
delete data;
}
}
void BooksShelf::setRelativePath(QString aPath)
{
if (iRelativePath != aPath) {
iRelativePath = aPath;
updatePath();
Q_EMIT relativePathChanged();
}
}
void BooksShelf::setDevice(QString aDevice)
{
if (device() != aDevice) {
iStorage = BooksStorageManager::instance()->storageForDevice(aDevice);
updatePath();
Q_EMIT deviceChanged();
}
}
void BooksShelf::updatePath()
{
const QString oldPath = iPath;
iPath.clear();
if (iStorage.isValid()) {
QString newPath(iStorage.root());
if (!iRelativePath.isEmpty()) {
if (!newPath.endsWith('/')) newPath += '/';
newPath += iRelativePath;
}
iPath = QDir::cleanPath(newPath);
}
if (oldPath != iPath) {
const int oldCount = iList.count();
const int oldDummyItemIndex = iDummyItemIndex;
beginResetModel();
HDEBUG(iPath);
removeAllBooks();
iDummyItemIndex = -1;
if (!iPath.isEmpty()) loadBookList();
endResetModel();
Q_EMIT pathChanged();
if (oldDummyItemIndex != iDummyItemIndex) {
Q_EMIT dummyItemIndexChanged();
if (oldDummyItemIndex <0 || iDummyItemIndex < 0) {
Q_EMIT hasDummyItemChanged();
}
}
if (oldCount != iList.count()) {
Q_EMIT countChanged();
}
}
}
void BooksShelf::onLoadTaskDone()
{
HASSERT(iLoadTask);
HASSERT(iLoadTask == sender());
2015-07-06 18:11:03 +03:00
iLoadTask->release(this);
2015-06-28 14:22:35 +03:00
iLoadTask = NULL;
Q_EMIT loadingChanged();
}
void BooksShelf::onBookFound(BooksBook* aBook)
{
if (iLoadTask && iLoadTask == sender()) {
beginInsertRows(QModelIndex(), iList.count(), iList.count());
2015-07-06 19:07:27 +03:00
iList.append(new Data(this, aBook->retain(), false));
2015-06-28 14:22:35 +03:00
endInsertRows();
Q_EMIT bookAdded(aBook);
Q_EMIT countChanged();
}
}
void BooksShelf::loadBookList()
{
if (!iList.isEmpty()) {
beginResetModel();
removeAllBooks();
endResetModel();
}
const bool wasLoading = loading();
if (iLoadTask) iLoadTask->release(this);
if (iPath.isEmpty()) {
iLoadTask = NULL;
} else {
HDEBUG(iPath);
iLoadTask = new LoadTask(iStorage, iPath, stateFileName());
connect(iLoadTask, SIGNAL(bookFound(BooksBook*)),
SLOT(onBookFound(BooksBook*)), Qt::QueuedConnection);
connect(iLoadTask, SIGNAL(done()), SLOT(onLoadTaskDone()));
iTaskQueue->submit(iLoadTask);
}
if (wasLoading != loading()) {
Q_EMIT loadingChanged();
}
}
void BooksShelf::saveState()
{
QStringList order;
const int n = iList.count();
for (int i=0; i<n; i++) {
order.append(iList.at(i)->fileName());
}
QVariantMap state;
state.insert(SHELF_STATE_ORDER, order);
if (HarbourJson::save(stateFileName(), state)) {
HDEBUG("wrote" << stateFileName());
}
}
void BooksShelf::queueStateSave()
{
if (iEditMode) {
iSaveTimer->requestSave();
}
}
QString BooksShelf::stateFileName() const
{
return iStorage.isValid() ?
iStorage.configDir().path() + ("/" SHELF_STATE_FILE) :
QString();
}
int BooksShelf::bookIndex(BooksBook* aBook) const
{
if (aBook) {
const int n = iList.count();
for (int i=0; i<n; i++) {
if (iList.at(i)->book() == aBook) {
return i;
}
}
}
return -1;
}
int BooksShelf::itemIndex(QString aFileName, int aStartIndex) const
{
if (!aFileName.isEmpty()) {
const int n = iList.count();
for (int i=aStartIndex; i<n; i++) {
if (iList.at(i)->fileName() == aFileName) {
return i;
}
}
}
return -1;
}
void BooksShelf::setHasDummyItem(bool aHasDummyItem)
{
if (aHasDummyItem && !hasDummyItem()) {
iDummyItemIndex = iList.count();
beginInsertRows(QModelIndex(), iDummyItemIndex, iDummyItemIndex);
iList.append(new Data(this, NULL, false));
endInsertRows();
Q_EMIT countChanged();
Q_EMIT hasDummyItemChanged();
Q_EMIT dummyItemIndexChanged();
} else if (!aHasDummyItem && hasDummyItem()) {
remove(iDummyItemIndex);
}
}
void BooksShelf::setEditMode(bool aEditMode)
{
if (iEditMode != aEditMode) {
iEditMode = aEditMode;
HDEBUG(iEditMode);
if (iSaveTimer->saveRequested()) {
iSaveTimer->cancelSave();
saveState();
}
setHasDummyItem(false);
Q_EMIT editModeChanged();
}
}
void BooksShelf::setDummyItemIndex(int aIndex)
{
if (validIndex(aIndex) && hasDummyItem() && iDummyItemIndex != aIndex) {
const int oldDummyItemIndex = iDummyItemIndex;
iDummyItemIndex = aIndex;
move(oldDummyItemIndex, aIndex);
Q_EMIT dummyItemIndexChanged();
}
}
BooksItem* BooksShelf::retain()
{
if (iRef.load() >= 0) {
iRef.ref();
}
return this;
}
void BooksShelf::release()
{
if (iRef.load() >= 0 && !iRef.deref()) {
delete this;
}
}
QObject* BooksShelf::object()
{
return this;
}
BooksShelf* BooksShelf::shelf()
{
return this;
}
BooksBook* BooksShelf::book()
{
return NULL;
}
QString BooksShelf::name() const
{
return iName;
}
QString BooksShelf::fileName() const
{
return iFileName;
}
int BooksShelf::count() const
{
return iList.count();
}
QObject* BooksShelf::get(int aIndex) const
{
if (validIndex(aIndex)) {
return iList.at(aIndex)->object();
}
HWARN("invalid index" << aIndex);
return NULL;
}
BooksBook* BooksShelf::bookAt(int aIndex) const
{
if (validIndex(aIndex)) {
return iList.at(aIndex)->book();
}
HWARN("invalid index" << aIndex);
return NULL;
}
bool BooksShelf::drop(QObject* aItem)
{
if (iDummyItemIndex >= 0) {
BooksBook* book = qobject_cast<BooksBook*>(aItem);
if (!book) {
HWARN("unexpected drop object");
} else if (itemIndex(book->fileName()) >= 0) {
HWARN("duplicate file name");
setHasDummyItem(false);
} else {
HDEBUG("copying" << book->name() << "to" << qPrintable(path()));
book->setCopyingOut(true);
// Dropped object replaces the dummy placeholder object
QModelIndex index(createIndex(iDummyItemIndex, 0));
Data* data = iList.at(iDummyItemIndex);
HASSERT(!data->iItem);
iDummyItemIndex = -1;
// Don't connect signals since it's not our item
data->setBook(book, true);
// Start copying the data
iTaskQueue->submit(new CopyTask(data, book));
Q_EMIT hasDummyItemChanged();
Q_EMIT dummyItemIndexChanged();
Q_EMIT dataChanged(index, index);
return true;
}
} else {
HWARN("unexpected drop");
}
return false;
}
void BooksShelf::move(int aFrom, int aTo)
{
if (aFrom != aTo) {
if (validIndex(aFrom) && validIndex(aTo)) {
HDEBUG(iList.at(aFrom)->name() << "from" << aFrom << "to" << aTo);
int dest = (aTo < aFrom) ? aTo : (aTo+1);
beginMoveRows(QModelIndex(), aFrom, aFrom, QModelIndex(), dest);
iList.move(aFrom, aTo);
queueStateSave();
endMoveRows();
} else {
HWARN("invalid move" << aFrom << "->" << aTo);
}
}
}
void BooksShelf::remove(int aIndex)
{
BooksBook* book = removeBook(aIndex);
if (book) {
Q_EMIT bookRemoved(book);
}
}
BooksBook* BooksShelf::removeBook(int aIndex)
{
if (validIndex(aIndex)) {
HDEBUG(iList.at(aIndex)->name());
beginRemoveRows(QModelIndex(), aIndex, aIndex);
BooksBook* book = iList.at(aIndex)->book();
if (book) {
DeleteTask* task = new DeleteTask(book);
iDeleteTasks.append(task);
iTaskQueue->submit(task);
}
if (iDummyItemIndex == aIndex) {
iDummyItemIndex = -1;
Q_EMIT hasDummyItemChanged();
Q_EMIT dummyItemIndexChanged();
}
delete iList.takeAt(aIndex);
queueStateSave();
Q_EMIT countChanged();
endRemoveRows();
return book;
}
return NULL;
}
void BooksShelf::removeAll()
{
if (!iList.isEmpty()) {
beginRemoveRows(QModelIndex(), 0, iList.count()-1);
const int n = iList.count();
for (int i=0; i<n; i++) {
BooksBook* book = iList.at(i)->book();
if (book) {
DeleteTask* task = new DeleteTask(book);
iDeleteTasks.append(task);
iTaskQueue->submit(task);
Q_EMIT bookRemoved(book);
}
}
if (iDummyItemIndex >= 0) {
iDummyItemIndex = -1;
Q_EMIT hasDummyItemChanged();
Q_EMIT dummyItemIndexChanged();
}
qDeleteAll(iList);
iList.clear();
queueStateSave();
Q_EMIT countChanged();
endRemoveRows();
}
}
bool BooksShelf::deleteRequested(int aIndex) const
{
if (validIndex(aIndex)) {
return iList.at(aIndex)->iDeleteRequested;
} else {
return false;
}
}
void BooksShelf::setDeleteRequested(int aIndex, bool aValue)
{
if (validIndex(aIndex)) {
Data* data = iList.at(aIndex);
if (data->iDeleteRequested != aValue) {
if (aValue) {
if (!data->copyingIn() && !data->copyingOut()) {
data->iDeleteRequested = true;
HDEBUG(aValue << data->name());
emitDataChangedSignal(aIndex, BooksItemDeleteRequested);
}
} else {
data->iDeleteRequested = false;
HDEBUG(aValue << data->name());
emitDataChangedSignal(aIndex, BooksItemDeleteRequested);
}
}
}
}
void BooksShelf::cancelAllDeleteRequests()
{
for (int i=iList.count()-1; i>=0; i--) {
setDeleteRequested(i, false);
}
}
void BooksShelf::importBook(QObject* aBook)
{
BooksBook* book = qobject_cast<BooksBook*>(aBook);
if (!book) {
HWARN("unexpected import object");
} else if (itemIndex(book->fileName()) >= 0) {
HWARN("duplicate file name" << book->fileName());
} else {
HDEBUG(qPrintable(book->path()) << "->" << qPrintable(iPath));
beginInsertRows(QModelIndex(), 0, 0);
Data* data = new Data(this, book->retain(), true);
iList.insert(0, data);
iTaskQueue->submit(new CopyTask(data, book));
endInsertRows();
Q_EMIT countChanged();
saveState();
}
}
void BooksShelf::emitDataChangedSignal(int aRow, int aRole)
{
if (aRow >= 0) {
QModelIndex index(createIndex(aRow, 0));
QVector<int> roles;
roles.append(aRole);
Q_EMIT dataChanged(index, index, roles);
}
}
void BooksShelf::onBookAccessibleChanged()
{
int row = bookIndex(qobject_cast<BooksBook*>(sender()));
if (row >= 0) {
HDEBUG(iList.at(row)->name() << iList.at(row)->accessible());
emitDataChangedSignal(row, BooksItemAccessible);
}
}
void BooksShelf::onBookCopyingOutChanged()
{
int row = bookIndex(qobject_cast<BooksBook*>(sender()));
if (row >= 0) {
HDEBUG(iList.at(row)->name() << iList.at(row)->copyingOut());
emitDataChangedSignal(row, BooksItemCopyingOut);
}
}
void BooksShelf::onBookMovedAway()
{
2015-07-06 19:07:27 +03:00
BooksBook* book = qobject_cast<BooksBook*>(sender());
HASSERT(book);
if (book) {
const int row = bookIndex(book);
HDEBUG(book->title() << row);
if (row >= 0) {
HDEBUG(iList.at(row)->name());
remove(row);
}
2015-06-28 14:22:35 +03:00
}
}
void BooksShelf::onCopyTaskPercentChanged()
{
CopyTask* task = qobject_cast<CopyTask*>(sender());
HASSERT(task);
if (task) {
HDEBUG(task->iDest << task->iCopyPercent);
const int row = iList.indexOf(task->iData);
emitDataChangedSignal(row, BooksItemCopyPercent);
}
}
void BooksShelf::onCopyTaskDone()
{
CopyTask* task = qobject_cast<CopyTask*>(sender());
HASSERT(task);
if (task) {
HDEBUG(qPrintable(task->iSource) << "->" << qPrintable(task->iDest) <<
"copy" << (task->iSuccess ? "done" : "FAILED"));
Data* data = task->iData;
const int row = iList.indexOf(data);
HASSERT(row >= 0);
BooksBook* copy = NULL;
BooksBook* src = data->book();
HASSERT(src);
if (src) {
src->retain();
if (task->iSuccess) {
ZLFile file(task->iDest.toStdString());
shared_ptr<Book> book = Book::loadFromFile(file);
if (!book.isNull()) {
copy = new BooksBook(iStorage, book);
copy->setLastPos(src->lastPos());
copy->setCoverImage(src->coverImage());
copy->requestCoverImage();
} else {
HWARN("can't open copied book:" << file.path().c_str());
}
}
}
// Disassociate book data from the copy task
data->iCopyTask = NULL;
task->iData = NULL;
task->release(this);
// Notify the source shelf. This will actually remove the source file.
if (copy) {
Q_EMIT src->movedAway();
}
if (src) {
src->setCopyingOut(false);
src->release();
}
if (copy) {
// We own this item now, connect the signals
data->setBook(copy, false);
Q_EMIT bookAdded(copy);
// The entire row has changed
QModelIndex index(createIndex(row, 0));
Q_EMIT dataChanged(index, index);
} else {
removeBook(row);
}
}
}
void BooksShelf::onDeleteTaskDone()
{
2015-07-06 18:11:03 +03:00
DeleteTask* task = qobject_cast<DeleteTask*>(sender());
HASSERT(task);
if (task) {
task->release(this);
HVERIFY(iDeleteTasks.removeOne(task));
}
2015-06-28 14:22:35 +03:00
}
QHash<int,QByteArray> BooksShelf::roleNames() const
{
QHash<int, QByteArray> roles;
roles.insert(BooksItemName, "name");
roles.insert(BooksItemBook, "book");
roles.insert(BooksItemShelf, "shelf");
roles.insert(BooksItemAccessible, "accessible");
roles.insert(BooksItemCopyingOut, "copyingOut");
roles.insert(BooksItemCopyingIn, "copyingIn");
roles.insert(BooksItemCopyPercent, "copyPercent");
roles.insert(BooksItemDummy, "dummy");
roles.insert(BooksItemDeleteRequested, "deleteRequested");
return roles;
}
int BooksShelf::rowCount(const QModelIndex&) const
{
return iList.count();
}
QVariant BooksShelf::data(const QModelIndex& aIndex, int aRole) const
{
const int i = aIndex.row();
if (validIndex(i)) {
Data* data = iList.at(i);
switch (aRole) {
case BooksItemName: return data->name();
case BooksItemBook: return QVariant::fromValue(data->book());
case BooksItemShelf: return QVariant::fromValue(data->shelf());
case BooksItemAccessible: return data->accessible();
case BooksItemCopyingOut: return data->copyingOut();
case BooksItemCopyingIn: return data->copyingIn();
case BooksItemCopyPercent: return data->copyPercent();
case BooksItemDummy: return QVariant::fromValue(!data->iItem);
case BooksItemDeleteRequested: return data->iDeleteRequested;
}
}
return QVariant();
}
#include "BooksShelf.moc"