1 /**********************************************************************
2 
3 Audacity: A Digital Audio Editor
4 
5 ProjectFileIO.h
6 
7 Paul Licameli split from AudacityProject.h
8 
9 **********************************************************************/
10 
11 #ifndef __AUDACITY_PROJECT_FILE_IO__
12 #define __AUDACITY_PROJECT_FILE_IO__
13 
14 #include <memory>
15 #include <unordered_set>
16 
17 #include "ClientData.h" // to inherit
18 #include "Prefs.h" // to inherit
19 #include "XMLTagHandler.h" // to inherit
20 
21 struct sqlite3;
22 struct sqlite3_context;
23 struct sqlite3_stmt;
24 struct sqlite3_value;
25 
26 class AudacityProject;
27 class DBConnection;
28 struct DBConnectionErrors;
29 class ProjectSerializer;
30 class SqliteSampleBlock;
31 class TrackList;
32 class WaveTrack;
33 
34 namespace BasicUI{ class WindowPlacement; }
35 
36 using WaveTrackArray = std::vector < std::shared_ptr < WaveTrack > >;
37 
38 // From SampleBlock.h
39 using SampleBlockID = long long;
40 
41 using Connection = std::unique_ptr<DBConnection>;
42 
43 using BlockIDs = std::unordered_set<SampleBlockID>;
44 
45 // An event processed by the project in the main thread after a checkpoint
46 // failure was detected in a worker thread
47 wxDECLARE_EXPORTED_EVENT( AUDACITY_DLL_API,
48                           EVT_CHECKPOINT_FAILURE, wxCommandEvent );
49 
50 // An event processed by the project in the main thread after failure to
51 // reconnect to the database, after temporary close and attempted file movement
52 wxDECLARE_EXPORTED_EVENT( AUDACITY_DLL_API,
53                           EVT_RECONNECTION_FAILURE, wxCommandEvent );
54 
55 ///\brief Object associated with a project that manages reading and writing
56 /// of Audacity project file formats, and autosave
57 class AUDACITY_DLL_API ProjectFileIO final
58    : public ClientData::Base
59    , public XMLTagHandler
60    , private PrefsListener
61    , public std::enable_shared_from_this<ProjectFileIO>
62 {
63 public:
64    // Call this static function once before constructing any instances of this
65    // class.  Reinvocations have no effect.  Return value is true for success.
66    static bool InitializeSQL();
67 
68    static ProjectFileIO &Get( AudacityProject &project );
69    static const ProjectFileIO &Get( const AudacityProject &project );
70 
71    explicit ProjectFileIO( AudacityProject &project );
72 
73    ProjectFileIO( const ProjectFileIO & ) PROHIBITED;
74    ProjectFileIO &operator=( const ProjectFileIO & ) PROHIBITED;
75    ~ProjectFileIO();
76 
77    // It seems odd to put this method in this class, but the results do depend
78    // on what is discovered while opening the file, such as whether it is a
79    // recovery file
80    void SetProjectTitle(int number = -1);
81 
82    // Should be empty or a fully qualified file name
83    const FilePath &GetFileName() const;
84    void SetFileName( const FilePath &fileName );
85 
86    bool IsModified() const;
87    bool IsTemporary() const;
88    bool IsRecovered() const;
89 
90    bool AutoSave(bool recording = false);
91    bool AutoSaveDelete(sqlite3 *db = nullptr);
92 
93    bool OpenProject();
94    bool CloseProject();
95    bool ReopenProject();
96 
97    bool LoadProject(const FilePath &fileName, bool ignoreAutosave);
98    bool UpdateSaved(const TrackList *tracks = nullptr);
99    bool SaveProject(const FilePath &fileName, const TrackList *lastSaved);
100    bool SaveCopy(const FilePath& fileName);
101 
102    wxLongLong GetFreeDiskSpace() const;
103 
104    // Returns the bytes used for the given sample block
105    int64_t GetBlockUsage(SampleBlockID blockid);
106 
107    // Returns the bytes used for all blocks owned by the given track list
108    int64_t GetCurrentUsage(
109          const std::vector<const TrackList*> &trackLists) const;
110 
111    // Return the bytes used by all sample blocks in the project file, whether
112    // they are attached to the active tracks or held by the Undo manager.
113    int64_t GetTotalUsage();
114 
115    // Return the bytes used for the given block using the connection to a
116    // specific database. This is the workhorse for the above 3 methods.
117    static int64_t GetDiskUsage(DBConnection &conn, SampleBlockID blockid);
118 
119    // Displays an error dialog with a button that offers help
120    void ShowError(const BasicUI::WindowPlacement &placement,
121                   const TranslatableString &dlogTitle,
122                   const TranslatableString &message,
123                   const wxString &helpPage);
124    const TranslatableString &GetLastError() const;
125    const TranslatableString &GetLibraryError() const;
126    int GetLastErrorCode() const;
127    const wxString &GetLastLog() const;
128 
129    // Provides a means to bypass "DELETE"s at shutdown if the database
130    // is just going to be deleted anyway.  This prevents a noticeable
131    // delay caused by SampleBlocks being deleted when the Sequences that
132    // own them are deleted.
133    //
134    // This is definitely hackage territory.  While this ability would
135    // still be needed, I think handling it in a DB abstraction might be
136    // a tad bit cleaner.
137    //
138    // For it's usage, see:
139    //    SqliteSampleBlock::~SqliteSampleBlock()
140    //    ProjectManager::OnCloseWindow()
141    void SetBypass();
142 
143 private:
144    //! Strings like -wal that may be appended to main project name to get other files created by
145    //! the database system
146    static const std::vector<wxString> &AuxiliaryFileSuffixes();
147 
148    //! Generate a name for short-lived backup project files from an existing project
149    static FilePath SafetyFileName(const FilePath &src);
150 
151    //! Rename a file or put up appropriate warning message.
152    /*! Failure might happen when renaming onto another device, doing copy of contents */
153    bool RenameOrWarn(const FilePath &src, const FilePath &dst);
154 
155    bool MoveProject(const FilePath &src, const FilePath &dst);
156 
157 public:
158    //! Remove any files associated with a project at given path; return true if successful
159    static bool RemoveProject(const FilePath &filename);
160 
161    // Object manages the temporary backing-up of project paths while
162    // trying to overwrite with new contents, and restoration in case of failure
163    class BackupProject {
164    public:
165       //! Rename project file at path, and any auxiliary files, to backup path names
166       BackupProject( ProjectFileIO &projectFileIO, const FilePath &path );
167       //! Returns false if the renaming in the constructor failed
IsOk()168       bool IsOk() { return !mPath.empty(); }
169       //! if `!IsOk()` do nothing; else remove backup files
170       void Discard();
171       //! if `!IsOk()` do nothing; else if `Discard()` was not called, undo the renaming
172       ~BackupProject();
173    private:
174       FilePath mPath, mSafety;
175    };
176 
177    // Remove all unused space within a project file
178    void Compact(
179       const std::vector<const TrackList *> &tracks, bool force = false);
180 
181    // The last compact check did actually compact the project file if true
182    bool WasCompacted();
183 
184    // The last compact check found unused blocks in the project file
185    bool HadUnused();
186 
187    // In one SQL command, delete sample blocks with ids in the given set, or
188    // (when complement is true), with ids not in the given set.
189    bool DeleteBlocks(const BlockIDs &blockids, bool complement);
190 
191    // Type of function that is given the fields of one row and returns
192    // 0 for success or non-zero to stop the query
193    using ExecCB = std::function<int(int cols, char **vals, char **names)>;
194 
195    //! Return true if a connection is now open
196    bool HasConnection() const;
197 
198    //! Return a reference to a connection, creating it as needed on demand; throw on failure
199    DBConnection &GetConnection();
200 
201    //! Return a strings representation of the active project XML doc
202    wxString GenerateDoc();
203 
204 private:
205    void OnCheckpointFailure();
206 
207    void WriteXMLHeader(XMLWriter &xmlFile) const;
208    void WriteXML(XMLWriter &xmlFile, bool recording = false,
209       const TrackList *tracks = nullptr) /* not override */;
210 
211    // XMLTagHandler callback methods
212    bool HandleXMLTag(const std::string_view& tag, const AttributesList &attrs) override;
213    XMLTagHandler *HandleXMLChild(const std::string_view& tag) override;
214 
215    void UpdatePrefs() override;
216 
217    int Exec(const char *query, const ExecCB &callback, bool silent = false);
218 
219    // The opening of the database may be delayed until demanded.
220    // Returns a non-null pointer to an open database, or throws an exception
221    // if opening fails.
222    sqlite3 *DB();
223 
224    bool OpenConnection(FilePath fileName = {});
225    bool CloseConnection();
226 
227    // Put the current database connection aside, keeping it open, so that
228    // another may be opened with OpenDB()
229    void SaveConnection();
230 
231    // Close any set-aside connection
232    void DiscardConnection();
233 
234    // Close any current connection and switch back to using the saved
235    void RestoreConnection();
236 
237    // Use a connection that is already open rather than invoke OpenConnection
238    void UseConnection(Connection &&conn, const FilePath &filePath);
239 
240    bool Query(const char *sql, const ExecCB &callback, bool silent = false);
241 
242    bool GetValue(const char *sql, wxString &value, bool silent = false);
243    bool GetValue(const char *sql, int64_t &value, bool silent = false);
244 
245    bool CheckVersion();
246    bool InstallSchema(sqlite3 *db, const char *schema = "main");
247 
248    // Write project or autosave XML (binary) documents
249    bool WriteDoc(const char *table, const ProjectSerializer &autosave, const char *schema = "main");
250 
251    // Application defined function to verify blockid exists is in set of blockids
252    static void InSet(sqlite3_context *context, int argc, sqlite3_value **argv);
253 
254    // Return a database connection if successful, which caller must close
255    bool CopyTo(const FilePath &destpath,
256       const TranslatableString &msg,
257       bool isTemporary,
258       bool prune = false,
259       const std::vector<const TrackList *> &tracks = {} /*!<
260          First track list (or if none, then the project's track list) are tracks to write into document blob;
261          That list, plus any others, contain tracks whose sample blocks must be kept
262       */
263    );
264 
265    //! Just set stored errors
266    void SetError(const TranslatableString & msg,
267        const TranslatableString& libraryError = {},
268        int errorCode = {});
269 
270    //! Set stored errors and write to log; and default libraryError to what database library reports
271    void SetDBError(const TranslatableString & msg,
272        const TranslatableString& libraryError = {},
273        int errorCode = -1);
274 
275    bool ShouldCompact(const std::vector<const TrackList *> &tracks);
276 
277    // Gets values from SQLite B-tree structures
278    static unsigned int get2(const unsigned char *ptr);
279    static unsigned int get4(const unsigned char *ptr);
280    static int get_varint(const unsigned char *ptr, int64_t *out);
281 
282 private:
283    Connection &CurrConn();
284 
285    // non-static data members
286    AudacityProject &mProject;
287 
288    std::shared_ptr<DBConnectionErrors> mpErrors;
289 
290    // The project's file path
291    FilePath mFileName;
292 
293    // Has this project been recovered from an auto-saved version
294    bool mRecovered;
295 
296    // Has this project been modified
297    bool mModified;
298 
299    // Is this project still a temporary/unsaved project
300    bool mTemporary;
301 
302    // Project was compacted last time Compact() ran
303    bool mWasCompacted;
304 
305    // Project had unused blocks during last Compact()
306    bool mHadUnused;
307 
308    Connection mPrevConn;
309    FilePath mPrevFileName;
310    bool mPrevTemporary;
311 };
312 
313 class wxTopLevelWindow;
314 
315 // TitleRestorer restores project window titles to what they were, in its destructor.
316 class TitleRestorer{
317 public:
318    TitleRestorer( wxTopLevelWindow &window, AudacityProject &project );
319    ~TitleRestorer();
320    wxString sProjNumber;
321    wxString sProjName;
322    size_t UnnamedCount;
323 };
324 
325 // This event is emitted by the project when there is a change
326 // in its title
327 wxDECLARE_EXPORTED_EVENT(AUDACITY_DLL_API,
328                          EVT_PROJECT_TITLE_CHANGE, wxCommandEvent);
329 
330 //! Makes a temporary project that doesn't display on the screen
331 class AUDACITY_DLL_API InvisibleTemporaryProject
332 {
333 public:
334    InvisibleTemporaryProject();
335    ~InvisibleTemporaryProject();
Project()336    AudacityProject &Project()
337    {
338       return *mpProject;
339    }
340 private:
341    std::shared_ptr<AudacityProject> mpProject;
342 };
343 
344 #endif
345