Loading files
c730ec352This file is pinned to the selected release. Download the original to open it in your design tool.
/*
* LibrePCB - Professional EDA for everyone!
* Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
* https://librepcb.org/
*
* 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 LIBREPCB_CORE_TRANSACTIONALFILESYSTEM_H
#define LIBREPCB_CORE_TRANSACTIONALFILESYSTEM_H
/*******************************************************************************
* Includes
******************************************************************************/
#include "../exceptions.h"
#include "directorylock.h"
#include "filesystem.h"
#include <QtCore>
#include <memory>
/*******************************************************************************
* Namespace / Forward Declarations
******************************************************************************/
namespace librepcb {
class ZipWriter;
/*******************************************************************************
* Class TransactionalFileSystem
******************************************************************************/
/**
* @brief Transactional ::librepcb::FileSystem implementation
*
* This is an implementation of the ::librepcb::FileSystem interface with many
* features needed to create, open and save LibrePCB library elements and
* projects in a very safe way to always guarantee consistency of all files.
*
* It handles following things:
* - Supports read-only access to the file system to guarantee absolutely
* nothing is written to the disk.
* - In R/W mode, it locks the accessed directory to avoid parallel usage (see
* @ref doc_project_lock)
* - Supports periodic saving to allow restoring the last autosave backup after
* an application crash (see @ref doc_project_autosave).
* - Holds all file modifications in memory and allows to write those in an
* atomic way to the disk (see @ref doc_project_save).
* - Allows to export the whole file system to a ZIP file.
*
* In addition, all public methods of this class are thread-safe, i.e.
* concurrent access to the file system from multiple threads is allowed.
* However, be careful anyway as thread-safety does not mean you cannot
* generate an inconsisntent content of the file system. Generally it's
* recommended to make write operations only from one thread, and only
* read operations from all other threads.
*/
class TransactionalFileSystem final : public FileSystem {
Q_OBJECT
public:
/**
* @brief Function to filter files
*
* @param filePath The relative file path to filter.
*
* @retval true Include file.
* @retval false Do not include file.
*/
typedef std::function<bool(const QString& filePath)> FilterFunction;
/**
* @brief Callback type used to determine whether a backup should be restored
* or not
*
* @param dir The directory to be restored.
*
* @retval true Restore backup.
* @retval false Do not restore backup.
*
* @throw ::librepcb::Exception to abort opening the directory.
*/
typedef std::function<bool(const FilePath& dir)> RestoreCallback;
/**
* @brief Convenience class providing standard implementations for
* ::librepcb::TransactionalFileSystem::RestoreCallback
*
* @note This is a class just to put some functions into their own scope.
*/
struct RestoreMode {
/**
* @brief Never restore a backup
*
* @param dir The directory to be opened.
*
* @return false
*/
static bool no(const FilePath& dir) {
Q_UNUSED(dir);
return false;
}
/**
* @brief Always restore the backup, if there is any
*
* @param dir The directory to be opened.
*
* @return true
*/
static bool yes(const FilePath& dir) {
Q_UNUSED(dir);
return true;
}
/**
* @brief If there exists a backup, abort opening the directory by raising
* an exception
*
* @param dir The directory to be opened.
*
* @return Nothing, since an exception is thrown.
*
* @throw ::librepcb::RuntimeError
*/
static bool abort(const FilePath& dir) {
throw RuntimeError(__FILE__, __LINE__,
QString("Autosave backup detected in directory '%1'.")
.arg(dir.toNative()));
}
};
/**
* @brief Snapshot of modifications made to the file system
*/
struct State {
QHash<QString, QByteArray> modifiedFiles;
QSet<QString> removedFiles;
QSet<QString> removedDirs;
};
// Constructors / Destructor
TransactionalFileSystem() = delete;
TransactionalFileSystem(
const FilePath& filepath, bool writable = false,
RestoreCallback restoreCallback = RestoreCallback(),
DirectoryLock::LockHandlerCallback lockCallback = nullptr,
QObject* parent = nullptr);
TransactionalFileSystem(const TransactionalFileSystem& other) = delete;
~TransactionalFileSystem() noexcept override;
// Getters
const FilePath& getPath() const noexcept { return mFilePath; }
bool isWritable() const noexcept { return mIsWritable; }
bool isRestoredFromAutosave() const noexcept { return mRestoredFromAutosave; }
// Inherited from FileSystem
FilePath getAbsPath(const QString& path = "") const noexcept override;
const QStringList getDirs(const QString& path = "") const noexcept override;
const QStringList getFiles(const QString& path = "") const noexcept override;
bool fileExists(const QString& path) const noexcept override;
QByteArray read(const QString& path) const override;
QByteArray readIfExists(const QString& path) const override;
void write(const QString& path, const QByteArray& content) override;
void renameFile(const QString& src, const QString& dst) override;
void removeFile(const QString& path) override;
void removeDirRecursively(const QString& path = "") override;
// General Methods
State saveState() const noexcept { return mState; }
void restoreState(const State& state) noexcept { mState = state; }
void loadFromZip(QByteArray content);
void loadFromZip(const FilePath& fp);
QByteArray exportToZip(FilterFunction filter = nullptr) const;
void exportToZip(const FilePath& fp, FilterFunction filter = nullptr) const;
void discardChanges() noexcept;
QStringList checkForModifications() const;
void autosave();
void save();
void releaseLock();
// Static Methods
static std::shared_ptr<TransactionalFileSystem> open(
const FilePath& filepath, bool writable,
RestoreCallback restoreCallback = &RestoreMode::no,
DirectoryLock::LockHandlerCallback lockCallback = nullptr,
QObject* parent = nullptr) {
return std::make_shared<TransactionalFileSystem>(
filepath, writable, restoreCallback, lockCallback, parent);
}
static std::shared_ptr<TransactionalFileSystem> openRO(
const FilePath& filepath,
const RestoreCallback& restoreCallback = &RestoreMode::no,
QObject* parent = nullptr) {
return open(filepath, false, restoreCallback, nullptr, parent);
}
static std::shared_ptr<TransactionalFileSystem> openRW(
const FilePath& filepath,
const RestoreCallback& restoreCallback = &RestoreMode::no,
const DirectoryLock::LockHandlerCallback& lockCallback = nullptr,
QObject* parent = nullptr) {
return open(filepath, true, restoreCallback, lockCallback, parent);
}
static QString cleanPath(QString path) noexcept;
private: // Methods
bool isRemoved(const QString& path) const noexcept;
void exportDirToZip(ZipWriter& zip, const FilePath& zipFp, const QString& dir,
FilterFunction filter) const;
void saveDiff(const QString& type) const;
void loadDiff(const FilePath& fp);
void removeDiff(const QString& type);
void sanitizePathOrThrow(const QString& cleanedPath) const;
bool checkIfPathIsSafe(const QString& cleanedPath) const noexcept;
private: // Data
const FilePath mFilePath;
bool mIsWritable;
DirectoryLock mLock;
bool mRestoredFromAutosave;
mutable QRecursiveMutex mMutex;
// File system modifications
State mState;
};
/*******************************************************************************
* End of File
******************************************************************************/
} // namespace librepcb
#endif