Commit e4fc9d4e authored by Dmitry Kazakov's avatar Dmitry Kazakov
Browse files

Fixed a concurrent File Layer source image change

Now when the source image is changed we use an iterational algorithm
which is usually applied to a SeqLock problem:

1) Save the size and lastModified tag of the image
2) Copy the image to the temporary directory
3) Check whether the image has changed during the copy operation
4) If everything is ok load the image from a temporary copy. If not,
   restart.

BUG:325686
parent c1ebb834
......@@ -87,6 +87,7 @@ set(kritaui_LIB_SRCS
kis_part2.cpp
kis_factory2.cc
kis_file_layer.cpp
kis_safe_document_loader.cpp
kis_filter_manager.cc
kis_filters_model.cc
kis_flipbook.cpp
......
......@@ -21,7 +21,6 @@
#include "kis_transform_worker.h"
#include "kis_filter_strategy.h"
#include "kis_doc2.h"
#include "kis_node_progress_proxy.h"
#include "kis_node_visitor.h"
......@@ -30,42 +29,36 @@
KisFileLayer::KisFileLayer(KisImageWSP image, const QString &basePath, const QString &filename, bool scaleToImageResolution, const QString &name, quint8 opacity)
: KisExternalLayer(image, name, opacity)
, m_doc(new KisDoc2())
, m_basePath(basePath)
, m_filename(filename)
, m_scaleToImageResolution(scaleToImageResolution)
{
if (QFile::exists(path())) {
m_fileWatcher.addPath(path());
}
connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), SLOT(reloadImage()));
reloadImage();
connect(&m_loader, SIGNAL(loadingFinished()), SLOT(slotLoadingFinished()));
m_loader.setPath(path());
m_loader.reloadImage();
}
KisFileLayer::~KisFileLayer()
{
delete m_doc;
}
KisFileLayer::KisFileLayer(const KisFileLayer &rhs)
: KisExternalLayer(rhs)
, m_doc(new KisDoc2())
{
connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), SLOT(reloadImage()));
m_basePath = rhs.m_basePath;
m_filename = rhs.m_filename;
Q_ASSERT(QFile::exists(rhs.path()));
if (QFile::exists(path())) {
m_fileWatcher.addPath(path());
}
m_scaleToImageResolution = rhs.m_scaleToImageResolution;
reloadImage();
connect(&m_loader, SIGNAL(loadingFinished()), SLOT(slotLoadingFinished()));
m_loader.setPath(path());
m_loader.reloadImage();
}
void KisFileLayer::resetCache()
{
reloadImage();
m_loader.reloadImage();
}
const KoColorSpace *KisFileLayer::colorSpace() const
......@@ -92,11 +85,11 @@ KoDocumentSectionModel::PropertyList KisFileLayer::sectionModelProperties() cons
void KisFileLayer::setFileName(const QString &basePath, const QString &filename)
{
m_fileWatcher.removePath(m_basePath + '/' + m_filename);
m_basePath = basePath;
m_filename = filename;
m_fileWatcher.addPath(m_basePath + '/' + m_filename);
reloadImage();
m_loader.setPath(path());
m_loader.reloadImage();
}
QString KisFileLayer::fileName() const
......@@ -117,7 +110,7 @@ QString KisFileLayer::path() const
void KisFileLayer::setScaleToImageResolution(bool scale)
{
m_scaleToImageResolution = scale;
reloadImage();
m_loader.reloadImage();
}
bool KisFileLayer::scaleToImageResolution() const
......@@ -125,11 +118,9 @@ bool KisFileLayer::scaleToImageResolution() const
return m_scaleToImageResolution;
}
void KisFileLayer::reloadImage()
void KisFileLayer::slotLoadingFinished()
{
m_doc->openUrl(path());
KisImageWSP importedImage = m_doc->image();
KisImageWSP importedImage = m_loader.image();
m_image = importedImage->projection();
if (m_scaleToImageResolution && (image()->xRes() != importedImage->xRes()
......@@ -141,7 +132,6 @@ void KisFileLayer::reloadImage()
worker.run();
}
setDirty();
}
......
......@@ -19,10 +19,8 @@
#define KIS_FILE_LAYER_H
#include "kis_external_layer_iface.h"
#include "kis_safe_document_loader.h"
#include <QFileSystemWatcher>
class KisDoc2;
class KisPart2;
/**
......@@ -57,20 +55,15 @@ public:
void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter);
public slots:
void reloadImage();
void slotLoadingFinished();
private:
KisDoc2 *m_doc;
QString m_basePath;
QString m_filename;
bool m_scaleToImageResolution;
KisPaintDeviceSP m_image;
QFileSystemWatcher m_fileWatcher;
KisSafeDocumentLoader m_loader;
};
#endif // KIS_FILE_LAYER_H
/*
* Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_safe_document_loader.h"
#include <QTimer>
#include <QFileSystemWatcher>
#include <QApplication>
#include "kis_doc2.h"
#include "kis_image.h"
#include "kis_signal_compressor.h"
struct KisSafeDocumentLoader::Private
{
Private()
: doc(new KisDoc2()),
fileChangedSignalCompressor(500 /* ms */, KisSignalCompressor::POSTPONE),
isLoading(false),
fileChangedFlag(false)
{
}
QScopedPointer<KisDoc2> doc;
QFileSystemWatcher fileWatcher;
KisSignalCompressor fileChangedSignalCompressor;
QTimer delayedLoadTimer;
bool isLoading;
bool fileChangedFlag;
QString path;
QString temporaryPath;
qint64 initialFileSize;
QDateTime initialFileTimeStamp;
};
KisSafeDocumentLoader::KisSafeDocumentLoader(const QString &path, QObject *parent)
: QObject(parent),
m_d(new Private())
{
connect(&m_d->fileWatcher, SIGNAL(fileChanged(QString)),
SLOT(fileChanged()));
connect(&m_d->fileWatcher, SIGNAL(fileChanged(QString)),
&m_d->fileChangedSignalCompressor, SLOT(start()));
connect(&m_d->fileChangedSignalCompressor, SIGNAL(timeout()),
SLOT(fileChangedCompressed()));
connect(&m_d->delayedLoadTimer, SIGNAL(timeout()),
SLOT(delayedLoadStart()));
m_d->delayedLoadTimer.setSingleShot(true);
m_d->delayedLoadTimer.setInterval(100 /* ms */);
setPath(path);
}
KisSafeDocumentLoader::~KisSafeDocumentLoader()
{
}
KisImageWSP KisSafeDocumentLoader::image() const
{
return m_d->doc->image();
}
void KisSafeDocumentLoader::setPath(const QString &path)
{
if (path.isEmpty()) return;
if (!m_d->path.isEmpty()) {
m_d->fileWatcher.removePath(m_d->path);
}
m_d->path = path;
m_d->fileWatcher.addPath(m_d->path);
}
void KisSafeDocumentLoader::reloadImage()
{
fileChangedCompressed(true);
}
void KisSafeDocumentLoader::fileChanged()
{
m_d->fileChangedFlag = true;
}
void KisSafeDocumentLoader::fileChangedCompressed(bool sync)
{
if (m_d->isLoading) return;
QFileInfo initialFileInfo(m_d->path);
m_d->initialFileSize = initialFileInfo.size();
m_d->initialFileTimeStamp = initialFileInfo.lastModified();
// it may happen when the file is flushed by
// so other application
if (!m_d->initialFileSize) return;
m_d->isLoading = true;
m_d->fileChangedFlag = false;
m_d->temporaryPath =
QDir::tempPath() + QDir::separator() +
QString("krita_file_layer_copy_%1_%2.%3")
.arg(QApplication::applicationPid())
.arg(qrand())
.arg(initialFileInfo.suffix());
QFile::copy(m_d->path, m_d->temporaryPath);
if (!sync) {
m_d->delayedLoadTimer.start();
} else {
QApplication::processEvents();
delayedLoadStart();
}
}
void KisSafeDocumentLoader::delayedLoadStart()
{
QFileInfo originalInfo(m_d->path);
QFileInfo tempInfo(m_d->temporaryPath);
bool successfullyLoaded = false;
if (!m_d->fileChangedFlag &&
originalInfo.size() == m_d->initialFileSize &&
originalInfo.lastModified() == m_d->initialFileTimeStamp &&
tempInfo.size() == m_d->initialFileSize) {
successfullyLoaded = m_d->doc->openUrl(m_d->temporaryPath);
} else {
qDebug() << "File was modified externally. Restarting.";
qDebug() << ppVar(m_d->fileChangedFlag);
qDebug() << ppVar(m_d->initialFileSize);
qDebug() << ppVar(m_d->initialFileTimeStamp);
qDebug() << ppVar(originalInfo.size());
qDebug() << ppVar(originalInfo.lastModified());
qDebug() << ppVar(tempInfo.size());
}
QFile::remove(m_d->temporaryPath);
m_d->isLoading = false;
if (!successfullyLoaded) {
// Restart the attempt
m_d->fileChangedSignalCompressor.start();
} else {
emit loadingFinished();
}
}
/*
* Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_SAFE_DOCUMENT_LOADER_H
#define __KIS_SAFE_DOCUMENT_LOADER_H
#include <QObject>
#include "kis_types.h"
class KisDoc2;
class KisSafeDocumentLoader : public QObject
{
Q_OBJECT
public:
KisSafeDocumentLoader(const QString &path = "", QObject *parent = 0);
~KisSafeDocumentLoader();
public:
KisImageWSP image() const;
void setPath(const QString &path);
void reloadImage();
private slots:
void fileChanged();
void fileChangedCompressed(bool sync = false);
void delayedLoadStart();
signals:
void loadingFinished();
private:
struct Private;
Private * const m_d;
};
#endif /* __KIS_SAFE_DOCUMENT_LOADER_H */
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment