Skip to content

Commit

Permalink
Asynchronous autosave.
Browse files Browse the repository at this point in the history
  • Loading branch information
peter-x committed Apr 9, 2012
1 parent b0def32 commit e5b23ae
Show file tree
Hide file tree
Showing 10 changed files with 371 additions and 91 deletions.
78 changes: 78 additions & 0 deletions asyncwriter.cpp
@@ -0,0 +1,78 @@
#include "asyncwriter.h"

#include <QMutexLocker>

#include "fileio.h"

AsyncWriter::AsyncWriter(QObject *parent) :
QThread(parent), abort(false), waiting(false)
{
}

AsyncWriter::~AsyncWriter()
{
mutex.lock();
abort = true;
workToDo.wakeOne();
mutex.unlock();

wait();
}

void AsyncWriter::writeData(const QList<ScribblePage> &data, const QFile &file)
{
QMutexLocker locker(&mutex);

this->data = data;
this->file.setFileName(file.fileName());

if (!isRunning()) {
start();
} else {
workToDo.wakeOne();
}
}

void AsyncWriter::stopWriting()
{
QMutexLocker locker(&mutex);

file.setFileName(QString());

if (!waiting && isRunning())
workFinished.wait(&mutex, 3000);
}

void AsyncWriter::run()
{
forever {
mutex.lock();
QFile f;
f.setFileName(file.fileName());
const QList<ScribblePage> d = data;
mutex.unlock();

if (abort) {
workFinished.wakeAll();
return;
}

if (!f.fileName().isEmpty()) {
QByteArray output = ScribbleDocument::toXournalXMLFormat(d);
FileIO::writeGZFileLocked(f, output);
}

mutex.lock();
file.setFileName(QString());
if (abort) {
workFinished.wakeAll();
mutex.unlock();
return;
}
waiting = true;
workFinished.wakeAll();
workToDo.wait(&mutex);
waiting = false;
mutex.unlock();
}
}
41 changes: 41 additions & 0 deletions asyncwriter.h
@@ -0,0 +1,41 @@
#ifndef ASYNCIO_H
#define ASYNCIO_H

#include <QThread>
#include <QFile>
#include <QByteArray>
#include <QMutex>
#include <QWaitCondition>

#include "scribble_document.h"

/* Thread that is used to write data to a file asynchronously.
* At any time, there is at most one writing operation pending
* and any operation is allowed to fail. */
class AsyncWriter : public QThread
{
Q_OBJECT
public:
explicit AsyncWriter(QObject *parent = 0);
~AsyncWriter();

void writeData(const QList<ScribblePage> &data, const QFile &file);
void stopWriting();

signals:

protected:
void run();

private:
bool abort;
bool waiting;
QFile file;
QList<ScribblePage> data;

QMutex mutex;
QWaitCondition workToDo;
QWaitCondition workFinished;
};

#endif // ASYNCIO_H
41 changes: 41 additions & 0 deletions fileio.cpp
@@ -0,0 +1,41 @@
#include "fileio.h"

#include "filelocker.h"
#include "zlib.h"

QByteArray FileIO::readGZFileLocked(const QFile &file)
{
FileLocker locker(file);

gzFile f = gzopen(file.fileName().toLocal8Bit().constData(), "r");
if (f == 0)
return QByteArray();

QByteArray data;

char buffer[1024];
while (!gzeof(f)) {
int len = gzread(f, buffer, 1024);
if (len < 0) {
gzclose(f);
return QByteArray();
}
data.append(buffer, len);
}
gzclose(f);

return data;
}

bool FileIO::writeGZFileLocked(const QFile &file, const QByteArray &data)
{
FileLocker locker(file);

gzFile f = gzopen(file.fileName().toLocal8Bit().constData(), "w");
if (f == 0) {
return false;
}
gzwrite(f, data.data(), data.size());
gzclose(f);
return true;
}
14 changes: 14 additions & 0 deletions fileio.h
@@ -0,0 +1,14 @@
#ifndef FILEIO_H
#define FILEIO_H

#include <QByteArray>
#include <QFile>

class FileIO
{
public:
static QByteArray readGZFileLocked(const QFile &file);
static bool writeGZFileLocked(const QFile &file, const QByteArray &data);
};

#endif // FILEIO_H
64 changes: 64 additions & 0 deletions filelocker.h
@@ -0,0 +1,64 @@
#ifndef FILELOCKER_H
#define FILELOCKER_H

#include <QFile>
#include <QFileInfo>
#include <QMutex>
#include <QSet>

class FileLockerManager
{
public:
FileLockerManager() {
}

void lockFile(QString file) {
QMutexLocker locker(&mutex);
lockedFiles.insert(file);
}
void unlockFile(QString file) {
QMutexLocker locker(&mutex);
lockedFiles.remove(file);
}

static FileLockerManager &instance() {
static FileLockerManager manager;
return manager;
}

private:
QSet<QString> lockedFiles;
QMutex mutex;

Q_DISABLE_COPY(FileLockerManager)
};

class FileLocker
{
public:
inline explicit FileLocker(QString file) {
lockedFile = file;
lock();
}

inline explicit FileLocker(const QFile &file) {
lockedFile = QFileInfo(file).absoluteFilePath();
lock();
}

inline ~FileLocker() {
unlock();
}
private:
inline void lock() {
FileLockerManager::instance().lockFile(lockedFile);
}
inline void unlock() {
FileLockerManager::instance().unlockFile(lockedFile);
}

QString lockedFile;
Q_DISABLE_COPY(FileLocker)
};

