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