[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 <QScreen>
#include <unistd.h>
#include <errno.h>
#define BOOK_STATE_POSITION "position"
#define BOOK_COVER_SUFFIX ".cover."
@ -392,6 +395,11 @@ QString BooksBook::fileName() const
return iFileName;
}
QString BooksBook::path() const
{
return iPath;
}
bool BooksBook::accessible() const
{
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);
~BooksBook();
QString path() const { return iPath; }
QString title() const { return iTitle; }
QString authors() const { return iAuthors; }
BooksPos lastPos() const { return iLastPos; }
@ -95,8 +94,10 @@ public:
virtual BooksBook* book();
virtual QString name() const;
virtual QString fileName() const;
virtual QString path() const;
virtual bool accessible() const;
virtual void deleteFiles();
virtual bool copyTo(QDir aDestDir, CopyOperation* aObserver);
Q_SIGNALS:
void coverImageChanged();
@ -113,6 +114,8 @@ private Q_SLOTS:
private:
void init();
bool coverTaskDone();
bool makeLink(QString aDestPath);
static bool isCanceled(CopyOperation* aOperation);
private:
QAtomicInt iRef;
@ -136,4 +139,7 @@ private:
QML_DECLARE_TYPE(BooksBook)
inline bool BooksBook::isCanceled(CopyOperation* aObserver)
{ return aObserver && aObserver->isCanceled(); }
#endif // BOOKS_BOOK_H

View file

@ -36,12 +36,22 @@
#include "BooksTypes.h"
#include <QString>
#include <QObject>
#include <QDir>
class BooksItem
{
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* retain() = 0;
@ -52,8 +62,10 @@ public:
virtual BooksBook* book() = 0;
virtual QString name() const = 0;
virtual QString fileName() const = 0;
virtual QString path() const = 0;
virtual bool accessible() const = 0;
virtual void deleteFiles() = 0;
virtual bool copyTo(QDir aDestDir, CopyOperation* aOperation = NULL) = 0;
};
#endif // BOOKS_ITEM_H

View file