#endif // FILELOCKER_H
29 changes: 23 additions & 6 deletions mainwidget.cpp
Expand Up @@ -21,6 +21,7 @@
#include "mainwidget.h"

#include "filebrowser.h"
#include "fileio.h"

#include "onyx/screen/screen_proxy.h"
#include "onyx/screen/screen_update_watcher.h"
Expand Down Expand Up @@ -113,14 +114,16 @@ MainWidget::MainWidget(QWidget *parent) :
setLayout(layout);
onyx::screen::watcher().addWatcher(this);

asyncWriter = new AsyncWriter(this);

connect(document, SIGNAL(pageOrLayerNumberChanged(int,int,int,int)), SLOT(updateProgressBar(int,int,int,int)));
connect(scribbleArea, SIGNAL(resized(QSize)), document, SLOT(setViewSize(QSize)));
connect(statusBar, SIGNAL(progressClicked(int,int)), SLOT(setPage(int,int)));

connect(&touchListener, SIGNAL(touchData(TouchData &)), this, SLOT(touchEventDataReceived(TouchData &)));

QTimer *save_timer = new QTimer(this);
connect(save_timer, SIGNAL(timeout()), SLOT(save()));
connect(save_timer, SIGNAL(timeout()), SLOT(saveAsynchronously()));
/* save every 5 seconds */
save_timer->start(5000);
}
Expand All @@ -130,14 +133,15 @@ void MainWidget::loadFile(const QFile &file)
save();

/* TODO error message */
if (document->loadXournalFile(file)) {
QByteArray data = FileIO::readGZFileLocked(file);
if (document->loadXournalFile(data)) {
currentFile.setFileName(file.fileName());
}
}

void MainWidget::saveFile(const QFile &file)
{
document->saveXournalFile(file);
FileIO::writeGZFileLocked(file, document->toXournalXMLFormat());
currentFile.setFileName(file.fileName());
}

Expand Down Expand Up @@ -235,6 +239,7 @@ void MainWidget::open()
touchActive = false;

FileBrowser fileBrowser(this);
/* TODO save last path */
QString path = fileBrowser.showLoadFile(currentFile.fileName());
if (path.isEmpty())
return;
Expand All @@ -243,6 +248,15 @@ void MainWidget::open()
touchActive = true;
}

void MainWidget::save()
{
if (!currentFile.fileName().isEmpty()) {
asyncWriter->stopWriting();
/* save timeout cannot occur now since this is the same thread */
saveFile(currentFile);
}
}

void MainWidget::saveAs()
{
QString file = QFileDialog::getSaveFileName(this, "Save Scribble File",
Expand All @@ -251,6 +265,7 @@ void MainWidget::saveAs()
if (!file.isEmpty()) {
saveFile(QFile(file));
}
/* TODO set current filename */
}

void MainWidget::updateProgressBar(int currentPage, int maxPages, int currentLayer, int maxLayers)
Expand All @@ -263,8 +278,10 @@ void MainWidget::setPage(int percentage, int page)
document->setCurrentPage(page - 1);
}

void MainWidget::save()
void MainWidget::saveAsynchronously()
{
if (!currentFile.fileName().isEmpty())
document->saveXournalFile(currentFile);
if (!currentFile.fileName().isEmpty() && document->hasChangedSinceLastSave()) {
document->setSaved();
asyncWriter->writeData(document->getPagesCopy(), currentFile);
}
}
6 changes: 5 additions & 1 deletion mainwidget.h
Expand Up @@ -25,6 +25,7 @@

#include "onyx/ui/status_bar.h"

#include "asyncwriter.h"
#include "scribblearea.h"
#include "scribble_document.h"

Expand All @@ -37,16 +38,18 @@ class MainWidget : public QWidget
void saveFile(const QFile&);

signals:
void saveToGZFileAsynchronously(const QFile &file, const QByteArray &data);

public slots:
void save();
void saveAsynchronously();

private slots:
void touchEventDataReceived(TouchData &);
void mousePressEvent(QMouseEvent *ev);
void mouseMoveEvent(QMouseEvent *ev);
void mouseReleaseEvent(QMouseEvent *ev);

void save();
void saveAs();
void open();

Expand All @@ -63,6 +66,7 @@ private slots:

bool touchActive;

AsyncWriter *asyncWriter;
QFile currentFile;
ScribbleArea *scribbleArea;
ScribbleDocument *document;
Expand Down
11 changes: 8 additions & 3 deletions scribble.pro
Expand Up @@ -5,9 +5,11 @@ SOURCES += scribble.cpp \
scribblearea.cpp \
scribble_document.cpp \
filebrowser.cpp \
tree_view.cpp
tree_view.cpp \
fileio.cpp \
asyncwriter.cpp

LIBS += -L /usr/local/lib -lz -lonyxapp -lonyx_base -lonyx_ui -lonyx_screen -lonyx_sys -lonyx_wpa -lonyx_wireless -lonyx_data -lonyx_touch -lonyx_cms
LIBS += -lz -lonyxapp -lonyx_base -lonyx_ui -lonyx_screen -lonyx_sys -lonyx_wpa -lonyx_wireless -lonyx_data -lonyx_cms

INCLUDEPATH += /opt/onyx/arm/include

Expand All @@ -16,6 +18,9 @@ HEADERS += \
scribblearea.h \
scribble_document.h \
filebrowser.h \
tree_view.h
tree_view.h \
filelocker.h \
fileio.h \
asyncwriter.h

RESOURCES +=

0 comments on commit e5b23ae

Please sign in to comment.