[app] Preparing to support copying folders

This commit is contained in:
Slava Monich 2015-12-02 23:38:16 +02:00
parent 08c0904a84
commit 272a45279c
6 changed files with 202 additions and 121 deletions

View file

@ -52,6 +52,9 @@
#include <QGuiApplication> #include <QGuiApplication>
#include <QScreen> #include <QScreen>
#include <unistd.h>
#include <errno.h>
#define BOOK_STATE_POSITION "position" #define BOOK_STATE_POSITION "position"
#define BOOK_COVER_SUFFIX ".cover." #define BOOK_COVER_SUFFIX ".cover."
@ -392,6 +395,11 @@ QString BooksBook::fileName() const
return iFileName; return iFileName;
} }
QString BooksBook::path() const
{
return iPath;
}
bool BooksBook::accessible() const bool BooksBook::accessible() const
{ {
return !iCopyingOut; return !iCopyingOut;
@ -542,3 +550,95 @@ void BooksBook::deleteFiles()
} }
} }
} }
// 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;
}
bool BooksBook::copyTo(QDir aDestDir, CopyOperation* aOperation)
{
QString destPath(QFileInfo(aDestDir, iFileName).absoluteFilePath());
aDestDir.mkpath(aDestDir.path());
if (!isCanceled(aOperation) && makeLink(destPath)) {
return true;
} else if (isCanceled(aOperation)) {
return true;
} else {
bool success = false;
QFile src(iPath);
const qint64 total = src.size();
qint64 copied = 0;
if (src.open(QIODevice::ReadOnly)) {
QFile dest(destPath);
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();
if (copied == total) {
dest.setPermissions(BOOKS_FILE_PERMISSIONS);
success = true;
HDEBUG(total << "bytes copied from"<< qPrintable(iPath) <<
"to" << qPrintable(destPath));
} else {
if (isCanceled(aOperation)) {
HDEBUG("copy" << qPrintable(iPath) << "to" <<
qPrintable(destPath) << "cancelled");
} else {
HWARN(copied << "out of" << total <<
"bytes copied from" << qPrintable(iPath) <<
"to" << qPrintable(destPath));
}
dest.remove();
}
} else {
HWARN("failed to open" << qPrintable(destPath));
}
src.close();
} else {
HWARN("failed to open" << qPrintable(iPath));
}
return success;
}
}

View file

@ -70,7 +70,6 @@ public:
shared_ptr<Book> aBook); shared_ptr<Book> aBook);
~BooksBook(); ~BooksBook();
QString path() const { return iPath; }
QString title() const { return iTitle; } QString title() const { return iTitle; }
QString authors() const { return iAuthors; } QString authors() const { return iAuthors; }
BooksPos lastPos() const { return iLastPos; } BooksPos lastPos() const { return iLastPos; }
@ -95,8 +94,10 @@ public:
virtual BooksBook* book(); virtual BooksBook* book();
virtual QString name() const; virtual QString name() const;
virtual QString fileName() const; virtual QString fileName() const;
virtual QString path() const;
virtual bool accessible() const; virtual bool accessible() const;
virtual void deleteFiles(); virtual void deleteFiles();
virtual bool copyTo(QDir aDestDir, CopyOperation* aObserver);
Q_SIGNALS: Q_SIGNALS:
void coverImageChanged(); void coverImageChanged();
@ -113,6 +114,8 @@ private Q_SLOTS:
private: private:
void init(); void init();
bool coverTaskDone(); bool coverTaskDone();
bool makeLink(QString aDestPath);
static bool isCanceled(CopyOperation* aOperation);
private: private:
QAtomicInt iRef; QAtomicInt iRef;
@ -136,4 +139,7 @@ private:
QML_DECLARE_TYPE(BooksBook) QML_DECLARE_TYPE(BooksBook)
inline bool BooksBook::isCanceled(CopyOperation* aObserver)
{ return aObserver && aObserver->isCanceled(); }
#endif // BOOKS_BOOK_H #endif // BOOKS_BOOK_H

