1 /*
2  * LibrePCB - Professional EDA for everyone!
3  * Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
4  * https://librepcb.org/
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #ifndef LIBREPCB_TRANSACTIONALFILESYSTEM_H
21 #define LIBREPCB_TRANSACTIONALFILESYSTEM_H
22 
23 /*******************************************************************************
24  *  Includes
25  ******************************************************************************/
26 #include "directorylock.h"
27 #include "filesystem.h"
28 
29 #include <QtCore>
30 
31 #include <memory>
32 
33 /*******************************************************************************
34  *  Namespace / Forward Declarations
35  ******************************************************************************/
36 
37 class QuaZipFile;
38 
39 namespace librepcb {
40 
41 /*******************************************************************************
42  *  Class TransactionalFileSystem
43  ******************************************************************************/
44 
45 /**
46  * @brief Transactional ::librepcb::FileSystem implementation
47  *
48  * This is an implementation of the ::librepcb::FileSystem interface with many
49  * features needed to create, open and save LibrePCB library elements and
50  * projects in a very safe way to always guarantee consistency of all files.
51  *
52  * It handles following things:
53  *  - Supports read-only access to the file system to guarantee absolutely
54  *    nothing is written to the disk.
55  *  - In R/W mode, it locks the accessed directory to avoid parallel usage (see
56  *    @ref doc_project_lock)
57  *  - Supports periodic saving to allow restoring the last autosave backup after
58  *    an application crash (see @ref doc_project_autosave).
59  *  - Holds all file modifications in memory and allows to write those in an
60  *    atomic way to the disk (see @ref doc_project_save).
61  *  - Allows to export the whole file system to a ZIP file.
62  */
63 class TransactionalFileSystem final : public FileSystem {
64   Q_OBJECT
65 
66 public:
67   /**
68    * @brief Function to filter files
69    *
70    * @param filePath    The relative file path to filter.
71    *
72    * @retval true   Include file.
73    * @retval false  Do not include file.
74    */
75   typedef std::function<bool(const QString& filePath)> FilterFunction;
76 
77   /**
78    * @brief Callback type used to determine whether a backup should be restored
79    *        or not
80    *
81    * @param dir   The directory to be restored.
82    *
83    * @retval true   Restore backup.
84    * @retval false  Do not restore backup.
85    *
86    * @throw ::librepcb::Exception to abort opening the directory.
87    */
88   typedef std::function<bool(const FilePath& dir)> RestoreCallback;
89 
90   /**
91    * @brief Convenience class providing standard implementations for
92    *        ::librepcb::TransactionalFileSystem::RestoreCallback
93    *
94    * @note This is a class just to put some functions into their own scope.
95    */
96   struct RestoreMode {
97     /**
98      * @brief Never restore a backup
99      *
100      * @param dir The directory to be opened.
101      *
102      * @return false
103      */
noRestoreMode104     static bool no(const FilePath& dir) {
105       Q_UNUSED(dir);
106       return false;
107     }
108 
109     /**
110      * @brief Always restore the backup, if there is any
111      *
112      * @param dir The directory to be opened.
113      *
114      * @return true
115      */
yesRestoreMode116     static bool yes(const FilePath& dir) {
117       Q_UNUSED(dir);
118       return true;
119     }
120 
121     /**
122      * @brief If there exists a backup, abort opening the directory by raising
123      *        an exception
124      *
125      * @param dir The directory to be opened.
126      *
127      * @return Nothing, since an exception is thrown.
128      *
129      * @throw ::librepcb::RuntimeError
130      */
abortRestoreMode131     static bool abort(const FilePath& dir) {
132       throw RuntimeError(__FILE__, __LINE__,
133                          QString("Autosave backup detected in directory '%1'.")
134                              .arg(dir.toNative()));
135     }
136   };
137 
138   // Constructors / Destructor
139   TransactionalFileSystem() = delete;
140   TransactionalFileSystem(
141       const FilePath& filepath, bool writable = false,
142       RestoreCallback restoreCallback = RestoreCallback(),
143       DirectoryLock::LockHandlerCallback lockCallback = nullptr,
144       QObject* parent = nullptr);
145   TransactionalFileSystem(const TransactionalFileSystem& other) = delete;
146   virtual ~TransactionalFileSystem() noexcept;
147 
148   // Getters
getPath()149   const FilePath& getPath() const noexcept { return mFilePath; }
isWritable()150   bool isWritable() const noexcept { return mIsWritable; }
isRestoredFromAutosave()151   bool isRestoredFromAutosave() const noexcept { return mRestoredFromAutosave; }
152 
153   // Inherited from FileSystem
154   virtual FilePath getAbsPath(const QString& path = "") const noexcept override;
155   virtual QStringList getDirs(const QString& path = "") const noexcept override;
156   virtual QStringList getFiles(const QString& path = "") const
157       noexcept override;
158   virtual bool fileExists(const QString& path) const noexcept override;
159   virtual QByteArray read(const QString& path) const override;
160   virtual void write(const QString& path, const QByteArray& content) override;
161   virtual void removeFile(const QString& path) override;
162   virtual void removeDirRecursively(const QString& path = "") override;
163 
164   // General Methods
165   void loadFromZip(QByteArray content);
166   void loadFromZip(const FilePath& fp);
167   QByteArray exportToZip(FilterFunction filter = nullptr) const;
168   void exportToZip(const FilePath& fp, FilterFunction filter = nullptr) const;
169   void discardChanges() noexcept;
170   QStringList checkForModifications() const;
171   void autosave();
172   void save();
173 
174   // Static Methods
175   static std::shared_ptr<TransactionalFileSystem> open(
176       const FilePath& filepath, bool writable,
177       RestoreCallback restoreCallback = &RestoreMode::no,
178       DirectoryLock::LockHandlerCallback lockCallback = nullptr,
179       QObject* parent = nullptr) {
180     return std::make_shared<TransactionalFileSystem>(
181         filepath, writable, restoreCallback, lockCallback, parent);
182   }
183   static std::shared_ptr<TransactionalFileSystem> openRO(
184       const FilePath& filepath,
185       RestoreCallback restoreCallback = &RestoreMode::no,
186       QObject* parent = nullptr) {
187     return open(filepath, false, restoreCallback, nullptr, parent);
188   }
189   static std::shared_ptr<TransactionalFileSystem> openRW(
190       const FilePath& filepath,
191       RestoreCallback restoreCallback = &RestoreMode::no,
192       DirectoryLock::LockHandlerCallback lockCallback = nullptr,
193       QObject* parent = nullptr) {
194     return open(filepath, true, restoreCallback, lockCallback, parent);
195   }
196   static QString cleanPath(QString path) noexcept;
197 
198 private:  // Methods
199   bool isRemoved(const QString& path) const noexcept;
200   void exportDirToZip(QuaZipFile& file, const FilePath& zipFp,
201                       const QString& dir, FilterFunction filter) const;
202   void saveDiff(const QString& type) const;
203   void loadDiff(const FilePath& fp);
204   void removeDiff(const QString& type);
205 
206 private:  // Data
207   FilePath mFilePath;
208   bool mIsWritable;
209   DirectoryLock mLock;
210   bool mRestoredFromAutosave;
211 
212   // File system modifications
213   QHash<QString, QByteArray> mModifiedFiles;
214   QSet<QString> mRemovedFiles;
215   QSet<QString> mRemovedDirs;
216 };
217 
218 /*******************************************************************************
219  *  End of File
220  ******************************************************************************/
221 
222 }  // namespace librepcb
223 
224 #endif  // LIBREPCB_TRANSACTIONALFILESYSTEM_H
225