@ -39,7 +39,6 @@
#include "HarbourJson.h"
#include "HarbourDebug.h"
#include <unistd.h>
#include <errno.h>
enum BooksItemRole {
@ -49,40 +48,38 @@ enum BooksItemRole {
BooksItemAccessible,
BooksItemCopyingOut,
BooksItemCopyingIn,
BooksItemCopyPercent,
BooksItemCopyProgress,
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
class BooksShelf::CopyTask : public BooksTask, BooksItem::CopyOperation
{
Q_OBJECT
public:
CopyTask(BooksShelf::Data* aData, BooksBook* aBook);
CopyTask(BooksShelf::Data* aDestData, BooksItem* aSrcItem);
~CopyTask();
void performTask();
QString srcPath() const;
QString destPath() const;
bool linkFiles();
// BooksItem::CopyOperation
virtual bool isCanceled() const;
virtual void copyProgressChanged(int aProgress);
Q_SIGNALS:
void copyPercentChanged();
void copyProgressChanged();
public:
BooksShelf::Data* iData;
int iCopyPercent;
BooksShelf::Data* iDestData;
BooksItem* iSrcItem;
int iCopyProgress;
bool iSuccess;
QString iSource;
QString iDest;
};
// ==========================================================================
@ -240,6 +237,7 @@ public:
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; }
@ -248,7 +246,8 @@ public:
bool isShelf() const { return iItem && iItem->shelf(); }
bool copyingOut();
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 connectSignals(BooksBook* aBook);
@ -322,107 +321,53 @@ inline bool BooksShelf::Data::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())
BooksShelf::CopyTask::CopyTask(BooksShelf::Data* aDestData, BooksItem* aSrcItem) :
iDestData(aDestData),
iSrcItem(aSrcItem->retain()),
iCopyProgress(0),
iSuccess(false)
{
HDEBUG(qPrintable(iSource) << "->" << qPrintable(iDest));
if (iData->iCopyTask) {
iData->iCopyTask->release(iData->iShelf);
if (iDestData->iCopyTask) {
iDestData->iCopyTask->release(iDestData->iShelf);
}
iData->iCopyTask = this;
iData->iShelf->connect(this, SIGNAL(done()), SLOT(onCopyTaskDone()));
iData->iShelf->connect(this, SIGNAL(copyPercentChanged()),
SLOT(onCopyTaskPercentChanged()), Qt::QueuedConnection);
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(!iData);
HASSERT(!iDestData);
iSrcItem->release();
}
bool BooksShelf::CopyTask::linkFiles()
inline QString BooksShelf::CopyTask::srcPath() const
{
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;
return iSrcItem->path();
}
inline QString BooksShelf::CopyTask::destPath() const
{
return QFileInfo(iDestData->iShelf->path(),
iSrcItem->fileName()).absoluteFilePath();
}
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));
}
}
iSuccess = iSrcItem->copyTo(QDir(iDestData->iShelf->path()), this);
}
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;
}
QString BooksShelf::path() const
{
return iPath;
}
bool BooksShelf::accessible() const
{
return true;
@ -1115,20 +1065,19 @@ void BooksShelf::onBookMovedAway()
const int row = bookIndex(book);
HDEBUG(book->title() << row);
if (row >= 0) {
HDEBUG(iList.at(row)->name());
remove(row);
}
}
}
void BooksShelf::onCopyTaskPercentChanged()
void BooksShelf::onCopyTaskProgressChanged()
{
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);
HDEBUG(task->destPath() << task->iCopyProgress);
const int row = iList.indexOf(task->iDestData);
emitDataChangedSignal(row, BooksItemCopyProgress);
}
}
@ -1137,10 +1086,11 @@ void BooksShelf::onCopyTaskDone()
CopyTask* task = qobject_cast<CopyTask*>(sender());
HASSERT(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"));
Data* data = task->iData;
Data* data = task->iDestData;
const int row = iList.indexOf(data);
HASSERT(row >= 0);
@ -1150,21 +1100,21 @@ void BooksShelf::onCopyTaskDone()
if (src) {
src->retain();
if (task->iSuccess) {
shared_ptr<Book> book = BooksUtil::bookFromFile(task->iDest);
shared_ptr<Book> book = BooksUtil::bookFromFile(dest);
if (!book.isNull()) {
copy = new BooksBook(iStorage, iRelativePath, book);
copy->setLastPos(src->lastPos());
copy->setCoverImage(src->coverImage());
copy->requestCoverImage();
} 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
data->iCopyTask = NULL;
task->iData = NULL;
task->iDestData = NULL;
task->release(this);
// 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> roles;
@ -1226,7 +1182,7 @@ QHash<int,QByteArray> BooksShelf::roleNames() const
roles.insert(BooksItemAccessible, "accessible");
roles.insert(BooksItemCopyingOut, "copyingOut");
roles.insert(BooksItemCopyingIn, "copyingIn");
roles.insert(BooksItemCopyPercent, "copyPercent");
roles.insert(BooksItemCopyProgress, "copyProgress");
roles.insert(BooksItemDummy, "dummy");
roles.insert(BooksItemDeleteRequested, "deleteRequested");
return roles;
@ -1249,7 +1205,7 @@ QVariant BooksShelf::data(const QModelIndex& aIndex, int aRole) const
case BooksItemAccessible: return data->accessible();
case BooksItemCopyingOut: return data->copyingOut();
case BooksItemCopyingIn: return data->copyingIn();
case BooksItemCopyPercent: return data->copyPercent();
case BooksItemCopyProgress: return data->copyProgress();
case BooksItemDummy: return QVariant::fromValue(!data->iItem);
case BooksItemDeleteRequested: return data->iDeleteRequested;
}

View file

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

View file

@ -35,6 +35,7 @@
#define BOOKS_TYPES_H
#include "shared_ptr.h"
#include <QFile>
class BooksBook;
class BooksShelf;
@ -47,4 +48,8 @@ struct BooksMargins {
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