View file

@ -36,12 +36,22 @@
#include "BooksTypes.h" #include "BooksTypes.h"
#include <QString> #include <QDir>
#include <QObject>
class BooksItem class BooksItem
{ {
public: public:
enum {
PROGRESS_PRECISION = 1000,
MIN_PROGRESS_DELAY = 100 /* ms */
};
class CopyOperation {
public:
virtual bool isCanceled() const = 0;
virtual void copyProgressChanged(int aProgress) = 0;
};
virtual ~BooksItem() {} virtual ~BooksItem() {}
virtual BooksItem* retain() = 0; virtual BooksItem* retain() = 0;
@ -52,8 +62,10 @@ public:
virtual BooksBook* book() = 0; virtual BooksBook* book() = 0;
virtual QString name() const = 0; virtual QString name() const = 0;
virtual QString fileName() const = 0; virtual QString fileName() const = 0;
virtual QString path() const = 0;
virtual bool accessible() const = 0; virtual bool accessible() const = 0;
virtual void deleteFiles() = 0; virtual void deleteFiles() = 0;
virtual bool copyTo(QDir aDestDir, CopyOperation* aOperation = NULL) = 0;
}; };
#endif // BOOKS_ITEM_H #endif // BOOKS_ITEM_H

View file

@ -39,7 +39,6 @@
#include "HarbourJson.h" #include "HarbourJson.h"
#include "HarbourDebug.h" #include "HarbourDebug.h"
#include <unistd.h>
#include <errno.h> #include <errno.h>
enum BooksItemRole { enum BooksItemRole {
@ -49,40 +48,38 @@ enum BooksItemRole {
BooksItemAccessible, BooksItemAccessible,
BooksItemCopyingOut, BooksItemCopyingOut,
BooksItemCopyingIn, BooksItemCopyingIn,
BooksItemCopyPercent, BooksItemCopyProgress,
BooksItemDummy, BooksItemDummy,
BooksItemDeleteRequested 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_FILE BOOKS_STATE_FILE_SUFFIX
#define SHELF_STATE_ORDER "order" #define SHELF_STATE_ORDER "order"
class BooksShelf::CopyTask : public BooksTask class BooksShelf::CopyTask : public BooksTask, BooksItem::CopyOperation
{ {
Q_OBJECT Q_OBJECT
public: public:
CopyTask(BooksShelf::Data* aData, BooksBook* aBook); CopyTask(BooksShelf::Data* aDestData, BooksItem* aSrcItem);
~CopyTask(); ~CopyTask();
void performTask(); void performTask();
QString srcPath() const;
QString destPath() const;
bool linkFiles(); // BooksItem::CopyOperation
virtual bool isCanceled() const;
virtual void copyProgressChanged(int aProgress);
Q_SIGNALS: Q_SIGNALS:
void copyPercentChanged(); void copyProgressChanged();
public: public:
BooksShelf::Data* iData; BooksShelf::Data* iDestData;
int iCopyPercent; BooksItem* iSrcItem;
int iCopyProgress;
bool iSuccess; bool iSuccess;
QString iSource;
QString iDest;
}; };
// ========================================================================== // ==========================================================================
@ -240,6 +237,7 @@ public:
QString name() { return iItem ? iItem->name() : QString(); } QString name() { return iItem ? iItem->name() : QString(); }
QString fileName() { return iItem ? iItem->fileName() : QString(); } QString fileName() { return iItem ? iItem->fileName() : QString(); }
QString path() { return iItem ? iItem->path() : QString(); }
QObject* object() { return iItem ? iItem->object() : NULL; } QObject* object() { return iItem ? iItem->object() : NULL; }
BooksBook* book() { return iItem ? iItem->book() : NULL; } BooksBook* book() { return iItem ? iItem->book() : NULL; }
BooksShelf* shelf() { return iItem ? iItem->shelf() : NULL; } BooksShelf* shelf() { return iItem ? iItem->shelf() : NULL; }
@ -248,7 +246,8 @@ public:
bool isShelf() const { return iItem && iItem->shelf(); } bool isShelf() const { return iItem && iItem->shelf(); }
bool copyingOut(); bool copyingOut();
bool copyingIn() { return iCopyTask != NULL; } bool copyingIn() { return iCopyTask != NULL; }
int copyPercent() { return iCopyTask ? iCopyTask->iCopyPercent : 0; } double copyProgress() { return iCopyTask ? (iCopyTask->iCopyProgress/
((double)PROGRESS_PRECISION)) : 0.0; }
void setBook(BooksBook* aBook, bool aExternal); void setBook(BooksBook* aBook, bool aExternal);
void connectSignals(BooksBook* aBook); void connectSignals(BooksBook* aBook);
@ -322,107 +321,53 @@ inline bool BooksShelf::Data::copyingOut()
// BooksShelf::CopyTask // BooksShelf::CopyTask
// ========================================================================== // ==========================================================================
BooksShelf::CopyTask::CopyTask(BooksShelf::Data* aData, BooksBook* aBook) : BooksShelf::CopyTask::CopyTask(BooksShelf::Data* aDestData, BooksItem* aSrcItem) :
iData(aData), iDestData(aDestData),
iCopyPercent(0), iSrcItem(aSrcItem->retain()),
iSuccess(false), iCopyProgress(0),
iSource(aBook->path()), iSuccess(false)
iDest(QFileInfo(aData->iShelf->path(),
QFileInfo(iSource).fileName()).absoluteFilePath())
{ {
HDEBUG(qPrintable(iSource) << "->" << qPrintable(iDest)); if (iDestData->iCopyTask) {
if (iData->iCopyTask) { iDestData->iCopyTask->release(iDestData->iShelf);
iData->iCopyTask->release(iData->iShelf);
} }
iData->iCopyTask = this; iDestData->iCopyTask = this;
iData->iShelf->connect(this, SIGNAL(done()), SLOT(onCopyTaskDone())); iDestData->iShelf->connect(this, SIGNAL(done()), SLOT(onCopyTaskDone()));
iData->iShelf->connect(this, SIGNAL(copyPercentChanged()), iDestData->iShelf->connect(this, SIGNAL(copyProgressChanged()),
SLOT(onCopyTaskPercentChanged()), Qt::QueuedConnection); SLOT(onCopyTaskProgressChanged()), Qt::QueuedConnection);
HDEBUG(qPrintable(aSrcItem->path()) << "->" << destPath());
} }
BooksShelf::CopyTask::~CopyTask() BooksShelf::CopyTask::~CopyTask()
{ {
HASSERT(!iData); HASSERT(!iDestData);
iSrcItem->release();
} }
bool BooksShelf::CopyTask::linkFiles() inline QString BooksShelf::CopyTask::srcPath() const
{ {
QByteArray oldp(iSource.toLocal8Bit()); return iSrcItem->path();
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"); inline QString BooksShelf::CopyTask::destPath() const
} {
} else { return QFileInfo(iDestData->iShelf->path(),
HDEBUG("failed to convert" << oldp << "to locale encoding"); iSrcItem->fileName()).absoluteFilePath();
}
return iSuccess;
} }
void BooksShelf::CopyTask::performTask() void BooksShelf::CopyTask::performTask()
{ {
if (!isCanceled() && !linkFiles()) { iSuccess = iSrcItem->copyTo(QDir(iDestData->iShelf->path()), this);
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));
} }
bool BooksShelf::CopyTask::isCanceled() const
{
return BooksTask::isCanceled();
} }
void BooksShelf::CopyTask::copyProgressChanged(int aProgress)
{
iCopyProgress = aProgress;
Q_EMIT copyProgressChanged();
} }
// ========================================================================== // ==========================================================================
@ -873,6 +818,11 @@ QString BooksShelf::fileName() const
return iFileName; return iFileName;
} }
QString BooksShelf::path() const
{
return iPath;
}
bool BooksShelf::accessible() const bool BooksShelf::accessible() const
{ {
return true; return true;
@ -1115,20 +1065,19 @@ void BooksShelf::onBookMovedAway()
const int row = bookIndex(book); const int row = bookIndex(book);
HDEBUG(book->title() << row); HDEBUG(book->title() << row);
if (row >= 0) { if (row >= 0) {
HDEBUG(iList.at(row)->name());
remove(row); remove(row);
} }
} }
} }
void BooksShelf::onCopyTaskPercentChanged() void BooksShelf::onCopyTaskProgressChanged()
{ {
CopyTask* task = qobject_cast<CopyTask*>(sender()); CopyTask* task = qobject_cast<CopyTask*>(sender());
HASSERT(task); HASSERT(task);
if (task) { if (task) {
HDEBUG(task->iDest << task->iCopyPercent); HDEBUG(task->destPath() << task->iCopyProgress);
const int row = iList.indexOf(task->iData); const int row = iList.indexOf(task->iDestData);
emitDataChangedSignal(row, BooksItemCopyPercent); emitDataChangedSignal(row, BooksItemCopyProgress);
} }
} }
@ -1137,10 +1086,11 @@ void BooksShelf::onCopyTaskDone()
CopyTask* task = qobject_cast<CopyTask*>(sender()); CopyTask* task = qobject_cast<CopyTask*>(sender());
HASSERT(task); HASSERT(task);
if (task) { if (task) {
HDEBUG(qPrintable(task->iSource) << "->" << qPrintable(task->iDest) << QString dest = task->destPath();
HDEBUG(qPrintable(task->srcPath()) << "->" << qPrintable(dest) <<
"copy" << (task->iSuccess ? "done" : "FAILED")); "copy" << (task->iSuccess ? "done" : "FAILED"));
Data* data = task->iData; Data* data = task->iDestData;
const int row = iList.indexOf(data); const int row = iList.indexOf(data);
HASSERT(row >= 0); HASSERT(row >= 0);
@ -1150,21 +1100,21 @@ void BooksShelf::onCopyTaskDone()
if (src) { if (src) {
src->retain(); src->retain();
if (task->iSuccess) { if (task->iSuccess) {
shared_ptr<Book> book = BooksUtil::bookFromFile(task->iDest); shared_ptr<Book> book = BooksUtil::bookFromFile(dest);
if (!book.isNull()) { if (!book.isNull()) {
copy = new BooksBook(iStorage, iRelativePath, book); copy = new BooksBook(iStorage, iRelativePath, book);
copy->setLastPos(src->lastPos()); copy->setLastPos(src->lastPos());
copy->setCoverImage(src->coverImage()); copy->setCoverImage(src->coverImage());
copy->requestCoverImage(); copy->requestCoverImage();
} else { } else {
HWARN("can't open copied book" << qPrintable(task->iDest)); HWARN("can't open copied book" << qPrintable(dest));
} }
} }
} }
// Disassociate book data from the copy task // Disassociate book data from the copy task
data->iCopyTask = NULL; data->iCopyTask = NULL;
task->iData = NULL; task->iDestData = NULL;
task->release(this); task->release(this);
// Notify the source shelf. This will actually remove the source file. // Notify the source shelf. This will actually remove the source file.
@ -1217,6 +1167,12 @@ void BooksShelf::deleteFiles()
} }
} }
bool BooksShelf::copyTo(QDir aDestDir, CopyOperation* aOperation)
{
HWARN("copying folders is not implemented!!");
return false;
}
QHash<int,QByteArray> BooksShelf::roleNames() const QHash<int,QByteArray> BooksShelf::roleNames() const
{ {
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
@ -1226,7 +1182,7 @@ QHash<int,QByteArray> BooksShelf::roleNames() const
roles.insert(BooksItemAccessible, "accessible"); roles.insert(BooksItemAccessible, "accessible");
roles.insert(BooksItemCopyingOut, "copyingOut"); roles.insert(BooksItemCopyingOut, "copyingOut");
roles.insert(BooksItemCopyingIn, "copyingIn"); roles.insert(BooksItemCopyingIn, "copyingIn");
roles.insert(BooksItemCopyPercent, "copyPercent"); roles.insert(BooksItemCopyProgress, "copyProgress");
roles.insert(BooksItemDummy, "dummy"); roles.insert(BooksItemDummy, "dummy");
roles.insert(BooksItemDeleteRequested, "deleteRequested"); roles.insert(BooksItemDeleteRequested, "deleteRequested");
return roles; return roles;
@ -1249,7 +1205,7 @@ QVariant BooksShelf::data(const QModelIndex& aIndex, int aRole) const
case BooksItemAccessible: return data->accessible(); case BooksItemAccessible: return data->accessible();
case BooksItemCopyingOut: return data->copyingOut(); case BooksItemCopyingOut: return data->copyingOut();
case BooksItemCopyingIn: return data->copyingIn(); case BooksItemCopyingIn: return data->copyingIn();
case BooksItemCopyPercent: return data->copyPercent(); case BooksItemCopyProgress: return data->copyProgress();
case BooksItemDummy: return QVariant::fromValue(!data->iItem); case BooksItemDummy: return QVariant::fromValue(!data->iItem);
case BooksItemDeleteRequested: return data->iDeleteRequested; case BooksItemDeleteRequested: return data->iDeleteRequested;
} }

