Skip to content
This repository has been archived by the owner on Nov 29, 2020. It is now read-only.

Commit

Permalink
Factor out "write-temp-file-then-replace" code into AtomicFileOverwri…
Browse files Browse the repository at this point in the history
…ter class.

git-svn-id: https://scantailor.svn.sourceforge.net/svnroot/scantailor/trunk@133 80b70061-164d-0410-b7b7-e274ec932871
  • Loading branch information
jart committed Aug 31, 2008
1 parent a627fc9 commit bc170df
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 24 deletions.
81 changes: 81 additions & 0 deletions AtomicFileOverwriter.cpp
@@ -0,0 +1,81 @@
/*
Scan Tailor - Interactive post-processing tool for scanned pages.
Copyright (C) 2007-2008 Joseph Artsimovich <joseph_a@mail.ru>
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 3 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, see <http://www.gnu.org/licenses/>.
*/

#include "AtomicFileOverwriter.h"
#include "Utils.h"
#include <QString>
#include <QFile>
#include <QTemporaryFile>

AtomicFileOverwriter::AtomicFileOverwriter()
{
}

AtomicFileOverwriter::~AtomicFileOverwriter()
{
abort();
}

QIODevice*
AtomicFileOverwriter::startWriting(QString const& file_path)
{
abort();

m_ptrTempFile.reset(new QTemporaryFile(file_path));
m_ptrTempFile->setAutoRemove(false);
if (!m_ptrTempFile->open()) {
m_ptrTempFile->reset();
}

return m_ptrTempFile.get();
}

bool
AtomicFileOverwriter::commit()
{
if (!m_ptrTempFile.get()) {
return false;
}

QString const temp_file_path(m_ptrTempFile->fileName());
QString const target_path(m_ptrTempFile->fileTemplate());

// Yes, we have to destroy this object here, because:
// 1. Under Windows, open files can't be renamed or deleted.
// 2. QTemporaryFile::close() doesn't really close it.
m_ptrTempFile.reset();

if (!Utils::renameFile(temp_file_path, target_path)) {
QFile::remove(temp_file_path);
return false;
}

return true;
}

void
AtomicFileOverwriter::abort()
{
if (!m_ptrTempFile.get()) {
return;
}

QString const temp_file_path(m_ptrTempFile->fileName());
m_ptrTempFile.reset(); // See comments in commit()
QFile::remove(temp_file_path);
}
75 changes: 75 additions & 0 deletions AtomicFileOverwriter.h
@@ -0,0 +1,75 @@
/*
Scan Tailor - Interactive post-processing tool for scanned pages.
Copyright (C) 2007-2008 Joseph Artsimovich <joseph_a@mail.ru>
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 3 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, see <http://www.gnu.org/licenses/>.
*/

#ifndef ATOMICFILEOVERWRITER_H_
#define ATOMICFILEOVERWRITER_H_

#include "NonCopyable.h"
#include <memory>

class QString;
class QIODevice;
class QTemporaryFile;

/**
* \brief Overwrites files by writing to a temporary file and then replacing
* the target file with it.
*
* Because renaming across volumes doesn't work, we create a temporary file
* in the same directory as the target file.
*/
class AtomicFileOverwriter
{
DECLARE_NON_COPYABLE(AtomicFileOverwriter)
public:
AtomicFileOverwriter();

/**
* \brief Destroys the object and calls abort() if necessary.
*/
~AtomicFileOverwriter();

/**
* \brief Start writing to a temporary file.
*
* \returns A temporary file as QIODevice, or null of temporary file
* could not be opened. In latter case, calling abort()
* is not necessary.
*
* If a file is already being written, it calles abort() and then
* proceeds as usual.
*/
QIODevice* startWriting(QString const& file_path);

/**
* \brief Replaces the target file with the temporary one.
*
* If replacing failed, false is returned and the temporary file
* is removed.
*/
bool commit();

/**
* \brief Removes the temporary file without touching the target one.
*/
void abort();
private:
std::auto_ptr<QTemporaryFile> m_ptrTempFile;
};

