/* * Copyright (C) 2015-2018 Jolla Ltd. * Copyright (C) 2015-2018 Slava Monich * * 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 Jolla Ltd 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" #include "HarbourDebug.h" #include "HarbourJson.h" #include "HarbourTask.h" #include enum BooksItemRole { BooksItemName = Qt::UserRole, BooksItemBook, BooksItemShelf, BooksItemAccessible, BooksItemCopyingOut, BooksItemCopyingIn, BooksItemCopyProgress, BooksItemDummy, BooksItemDeleteRequested }; #define SHELF_STATE_FILE BOOKS_STATE_FILE_SUFFIX #define SHELF_STATE_ORDER "order" class BooksShelf::CopyTask : public HarbourTask, BooksItem::CopyOperation { Q_OBJECT public: CopyTask(QThreadPool* aPool, BooksShelf::Data* aDestData, BooksItem* aSrcItem); ~CopyTask(); void performTask(); QString srcPath() const; QString destPath() const; // BooksItem::CopyOperation virtual bool isCanceled() const; virtual void copyProgressChanged(int aProgress); Q_SIGNALS: void copyProgressChanged(); public: BooksShelf::Data* iDestData; BooksStorage iDestStorage; QString iDestRelPath; QString iDestAbsPath; BooksItem* iSrcItem; BooksItem* iDestItem; int iCopyProgress; }; // ========================================================================== // BooksShelf::LoadTask // ========================================================================== class BooksShelf::LoadTask : public HarbourTask { public: LoadTask(QThreadPool* aPool, BooksStorage aStorage, QString aRelPath, QString aStateFile) : HarbourTask(aPool), iStorage(aStorage), iRelativePath(aRelPath), iStateFilePath(aStateFile) {} ~LoadTask(); void performTask(); int findBook(QString aFileName) const; static int find(QFileInfoList aList, QString aFileName, int aStart); public: BooksStorage iStorage; QString iRelativePath; QString iStateFilePath; QList iItems; }; BooksShelf::LoadTask::~LoadTask() { const int n = iItems.count(); for (int i=0; irelease(); } int BooksShelf::LoadTask::find(QFileInfoList aList, QString aName, int aStart) { if (!aName.isEmpty()) { const int n = aList.count(); for (int i=aStart; ibook() && item->fileName() == aFileName) { 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); // 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= 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 book = BooksUtil::bookFromFile(path); if (!book.isNull()) { BooksBook* newBook = new BooksBook(iStorage, iRelativePath, book); iItems.append(newBook); HDEBUG("[" << iItems.size() << "]" << qPrintable(newBook->fileName()) << newBook->title()); } else { HDEBUG("not a book:" << qPrintable(path)); } } } } // 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(); } QString path() { return iItem ? iItem->path() : 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(); } bool copyingOut(); bool copyingIn() { return iCopyTask != NULL; } double copyProgress() { return iCopyTask ? (iCopyTask->iCopyProgress/ ((double)PROGRESS_PRECISION)) : 0.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(QThreadPool* aPool, BooksShelf::Data* aDestData, BooksItem* aSrcItem) : HarbourTask(aPool), iDestData(aDestData), iDestStorage(aDestData->iShelf->storage()), iDestRelPath(aDestData->iShelf->relativePath()), iDestAbsPath(iDestStorage.fullPath(iDestRelPath + "/" + aSrcItem->fileName())), iSrcItem(aSrcItem->retain()), iDestItem(NULL), iCopyProgress(0) { if (iDestData->iCopyTask) { iDestData->iCopyTask->release(iDestData->iShelf); } iDestData->iCopyTask = this; iDestData->iShelf->connect(this, SIGNAL(done()), SLOT(onCopyTaskDone())); iDestData->iShelf->connect(this, SIGNAL(copyProgressChanged()), SLOT(onCopyTaskProgressChanged()), Qt::QueuedConnection); HDEBUG(qPrintable(aSrcItem->path()) << "->" << destPath()); } BooksShelf::CopyTask::~CopyTask() { HASSERT(!iDestData); iSrcItem->release(); if (iDestItem) iDestItem->release(); } inline QString BooksShelf::CopyTask::srcPath() const { return iSrcItem->path(); } inline QString BooksShelf::CopyTask::destPath() const { return iDestAbsPath; } void BooksShelf::CopyTask::performTask() { iDestItem = iSrcItem->copyTo(iDestStorage, iDestRelPath, this); } bool BooksShelf::CopyTask::isCanceled() const { return HarbourTask::isCanceled(); } void BooksShelf::CopyTask::copyProgressChanged(int aProgress) { iCopyProgress = aProgress; Q_EMIT copyProgressChanged(); } // ========================================================================== // BooksShelf::DeleteTask // ========================================================================== class BooksShelf::DeleteTask : public HarbourTask { Q_OBJECT public: DeleteTask(QThreadPool* aPool, BooksItem* aItem); ~DeleteTask(); void performTask(); public: BooksItem* iItem; }; BooksShelf::DeleteTask::DeleteTask(QThreadPool* aPool, BooksItem* aItem) : HarbourTask(aPool), iItem(aItem) { iItem->retain(); } BooksShelf::DeleteTask::~DeleteTask() { iItem->release(); } void BooksShelf::DeleteTask::performTask() { if (isCanceled()) { HDEBUG("cancelled" << iItem->fileName()); } 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; iiList.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(); } } // ========================================================================== // BooksShelf // ========================================================================== BooksShelf::BooksShelf(QObject* aParent) : QAbstractListModel(aParent), iLoadBookList(true), iLoadTask(NULL), iDummyItemIndex(-1), iEditMode(false), iRef(-1), iSaveTimer(new BooksSaveTimer(this)), iTaskQueue(BooksTaskQueue::defaultQueue()) { init(); connect(iSaveTimer, SIGNAL(save()), SLOT(saveState())); } BooksShelf::BooksShelf(BooksStorage aStorage, QString aRelativePath) : iLoadBookList(false), iLoadTask(NULL), iRelativePath(aRelativePath), iStorage(aStorage), iDummyItemIndex(-1), iEditMode(false), iRef(1), iSaveTimer(NULL), iTaskQueue(BooksTaskQueue::defaultQueue()) { 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); updatePath(); } BooksShelf::~BooksShelf() { const int n = iDeleteTasks.count(); for (int i=0; irelease(this); if (iLoadTask) iLoadTask->release(this); if (iSaveTimer && iSaveTimer->saveRequested()) saveState(); qDeleteAll(iList); HDEBUG("destroyed"); } void BooksShelf::init() { #if QT_VERSION < 0x050000 setRoleNames(roleNames()); #endif moveToThread(qApp->thread()); connect(BooksStorageManager::instance(), SIGNAL(storageReplaced(BooksStorage,BooksStorage)), SLOT(onStorageReplaced(BooksStorage,BooksStorage))); } 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::onStorageReplaced(BooksStorage aOld, BooksStorage aNew) { if (iStorage == aOld) { iStorage = aNew; updatePath(); } } void BooksShelf::setName(QString aName) { if (iStorage.isValid() && BooksUtil::isValidFileName(aName) && iFileName != aName) { QString parentDir; const int lastSlash = iRelativePath.lastIndexOf('/'); if (lastSlash > 0) { parentDir = iRelativePath.left(lastSlash); } QString newRelativePath; if (!parentDir.isEmpty()) { newRelativePath = parentDir; newRelativePath += '/'; } newRelativePath += aName; const QString oldPath = iStorage.fullPath(iRelativePath); const QString newPath = iStorage.fullPath(newRelativePath); HDEBUG("renaming" << qPrintable(oldPath) << "->" << qPrintable(newPath)); if (rename(qPrintable(oldPath), qPrintable(newPath)) == 0) { // Rename the config/cache directory too const QString oldConfPath = iStorage.fullConfigPath(iRelativePath); const QString newConfPath = iStorage.fullConfigPath(newRelativePath); HDEBUG(qPrintable(oldConfPath) << "->" << qPrintable(newConfPath)); if (rename(qPrintable(oldConfPath), qPrintable(newConfPath))) { HWARN(strerror(errno)); } const QString oldFileName(iFileName); iRelativePath = newRelativePath; iPath = iStorage.fullPath(iRelativePath); updateFileName(); Q_EMIT pathChanged(); Q_EMIT relativePathChanged(); // Since this directiry has been renamed, we need to update the // order of objects in the parent directory. QVariantMap state; const QString stateFile = stateFileName(parentDir); if (HarbourJson::load(stateFile, state)) { QVariantList order = state.value(SHELF_STATE_ORDER).toList(); int i, n = order.count(); for (i=0; ibook(); if (book) { Q_EMIT bookRemoved(book); } delete data; } endRemoveRows(); } iDummyItemIndex = -1; if (!iPath.isEmpty() && iLoadBookList) loadBookList(); Q_EMIT pathChanged(); if (oldDummyItemIndex != iDummyItemIndex) { Q_EMIT dummyItemIndexChanged(); if (oldDummyItemIndex <0 || iDummyItemIndex < 0) { Q_EMIT hasDummyItemChanged(); } } counts.emitSignals(this); } } void BooksShelf::updateFileName() { const int slashPos = iRelativePath.lastIndexOf('/'); const QString fileName = (slashPos >= 0) ? iRelativePath.right(iRelativePath.length() - slashPos - 1) : iRelativePath; if (iFileName != fileName) { iFileName = fileName; Q_EMIT nameChanged(); } } 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; iiItems.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; } } void BooksShelf::loadBookList() { BooksLoadingSignalBlocker block(this); if (iLoadTask) iLoadTask->release(this); if (iPath.isEmpty()) { iLoadTask = NULL; } else { HDEBUG(iPath); (iLoadTask = new LoadTask(iTaskQueue->pool(), iStorage, iRelativePath, stateFileName()))->submit(this, SLOT(onLoadTaskDone())); } } void BooksShelf::saveState() { QStringList order; const int n = iList.count(); for (int i=0; ifileName()); } QVariantMap state; state.insert(SHELF_STATE_ORDER, order); if (HarbourJson::save(stateFileName(), state)) { HDEBUG("wrote" << qPrintable(stateFileName())); } } void BooksShelf::queueStateSave() { if (iEditMode && iSaveTimer) { iSaveTimer->requestSave(); } } QString BooksShelf::stateFileName(QString aRelativePath) const { if (iStorage.isValid()) { QString path(iStorage.configDir().path()); if (!aRelativePath.isEmpty()) { path += "/"; path += aRelativePath; } path += "/" SHELF_STATE_FILE; return path; } else { return QString(); } } int BooksShelf::bookIndex(BooksBook* aBook) const { if (aBook) { const int n = iList.count(); for (int i=0; ibook() == 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; ifileName() == 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()) { iSaveTimer->saveNow(); } 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; } QString BooksShelf::fileName() const { return iFileName; } QString BooksShelf::path() const { return iPath; } bool BooksShelf::accessible() const { return true; } int BooksShelf::count() const { return iList.count(); } int BooksShelf::bookCount() const { int n=0, total = iList.count(); for(int i=0; ibook()) n++; } return n; } int BooksShelf::shelfCount() const { int n=0, total = iList.count(); for(int i=0; ishelf()) n++; } return n; } 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(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 (new CopyTask(iTaskQueue->pool(), data, book))->submit(); 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) { BooksItem* item = iList.at(aIndex)->iItem; if (item) { DeleteTask* task = new DeleteTask(iTaskQueue->pool(), item); iDeleteTasks.append(task); task->submit(); BooksBook* book = item->book(); if (book) { book->cancelCoverRequest(); Q_EMIT bookRemoved(book); } } } void BooksShelf::remove(int aIndex) { if (validIndex(aIndex)) { Counts counts(this); HDEBUG(iList.at(aIndex)->name()); beginRemoveRows(QModelIndex(), aIndex, aIndex); submitDeleteTask(aIndex); if (iDummyItemIndex == aIndex) { iDummyItemIndex = -1; Q_EMIT hasDummyItemChanged(); Q_EMIT dummyItemIndexChanged(); } delete iList.takeAt(aIndex); queueStateSave(); counts.emitSignals(this); endRemoveRows(); } } void BooksShelf::removeAll() { if (!iList.isEmpty()) { Counts counts(this); beginRemoveRows(QModelIndex(), 0, iList.count()-1); const int n = iList.count(); for (int i=0; i= 0) { iDummyItemIndex = -1; Q_EMIT hasDummyItemChanged(); Q_EMIT dummyItemIndexChanged(); } qDeleteAll(iList); iList.clear(); queueStateSave(); counts.emitSignals(this); 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(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); Data* data = new Data(this, book->retain(), true); iList.insert(0, data); (new CopyTask(iTaskQueue->pool(), data, book))->submit(); counts.emitSignals(this); endInsertRows(); saveState(); } } void BooksShelf::emitDataChangedSignal(int aRow, int aRole) { if (aRow >= 0) { QModelIndex index(createIndex(aRow, 0)); QVector roles; roles.append(aRole); Q_EMIT dataChanged(index, index, roles); } } void BooksShelf::onBookAccessibleChanged() { int row = bookIndex(qobject_cast(sender())); if (row >= 0) { HDEBUG(iList.at(row)->name() << iList.at(row)->accessible()); emitDataChangedSignal(row, BooksItemAccessible); } } void BooksShelf::onBookCopyingOutChanged() { int row = bookIndex(qobject_cast(sender())); if (row >= 0) { HDEBUG(iList.at(row)->name() << iList.at(row)->copyingOut()); emitDataChangedSignal(row, BooksItemCopyingOut); } } void BooksShelf::onBookMovedAway() { BooksBook* book = qobject_cast(sender()); HASSERT(book); if (book) { const int row = bookIndex(book); HDEBUG(book->title() << row); if (row >= 0) { remove(row); } } } void BooksShelf::onCopyTaskProgressChanged() { CopyTask* task = qobject_cast(sender()); HASSERT(task); if (task) { HDEBUG(task->destPath() << task->iCopyProgress); const int row = iList.indexOf(task->iDestData); emitDataChangedSignal(row, BooksItemCopyProgress); } } void BooksShelf::onCopyTaskDone() { CopyTask* task = qobject_cast(sender()); HASSERT(task); if (task) { QString dest = task->destPath(); HDEBUG(qPrintable(task->srcPath()) << "->" << qPrintable(dest) << "copy" << (task->iDestItem ? "done" : "FAILED")); Data* data = task->iDestData; const int row = iList.indexOf(data); HASSERT(row >= 0); BooksBook* copy = NULL; BooksBook* src = data->book(); HASSERT(src); if (src) { src->retain(); if (task->iDestItem) { copy = task->iDestItem->book(); if (copy) { copy->retain(); copy->setPageStack(src->pageStack(), src->pageStackPos()); copy->setCoverImage(src->coverImage()); copy->requestCoverImage(); } else { HWARN("not a book:" << qPrintable(dest)); } } } // Disassociate book data from the copy task data->iCopyTask = NULL; task->iDestData = 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); } } } void BooksShelf::onDeleteTaskDone() { DeleteTask* task = qobject_cast(sender()); HASSERT(task); if (task) { task->release(this); HVERIFY(iDeleteTasks.removeOne(task)); } } 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); } } } BooksItem* BooksShelf::copyTo(const BooksStorage& aStorage, QString aRelPath, CopyOperation* aObserver) { HWARN("copying folders is not implemented!!"); return NULL; } QHash BooksShelf::roleNames() const { QHash 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(BooksItemCopyProgress, "copyProgress"); 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 BooksItemCopyProgress: return data->copyProgress(); case BooksItemDummy: return QVariant::fromValue(!data->iItem); case BooksItemDeleteRequested: return data->iDeleteRequested; } } return QVariant(); } #include "BooksShelf.moc"