mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-04-08 03:35:35 +08:00
refs #545 CAtomicFile used in CValueCache to avoid corruption in case serialization is interrupted.
This commit is contained in:
91
src/blackmisc/atomicfile.cpp
Normal file
91
src/blackmisc/atomicfile.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
/* Copyright (C) 2015
|
||||
* swift project Community / Contributors
|
||||
*
|
||||
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
|
||||
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
|
||||
* including this file, may be copied, modified, propagated, or distributed except according to the terms
|
||||
* contained in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "blackmisc/atomicfile.h"
|
||||
#include "blackmisc/algorithm.h"
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
#if defined(Q_OS_POSIX)
|
||||
#include <stdio.h>
|
||||
#elif defined(Q_OS_WIN32)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace BlackMisc
|
||||
{
|
||||
|
||||
bool CAtomicFile::open(CAtomicFile::OpenMode mode)
|
||||
{
|
||||
Q_ASSERT_X(!(mode & (ReadOnly | Append)), Q_FUNC_INFO, "ReadOnly and Append flags are incompatible with CAtomicFile");
|
||||
|
||||
m_originalFilename = fileName();
|
||||
QFileInfo fileInfo(fileName());
|
||||
setFileName(QFileInfo(fileInfo.dir(), ".tmp." + fileInfo.fileName() + "." + randomSuffix()).filePath());
|
||||
if (exists()) { remove(); }
|
||||
|
||||
bool ok = QFile::open(mode);
|
||||
if (! ok) { setFileName(m_originalFilename); }
|
||||
return ok;
|
||||
}
|
||||
|
||||
void CAtomicFile::close()
|
||||
{
|
||||
if (! isOpen()) { return; }
|
||||
|
||||
QFile::close();
|
||||
|
||||
if (error() == NoError) { replaceOriginal(); }
|
||||
setFileName(m_originalFilename);
|
||||
}
|
||||
|
||||
bool CAtomicFile::checkedClose()
|
||||
{
|
||||
close();
|
||||
return error() == NoError;
|
||||
}
|
||||
|
||||
CAtomicFile::FileError CAtomicFile::error() const
|
||||
{
|
||||
if (m_renameError) { return RenameError; }
|
||||
return QFile::error();
|
||||
}
|
||||
|
||||
void CAtomicFile::unsetError()
|
||||
{
|
||||
m_renameError = false;
|
||||
QFile::unsetError();
|
||||
}
|
||||
|
||||
QString CAtomicFile::randomSuffix()
|
||||
{
|
||||
Q_CONSTEXPR auto max = 2176782335;
|
||||
return QString::number(std::uniform_int_distribution<std::decay<decltype(max)>::type>(0, max)(Private::defaultRandomGenerator()), 36);
|
||||
}
|
||||
|
||||
#if defined(Q_OS_POSIX)
|
||||
void CAtomicFile::replaceOriginal()
|
||||
{
|
||||
auto result = ::rename(qPrintable(fileName()), qPrintable(m_originalFilename));
|
||||
if (result < 0) { m_renameError = true; }
|
||||
}
|
||||
#elif defined(Q_OS_WIN32)
|
||||
void CAtomicFile::replaceOriginal()
|
||||
{
|
||||
auto result = MoveFileExA(qPrintable(fileName()), qPrintable(m_originalFilename), MOVEFILE_REPLACE_EXISTING);
|
||||
if (! result) { m_renameError = true; }
|
||||
}
|
||||
#else
|
||||
void CAtomicFile::replaceOriginal()
|
||||
{
|
||||
if (exists(m_originalFilename)) { remove(m_originalFilename); }
|
||||
rename(m_originalFilename);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
61
src/blackmisc/atomicfile.h
Normal file
61
src/blackmisc/atomicfile.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/* Copyright (C) 2015
|
||||
* swift project Community / Contributors
|
||||
*
|
||||
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
|
||||
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
|
||||
* including this file, may be copied, modified, propagated, or distributed except according to the terms
|
||||
* contained in the LICENSE file.
|
||||
*/
|
||||
|
||||
//! \file
|
||||
|
||||
#ifndef BLACKMISC_ATOMICFILE_H
|
||||
#define BLACKMISC_ATOMICFILE_H
|
||||
|
||||
#include <QFile>
|
||||
#include "blackmisc/blackmiscexport.h"
|
||||
|
||||
namespace BlackMisc
|
||||
{
|
||||
/*!
|
||||
* A subclass of QFile which writes to a temporary file while it is open, then renames the file
|
||||
* when it is closed, so that it overwrites the target file as a single, atomic transaction.
|
||||
*
|
||||
* If the application crashes while data is still being written, the original file is unchanged.
|
||||
*/
|
||||
class BLACKMISC_EXPORT CAtomicFile : public QFile
|
||||
{
|
||||
public:
|
||||
//! \copydoc QFile::QFile(const QString &)
|
||||
CAtomicFile(const QString &filename) : QFile(filename) {}
|
||||
|
||||
//! \copydoc QFile::~QFile
|
||||
~CAtomicFile() { close(); }
|
||||
|
||||
//! \copydoc QFile::open
|
||||
//! Just before opening the file, the filename is changed so we actually write to a temporary file.
|
||||
virtual bool open(OpenMode mode) override;
|
||||
|
||||
//! \copydoc QFileDevice::close
|
||||
//! After closing the file, it is renamed so that it overwrites the target file.
|
||||
virtual void close() override;
|
||||
|
||||
//! Calls close() and returns false if there was an error at any stage.
|
||||
bool checkedClose();
|
||||
|
||||
//! \copydoc QFileDevice::error
|
||||
FileError error() const;
|
||||
|
||||
//! \copydoc QFileDevice::unsetError
|
||||
void unsetError();
|
||||
|
||||
private:
|
||||
static QString randomSuffix();
|
||||
void replaceOriginal();
|
||||
|
||||
QString m_originalFilename;
|
||||
bool m_renameError = false;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "blackmisc/logmessage.h"
|
||||
#include "blackmisc/algorithm.h"
|
||||
#include "blackmisc/lockfree.h"
|
||||
#include "blackmisc/atomicfile.h"
|
||||
#include <QThread>
|
||||
#include <QJsonDocument>
|
||||
|
||||
@@ -230,23 +231,25 @@ namespace BlackMisc
|
||||
}
|
||||
for (auto it = namespaces.cbegin(); it != namespaces.cend(); ++it)
|
||||
{
|
||||
QFile file(dir + "/" + it.key() + ".json");
|
||||
if (! file.open(QFile::ReadWrite | QFile::Text))
|
||||
QFile readFile(dir + "/" + it.key() + ".json");
|
||||
if (! readFile.open(QFile::ReadWrite | QFile::Text))
|
||||
{
|
||||
return CLogMessage(this).error("Failed to open %1: %2") << file.fileName() << file.errorString();
|
||||
return CLogMessage(this).error("Failed to open %1: %2") << readFile.fileName() << readFile.errorString();
|
||||
}
|
||||
auto json = QJsonDocument::fromJson(file.readAll());
|
||||
auto json = QJsonDocument::fromJson(readFile.readAll());
|
||||
if (json.isArray() || (json.isNull() && ! json.isEmpty()))
|
||||
{
|
||||
return CLogMessage(this).error("Invalid JSON format in %1") << file.fileName();
|
||||
return CLogMessage(this).error("Invalid JSON format in %1") << readFile.fileName();
|
||||
}
|
||||
CVariantMap storedValues;
|
||||
storedValues.convertFromJson(json.object());
|
||||
storedValues.insert(*it);
|
||||
json.setObject(storedValues.toJson());
|
||||
if (! (file.seek(0) && file.resize(0) && file.write(json.toJson()) > 0))
|
||||
readFile.close();
|
||||
CAtomicFile writeFile(readFile.fileName());
|
||||
if (! (writeFile.open(QFile::WriteOnly | QFile::Text) && writeFile.write(json.toJson()) > 0 && writeFile.checkedClose()))
|
||||
{
|
||||
return CLogMessage(this).error("Failed to write to %1: %2") << file.fileName() << file.errorString();
|
||||
return CLogMessage(this).error("Failed to write to %1: %2") << writeFile.fileName() << writeFile.errorString();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
|
||||
Reference in New Issue
Block a user