#endif
1 change: 1 addition & 0 deletions CMakeLists.txt
Expand Up @@ -216,6 +216,7 @@ SET(
ProjectWriter.cpp ProjectWriter.h ProjectWriter.cpp ProjectWriter.h
XmlMarshaller.cpp XmlMarshaller.h XmlMarshaller.cpp XmlMarshaller.h
XmlUnmarshaller.cpp XmlUnmarshaller.h XmlUnmarshaller.cpp XmlUnmarshaller.h
AtomicFileOverwriter.cpp AtomicFileOverwriter.h
Utils.cpp Utils.h Utils.cpp Utils.h
AutoManualMode.h AutoManualMode.h
AbstractCommand.h AbstractCommand.h
Expand Down
36 changes: 12 additions & 24 deletions ThumbnailPixmapCache.cpp
Expand Up @@ -19,7 +19,7 @@
#include "ThumbnailPixmapCache.h" #include "ThumbnailPixmapCache.h"
#include "ImageId.h" #include "ImageId.h"
#include "ImageLoader.h" #include "ImageLoader.h"
#include "Utils.h" #include "AtomicFileOverwriter.h"
#include "imageproc/Scale.h" #include "imageproc/Scale.h"
#include <QCoreApplication> #include <QCoreApplication>
#include <QCryptographicHash> #include <QCryptographicHash>
Expand All @@ -29,8 +29,6 @@
#include <QFileInfo> #include <QFileInfo>
#include <QDir> #include <QDir>
#include <QFile> #include <QFile>
#include <QTemporaryFile>
#include <QAbstractFileEngine>
#include <QString> #include <QString>
#include <QChar> #include <QChar>
#include <QImage> #include <QImage>
Expand Down Expand Up @@ -453,16 +451,10 @@ ThumbnailPixmapCache::Impl::ensureThumbnailExists(


QImage const thumbnail(makeThumbnail(image, max_thumb_size)); QImage const thumbnail(makeThumbnail(image, max_thumb_size));


// To avoid concurrently writing to the same file, we write AtomicFileOverwriter overwriter;
// to a temporary file and then replace the original. QIODevice* iodev = overwriter.startWriting(thumb_file_path);
QTemporaryFile temp_file(thumb_file_path); if (thumbnail.save(iodev, "PNG")) {
temp_file.setAutoRemove(true); overwriter.commit();
if (temp_file.open()) {
if (thumbnail.save(&temp_file, "PNG")) {
if (Utils::renameFile(temp_file.fileName(), thumb_file_path)) {
temp_file.setAutoRemove(false);
}
}
} }
} }


Expand All @@ -487,17 +479,13 @@ ThumbnailPixmapCache::Impl::recreateThumbnail(
QImage const thumbnail(makeThumbnail(image, max_thumb_size)); QImage const thumbnail(makeThumbnail(image, max_thumb_size));
bool thumb_written = false; bool thumb_written = false;


// To avoid concurrently writing to the same file, we write // Note that we may be called from multiple threads at the same time.
// to a temporary file and then replace the original. AtomicFileOverwriter overwriter;
QTemporaryFile temp_file(thumb_file_path); QIODevice* iodev = overwriter.startWriting(thumb_file_path);
temp_file.setAutoRemove(true); if (thumbnail.save(iodev, "PNG")) {
if (temp_file.open()) { thumb_written = overwriter.commit();
if (thumbnail.save(&temp_file, "PNG")) { } else {
if (Utils::renameFile(temp_file.fileName(), thumb_file_path)) { overwriter.abort();
thumb_written = true;
temp_file.setAutoRemove(false);
}
}
} }


if (!thumb_written) { if (!thumb_written) {
Expand Down

0 comments on commit bc170df

Please sign in to comment.