View file

@ -41,6 +41,7 @@
#include "BooksTaskQueue.h" #include "BooksTaskQueue.h"
#include "BooksLoadingProperty.h" #include "BooksLoadingProperty.h"
#include <QDir>
#include <QHash> #include <QHash>
#include <QVariant> #include <QVariant>
#include <QByteArray> #include <QByteArray>
@ -85,7 +86,6 @@ public:
int count() const; int count() const;
int bookCount() const; int bookCount() const;
int shelfCount() const; int shelfCount() const;
QString path() const { return iPath; }
QString relativePath() const { return iRelativePath; } QString relativePath() const { return iRelativePath; }
void setRelativePath(QString aPath); void setRelativePath(QString aPath);
BooksBook* bookAt(int aIndex) const; BooksBook* bookAt(int aIndex) const;
@ -117,8 +117,10 @@ public:
virtual BooksBook* book(); virtual BooksBook* book();
virtual QString name() const; virtual QString name() const;
virtual QString fileName() const; virtual QString fileName() const;
virtual QString path() const;
virtual bool accessible() const; virtual bool accessible() const;
virtual void deleteFiles(); virtual void deleteFiles();
virtual bool copyTo(QDir aDestDir, CopyOperation* aOperation);
Q_SIGNALS: Q_SIGNALS:
void loadingChanged(); void loadingChanged();
@ -140,7 +142,7 @@ private Q_SLOTS:
void onBookAccessibleChanged(); void onBookAccessibleChanged();
void onBookCopyingOutChanged(); void onBookCopyingOutChanged();
void onBookMovedAway(); void onBookMovedAway();
void onCopyTaskPercentChanged(); void onCopyTaskProgressChanged();
void onCopyTaskDone(); void onCopyTaskDone();
void onDeleteTaskDone(); void onDeleteTaskDone();
void saveState(); void saveState();

View file

@ -35,6 +35,7 @@
#define BOOKS_TYPES_H #define BOOKS_TYPES_H
#include "shared_ptr.h" #include "shared_ptr.h"
#include <QFile>
class BooksBook; class BooksBook;
class BooksShelf; class BooksShelf;
@ -47,4 +48,8 @@ struct BooksMargins {
BooksMargins() : iLeft(0), iRight(0), iTop(0), iBottom(0) {} BooksMargins() : iLeft(0), iRight(0), iTop(0), iBottom(0) {}
}; };
#define BOOKS_FILE_PERMISSIONS \
(QFile::ReadOwner | QFile::WriteOwner | \
QFile::ReadGroup | QFile::ReadOther)
#endif // BOOKS_TYPES_H #endif // BOOKS_TYPES_H