harbour-books/app/src/BooksShelf.cpp

1191 lines
34 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 "BooksUtil.h"
2015-06-28 14:22:35 +03:00
#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
{
public:
LoadTask(BooksStorage aStorage, QString aRelPath, QString aStateFile) :
iStorage(aStorage), iRelativePath(aRelPath),
iStateFilePath(aStateFile) {}
2015-06-28 14:22:35 +03:00
~LoadTask();
void performTask();
int findBook(QString aFileName) const;
static int find(QFileInfoList aList, QString aFileName, int aStart);
public:
BooksStorage iStorage;
QString iRelativePath;
2015-06-28 14:22:35 +03:00
QString iStateFilePath;
QList<BooksItem*> iItems;
2015-06-28 14:22:35 +03:00
};
BooksShelf::LoadTask::~LoadTask()
{
const int n = iItems.count();
for (int i=0; i<n; i++) iItems.at(i)->release();
2015-06-28 14:22:35 +03:00
}
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 = iItems.count();
2015-06-28 14:22:35 +03:00
for (int i=0; i<n; i++) {
BooksItem* item = iItems.at(i);
if (item->book() && item->fileName() == aFileName) {
2015-06-28 14:22:35 +03:00
return i;
}
}
}
return -1;
}
void BooksShelf::LoadTask::performTask()
{
if (!isCanceled()) {
QString path(iStorage.fullPath(iRelativePath));
HDEBUG("checking" << path);
QDir dir(path);
QFileInfoList list = dir.entryInfoList(QDir::Files |
QDir::Dirs | QDir::NoDotAndDotDot, QDir::Time);
2015-06-28 14:22:35 +03:00
// 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++) {
const QFileInfo& info = list.at(i);
QString path(info.filePath());
if (info.isDir()) {
HDEBUG("directory:" << qPrintable(path));
QString folderPath(iRelativePath);
if (!folderPath.isEmpty() && !folderPath.endsWith('/')) {
folderPath += '/';
}
folderPath += info.fileName();
BooksShelf* newShelf = new BooksShelf(iStorage, folderPath);
newShelf->moveToThread(thread());
iItems.append(newShelf);
2015-06-28 14:22:35 +03:00
} else {
shared_ptr<Book> book = BooksUtil::bookFromFile(path);
if (!book.isNull()) {
BooksBook* newBook = new BooksBook(iStorage,
iRelativePath, book);
newBook->moveToThread(thread());
iItems.append(newBook);
HDEBUG("[" << iItems.size() << "]" <<
qPrintable(newBook->fileName()) <<
newBook->title());
} else {
HDEBUG("not a book:" << qPrintable(path));
}
2015-06-28 14:22:35 +03:00
}
}
}
// 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() const { return !iCopyTask && iItem && iItem->accessible(); }
bool isBook() const { return iItem && iItem->book(); }
bool isShelf() const { return iItem && iItem->shelf(); }
2015-06-28 14:22:35 +03:00
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::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(BooksItem* aItem);
2015-06-28 14:22:35 +03:00
~DeleteTask();
void performTask();
public:
BooksItem* iItem;
2015-06-28 14:22:35 +03:00
};
BooksShelf::DeleteTask::DeleteTask(BooksItem* aItem) :
iItem(aItem)
2015-06-28 14:22:35 +03:00
{
iItem->retain();
2015-06-28 14:22:35 +03:00
}
BooksShelf::DeleteTask::~DeleteTask()
{
iItem->release();
2015-06-28 14:22:35 +03:00
}
void BooksShelf::DeleteTask::performTask()
{
if (isCanceled()) {
HDEBUG("cancelled" << iItem->fileName());
2015-06-28 14:22:35 +03:00
} else {
iItem->deleteFiles();
}
}
// ==========================================================================
// BooksShelf::Counts
// ==========================================================================
class BooksShelf::Counts {
public:
Counts(BooksShelf* aShelf);
void count(BooksShelf* aShelf);
void emitSignals(BooksShelf* aShelf);
int iTotalCount;
int iBookCount;
int iShelfCount;
};
BooksShelf::Counts::Counts(BooksShelf* aShelf)
{
count(aShelf);
}
void BooksShelf::Counts::count(BooksShelf* aShelf)
{
iTotalCount = aShelf->iList.count(),
iBookCount = 0;
iShelfCount = 0;
for (int i=0; i<iTotalCount; i++) {
const Data* data = aShelf->iList.at(i);
if (data->isBook()) {
iBookCount++;
} else if (data->isShelf()) {
iShelfCount++;
}
}
}
void BooksShelf::Counts::emitSignals(BooksShelf* aShelf)
{
const int oldTotalCount = iTotalCount;
const int oldBookCount = iBookCount;
const int oldShelfCount = iShelfCount;
count(aShelf);
if (oldBookCount != iBookCount) {
Q_EMIT aShelf->bookCountChanged();
}
if (oldShelfCount != iShelfCount) {
Q_EMIT aShelf->shelfCountChanged();
}
if (oldTotalCount != iTotalCount) {
Q_EMIT aShelf->countChanged();
2015-06-28 14:22:35 +03:00
}
}
// ==========================================================================
// BooksShelf
// ==========================================================================
BooksShelf::BooksShelf(QObject* aParent) :
QAbstractListModel(aParent),
2015-11-30 00:18:09 +03:00
iLoadBookList(true),
2015-06-28 14:22:35 +03:00
iLoadTask(NULL),
iDummyItemIndex(-1),
iEditMode(false),
iRef(-1),
iSaveTimer(new BooksSaveTimer(this)),
iTaskQueue(BooksTaskQueue::instance())
{
init();
2015-06-28 14:22:35 +03:00
connect(iSaveTimer, SIGNAL(save()), SLOT(saveState()));
}
BooksShelf::BooksShelf(BooksStorage aStorage, QString aRelativePath) :
2015-11-30 00:18:09 +03:00
iLoadBookList(false),
iLoadTask(NULL),
iRelativePath(aRelativePath),
iStorage(aStorage),
iDummyItemIndex(-1),
iEditMode(false),
iRef(1),
iSaveTimer(NULL),
iTaskQueue(BooksTaskQueue::instance())
{
init();
// Refcounted BooksShelf objects are managed by C++ code
// They also don't need to read the content of the directory -
// only the objects allocated by QML do that.
QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
int slashPos = aRelativePath.lastIndexOf('/');
iFileName = (slashPos >= 0) ?
aRelativePath.right(aRelativePath.length() - slashPos - 1) :
aRelativePath;
updatePath();
}
2015-06-28 14:22:35 +03:00
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 && iSaveTimer->saveRequested()) saveState();
qDeleteAll(iList);
2015-06-28 14:22:35 +03:00
HDEBUG("destroyed");
}
void BooksShelf::init()
2015-06-28 14:22:35 +03:00
{
#if QT_VERSION < 0x050000
setRoleNames(roleNames());
#endif
QQmlEngine::setObjectOwnership(&iStorage, QQmlEngine::CppOwnership);
2015-06-28 14:22:35 +03:00
}
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()
{
BooksLoadingSignalBlocker block(this);
2015-06-28 14:22:35 +03:00
const QString oldPath = iPath;
iPath.clear();
if (iStorage.isValid()) {
iPath = iStorage.fullPath(iRelativePath);
2015-06-28 14:22:35 +03:00
}
if (oldPath != iPath) {
const int oldDummyItemIndex = iDummyItemIndex;
Counts counts(this);
2015-06-28 14:22:35 +03:00
HDEBUG(iPath);
// Clear the model
if (!iList.isEmpty()) {
beginRemoveRows(QModelIndex(), 0, iList.size()-1);
while (!iList.isEmpty()) {
Data* data = iList.takeLast();
BooksBook* book = data->book();
if (book) {
Q_EMIT bookRemoved(book);
}
delete data;
}
endRemoveRows();
}
2015-06-28 14:22:35 +03:00
iDummyItemIndex = -1;
2015-11-30 00:18:09 +03:00
if (!iPath.isEmpty() && iLoadBookList) loadBookList();
2015-06-28 14:22:35 +03:00
Q_EMIT pathChanged();
if (oldDummyItemIndex != iDummyItemIndex) {
Q_EMIT dummyItemIndexChanged();
if (oldDummyItemIndex <0 || iDummyItemIndex < 0) {
Q_EMIT hasDummyItemChanged();
}
}
counts.emitSignals(this);
2015-06-28 14:22:35 +03:00
}
}
void BooksShelf::onLoadTaskDone()
{
HASSERT(iLoadTask);
HASSERT(iLoadTask == sender());
if (iLoadTask && iLoadTask == sender()) {
BooksLoadingSignalBlocker block(this);
const int oldSize = iList.size();
const int newSize = iLoadTask->iItems.size();
HASSERT(iList.isEmpty());
if (newSize > 0) {
Counts counts(this);
beginInsertRows(QModelIndex(), oldSize, oldSize + newSize - 1);
for (int i=0; i<newSize; i++) {
BooksItem* item = iLoadTask->iItems.at(i);
BooksBook* book = item->book();
if (book) {
Q_EMIT bookAdded(book);
}
iList.append(new Data(this, item->retain(), false));
}
endInsertRows();
counts.emitSignals(this);
}
iLoadTask->release(this);
iLoadTask = NULL;
2015-06-28 14:22:35 +03:00
}
}
void BooksShelf::loadBookList()
{
BooksLoadingSignalBlocker block(this);
2015-06-28 14:22:35 +03:00
if (iLoadTask) iLoadTask->release(this);
if (iPath.isEmpty()) {
iLoadTask = NULL;
} else {
HDEBUG(iPath);
iLoadTask = new LoadTask(iStorage, iRelativePath, stateFileName());
2015-06-28 14:22:35 +03:00
connect(iLoadTask, SIGNAL(done()), SLOT(onLoadTaskDone()));
iTaskQueue->submit(iLoadTask);
}
}
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) {
2015-06-28 14:22:35 +03:00
iSaveTimer->requestSave();
}
}
QString BooksShelf::stateFileName() const
{
return iStorage.isValid() ?
iStorage.configDir().path() + "/" + iRelativePath + ("/" SHELF_STATE_FILE) :
2015-06-28 14:22:35 +03:00
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 && iSaveTimer->saveRequested()) {
2015-06-28 14:22:35 +03:00
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 iFileName;
2015-06-28 14:22:35 +03:00
}
QString BooksShelf::fileName() const
{
return iFileName;
}
bool BooksShelf::accessible() const
{
return true;
}
2015-06-28 14:22:35 +03:00
int BooksShelf::count() const
{
return iList.count();
}
int BooksShelf::bookCount() const
{
int n=0, total = iList.count();
for(int i=0; i<total; i++) {
if (iList.at(i)->book()) n++;
}
return n;
}
int BooksShelf::shelfCount() const
{
int n=0, total = iList.count();
for(int i=0; i<total; i++) {
if (iList.at(i)->shelf()) n++;
}
return n;
}
2015-06-28 14:22:35 +03:00
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::submitDeleteTask(int aIndex)
2015-06-28 14:22:35 +03:00
{
BooksItem* item = iList.at(aIndex)->iItem;
if (item) {
DeleteTask* task = new DeleteTask(item);
iDeleteTasks.append(task);
iTaskQueue->submit(task);
BooksBook* book = item->book();
if (book) {
book->cancelCoverRequest();
Q_EMIT bookRemoved(book);
}
2015-06-28 14:22:35 +03:00
}
}
void BooksShelf::remove(int aIndex)
2015-06-28 14:22:35 +03:00
{
if (validIndex(aIndex)) {
Counts counts(this);
2015-06-28 14:22:35 +03:00
HDEBUG(iList.at(aIndex)->name());
beginRemoveRows(QModelIndex(), aIndex, aIndex);
submitDeleteTask(aIndex);
2015-06-28 14:22:35 +03:00
if (iDummyItemIndex == aIndex) {
iDummyItemIndex = -1;
Q_EMIT hasDummyItemChanged();
Q_EMIT dummyItemIndexChanged();
}
delete iList.takeAt(aIndex);
queueStateSave();
counts.emitSignals(this);
2015-06-28 14:22:35 +03:00
endRemoveRows();
}
}
void BooksShelf::removeAll()
{
if (!iList.isEmpty()) {
Counts counts(this);
2015-06-28 14:22:35 +03:00
beginRemoveRows(QModelIndex(), 0, iList.count()-1);
const int n = iList.count();
for (int i=0; i<n; i++) {
submitDeleteTask(i);
2015-06-28 14:22:35 +03:00
}
if (iDummyItemIndex >= 0) {
iDummyItemIndex = -1;
Q_EMIT hasDummyItemChanged();
Q_EMIT dummyItemIndexChanged();
}
qDeleteAll(iList);
iList.clear();
queueStateSave();
counts.emitSignals(this);
2015-06-28 14:22:35 +03:00
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);
Counts counts(this);
2015-06-28 14:22:35 +03:00
Data* data = new Data(this, book->retain(), true);
iList.insert(0, data);
iTaskQueue->submit(new CopyTask(data, book));
counts.emitSignals(this);
2015-06-28 14:22:35 +03:00
endInsertRows();
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) {
shared_ptr<Book> book = BooksUtil::bookFromFile(task->iDest);
2015-06-28 14:22:35 +03:00
if (!book.isNull()) {
copy = new BooksBook(iStorage, iRelativePath, book);
2015-06-28 14:22:35 +03:00
copy->setLastPos(src->lastPos());
copy->setCoverImage(src->coverImage());
copy->requestCoverImage();
} else {
HWARN("can't open copied book" << qPrintable(task->iDest));
2015-06-28 14:22:35 +03:00
}
}
}
// 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 {
remove(row);
2015-06-28 14:22:35 +03:00
}
}
}
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
}
void BooksShelf::deleteFiles()
{
if (iStorage.isValid()) {
QString path(iStorage.fullPath(iRelativePath));
HDEBUG("removing" << path);
if (!QDir(path).removeRecursively()) {
HWARN("some content couldn't be deleted under" << path);
}
path = iStorage.configDir().path() + "/" + iRelativePath;
HDEBUG("removing" << path);
if (!QDir(path).removeRecursively()) {
HWARN("some content couldn't be deleted under" << path);
}
}
}
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"