Audacity  3.0.3
ProjectFileIO.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3 Audacity: A Digital Audio Editor
4 
5 ProjectFileIO.cpp
6 
7 Paul Licameli split from AudacityProject.cpp
8 
9 **********************************************************************/
10 
11 #include "ProjectFileIO.h"
12 
13 #include <atomic>
14 #include <sqlite3.h>
15 #include <wx/app.h>
16 #include <wx/crt.h>
17 #include <wx/frame.h>
18 #include <wx/sstream.h>
19 #include <wx/xml/xml.h>
20 
21 #include "ActiveProjects.h"
22 #include "CodeConversions.h"
23 #include "DBConnection.h"
24 #include "Project.h"
25 #include "ProjectFileIORegistry.h"
26 #include "ProjectSerializer.h"
27 #include "ProjectSettings.h"
28 #include "SampleBlock.h"
29 #include "Tags.h"
30 #include "TempDirectory.h"
31 #include "ViewInfo.h"
32 #include "WaveTrack.h"
35 #include "BasicUI.h"
36 #include "widgets/ProgressDialog.h"
37 #include "wxFileNameWrapper.h"
38 #include "xml/XMLFileReader.h"
39 #include "SentryHelper.h"
40 
41 // Don't change this unless the file format changes
42 // in an irrevocable way
43 #define AUDACITY_FILE_FORMAT_VERSION "1.3.0"
44 
45 #undef NO_SHM
46 #if !defined(__WXMSW__)
47  #define NO_SHM
48 #endif
49 
50 wxDEFINE_EVENT(EVT_PROJECT_TITLE_CHANGE, wxCommandEvent);
51 wxDEFINE_EVENT( EVT_CHECKPOINT_FAILURE, wxCommandEvent);
52 wxDEFINE_EVENT( EVT_RECONNECTION_FAILURE, wxCommandEvent);
53 
54 // Used to convert 4 byte-sized values into an integer for use in SQLite
55 // PRAGMA statements. These values will be store in the database header.
56 //
57 // Note that endianness is not an issue here since SQLite integers are
58 // architecture independent.
59 #define PACK(b1, b2, b3, b4) ((b1 << 24) | (b2 << 16) | (b3 << 8) | b4)
60 
61 // The ProjectFileID is stored in the SQLite database header to identify the file
62 // as an Audacity project file. It can be used by applications that identify file
63 // types, such as the Linux "file" command.
64 static const int ProjectFileID = PACK('A', 'U', 'D', 'Y');
65 
66 // The "ProjectFileVersion" represents the version of Audacity at which a specific
67 // database schema was used. It is assumed that any changes to the database schema
68 // will require a new Audacity version so if schema changes are required set this
69 // to the new release being produced.
70 //
71 // This version is checked before accessing any tables in the database since there's
72 // no guarantee what tables exist. If it's found that the database is newer than the
73 // currently running Audacity, an error dialog will be displayed informing the user
74 // that they need a newer version of Audacity.
75 //
76 // Note that this is NOT the "schema_version" that SQLite maintains. The value
77 // specified here is stored in the "user_version" field of the SQLite database
78 // header.
79 static const int ProjectFileVersion = PACK(3, 0, 0, 0);
80 
81 // Navigation:
82 //
83 // Bindings are marked out in the code by, e.g.
84 // BIND SQL sampleblocks
85 // A search for "BIND SQL" will find all bindings.
86 // A search for "SQL sampleblocks" will find all SQL related
87 // to sampleblocks.
88 
89 static const char *ProjectFileSchema =
90  // These are persistent and not connection based
91  //
92  // See the CMakeList.txt for the SQLite lib for more
93  // settings.
94  "PRAGMA <schema>.application_id = %d;"
95  "PRAGMA <schema>.user_version = %d;"
96  ""
97  // project is a binary representation of an XML file.
98  // it's in binary for speed.
99  // One instance only. id is always 1.
100  // dict is a dictionary of fieldnames.
101  // doc is the binary representation of the XML
102  // in the doc, fieldnames are replaced by 2 byte dictionary
103  // index numbers.
104  // This is all opaque to SQLite. It just sees two
105  // big binary blobs.
106  // There is no limit to document blob size.
107  // dict will be smallish, with an entry for each
108  // kind of field.
109  "CREATE TABLE IF NOT EXISTS <schema>.project"
110  "("
111  " id INTEGER PRIMARY KEY,"
112  " dict BLOB,"
113  " doc BLOB"
114  ");"
115  ""
116  // CREATE SQL autosave
117  // autosave is a binary representation of an XML file.
118  // it's in binary for speed.
119  // One instance only. id is always 1.
120  // dict is a dictionary of fieldnames.
121  // doc is the binary representation of the XML
122  // in the doc, fieldnames are replaced by 2 byte dictionary
123  // index numbers.
124  // This is all opaque to SQLite. It just sees two
125  // big binary blobs.
126  // There is no limit to document blob size.
127  // dict will be smallish, with an entry for each
128  // kind of field.
129  "CREATE TABLE IF NOT EXISTS <schema>.autosave"
130  "("
131  " id INTEGER PRIMARY KEY,"
132  " dict BLOB,"
133  " doc BLOB"
134  ");"
135  ""
136  // CREATE SQL sampleblocks
137  // 'samples' are fixed size blocks of int16, int32 or float32 numbers.
138  // The blocks may be partially empty.
139  // The quantity of valid data in the blocks is
140  // provided in the project blob.
141  //
142  // sampleformat specifies the format of the samples stored.
143  //
144  // blockID is a 64 bit number.
145  //
146  // Rows are immutable -- never updated after addition, but may be
147  // deleted.
148  //
149  // summin to summary64K are summaries at 3 distance scales.
150  "CREATE TABLE IF NOT EXISTS <schema>.sampleblocks"
151  "("
152  " blockid INTEGER PRIMARY KEY AUTOINCREMENT,"
153  " sampleformat INTEGER,"
154  " summin REAL,"
155  " summax REAL,"
156  " sumrms REAL,"
157  " summary256 BLOB,"
158  " summary64k BLOB,"
159  " samples BLOB"
160  ");";
161 
162 // This singleton handles initialization/shutdown of the SQLite library.
163 // It is needed because our local SQLite is built with SQLITE_OMIT_AUTOINIT
164 // defined.
165 //
166 // It's safe to use even if a system version of SQLite is used that didn't
167 // have SQLITE_OMIT_AUTOINIT defined.
169 {
170 public:
172  {
173  // Enable URI filenames for all connections
174  mRc = sqlite3_config(SQLITE_CONFIG_URI, 1);
175  if (mRc == SQLITE_OK)
176  {
177  mRc = sqlite3_config(SQLITE_CONFIG_LOG, LogCallback, nullptr);
178  if (mRc == SQLITE_OK)
179  {
180  mRc = sqlite3_initialize();
181  }
182  }
183 
184 #ifdef NO_SHM
185  if (mRc == SQLITE_OK)
186  {
187  // Use the "unix-excl" VFS to make access to the DB exclusive. This gets
188  // rid of the "<database name>-shm" shared memory file.
189  //
190  // Though it shouldn't, it doesn't matter if this fails.
191  auto vfs = sqlite3_vfs_find("unix-excl");
192  if (vfs)
193  {
194  sqlite3_vfs_register(vfs, 1);
195  }
196  }
197 #endif
198  }
200  {
201  // This function must be called single-threaded only
202  // It returns a value, but there's nothing we can do with it
203  (void) sqlite3_shutdown();
204  }
205 
206  static void LogCallback(void *WXUNUSED(arg), int code, const char *msg)
207  {
208  wxLogMessage("sqlite3 message: (%d) %s", code, msg);
209  }
210 
211  int mRc;
212 };
213 
215 {
216  static SQLiteIniter sqliteIniter;
217  return sqliteIniter.mRc == SQLITE_OK;
218 }
219 
220 static void RefreshAllTitles(bool bShowProjectNumbers )
221 {
222  for ( auto pProject : AllProjects{} ) {
223  if ( !GetProjectFrame( *pProject ).IsIconized() ) {
224  ProjectFileIO::Get( *pProject ).SetProjectTitle(
225  bShowProjectNumbers ? pProject->GetProjectNumber() : -1 );
226  }
227  }
228 }
229 
231  wxTopLevelWindow &window, AudacityProject &project )
232 {
233  if( window.IsIconized() )
234  window.Restore();
235  window.Raise(); // May help identifying the window on Mac
236 
237  // Construct this project's name and number.
238  sProjName = project.GetProjectName();
239  if ( sProjName.empty() ) {
240  sProjName = _("<untitled>");
241  UnnamedCount = std::count_if(
242  AllProjects{}.begin(), AllProjects{}.end(),
243  []( const AllProjects::value_type &ptr ){
244  return ptr->GetProjectName().empty();
245  }
246  );
247  if ( UnnamedCount > 1 ) {
248  sProjNumber.Printf(
249  _("[Project %02i] "), project.GetProjectNumber() + 1 );
250  RefreshAllTitles( true );
251  }
252  }
253  else
254  UnnamedCount = 0;
255 }
256 
258  if( UnnamedCount > 1 )
259  RefreshAllTitles( false );
260 }
261 
263  []( AudacityProject &parent ){
264  auto result = std::make_shared< ProjectFileIO >( parent );
265  return result;
266  }
267 };
268 
270 {
271  auto &result = project.AttachedObjects::Get< ProjectFileIO >( sFileIOKey );
272  return result;
273 }
274 
276 {
277  return Get( const_cast< AudacityProject & >( project ) );
278 }
279 
281  : mProject{ project }
282  , mpErrors{ std::make_shared<DBConnectionErrors>() }
283 {
284  mPrevConn = nullptr;
285 
286  mRecovered = false;
287  mModified = false;
288  mTemporary = true;
289 
290  UpdatePrefs();
291 }
292 
294 {
295 }
296 
298 {
299  auto &connectionPtr = ConnectionPtr::Get( mProject );
300  return connectionPtr.mpConnection != nullptr;
301 }
302 
304 {
305  auto &curConn = CurrConn();
306  if (!curConn)
307  {
308  if (!OpenConnection())
309  {
311  {
313  XO("Failed to open the project's database"),
314  XO("Warning"),
315  "Error:_Disk_full_or_not_writable"
316  };
317  }
318  }
319 
320  return *curConn;
321 }
322 
324 {
325  auto &trackList = TrackList::Get( mProject );
326 
327  XMLStringWriter doc;
328  WriteXMLHeader(doc);
329  WriteXML(doc, false, trackList.empty() ? nullptr : &trackList);
330  return doc;
331 }
332 
334 {
335  return GetConnection().DB();
336 }
337 
342 bool ProjectFileIO::OpenConnection(FilePath fileName /* = {} */)
343 {
344  auto &curConn = CurrConn();
345  wxASSERT(!curConn);
346  bool isTemp = false;
347 
348  if (fileName.empty())
349  {
350  fileName = GetFileName();
351  if (fileName.empty())
352  {
354  isTemp = true;
355  }
356  }
357  else
358  {
359  // If this project resides in the temporary directory, then we'll mark it
360  // as temporary.
361  wxFileName temp(TempDirectory::TempDir(), wxT(""));
362  wxFileName file(fileName);
363  file.SetFullName(wxT(""));
364  if (file == temp)
365  {
366  isTemp = true;
367  }
368  }
369 
370  // Pass weak_ptr to project into DBConnection constructor
371  curConn = std::make_unique<DBConnection>(
372  mProject.shared_from_this(), mpErrors, [this]{ OnCheckpointFailure(); } );
373  auto rc = curConn->Open(fileName);
374  if (rc != SQLITE_OK)
375  {
376  // Must use SetError() here since we do not have an active DB
377  SetError(
378  XO("Failed to open database file:\n\n%s").Format(fileName),
379  {},
380  rc
381  );
382  curConn.reset();
383  return false;
384  }
385 
386  if (!CheckVersion())
387  {
388  CloseConnection();
389  curConn.reset();
390  return false;
391  }
392 
393  mTemporary = isTemp;
394 
395  SetFileName(fileName);
396 
397  return true;
398 }
399 
401 {
402  auto &curConn = CurrConn();
403  if (!curConn)
404  return false;
405 
406  if (!curConn->Close())
407  {
408  return false;
409  }
410  curConn.reset();
411 
412  SetFileName({});
413 
414  return true;
415 }
416 
417 // Put the current database connection aside, keeping it open, so that
418 // another may be opened with OpenConnection()
420 {
421  // Should do nothing in proper usage, but be sure not to leak a connection:
423 
424  mPrevConn = std::move(CurrConn());
427 
428  SetFileName({});
429 }
430 
431 // Close any set-aside connection
433 {
434  if (mPrevConn)
435  {
436  if (!mPrevConn->Close())
437  {
438  // Store an error message
439  SetDBError(
440  XO("Failed to discard connection")
441  );
442  }
443 
444  // If this is a temporary project, we no longer want to keep the
445  // project file.
446  if (mPrevTemporary)
447  {
448  // This is just a safety check.
449  wxFileName temp(TempDirectory::TempDir(), wxT(""));
450  wxFileName file(mPrevFileName);
451  file.SetFullName(wxT(""));
452  if (file == temp)
453  {
455  {
456  wxLogMessage("Failed to remove temporary project %s", mPrevFileName);
457  }
458  }
459  }
460  mPrevConn = nullptr;
461  mPrevFileName.clear();
462  }
463 }
464 
465 // Close any current connection and switch back to using the saved
467 {
468  auto &curConn = CurrConn();
469  if (curConn)
470  {
471  if (!curConn->Close())
472  {
473  // Store an error message
474  SetDBError(
475  XO("Failed to restore connection")
476  );
477  }
478  }
479 
480  curConn = std::move(mPrevConn);
483 
484  mPrevFileName.clear();
485 }
486 
487 void ProjectFileIO::UseConnection(Connection &&conn, const FilePath &filePath)
488 {
489  auto &curConn = CurrConn();
490  wxASSERT(!curConn);
491 
492  curConn = std::move(conn);
493  SetFileName(filePath);
494 }
495 
496 static int ExecCallback(void *data, int cols, char **vals, char **names)
497 {
498  auto &cb = *static_cast<const ProjectFileIO::ExecCB *>(data);
499  // Be careful not to throw anything across sqlite3's stack frames.
500  return GuardedCall<int>(
501  [&]{ return cb(cols, vals, names); },
502  MakeSimpleGuard( 1 )
503  );
504 }
505 
506 int ProjectFileIO::Exec(const char *query, const ExecCB &callback)
507 {
508  char *errmsg = nullptr;
509 
510  const void *ptr = &callback;
511  int rc = sqlite3_exec(DB(), query, ExecCallback,
512  const_cast<void*>(ptr), &errmsg);
513 
514  if (rc != SQLITE_ABORT && errmsg)
515  {
516  ADD_EXCEPTION_CONTEXT("sqlite3.query", query);
517  ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
518 
519  SetDBError(
520  XO("Failed to execute a project file command:\n\n%s").Format(query),
521  Verbatim(errmsg),
522  rc
523  );
524  }
525  if (errmsg)
526  {
527  sqlite3_free(errmsg);
528  }
529 
530  return rc;
531 }
532 
533 bool ProjectFileIO::Query(const char *sql, const ExecCB &callback)
534 {
535  int rc = Exec(sql, callback);
536  // SQLITE_ABORT is a non-error return only meaning the callback
537  // stopped the iteration of rows early
538  if ( !(rc == SQLITE_OK || rc == SQLITE_ABORT) )
539  {
540  return false;
541  }
542 
543  return true;
544 }
545 
546 bool ProjectFileIO::GetValue(const char *sql, wxString &result)
547 {
548  // Retrieve the first column in the first row, if any
549  result.clear();
550  auto cb = [&result](int cols, char **vals, char **){
551  if (cols > 0)
552  result = vals[0];
553  // Stop after one row
554  return 1;
555  };
556 
557  return Query(sql, cb);
558 }
559 
560 bool ProjectFileIO::GetBlob(const char *sql, wxMemoryBuffer &buffer)
561 {
562  auto db = DB();
563  int rc;
564 
565  buffer.Clear();
566 
567  sqlite3_stmt *stmt = nullptr;
568  auto cleanup = finally([&]
569  {
570  if (stmt)
571  {
572  sqlite3_finalize(stmt);
573  }
574  });
575 
576  rc = sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr);
577  if (rc != SQLITE_OK)
578  {
579  ADD_EXCEPTION_CONTEXT("sqlite3.query", sql);
580  ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
581  ADD_EXCEPTION_CONTEXT("sqlite3.context", "ProjectGileIO::GetBlob::prepare");
582 
583  SetDBError(
584  XO("Unable to prepare project file command:\n\n%s").Format(sql)
585  );
586  return false;
587  }
588 
589  rc = sqlite3_step(stmt);
590 
591  // A row wasn't found...not an error
592  if (rc == SQLITE_DONE)
593  {
594  return true;
595  }
596 
597  if (rc != SQLITE_ROW)
598  {
599  ADD_EXCEPTION_CONTEXT("sqlite3.query", sql);
600  ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
601  ADD_EXCEPTION_CONTEXT("sqlite3.context", "ProjectGileIO::GetBlob::step");
602 
603  SetDBError(
604  XO("Failed to retrieve data from the project file.\nThe following command failed:\n\n%s").Format(sql)
605  );
606  // AUD TODO handle error
607  return false;
608  }
609 
610  const void *blob = sqlite3_column_blob(stmt, 0);
611  int size = sqlite3_column_bytes(stmt, 0);
612 
613  buffer.AppendData(blob, size);
614 
615  return true;
616 }
617 
619 {
620  auto db = DB();
621  int rc;
622 
623  // Install our schema if this is an empty DB
624  wxString result;
625  if (!GetValue("SELECT Count(*) FROM sqlite_master WHERE type='table';", result))
626  {
627  // Bug 2718 workaround for a better error message:
628  // If at this point we get SQLITE_CANTOPEN, then the directory is read-only
629  if (GetLastErrorCode() == SQLITE_CANTOPEN)
630  {
631  SetError(
632  /* i18n-hint: An error message. */
633  XO("Project is in a read only directory\n(Unable to create the required temporary files)"),
635  );
636  }
637 
638  return false;
639  }
640 
641  // If the return count is zero, then there are no tables defined, so this
642  // must be a new project file.
643  if (wxStrtol<char **>(result, nullptr, 10) == 0)
644  {
645  return InstallSchema(db);
646  }
647 
648  // Check for our application ID
649  if (!GetValue("PRAGMA application_ID;", result))
650  {
651  return false;
652  }
653 
654  // It's a database that SQLite recognizes, but it's not one of ours
655  if (wxStrtoul<char **>(result, nullptr, 10) != ProjectFileID)
656  {
657  SetError(XO("This is not an Audacity project file"));
658  return false;
659  }
660 
661  // Get the project file version
662  if (!GetValue("PRAGMA user_version;", result))
663  {
664  return false;
665  }
666 
667  long version = wxStrtol<char **>(result, nullptr, 10);
668 
669  // Project file version is higher than ours. We will refuse to
670  // process it since we can't trust anything about it.
671  if (version > ProjectFileVersion)
672  {
673  SetError(
674  XO("This project was created with a newer version of Audacity.\n\nYou will need to upgrade to open it.")
675  );
676  return false;
677  }
678 
679  // Project file is older than ours, ask the user if it's okay to
680  // upgrade.
681  if (version < ProjectFileVersion)
682  {
683  return UpgradeSchema();
684  }
685 
686  return true;
687 }
688 
689 bool ProjectFileIO::InstallSchema(sqlite3 *db, const char *schema /* = "main" */)
690 {
691  int rc;
692 
693  wxString sql;
695  sql.Replace("<schema>", schema);
696 
697  rc = sqlite3_exec(db, sql, nullptr, nullptr, nullptr);
698  if (rc != SQLITE_OK)
699  {
700  SetDBError(
701  XO("Unable to initialize the project file")
702  );
703  return false;
704  }
705 
706  return true;
707 }
708 
710 {
711  // To do
712  return true;
713 }
714 
715 // The orphan block handling should be removed once autosave and related
716 // blocks become part of the same transaction.
717 
718 // An SQLite function that takes a blockid and looks it up in a set of
719 // blockids captured during project load. If the blockid isn't found
720 // in the set, it will be deleted.
721 void ProjectFileIO::InSet(sqlite3_context *context, int argc, sqlite3_value **argv)
722 {
723  BlockIDs *blockids = (BlockIDs *) sqlite3_user_data(context);
724  SampleBlockID blockid = sqlite3_value_int64(argv[0]);
725 
726  sqlite3_result_int(context, blockids->find(blockid) != blockids->end());
727 }
728 
729 bool ProjectFileIO::DeleteBlocks(const BlockIDs &blockids, bool complement)
730 {
731  auto db = DB();
732  int rc;
733 
734  auto cleanup = finally([&]
735  {
736  // Remove our function, whether it was successfully defined or not.
737  sqlite3_create_function(db, "inset", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr, nullptr, nullptr, nullptr);
738  });
739 
740  // Add the function used to verify each row's blockid against the set of active blockids
741  const void *p = &blockids;
742  rc = sqlite3_create_function(db, "inset", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC, const_cast<void*>(p), InSet, nullptr, nullptr);
743  if (rc != SQLITE_OK)
744  {
745  ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
746  ADD_EXCEPTION_CONTEXT("sqlite3.context", "ProjectGileIO::DeleteBlocks::create_function");
747 
748  /* i18n-hint: An error message. Don't translate inset or blockids.*/
749  SetDBError(XO("Unable to add 'inset' function (can't verify blockids)"));
750  return false;
751  }
752 
753  // Delete all rows in the set, or not in it
754  // This is the first command that writes to the database, and so we
755  // do more informative error reporting than usual, if it fails.
756  auto sql = wxString::Format(
757  "DELETE FROM sampleblocks WHERE %sinset(blockid);",
758  complement ? "NOT " : "" );
759  rc = sqlite3_exec(db, sql, nullptr, nullptr, nullptr);
760  if (rc != SQLITE_OK)
761  {
762  ADD_EXCEPTION_CONTEXT("sqlite3.query", sql.ToStdString());
763  ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
764  ADD_EXCEPTION_CONTEXT("sqlite3.context", "ProjectGileIO::GetBlob");
765 
766  if( rc==SQLITE_READONLY)
767  /* i18n-hint: An error message. Don't translate blockfiles.*/
768  SetDBError(XO("Project is read only\n(Unable to work with the blockfiles)"));
769  else if( rc==SQLITE_LOCKED)
770  /* i18n-hint: An error message. Don't translate blockfiles.*/
771  SetDBError(XO("Project is locked\n(Unable to work with the blockfiles)"));
772  else if( rc==SQLITE_BUSY)
773  /* i18n-hint: An error message. Don't translate blockfiles.*/
774  SetDBError(XO("Project is busy\n(Unable to work with the blockfiles)"));
775  else if( rc==SQLITE_CORRUPT)
776  /* i18n-hint: An error message. Don't translate blockfiles.*/
777  SetDBError(XO("Project is corrupt\n(Unable to work with the blockfiles)"));
778  else if( rc==SQLITE_PERM)
779  /* i18n-hint: An error message. Don't translate blockfiles.*/
780  SetDBError(XO("Some permissions issue\n(Unable to work with the blockfiles)"));
781  else if( rc==SQLITE_IOERR)
782  /* i18n-hint: An error message. Don't translate blockfiles.*/
783  SetDBError(XO("A disk I/O error\n(Unable to work with the blockfiles)"));
784  else if( rc==SQLITE_AUTH)
785  /* i18n-hint: An error message. Don't translate blockfiles.*/
786  SetDBError(XO("Not authorized\n(Unable to work with the blockfiles)"));
787  else
788  /* i18n-hint: An error message. Don't translate blockfiles.*/
789  SetDBError(XO("Unable to work with the blockfiles"));
790 
791  return false;
792  }
793 
794  // Mark the project recovered if we deleted any rows
795  int changes = sqlite3_changes(db);
796  if (changes > 0)
797  {
798  wxLogInfo(XO("Total orphan blocks deleted %d").Translation(), changes);
799  mRecovered = true;
800  }
801 
802  return true;
803 }
804 
805 bool ProjectFileIO::CopyTo(const FilePath &destpath,
806  const TranslatableString &msg,
807  bool isTemporary,
808  bool prune /* = false */,
809  const std::vector<const TrackList *> &tracks /* = {} */)
810 {
811  auto pConn = CurrConn().get();
812  if (!pConn)
813  return false;
814 
815  // Get access to the active tracklist
816  auto pProject = &mProject;
817 
818  SampleBlockIDSet blockids;
819 
820  // Collect all active blockids
821  if (prune)
822  {
823  for (auto trackList : tracks)
824  if (trackList)
825  InspectBlocks( *trackList, {}, &blockids );
826  }
827  // Collect ALL blockids
828  else
829  {
830  auto cb = [&blockids](int cols, char **vals, char **){
831  SampleBlockID blockid;
832  wxString{ vals[0] }.ToLongLong(&blockid);
833  blockids.insert(blockid);
834  return 0;
835  };
836 
837  if (!Query("SELECT blockid FROM sampleblocks;", cb))
838  {
839  // Error message already captured.
840  return false;
841  }
842  }
843 
844  // Create the project doc
845  ProjectSerializer doc;
846  WriteXMLHeader(doc);
847  WriteXML(doc, false, tracks.empty() ? nullptr : tracks[0]);
848 
849  auto db = DB();
850  Connection destConn = nullptr;
851  bool success = false;
852  int rc = SQLITE_OK;
854 
855  // Cleanup in case things go awry
856  auto cleanup = finally([&]
857  {
858  if (!success)
859  {
860  if (destConn)
861  {
862  destConn->Close();
863  destConn = nullptr;
864  }
865 
866  // Rollback transaction in case one was active.
867  // If this fails (probably due to memory or disk space), the transaction will
868  // (presumably) stil be active, so further updates to the project file will
869  // fail as well. Not really much we can do about it except tell the user.
870  auto result = sqlite3_exec(db, "ROLLBACK;", nullptr, nullptr, nullptr);
871 
872  // Only capture the error if there wasn't a previous error
873  if (result != SQLITE_OK && (rc == SQLITE_DONE || rc == SQLITE_OK))
874  {
875  ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
877  "sqlite3.context", "ProjectGileIO::CopyTo.cleanup");
878 
879  SetDBError(
880  XO("Failed to rollback transaction during import")
881  );
882  }
883 
884  // And detach the outbound DB in case (if it's attached). Don't check for
885  // errors since it may not be attached. But, if it is and the DETACH fails,
886  // subsequent CopyTo() actions will fail until Audacity is relaunched.
887  sqlite3_exec(db, "DETACH DATABASE outbound;", nullptr, nullptr, nullptr);
888 
889  // RemoveProject not necessary to clean up attached database
890  wxRemoveFile(destpath);
891  }
892  });
893 
894  // Attach the destination database
895  wxString sql;
896  wxString dbName = destpath;
897  // Bug 2793: Quotes in name need escaping for sqlite3.
898  dbName.Replace( "'", "''");
899  sql.Printf("ATTACH DATABASE '%s' AS outbound;", dbName.ToUTF8());
900 
901  rc = sqlite3_exec(db, sql, nullptr, nullptr, nullptr);
902  if (rc != SQLITE_OK)
903  {
904  SetDBError(
905  XO("Unable to attach destination database")
906  );
907  return false;
908  }
909 
910  // Ensure attached DB connection gets configured
911  //
912  // NOTE: Between the above attach and setting the mode here, a normal DELETE
913  // mode journal will be used and will briefly appear in the filesystem.
914  if ( pConn->FastMode("outbound") != SQLITE_OK)
915  {
916  SetDBError(
917  XO("Unable to switch to fast journaling mode")
918  );
919 
920  return false;
921  }
922 
923  // Install our schema into the new database
924  if (!InstallSchema(db, "outbound"))
925  {
926  // Message already set
927  return false;
928  }
929 
930  {
931  // Ensure statement gets cleaned up
932  sqlite3_stmt *stmt = nullptr;
933  auto cleanup = finally([&]
934  {
935  if (stmt)
936  {
937  // No need to check return code
938  sqlite3_finalize(stmt);
939  }
940  });
941 
942  // Prepare the statement only once
943  rc = sqlite3_prepare_v2(db,
944  "INSERT INTO outbound.sampleblocks"
945  " SELECT * FROM main.sampleblocks"
946  " WHERE blockid = ?;",
947  -1,
948  &stmt,
949  nullptr);
950  if (rc != SQLITE_OK)
951  {
952  ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
954  "sqlite3.context", "ProjectGileIO::CopyTo.prepare");
955 
956  SetDBError(
957  XO("Unable to prepare project file command:\n\n%s").Format(sql)
958  );
959  return false;
960  }
961 
962  /* i18n-hint: This title appears on a dialog that indicates the progress
963  in doing something.*/
964  ProgressDialog progress(XO("Progress"), msg, pdlgHideStopButton);
966 
967  wxLongLong_t count = 0;
968  wxLongLong_t total = blockids.size();
969 
970  // Start a transaction. Since we're running without a journal,
971  // this really doesn't provide rollback. It just prevents SQLite
972  // from auto committing after each step through the loop.
973  //
974  // Also note that we will have an open transaction if we fail
975  // while copying the blocks. This is fine since we're just going
976  // to delete the database anyway.
977  sqlite3_exec(db, "BEGIN;", nullptr, nullptr, nullptr);
978 
979  // Copy sample blocks from the main DB to the outbound DB
980  for (auto blockid : blockids)
981  {
982  // Bind statement parameters
983  rc = sqlite3_bind_int64(stmt, 1, blockid);
984  if (rc != SQLITE_OK)
985  {
986  ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
988  "sqlite3.context", "ProjectGileIO::CopyTo.bind");
989 
990  SetDBError(
991  XO("Failed to bind SQL parameter")
992  );
993 
994  return false;
995  }
996 
997  // Process it
998  rc = sqlite3_step(stmt);
999  if (rc != SQLITE_DONE)
1000  {
1001  ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
1003  "sqlite3.context", "ProjectGileIO::CopyTo.step");
1004 
1005  SetDBError(
1006  XO("Failed to update the project file.\nThe following command failed:\n\n%s").Format(sql)
1007  );
1008  return false;
1009  }
1010 
1011  // Reset statement to beginning
1012  if (sqlite3_reset(stmt) != SQLITE_OK)
1013  {
1014  ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
1016  "sqlite3.context", "ProjectGileIO::CopyTo.reset");
1017 
1019  }
1020 
1021  result = progress.Update(++count, total);
1022  if (result != ProgressResult::Success)
1023  {
1024  // Note that we're not setting success, so the finally
1025  // block above will take care of cleaning up
1026  return false;
1027  }
1028  }
1029 
1030  // Write the doc.
1031  //
1032  // If we're compacting a temporary project (user initiated from the File
1033  // menu), then write the doc to the "autosave" table since temporary
1034  // projects do not have a "project" doc.
1035  if (!WriteDoc(isTemporary ? "autosave" : "project", doc, "outbound"))
1036  {
1037  return false;
1038  }
1039 
1040  // See BEGIN above...
1041  sqlite3_exec(db, "COMMIT;", nullptr, nullptr, nullptr);
1042  }
1043 
1044  // Detach the destination database
1045  rc = sqlite3_exec(db, "DETACH DATABASE outbound;", nullptr, nullptr, nullptr);
1046  if (rc != SQLITE_OK)
1047  {
1048  ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
1049  ADD_EXCEPTION_CONTEXT("sqlite3.context", "ProjectGileIO::CopyTo::detach");
1050 
1051  SetDBError(
1052  XO("Destination project could not be detached")
1053  );
1054 
1055  return false;
1056  }
1057 
1058  // Tell cleanup everything is good to go
1059  success = true;
1060 
1061  return true;
1062 }
1063 
1064 bool ProjectFileIO::ShouldCompact(const std::vector<const TrackList *> &tracks)
1065 {
1066  SampleBlockIDSet active;
1067  unsigned long long current = 0;
1068 
1069  {
1070  auto fn = BlockSpaceUsageAccumulator( current );
1071  for (auto pTracks : tracks)
1072  if (pTracks)
1073  InspectBlocks( *pTracks, fn,
1074  &active // Visit unique blocks only
1075  );
1076  }
1077 
1078  // Get the number of blocks and total length from the project file.
1079  unsigned long long total = GetTotalUsage();
1080  unsigned long long blockcount = 0;
1081 
1082  auto cb = [&blockcount](int cols, char **vals, char **)
1083  {
1084  // Convert
1085  wxString(vals[0]).ToULongLong(&blockcount);
1086  return 0;
1087  };
1088 
1089  if (!Query("SELECT Count(*) FROM sampleblocks;", cb) || blockcount == 0)
1090  {
1091  // Shouldn't compact since we don't have the full picture
1092  return false;
1093  }
1094 
1095  // Remember if we had unused blocks in the project file
1096  mHadUnused = (blockcount > active.size());
1097 
1098  // Let's make a percentage...should be plenty of head room
1099  current *= 100;
1100 
1101  wxLogDebug(wxT("used = %lld total = %lld %lld"), current, total, total ? current / total : 0);
1102  if (!total || current / total > 80)
1103  {
1104  wxLogDebug(wxT("not compacting"));
1105  return false;
1106  }
1107  wxLogDebug(wxT("compacting"));
1108 
1109  return true;
1110 }
1111 
1113 {
1114  auto &connectionPtr = ConnectionPtr::Get( mProject );
1115  return connectionPtr.mpConnection;
1116 }
1117 
1118 const std::vector<wxString> &ProjectFileIO::AuxiliaryFileSuffixes()
1119 {
1120  static const std::vector<wxString> strings {
1121  "-wal",
1122 #ifndef NO_SHM
1123  "-shm",
1124 #endif
1125  };
1126  return strings;
1127 }
1128 
1130 {
1131  wxFileNameWrapper fn{ src };
1132 
1133  // Extra characters inserted into filename before extension
1134  wxString extra =
1135 #ifdef __WXGTK__
1136  wxT("~")
1137 #else
1138  wxT(".bak")
1139 #endif
1140  ;
1141 
1142  int nn = 1;
1143  auto numberString = [](int num) -> wxString {
1144  return num == 1 ? wxString{} : wxString::Format(".%d", num);
1145  };
1146 
1147  auto suffixes = AuxiliaryFileSuffixes();
1148  suffixes.push_back({});
1149 
1150  // Find backup paths not already occupied; check all auxiliary suffixes
1151  const auto name = fn.GetName();
1152  FilePath result;
1153  do {
1154  fn.SetName( name + numberString(nn++) + extra );
1155  result = fn.GetFullPath();
1156  }
1157  while( std::any_of(suffixes.begin(), suffixes.end(), [&](auto &suffix){
1158  return wxFileExists(result + suffix);
1159  }) );
1160 
1161  return result;
1162 }
1163 
1164 bool ProjectFileIO::RenameOrWarn(const FilePath &src, const FilePath &dst)
1165 {
1166  std::atomic_bool done = {false};
1167  bool success = false;
1168  auto thread = std::thread([&]
1169  {
1170  success = wxRenameFile(src, dst);
1171  done = true;
1172  });
1173 
1174  // Provides a progress dialog with indeterminate mode
1175  using namespace BasicUI;
1177  XO("Copying Project"), XO("This may take several seconds"));
1178  wxASSERT(pd);
1179 
1180  // Wait for the checkpoints to end
1181  while (!done)
1182  {
1183  wxMilliSleep(50);
1184  pd->Pulse();
1185  }
1186  thread.join();
1187 
1188  if (!success)
1189  {
1191  XO("Error Writing to File"),
1192  XO("Audacity failed to write file %s.\n"
1193  "Perhaps disk is full or not writable.\n"
1194  "For tips on freeing up space, click the help button.")
1195  .Format(dst),
1196  "Error:_Disk_full_or_not_writable"
1197  );
1198  return false;
1199  }
1200 
1201  return true;
1202 }
1203 
1204 bool ProjectFileIO::MoveProject(const FilePath &src, const FilePath &dst)
1205 {
1206  // Assume the src database file is not busy.
1207  if (!RenameOrWarn(src, dst))
1208  return false;
1209 
1210  // So far so good, but the separate -wal and -shm files might yet exist,
1211  // as when checkpointing failed for limited space on the drive.
1212  // If so move them too or else lose data.
1213 
1214  std::vector< std::pair<FilePath, FilePath> > pairs{ { src, dst } };
1215  bool success = false;
1216  auto cleanup = finally([&]{
1217  if (!success) {
1218  // If any one of the renames failed, back out the previous ones.
1219  // This should be a no-fail recovery! Not clear what to do if any
1220  // of these renames fails.
1221  for (auto &pair : pairs) {
1222  if (!(pair.first.empty() && pair.second.empty()))
1223  wxRenameFile(pair.second, pair.first);
1224  }
1225  }
1226  });
1227 
1228  for (const auto &suffix : AuxiliaryFileSuffixes()) {
1229  auto srcName = src + suffix;
1230  if (wxFileExists(srcName)) {
1231  auto dstName = dst + suffix;
1232  if (!RenameOrWarn(srcName, dstName))
1233  return false;
1234  pairs.push_back({ srcName, dstName });
1235  }
1236  }
1237 
1238  return (success = true);
1239 }
1240 
1242 {
1243  if (!wxFileExists(filename))
1244  return false;
1245 
1246  bool success = wxRemoveFile(filename);
1247  auto &suffixes = AuxiliaryFileSuffixes();
1248  for (const auto &suffix : suffixes) {
1249  auto file = filename + suffix;
1250  if (wxFileExists(file))
1251  success = wxRemoveFile(file) && success;
1252  }
1253  return success;
1254 }
1255 
1257  ProjectFileIO &projectFileIO, const FilePath &path )
1258 {
1259  auto safety = SafetyFileName(path);
1260  if (!projectFileIO.MoveProject(path, safety))
1261  return;
1262 
1263  mPath = path;
1264  mSafety = safety;
1265 }
1266 
1268 {
1269  if (!mPath.empty()) {
1270  // Succeeded; don't need the safety files
1271  RemoveProject(mSafety);
1272  mSafety.clear();
1273  }
1274 }
1275 
1277 {
1278  if (!mPath.empty()) {
1279  if (!mSafety.empty()) {
1280  // Failed; restore from safety files
1281  auto suffixes = AuxiliaryFileSuffixes();
1282  suffixes.push_back({});
1283  for (const auto &suffix : suffixes) {
1284  auto path = mPath + suffix;
1285  if (wxFileExists(path))
1286  wxRemoveFile(path);
1287  wxRenameFile(mSafety + suffix, mPath + suffix);
1288  }
1289  }
1290  }
1291 }
1292 
1294  const std::vector<const TrackList *> &tracks, bool force)
1295 {
1296  // Haven't compacted yet
1297  mWasCompacted = false;
1298 
1299  // Assume we have unused blocks until we find out otherwise. That way cleanup
1300  // at project close time will still occur.
1301  mHadUnused = true;
1302 
1303  // If forcing compaction, bypass inspection.
1304  if (!force)
1305  {
1306  // Don't compact if this is a temporary project or if it's determined there are not
1307  // enough unused blocks to make it worthwhile.
1308  if (IsTemporary() || !ShouldCompact(tracks))
1309  {
1310  // Delete the AutoSave doc it if exists
1311  if (IsModified())
1312  {
1313  // PRL: not clear what to do if the following fails, but the worst should
1314  // be, the project may reopen in its present state as a recovery file, not
1315  // at the last saved state.
1316  // REVIEW: Could the autosave file be corrupt though at that point, and so
1317  // prevent recovery?
1318  // LLL: I believe Paul is correct since it's deleted with a single SQLite
1319  // transaction. The next time the file opens will just invoke recovery.
1320  (void) AutoSaveDelete();
1321  }
1322 
1323  return;
1324  }
1325  }
1326 
1327  wxString origName = mFileName;
1328  wxString backName = origName + "_compact_back";
1329  wxString tempName = origName + "_compact_temp";
1330 
1331  // Copy the original database to a new database. Only prune sample blocks if
1332  // we have a tracklist.
1333  // REVIEW: Compact can fail on the CopyTo with no error messages. That's OK?
1334  // LLL: We could display an error message or just ignore the failure and allow
1335  // the file to be compacted the next time it's saved.
1336  if (CopyTo(tempName, XO("Compacting project"), IsTemporary(), !tracks.empty(), tracks))
1337  {
1338  // Must close the database to rename it
1339  if (CloseConnection())
1340  {
1341  // Only use the new file if it is actually smaller than the original.
1342  //
1343  // If the original file doesn't have anything to compact (original and new
1344  // are basically identical), the file could grow by a few pages because of
1345  // differences in how SQLite constructs the b-tree.
1346  //
1347  // In this case, just toss the new file and continue to use the original.
1348  //
1349  // Also, do this after closing the connection so that the -wal file
1350  // gets cleaned up.
1351  if (wxFileName::GetSize(tempName) < wxFileName::GetSize(origName))
1352  {
1353  // Rename the original to backup
1354  if (wxRenameFile(origName, backName))
1355  {
1356  // Rename the temporary to original
1357  if (wxRenameFile(tempName, origName))
1358  {
1359  // Open the newly compacted original file
1360  if (OpenConnection(origName))
1361  {
1362  // Remove the old original file
1363  if (!wxRemoveFile(backName))
1364  {
1365  // Just log the error, nothing can be done to correct it
1366  // and WX should have logged another message showing the
1367  // system error code.
1368  wxLogWarning(wxT("Compaction failed to delete backup %s"), backName);
1369  }
1370 
1371  // Remember that we compacted
1372  mWasCompacted = true;
1373 
1374  return;
1375  }
1376  else
1377  {
1378  wxLogWarning(wxT("Compaction failed to open new project %s"), origName);
1379  }
1380 
1381  if (!wxRenameFile(origName, tempName))
1382  {
1383  wxLogWarning(wxT("Compaction failed to rename orignal %s to temp %s"),
1384  origName, tempName);
1385  }
1386  }
1387  else
1388  {
1389  wxLogWarning(wxT("Compaction failed to rename temp %s to orig %s"),
1390  origName, tempName);
1391  }
1392 
1393  if (!wxRenameFile(backName, origName))
1394  {
1395  wxLogWarning(wxT("Compaction failed to rename back %s to orig %s"),
1396  backName, origName);
1397  }
1398  }
1399  else
1400  {
1401  wxLogWarning(wxT("Compaction failed to rename orig %s to back %s"),
1402  backName, origName);
1403  }
1404  }
1405 
1406  if (!OpenConnection(origName))
1407  {
1408  wxLogWarning(wxT("Compaction failed to reopen %s"), origName);
1409  }
1410  }
1411 
1412  // Did not achieve any real compaction
1413  // RemoveProject not needed for what was an attached database
1414  if (!wxRemoveFile(tempName))
1415  {
1416  // Just log the error, nothing can be done to correct it
1417  // and WX should have logged another message showing the
1418  // system error code.
1419  wxLogWarning(wxT("Failed to delete temporary file...ignoring"));
1420  }
1421  }
1422 
1423  return;
1424 }
1425 
1427 {
1428  return mWasCompacted;
1429 }
1430 
1432 {
1433  return mHadUnused;
1434 }
1435 
1437 {
1438  SetProjectTitle();
1439 }
1440 
1441 // Pass a number in to show project number, or -1 not to.
1443 {
1444  auto &project = mProject;
1445  auto pWindow = project.GetFrame();
1446  if (!pWindow)
1447  {
1448  return;
1449  }
1450  auto &window = *pWindow;
1451  wxString name = project.GetProjectName();
1452 
1453  // If we are showing project numbers, then we also explicitly show "<untitled>" if there
1454  // is none.
1455  if (number >= 0)
1456  {
1457  name =
1458  /* i18n-hint: The %02i is the project number, the %s is the project name.*/
1459  XO("[Project %02i] Audacity \"%s\"")
1460  .Format( number + 1,
1461  name.empty() ? XO("<untitled>") : Verbatim((const char *)name))
1462  .Translation();
1463  }
1464  // If we are not showing numbers, then <untitled> shows as 'Audacity'.
1465  else if (name.empty())
1466  {
1467  name = _TS("Audacity");
1468  }
1469 
1470  if (mRecovered)
1471  {
1472  name += wxT(" ");
1473  /* i18n-hint: E.g this is recovered audio that had been lost.*/
1474  name += _("(Recovered)");
1475  }
1476 
1477  if (name != window.GetTitle())
1478  {
1479  window.SetTitle( name );
1480  window.SetName(name); // to make the nvda screen reader read the correct title
1481 
1482  project.QueueEvent(
1483  safenew wxCommandEvent{ EVT_PROJECT_TITLE_CHANGE } );
1484  }
1485 }
1486 
1488 {
1489  return mFileName;
1490 }
1491 
1493 {
1494  auto &project = mProject;
1495 
1496  if (!mFileName.empty())
1497  {
1499  }
1500 
1501  mFileName = fileName;
1502 
1503  if (!mFileName.empty())
1504  {
1506  }
1507 
1508  if (IsTemporary())
1509  {
1510  project.SetProjectName({});
1511  }
1512  else
1513  {
1514  project.SetProjectName(wxFileName(mFileName).GetName());
1515  }
1516 
1517  SetProjectTitle();
1518 }
1519 
1520 bool ProjectFileIO::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
1521 {
1522  auto &project = mProject;
1523  auto &viewInfo = ViewInfo::Get(project);
1524  auto &settings = ProjectSettings::Get(project);
1525 
1526  wxString fileVersion;
1527  wxString audacityVersion;
1528  int requiredTags = 0;
1529  long longVpos = 0;
1530 
1531  // loop through attrs, which is a null-terminated list of
1532  // attribute-value pairs
1533  while (*attrs)
1534  {
1535  const wxChar *attr = *attrs++;
1536  const wxChar *value = *attrs++;
1537 
1538  if (!value || !XMLValueChecker::IsGoodString(value))
1539  {
1540  break;
1541  }
1542 
1543  if (viewInfo.ReadXMLAttribute(attr, value))
1544  {
1545  // We need to save vpos now and restore it below
1546  longVpos = std::max(longVpos, long(viewInfo.vpos));
1547  continue;
1548  }
1549 
1550  else if (!wxStrcmp(attr, wxT("version")))
1551  {
1552  fileVersion = value;
1553  requiredTags++;
1554  }
1555 
1556  else if (!wxStrcmp(attr, wxT("audacityversion")))
1557  {
1558  audacityVersion = value;
1559  requiredTags++;
1560  }
1561 
1562  else if (!wxStrcmp(attr, wxT("rate")))
1563  {
1564  double rate;
1565  Internat::CompatibleToDouble(value, &rate);
1566  settings.SetRate( rate );
1567  }
1568 
1569  else if (!wxStrcmp(attr, wxT("snapto")))
1570  {
1571  settings.SetSnapTo(wxString(value) == wxT("on") ? true : false);
1572  }
1573 
1574  else if (!wxStrcmp(attr, wxT("selectionformat")))
1575  {
1576  settings.SetSelectionFormat(
1578  }
1579 
1580  else if (!wxStrcmp(attr, wxT("audiotimeformat")))
1581  {
1582  settings.SetAudioTimeFormat(
1584  }
1585 
1586  else if (!wxStrcmp(attr, wxT("frequencyformat")))
1587  {
1588  settings.SetFrequencySelectionFormatName(
1590  }
1591 
1592  else if (!wxStrcmp(attr, wxT("bandwidthformat")))
1593  {
1594  settings.SetBandwidthSelectionFormatName(
1596  }
1597  } // while
1598 
1599  if (longVpos != 0)
1600  {
1601  // PRL: It seems this must happen after SetSnapTo
1602  viewInfo.vpos = longVpos;
1603  }
1604 
1605  if (requiredTags < 2)
1606  {
1607  return false;
1608  }
1609 
1610  // Parse the file version from the project
1611  int fver;
1612  int frel;
1613  int frev;
1614  if (!wxSscanf(fileVersion, wxT("%i.%i.%i"), &fver, &frel, &frev))
1615  {
1616  return false;
1617  }
1618 
1619  // Parse the file version Audacity was build with
1620  int cver;
1621  int crel;
1622  int crev;
1623  wxSscanf(wxT(AUDACITY_FILE_FORMAT_VERSION), wxT("%i.%i.%i"), &cver, &crel, &crev);
1624 
1625  int fileVer = ((fver *100)+frel)*100+frev;
1626  int codeVer = ((cver *100)+crel)*100+crev;
1627 
1628  if (codeVer<fileVer)
1629  {
1630  /* i18n-hint: %s will be replaced by the version number.*/
1631  auto msg = XO("This file was saved using Audacity %s.\nYou are using Audacity %s. You may need to upgrade to a newer version to open this file.")
1632  .Format(audacityVersion, AUDACITY_VERSION_STRING);
1633 
1634  ShowError( *ProjectFramePlacement(&project),
1635  XO("Can't open project file"),
1636  msg,
1637  "FAQ:Errors_opening_an_Audacity_project"
1638  );
1639 
1640  return false;
1641  }
1642 
1643  if (wxStrcmp(tag, wxT("project")))
1644  {
1645  return false;
1646  }
1647 
1648  // All other tests passed, so we succeed
1649  return true;
1650 }
1651 
1653 {
1654  auto &project = mProject;
1655  auto fn = ProjectFileIORegistry::Lookup(tag);
1656  if (fn)
1657  {
1658  return fn(project);
1659  }
1660 
1661  return nullptr;
1662 }
1663 
1665 {
1666  wxCommandEvent evt{ EVT_CHECKPOINT_FAILURE };
1667  mProject.ProcessEvent(evt);
1668 }
1669 
1671 {
1672  xmlFile.Write(wxT("<?xml "));
1673  xmlFile.Write(wxT("version=\"1.0\" "));
1674  xmlFile.Write(wxT("standalone=\"no\" "));
1675  xmlFile.Write(wxT("?>\n"));
1676 
1677  xmlFile.Write(wxT("<!DOCTYPE "));
1678  xmlFile.Write(wxT("project "));
1679  xmlFile.Write(wxT("PUBLIC "));
1680  xmlFile.Write(wxT("\"-//audacityproject-1.3.0//DTD//EN\" "));
1681  xmlFile.Write(wxT("\"http://audacity.sourceforge.net/xml/audacityproject-1.3.0.dtd\" "));
1682  xmlFile.Write(wxT(">\n"));
1683 }
1684 
1686  bool recording /* = false */,
1687  const TrackList *tracks /* = nullptr */)
1688 // may throw
1689 {
1690  auto &proj = mProject;
1691  auto &tracklist = tracks ? *tracks : TrackList::Get(proj);
1692  auto &viewInfo = ViewInfo::Get(proj);
1693  auto &tags = Tags::Get(proj);
1694  const auto &settings = ProjectSettings::Get(proj);
1695 
1696  //TIMER_START( "AudacityProject::WriteXML", xml_writer_timer );
1697 
1698  xmlFile.StartTag(wxT("project"));
1699  xmlFile.WriteAttr(wxT("xmlns"), wxT("http://audacity.sourceforge.net/xml/"));
1700 
1701  xmlFile.WriteAttr(wxT("version"), wxT(AUDACITY_FILE_FORMAT_VERSION));
1702  xmlFile.WriteAttr(wxT("audacityversion"), AUDACITY_VERSION_STRING);
1703 
1704  viewInfo.WriteXMLAttributes(xmlFile);
1705  xmlFile.WriteAttr(wxT("rate"), settings.GetRate());
1706  xmlFile.WriteAttr(wxT("snapto"), settings.GetSnapTo() ? wxT("on") : wxT("off"));
1707  xmlFile.WriteAttr(wxT("selectionformat"),
1708  settings.GetSelectionFormat().Internal());
1709  xmlFile.WriteAttr(wxT("frequencyformat"),
1710  settings.GetFrequencySelectionFormatName().Internal());
1711  xmlFile.WriteAttr(wxT("bandwidthformat"),
1712  settings.GetBandwidthSelectionFormatName().Internal());
1713 
1714  tags.WriteXML(xmlFile);
1715 
1716  unsigned int ndx = 0;
1717  tracklist.Any().Visit([&](const Track *t)
1718  {
1719  auto useTrack = t;
1720  if ( recording ) {
1721  // When append-recording, there is a temporary "shadow" track accumulating
1722  // changes and displayed on the screen but it is not yet part of the
1723  // regular track list. That is the one that we want to back up.
1724  // SubstitutePendingChangedTrack() fetches the shadow, if the track has
1725  // one, else it gives the same track back.
1726  useTrack = t->SubstitutePendingChangedTrack().get();
1727  }
1728  else if ( useTrack->GetId() == TrackId{} ) {
1729  // This is a track added during a non-appending recording that is
1730  // not yet in the undo history. The UndoManager skips backing it up
1731  // when pushing. Don't auto-save it.
1732  return;
1733  }
1734  useTrack->WriteXML(xmlFile);
1735  });
1736 
1737  xmlFile.EndTag(wxT("project"));
1738 
1739  //TIMER_STOP( xml_writer_timer );
1740 }
1741 
1742 bool ProjectFileIO::AutoSave(bool recording)
1743 {
1744  ProjectSerializer autosave;
1745  WriteXMLHeader(autosave);
1746  WriteXML(autosave, recording);
1747 
1748  if (WriteDoc("autosave", autosave))
1749  {
1750  mModified = true;
1751  return true;
1752  }
1753 
1754  return false;
1755 }
1756 
1757 bool ProjectFileIO::AutoSaveDelete(sqlite3 *db /* = nullptr */)
1758 {
1759  int rc;
1760 
1761  if (!db)
1762  {
1763  db = DB();
1764  }
1765 
1766  rc = sqlite3_exec(db, "DELETE FROM autosave;", nullptr, nullptr, nullptr);
1767  if (rc != SQLITE_OK)
1768  {
1769  ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
1770  ADD_EXCEPTION_CONTEXT("sqlite3.context", "ProjectGileIO::AutoSaveDelete");
1771 
1772  SetDBError(
1773  XO("Failed to remove the autosave information from the project file.")
1774  );
1775  return false;
1776  }
1777 
1778  mModified = false;
1779 
1780  return true;
1781 }
1782 
1783 bool ProjectFileIO::WriteDoc(const char *table,
1784  const ProjectSerializer &autosave,
1785  const char *schema /* = "main" */)
1786 {
1787  auto db = DB();
1788  int rc;
1789 
1790  // For now, we always use an ID of 1. This will replace the previously
1791  // written row every time.
1792  char sql[256];
1793  sqlite3_snprintf(sizeof(sql),
1794  sql,
1795  "INSERT INTO %s.%s(id, dict, doc) VALUES(1, ?1, ?2)"
1796  " ON CONFLICT(id) DO UPDATE SET dict = ?1, doc = ?2;",
1797  schema,
1798  table);
1799 
1800  sqlite3_stmt *stmt = nullptr;
1801  auto cleanup = finally([&]
1802  {
1803  if (stmt)
1804  {
1805  sqlite3_finalize(stmt);
1806  }
1807  });
1808 
1809  rc = sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr);
1810  if (rc != SQLITE_OK)
1811  {
1812  ADD_EXCEPTION_CONTEXT("sqlite3.query", sql);
1813  ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
1814  ADD_EXCEPTION_CONTEXT("sqlite3.context", "ProjectGileIO::WriteDoc::prepare");
1815 
1816  SetDBError(
1817  XO("Unable to prepare project file command:\n\n%s").Format(sql)
1818  );
1819  return false;
1820  }
1821 
1822  const wxMemoryBuffer &dict = autosave.GetDict();
1823  const wxMemoryBuffer &data = autosave.GetData();
1824 
1825  // Bind statement parameters
1826  // Might return SQL_MISUSE which means it's our mistake that we violated
1827  // preconditions; should return SQL_OK which is 0
1828  if (sqlite3_bind_blob(stmt, 1, dict.GetData(), dict.GetDataLen(), SQLITE_STATIC) ||
1829  sqlite3_bind_blob(stmt, 2, data.GetData(), data.GetDataLen(), SQLITE_STATIC))
1830  {
1831  ADD_EXCEPTION_CONTEXT("sqlite3.query", sql);
1832  ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
1833  ADD_EXCEPTION_CONTEXT("sqlite3.context", "ProjectGileIO::WriteDoc::bind");
1834 
1835  SetDBError(
1836  XO("Unable to bind to blob")
1837  );
1838  return false;
1839  }
1840 
1841  rc = sqlite3_step(stmt);
1842  if (rc != SQLITE_DONE)
1843  {
1844  ADD_EXCEPTION_CONTEXT("sqlite3.query", sql);
1845  ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
1846  ADD_EXCEPTION_CONTEXT("sqlite3.context", "ProjectGileIO::WriteDoc::step");
1847 
1848  SetDBError(
1849  XO("Failed to update the project file.\nThe following command failed:\n\n%s").Format(sql)
1850  );
1851  return false;
1852  }
1853 
1854  return true;
1855 }
1856 
1857 bool ProjectFileIO::LoadProject(const FilePath &fileName, bool ignoreAutosave)
1858 {
1859  bool success = false;
1860 
1861  auto cleanup = finally([&]
1862  {
1863  if (!success)
1864  {
1866  }
1867  });
1868 
1869  SaveConnection();
1870 
1871  // Open the project file
1872  if (!OpenConnection(fileName))
1873  {
1874  return false;
1875  }
1876 
1877  wxString project;
1878  wxMemoryBuffer buffer;
1879  bool usedAutosave = true;
1880 
1881  // Get the autosave doc, if any
1882  if (!ignoreAutosave &&
1883  !GetBlob("SELECT dict || doc FROM autosave WHERE id = 1;", buffer))
1884  {
1885  // Error already set
1886  return false;
1887  }
1888 
1889  // If we didn't have an autosave doc, load the project doc instead
1890  if (buffer.GetDataLen() == 0)
1891  {
1892  usedAutosave = false;
1893 
1894  if (!GetBlob("SELECT dict || doc FROM project WHERE id = 1;", buffer))
1895  {
1896  // Error already set
1897  return false;
1898  }
1899  }
1900 
1901  // Missing both the autosave and project docs. This can happen if the
1902  // system were to crash before the first autosave into a temporary file.
1903  // This should be a recoverable scenario.
1904  if (buffer.GetDataLen() == 0)
1905  {
1906  mRecovered = true;
1907  }
1908  else
1909  {
1910  project = ProjectSerializer::Decode(buffer);
1911  if (project.empty())
1912  {
1913  SetError(XO("Unable to decode project document"));
1914 
1915  return false;
1916  }
1917 
1918  XMLFileReader xmlFile;
1919 
1920  // Load 'er up
1921  success = xmlFile.ParseString(this, project);
1922  if (!success)
1923  {
1924  SetError(
1925  XO("Unable to parse project information."),
1926  xmlFile.GetErrorStr()
1927  );
1928  return false;
1929  }
1930 
1931  // Check for orphans blocks...sets mRecovered if any were deleted
1932 
1933  auto blockids = WaveTrackFactory::Get( mProject )
1935  ->GetActiveBlockIDs();
1936  if (blockids.size() > 0)
1937  {
1938  success = DeleteBlocks(blockids, true);
1939  if (!success)
1940  {
1941  return false;
1942  }
1943  }
1944 
1945  // Remember if we used autosave or not
1946  if (usedAutosave)
1947  {
1948  mRecovered = true;
1949  }
1950  }
1951 
1952  // Mark the project modified if we recovered it
1953  if (mRecovered)
1954  {
1955  mModified = true;
1956  }
1957 
1958  // A previously saved project will have a document in the project table, so
1959  // we use that knowledge to determine if this file is an unsaved/temporary
1960  // file or a permanent project file
1961  wxString result;
1962  success = GetValue("SELECT Count(*) FROM project;", result);
1963  if (!success)
1964  {
1965  return false;
1966  }
1967 
1968  mTemporary = !result.IsSameAs(wxT("1"));
1969 
1970  SetFileName(fileName);
1971 
1973 
1974  success = true;
1975 
1976  return true;
1977 }
1978 
1980 {
1981  ProjectSerializer doc;
1982  WriteXMLHeader(doc);
1983  WriteXML(doc, false, tracks);
1984 
1985  if (!WriteDoc("project", doc))
1986  {
1987  return false;
1988  }
1989 
1990  // Autosave no longer needed
1991  if (!AutoSaveDelete())
1992  {
1993  return false;
1994  }
1995 
1996  return true;
1997 }
1998 
1999 // REVIEW: This function is believed to report an error to the user in all cases
2000 // of failure. Callers are believed not to need to do so if they receive 'false'.
2001 // LLL: All failures checks should now be displaying an error.
2003  const FilePath &fileName, const TrackList *lastSaved)
2004 {
2005  // In the case where we're saving a temporary project to a permanent project,
2006  // we'll try to simply rename the project to save a bit of time. We then fall
2007  // through to the normal Save (not SaveAs) processing.
2008  if (IsTemporary() && mFileName != fileName)
2009  {
2010  FilePath savedName = mFileName;
2011  if (CloseConnection())
2012  {
2013  bool reopened = false;
2014  bool moved = false;
2015  if (true == (moved = MoveProject(savedName, fileName)))
2016  {
2017  if (OpenConnection(fileName))
2018  reopened = true;
2019  else {
2020  MoveProject(fileName, savedName);
2021  moved = false; // No longer moved
2022 
2023  reopened = OpenConnection(savedName);
2024  }
2025  }
2026  else {
2027  // Rename can fail -- if it's to a different device, requiring
2028  // real copy of contents, which might exhaust space
2029  reopened = OpenConnection(savedName);
2030  }
2031 
2032  // Warning issued in MoveProject()
2033  if (reopened && !moved) {
2034  return false;
2035  }
2036 
2037  if (!reopened) {
2038  wxTheApp->CallAfter([this]{
2039  ShowError( {},
2040  XO("Warning"),
2041  XO(
2042 "The project's database failed to reopen, "
2043 "possibly because of limited space on the storage device."),
2044  "Error:_Disk_full_or_not_writable"
2045  );
2046  wxCommandEvent evt{ EVT_RECONNECTION_FAILURE };
2047  mProject.ProcessEvent(evt);
2048  });
2049 
2050  return false;
2051  }
2052  }
2053  }
2054 
2055  // If we're saving to a different file than the current one, then copy the
2056  // current to the new file and make it the active file.
2057  if (mFileName != fileName)
2058  {
2059  // Do NOT prune here since we need to retain the Undo history
2060  // after we switch to the new file.
2061  if (!CopyTo(fileName, XO("Saving project"), false))
2062  {
2063  ShowError( {},
2064  XO("Error Saving Project"),
2066  "Error:_Disk_full_or_not_writable"
2067  );
2068  return false;
2069  }
2070 
2071  // Open the newly created database
2072  Connection newConn = std::make_unique<DBConnection>(
2073  mProject.shared_from_this(), mpErrors,
2074  [this]{ OnCheckpointFailure(); });
2075 
2076  // NOTE: There is a noticeable delay here when dealing with large multi-hour
2077  // projects that we just created. The delay occurs in Open() when it
2078  // calls SafeMode() and is due to the switch from the NONE journal mode
2079  // to the WAL journal mode.
2080  //
2081  // So, we do the Open() in a thread and display a progress dialog. Since
2082  // this is currently the only known instance where this occurs, we do the
2083  // threading here. If more instances are identified, then the threading
2084  // should be moved to DBConnection::Open(), wrapping the SafeMode() call
2085  // there.
2086  {
2087  std::atomic_bool done = {false};
2088  bool success = true;
2089  auto thread = std::thread([&]
2090  {
2091  auto rc = newConn->Open(fileName);
2092  if (rc != SQLITE_OK)
2093  {
2094  // Capture the error string
2095  SetError(Verbatim(sqlite3_errstr(rc)));
2096  success = false;
2097  }
2098  done = true;
2099  });
2100 
2101  // Provides a progress dialog with indeterminate mode
2102  using namespace BasicUI;
2103  auto pd = MakeGenericProgress({},
2104  XO("Syncing"), XO("This may take several seconds"));
2105  wxASSERT(pd);
2106 
2107  // Wait for the checkpoints to end
2108  while (!done)
2109  {
2110  wxMilliSleep(50);
2111  pd->Pulse();
2112  }
2113  thread.join();
2114 
2115  if (!success)
2116  {
2117  // Additional help via a Help button links to the manual.
2118  ShowError( {},
2119  XO("Error Saving Project"),
2120  XO("The project failed to open, possibly due to limited space\n"
2121  "on the storage device.\n\n%s").Format(GetLastError()),
2122  "Error:_Disk_full_or_not_writable");
2123 
2124  newConn = nullptr;
2125 
2126  // Clean up the destination project
2127  if (!wxRemoveFile(fileName))
2128  {
2129  wxLogMessage("Failed to remove destination project after open failure: %s", fileName);
2130  }
2131 
2132  return false;
2133  }
2134  }
2135 
2136  // Autosave no longer needed in original project file.
2137  if (!AutoSaveDelete())
2138  {
2139  // Additional help via a Help button links to the manual.
2140  ShowError( {},
2141  XO("Error Saving Project"),
2142  XO("Unable to remove autosave information, possibly due to limited space\n"
2143  "on the storage device.\n\n%s").Format(GetLastError()),
2144  "Error:_Disk_full_or_not_writable");
2145 
2146  newConn = nullptr;
2147 
2148  // Clean up the destination project
2149  if (!wxRemoveFile(fileName))
2150  {
2151  wxLogMessage("Failed to remove destination project after AutoSaveDelete failure: %s", fileName);
2152  }
2153 
2154  return false;
2155  }
2156 
2157  if (lastSaved) {
2158  // Bug2605: Be sure not to save orphan blocks
2159  bool recovered = mRecovered;
2160  SampleBlockIDSet blockids;
2161  InspectBlocks( *lastSaved, {}, &blockids );
2162  // TODO: Not sure what to do if the deletion fails
2163  DeleteBlocks(blockids, true);
2164  // Don't set mRecovered if any were deleted
2165  mRecovered = recovered;
2166  }
2167 
2168  // Try to compact the original project file.
2169  auto empty = TrackList::Create(&mProject);
2170  Compact( { lastSaved ? lastSaved : empty.get() }, true );
2171 
2172  // Safe to close the original project file now. Not much we can do if this fails,
2173  // but we should still be in good shape since we'll be switching to the newly
2174  // saved database below.
2175  CloseProject();
2176 
2177  // And make it the active project file
2178  UseConnection(std::move(newConn), fileName);
2179  }
2180  else
2181  {
2182  if ( !UpdateSaved( nullptr ) ) {
2183  ShowError( {},
2184  XO("Error Saving Project"),
2186  "Error:_Disk_full_or_not_writable"
2187  );
2188  return false;
2189  }
2190  }
2191 
2192  // Reaching this point defines success and all the rest are no-fail
2193  // operations:
2194 
2195  // No longer modified
2196  mModified = false;
2197 
2198  // No longer recovered
2199  mRecovered = false;
2200 
2201  // No longer a temporary project
2202  mTemporary = false;
2203 
2204  // Adjust the title
2205  SetProjectTitle();
2206 
2207  return true;
2208 }
2209 
2210 bool ProjectFileIO::SaveCopy(const FilePath& fileName)
2211 {
2212  return CopyTo(fileName, XO("Backing up project"), false, true,
2213  {&TrackList::Get(mProject)});
2214 }
2215 
2217 {
2218  return OpenConnection();
2219 }
2220 
2222 {
2223  auto &currConn = CurrConn();
2224  if (!currConn)
2225  {
2226  wxLogDebug("Closing project with no database connection");
2227  return true;
2228  }
2229 
2230  // Save the filename since CloseConnection() will clear it
2231  wxString filename = mFileName;
2232 
2233  // Not much we can do if this fails. The user will simply get
2234  // the recovery dialog upon next restart.
2235  if (CloseConnection())
2236  {
2237  // If this is a temporary project, we no longer want to keep the
2238  // project file.
2239  if (IsTemporary())
2240  {
2241  // This is just a safety check.
2242  wxFileName temp(TempDirectory::TempDir(), wxT(""));
2243  wxFileName file(filename);
2244  file.SetFullName(wxT(""));
2245  if (file == temp)
2246  RemoveProject(filename);
2247  }
2248  }
2249 
2250  return true;
2251 }
2252 
2254 {
2255  FilePath fileName = mFileName;
2256  if (!CloseConnection())
2257  {
2258  return false;
2259  }
2260 
2261  return OpenConnection(fileName);
2262 }
2263 
2265 {
2266  return mModified;
2267 }
2268 
2270 {
2271  return mTemporary;
2272 }
2273 
2275 {
2276  return mRecovered;
2277 }
2278 
2280 {
2281  wxLongLong freeSpace;
2282  if (wxGetDiskSpace(wxPathOnly(mFileName), NULL, &freeSpace))
2283  {
2285  // 4 GiB per-file maximum
2286  constexpr auto limit = 1ll << 32;
2287 
2288  // Opening a file only to find its length looks wasteful but
2289  // seems to be necessary at least on Windows with FAT filesystems.
2290  // I don't know if that is only a wxWidgets bug.
2291  auto length = wxFile{mFileName}.Length();
2292  // auto length = wxFileName::GetSize(mFileName);
2293 
2294  if (length == wxInvalidSize)
2295  length = 0;
2296  auto free = std::max<wxLongLong>(0, limit - length);
2297  freeSpace = std::min(freeSpace, free);
2298  }
2299  return freeSpace;
2300  }
2301 
2302  return -1;
2303 }
2304 
2307  const TranslatableString &dlogTitle,
2308  const TranslatableString &message,
2309  const wxString &helpPage)
2310 {
2311  using namespace audacity;
2312  using namespace BasicUI;
2313  ShowErrorDialog( placement, dlogTitle, message, helpPage,
2314  ErrorDialogOptions{ ErrorDialogType::ModalErrorReport }
2315  .Log(ToWString(GetLastLog())));
2316 }
2317 
2319 {
2320  return mpErrors->mLastError;
2321 }
2322 
2324 {
2325  return mpErrors->mLibraryError;
2326 }
2327 
2329 {
2330  return mpErrors->mErrorCode;
2331 }
2332 
2333 const wxString &ProjectFileIO::GetLastLog() const
2334 {
2335  return mpErrors->mLog;
2336 }
2337 
2339  const TranslatableString& msg, const TranslatableString& libraryError, int errorCode)
2340 {
2341  auto &currConn = CurrConn();
2342  if (currConn)
2343  currConn->SetError(msg, libraryError, errorCode);
2344 }
2345 
2347  const TranslatableString &msg, const TranslatableString &libraryError, int errorCode)
2348 {
2349  auto &currConn = CurrConn();
2350  if (currConn)
2351  currConn->SetDBError(msg, libraryError, errorCode);
2352 }
2353 
2355 {
2356  auto &currConn = CurrConn();
2357  if (!currConn)
2358  return;
2359 
2360  // Determine if we can bypass sample block deletes during shutdown.
2361  //
2362  // IMPORTANT:
2363  // If the project was compacted, then we MUST bypass further
2364  // deletions since the new file doesn't have the blocks that the
2365  // Sequences expect to be there.
2366 
2367  currConn->SetBypass( true );
2368 
2369  // Only permanent project files need cleaning at shutdown
2370  if (!IsTemporary() && !WasCompacted())
2371  {
2372  // If we still have unused blocks, then we must not bypass deletions
2373  // during shutdown. Otherwise, we would have orphaned blocks the next time
2374  // the project is opened.
2375  //
2376  // An example of when dead blocks will exist is when a user opens a permanent
2377  // project, adds a track (with samples) to it, and chooses not to save the
2378  // changes.
2379  if (HadUnused())
2380  {
2381  currConn->SetBypass( false );
2382  }
2383  }
2384 
2385  return;
2386 }
2387 
2389 {
2390  auto pConn = CurrConn().get();
2391  if (!pConn)
2392  return 0;
2393  return GetDiskUsage(*pConn, blockid);
2394 }
2395 
2397  const std::vector<const TrackList*> &trackLists) const
2398 {
2399  unsigned long long current = 0;
2400  const auto fn = BlockSpaceUsageAccumulator(current);
2401 
2402  // Must pass address of this set, even if not otherwise used, to avoid
2403  // possible multiple count of shared blocks
2404  SampleBlockIDSet seen;
2405  for (auto pTracks: trackLists)
2406  if (pTracks)
2407  InspectBlocks(*pTracks, fn, &seen);
2408 
2409  return current;
2410 }
2411 
2413 {
2414  auto pConn = CurrConn().get();
2415  if (!pConn)
2416  return 0;
2417  return GetDiskUsage(*pConn, 0);
2418 }
2419 
2420 //
2421 // Returns the amount of disk space used by the specified sample blockid or all
2422 // of the sample blocks if the blockid is 0. It does this by using the raw SQLite
2423 // pages available from the "sqlite_dbpage" virtual table to traverse the SQLite
2424 // table b-tree described here: https://www.sqlite.org/fileformat.html
2425 //
2426 int64_t ProjectFileIO::GetDiskUsage(DBConnection &conn, SampleBlockID blockid /* = 0 */)
2427 {
2428  // Information we need to track our travels through the b-tree
2429  typedef struct
2430  {
2431  int64_t pgno;
2432  int currentCell;
2433  int numCells;
2434  unsigned char data[65536];
2435  } page;
2436  std::vector<page> stack;
2437 
2438  int64_t total = 0;
2439  int64_t found = 0;
2440  int64_t right = 0;
2441  int rc;
2442 
2443  // Get the rootpage for the sampleblocks table.
2444  sqlite3_stmt *stmt =
2446  "SELECT rootpage FROM sqlite_master WHERE tbl_name = 'sampleblocks';");
2447  if (stmt == nullptr || sqlite3_step(stmt) != SQLITE_ROW)
2448  {
2449  ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(sqlite3_errcode(conn.DB())));
2450  ADD_EXCEPTION_CONTEXT("sqlite3.context", "ProjectGileIO::GetDiskUsage");
2451 
2452  return 0;
2453  }
2454 
2455  // And store it in our first stack frame
2456  stack.push_back({sqlite3_column_int64(stmt, 0)});
2457 
2458  // All done with the statement
2459  sqlite3_clear_bindings(stmt);
2460  sqlite3_reset(stmt);
2461 
2462  // Prepare/retrieve statement to read raw database page
2463  stmt = conn.Prepare(DBConnection::GetDBPage,
2464  "SELECT data FROM sqlite_dbpage WHERE pgno = ?1;");
2465  if (stmt == nullptr)
2466  {
2467  return 0;
2468  }
2469 
2470  // Traverse the b-tree until we've visited all of the leaf pages or until
2471  // we find the one corresponding to the passed in sample blockid. Because we
2472  // use an integer primary key for the sampleblocks table, the traversal will
2473  // be in ascending blockid sequence.
2474  do
2475  {
2476  // Acces the top stack frame
2477  page &pg = stack.back();
2478 
2479  // Read the page from the sqlite_dbpage table if it hasn't yet been loaded
2480  if (pg.numCells == 0)
2481  {
2482  // Bind the page number
2483  sqlite3_bind_int64(stmt, 1, pg.pgno);
2484 
2485  // And retrieve the page
2486  if (sqlite3_step(stmt) != SQLITE_ROW)
2487  {
2488  // REVIEW: Likely harmless failure - says size is zero on
2489  // this error.
2490  // LLL: Yea, but not much else we can do.
2491  return 0;
2492  }
2493 
2494  // Copy the page content to the stack frame
2495  memcpy(&pg.data,
2496  sqlite3_column_blob(stmt, 0),
2497  sqlite3_column_bytes(stmt, 0));
2498 
2499  // And retrieve the total number of cells within it
2500  pg.numCells = get2(&pg.data[3]);
2501 
2502  // Reset statement for next usage
2503  sqlite3_clear_bindings(stmt);
2504  sqlite3_reset(stmt);
2505  }
2506 
2507  //wxLogDebug("%*.*spgno %lld currentCell %d numCells %d", (stack.size() - 1) * 2, (stack.size() - 1) * 2, "", pg.pgno, pg.currentCell, pg.numCells);
2508 
2509  // Process an interior table b-tree page
2510  if (pg.data[0] == 0x05)
2511  {
2512  // Process the next cell if we haven't examined all of them yet
2513  if (pg.currentCell < pg.numCells)
2514  {
2515  // Remember the right-most leaf page number.
2516  right = get4(&pg.data[8]);
2517 
2518  // Iterate over the cells.
2519  //
2520  // If we're not looking for a specific blockid, then we always push the
2521  // target page onto the stack and leave the loop after a single iteration.
2522  //
2523  // Otherwise, we match the blockid against the highest integer key contained
2524  // within the cell and if the blockid falls within the cell, we stack the
2525  // page and stop the iteration.
2526  //
2527  // In theory, we could do a binary search for a specific blockid here, but
2528  // because our sample blocks are always large, we will get very few cells
2529  // per page...usually 6 or less.
2530  //
2531  // In both cases, the stacked page can be either an internal or leaf page.
2532  bool stacked = false;
2533  while (pg.currentCell < pg.numCells)
2534  {
2535  // Get the offset to this cell using the offset in the cell pointer
2536  // array.
2537  //
2538  // The cell pointer array starts immediately after the page header
2539  // at offset 12 and the retrieved offset is from the beginning of
2540  // the page.
2541  int celloff = get2(&pg.data[12 + (pg.currentCell * 2)]);
2542 
2543  // Bump to the next cell for the next iteration.
2544  pg.currentCell++;
2545 
2546  // Get the page number this cell describes
2547  int pagenum = get4(&pg.data[celloff]);
2548 
2549  // And the highest integer key, which starts at offset 4 within the cell.
2550  int64_t intkey = 0;
2551  get_varint(&pg.data[celloff + 4], &intkey);
2552 
2553  //wxLogDebug("%*.*sinternal - right %lld celloff %d pagenum %d intkey %lld", (stack.size() - 1) * 2, (stack.size() - 1) * 2, " ", right, celloff, pagenum, intkey);
2554 
2555  // Stack the described page if we're not looking for a specific blockid
2556  // or if this page contains the given blockid.
2557  if (!blockid || blockid <= intkey)
2558  {
2559  stack.push_back({pagenum, 0, 0});
2560  stacked = true;
2561  break;
2562  }
2563  }
2564 
2565  // If we pushed a new page onto the stack, we need to jump back up
2566  // to read the page
2567  if (stacked)
2568  {
2569  continue;
2570  }
2571  }
2572 
2573  // We've exhausted all the cells with this page, so we stack the right-most
2574  // leaf page. Ensure we only process it once.
2575  if (right)
2576  {
2577  stack.push_back({right, 0, 0});
2578  right = 0;
2579  continue;
2580  }
2581  }
2582  // Process a leaf table b-tree page
2583  else if (pg.data[0] == 0x0d)
2584  {
2585  // Iterate over the cells
2586  //
2587  // If we're not looking for a specific blockid, then just accumulate the
2588  // payload sizes. We will be reading every leaf page in the sampleblocks
2589  // table.
2590  //
2591  // Otherwise we break out when we find the matching blockid. In this case,
2592  // we only ever look at 1 leaf page.
2593  bool stop = false;
2594  for (int i = 0; i < pg.numCells; i++)
2595  {
2596  // Get the offset to this cell using the offset in the cell pointer
2597  // array.
2598  //
2599  // The cell pointer array starts immediately after the page header
2600  // at offset 8 and the retrieved offset is from the beginning of
2601  // the page.
2602  int celloff = get2(&pg.data[8 + (i * 2)]);
2603 
2604  // Get the total payload size in bytes of the described row.
2605  int64_t payload = 0;
2606  int digits = get_varint(&pg.data[celloff], &payload);
2607 
2608  // Get the integer key for this row.
2609  int64_t intkey = 0;
2610  get_varint(&pg.data[celloff + digits], &intkey);
2611 
2612  //wxLogDebug("%*.*sleaf - celloff %4d intkey %lld payload %lld", (stack.size() - 1) * 2, (stack.size() - 1) * 2, " ", celloff, intkey, payload);
2613 
2614  // Add this payload size to the total if we're not looking for a specific
2615  // blockid
2616  if (!blockid)
2617  {
2618  total += payload;
2619  }
2620  // Otherwise, return the payload size for a matching row
2621  else if (blockid == intkey)
2622  {
2623  return payload;
2624  }
2625  }
2626  }
2627 
2628  // Done with the current branch, so pop back up to the previous one (if any)
2629  stack.pop_back();
2630  } while (!stack.empty());
2631 
2632  // Return the total used for all sample blocks
2633  return total;
2634 }
2635 
2636 // Retrieves a 2-byte big-endian integer from the page data
2637 unsigned int ProjectFileIO::get2(const unsigned char *ptr)
2638 {
2639  return (ptr[0] << 8) | ptr[1];
2640 }
2641 
2642 // Retrieves a 4-byte big-endian integer from the page data
2643 unsigned int ProjectFileIO::get4(const unsigned char *ptr)
2644 {
2645  return ((unsigned int) ptr[0] << 24) |
2646  ((unsigned int) ptr[1] << 16) |
2647  ((unsigned int) ptr[2] << 8) |
2648  ((unsigned int) ptr[3]);
2649 }
2650 
2651 // Retrieves a variable length integer from the page data. Returns the
2652 // number of digits used to encode the integer and the stores the
2653 // value at the given location.
2654 int ProjectFileIO::get_varint(const unsigned char *ptr, int64_t *out)
2655 {
2656  int64_t val = 0;
2657  int i;
2658 
2659  for (i = 0; i < 8; ++i)
2660  {
2661  val = (val << 7) + (ptr[i] & 0x7f);
2662  if ((ptr[i] & 0x80) == 0)
2663  {
2664  *out = val;
2665  return i + 1;
2666  }
2667  }
2668 
2669  val = (val << 8) + (ptr[i] & 0xff);
2670  *out = val;
2671 
2672  return 9;
2673 }
2674 
2676  : mpProject{ std::make_shared< AudacityProject >() }
2677 {
2678 }
2679 
2681 {
2682  auto &projectFileIO = ProjectFileIO::Get( Project() );
2683  projectFileIO.SetBypass();
2684  auto &tracks = TrackList::Get( Project() );
2685  tracks.Clear();
2686 
2687  // Consume some delayed track list related events before destroying the
2688  // temporary project
2689  try { wxTheApp->Yield(); } catch(...) {}
2690 
2691  // Destroy the project and yield again to let delayed window deletions happen
2692  projectFileIO.CloseProject();
2693  mpProject.reset();
2694  try { wxTheApp->Yield(); } catch(...) {}
2695 }
ProjectFileIO::CopyTo
bool CopyTo(const FilePath &destpath, const TranslatableString &msg, bool isTemporary, bool prune=false, const std::vector< const TrackList * > &tracks={})
Definition: ProjectFileIO.cpp:805
XMLWriter
Base class for XMLFileWriter and XMLStringWriter that provides the general functionality for creating...
Definition: XMLWriter.h:23
WaveTrack.h
XMLWriter::EndTag
virtual void EndTag(const wxString &name)
Definition: XMLWriter.cpp:100
ProjectFileIO::HadUnused
bool HadUnused()
Definition: ProjectFileIO.cpp:1431
FilePath
wxString FilePath
Definition: Identifier.h:227
TranslatableString
Holds a msgid for the translation catalog; may also bind format arguments.
Definition: TranslatableString.h:32
ViewInfo::Get
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:156
AllProjects::begin
const_iterator begin() const
Definition: Project.cpp:33
TranslatableString::empty
bool empty() const
Definition: TranslatableString.h:72
BasicUI::ErrorDialogOptions::Log
ErrorDialogOptions && Log(std::wstring log_) &&
Definition: BasicUI.h:61
SQLiteIniter::~SQLiteIniter
~SQLiteIniter()
Definition: ProjectFileIO.cpp:199
WaveTrackFactory::Get
static WaveTrackFactory & Get(AudacityProject &project)
Definition: WaveTrack.cpp:2799
ProjectFileIO::IsRecovered
bool IsRecovered() const
Definition: ProjectFileIO.cpp:2274
ProjectSerializer.h
ProjectFileIO::GetFileName
const FilePath & GetFileName() const
Definition: ProjectFileIO.cpp:1487
ProjectSerializer::Decode
static wxString Decode(const wxMemoryBuffer &buffer)
Definition: ProjectSerializer.cpp:386
wxFileNameWrapper.h
ProjectFileIO::GetConnection
DBConnection & GetConnection()
Return a reference to a connection, creating it as needed on demand; throw on failure.
Definition: ProjectFileIO.cpp:303
BasicUI::ProgressResult::Success
@ Success
ProjectFileIO.h
ProjectFileIO::GetLibraryError
const TranslatableString & GetLibraryError() const
Definition: ProjectFileIO.cpp:2323
ProjectFileIO::SetFileName
void SetFileName(const FilePath &fileName)
Definition: ProjectFileIO.cpp:1492
SampleBlockIDSet
std::unordered_set< SampleBlockID > SampleBlockIDSet
Definition: WaveTrack.h:683
fn
static const auto fn
Definition: WaveformView.cpp:1114
wxFileNameWrapper
Definition: wxFileNameWrapper.h:21
SentryHelper.h
ProjectFileIO::ExecCB
std::function< int(int cols, char **vals, char **names)> ExecCB
Definition: ProjectFileIO.h:193
ProjectFileIO::UpgradeSchema
bool UpgradeSchema()
Definition: ProjectFileIO.cpp:709
ProjectFileIO::HandleXMLChild
XMLTagHandler * HandleXMLChild(const wxChar *tag) override
Definition: ProjectFileIO.cpp:1652
AudacityProject::GetFrame
wxFrame * GetFrame()
Definition: Project.h:121
BasicUI::ShowErrorDialog
void ShowErrorDialog(const WindowPlacement &placement, const TranslatableString &dlogTitle, const TranslatableString &message, const ManualPageID &helpPage, const ErrorDialogOptions &options={})
Show an error dialog with a link to the manual for further help.
Definition: BasicUI.h:233
Project.h
ProjectFileIO::WriteDoc
bool WriteDoc(const char *table, const ProjectSerializer &autosave, const char *schema="main")
Definition: ProjectFileIO.cpp:1783
ProjectFileIO::CurrConn
Connection & CurrConn()
Definition: ProjectFileIO.cpp:1112
AllProjects::end
const_iterator end() const
Definition: Project.cpp:38
pdlgHideStopButton
@ pdlgHideStopButton
Definition: ProgressDialog.h:38
ActiveProjects.h
ProjectFileVersion
static const int ProjectFileVersion
Definition: ProjectFileIO.cpp:79
Format
Abstract base class used in importing a file.
TrackList
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:1264
ProjectFileIO::AuxiliaryFileSuffixes
static const std::vector< wxString > & AuxiliaryFileSuffixes()
Definition: ProjectFileIO.cpp:1118
ProjectFileIO::LoadProject
bool LoadProject(const FilePath &fileName, bool ignoreAutosave)
Definition: ProjectFileIO.cpp:1857
BasicUI::ProgressResult
ProgressResult
Definition: BasicUI.h:145
ProjectFileIO::RemoveProject
static bool RemoveProject(const FilePath &filename)
Remove any files associated with a project at given path; return true if successful.
Definition: ProjectFileIO.cpp:1241
MakeSimpleGuard
SimpleGuard< R > MakeSimpleGuard(R value)
Convert a value to a handler function returning that value, suitable for GuardedCall<R>
Definition: AudacityException.h:167
ProjectFileIO::GetLastErrorCode
int GetLastErrorCode() const
Definition: ProjectFileIO.cpp:2328
Connection
std::unique_ptr< DBConnection > Connection
Definition: DBConnection.h:153
ProjectFileIO::~ProjectFileIO
~ProjectFileIO()
Definition: ProjectFileIO.cpp:293
XO
#define XO(s)
Definition: Internat.h:31
XMLStringWriter
Wrapper to output XML data to strings.
Definition: XMLWriter.h:136
ProjectFileIO::IsTemporary
bool IsTemporary() const
Definition: ProjectFileIO.cpp:2269
ProjectFileIO::Get
static ProjectFileIO & Get(AudacityProject &project)
Definition: ProjectFileIO.cpp:269
ProjectFileIO::CloseConnection
bool CloseConnection()
Definition: ProjectFileIO.cpp:400
ProjectSettings::Get
static ProjectSettings & Get(AudacityProject &project)
Definition: ProjectSettings.cpp:40
audacity
Definition: ErrorReportDialog.h:22
ProjectFileIO::DB
sqlite3 * DB()
Definition: ProjectFileIO.cpp:333
ProjectFileIO::InstallSchema
bool InstallSchema(sqlite3 *db, const char *schema="main")
Definition: ProjectFileIO.cpp:689
ProgressDialog.h
TrackInfo::UpdatePrefs
AUDACITY_DLL_API void UpdatePrefs(wxWindow *pParent)
ProjectSettings.h
ProjectFileIO::GetCurrentUsage
int64_t GetCurrentUsage(const std::vector< const TrackList * > &trackLists) const
Definition: ProjectFileIO.cpp:2396
ProjectFileIO::SafetyFileName
static FilePath SafetyFileName(const FilePath &src)
Generate a name for short-lived backup project files from an existing project.
Definition: ProjectFileIO.cpp:1129
SQLiteIniter::mRc
int mRc
Definition: ProjectFileIO.cpp:211
SQLiteIniter::SQLiteIniter
SQLiteIniter()
Definition: ProjectFileIO.cpp:171
DBConnection.h
Declare DBConnection, which maintains database connection and associated status and background thread...
ExecCallback
static int ExecCallback(void *data, int cols, char **vals, char **names)
Definition: ProjectFileIO.cpp:496
ProjectFileSchema
static const char * ProjectFileSchema
Definition: ProjectFileIO.cpp:89
DBConnection
Definition: DBConnection.h:40
InspectBlocks
void InspectBlocks(const TrackList &tracks, BlockInspector inspector, SampleBlockIDSet *pIDs)
Definition: WaveTrack.cpp:2780
ProjectFileIO::SaveConnection
void SaveConnection()
Definition: ProjectFileIO.cpp:419
ProjectFileIO::OpenProject
bool OpenProject()
Definition: ProjectFileIO.cpp:2216
ProjectFileIO::mFileName
FilePath mFileName
Definition: ProjectFileIO.h:292
ClientData::Site::RegisteredFactory
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
Definition: ClientData.h:266
ProjectFileID
static const int ProjectFileID
Definition: ProjectFileIO.cpp:64
XMLValueChecker::IsGoodString
static bool IsGoodString(const wxString &str)
Definition: XMLTagHandler.cpp:41
Tags.h
Tags::Get
static Tags & Get(AudacityProject &project)
Definition: Tags.cpp:237
SampleBlockID
long long SampleBlockID
Definition: ProjectFileIO.h:39
NumericConverter::BANDWIDTH
@ BANDWIDTH
Definition: NumericTextCtrl.h:55
ProjectFileIO::ShouldCompact
bool ShouldCompact(const std::vector< const TrackList * > &tracks)
Definition: ProjectFileIO.cpp:1064
ProjectFileIO::WriteXML
void WriteXML(XMLWriter &xmlFile, bool recording=false, const TrackList *tracks=nullptr)
Definition: ProjectFileIO.cpp:1685
_TS
#define _TS(s)
Definition: Internat.h:27
DBConnection::DB
sqlite3 * DB()
Definition: DBConnection.cpp:375
ProjectFileIO::Compact
void Compact(const std::vector< const TrackList * > &tracks, bool force=false)
Definition: ProjectFileIO.cpp:1293
InvisibleTemporaryProject::InvisibleTemporaryProject
InvisibleTemporaryProject()
Definition: ProjectFileIO.cpp:2675
ProjectFileIO::SetBypass
void SetBypass()
Definition: ProjectFileIO.cpp:2354
SampleBlock.h
ProjectFileIO::mModified
bool mModified
Definition: ProjectFileIO.h:298
ProjectFileIO::mPrevTemporary
bool mPrevTemporary
Definition: ProjectFileIO.h:311
SQLiteIniter::LogCallback
static void LogCallback(void *WXUNUSED(arg), int code, const char *msg)
Definition: ProjectFileIO.cpp:206
ProjectFileIO::mRecovered
bool mRecovered
Definition: ProjectFileIO.h:295
DBConnection::GetDBPage
@ GetDBPage
Definition: DBConnection.h:80
NumericConverter::LookupFormat
static NumericFormatSymbol LookupFormat(Type type, const wxString &id)
Definition: NumericTextCtrl.cpp:702
ProjectFileIORegistry::Lookup
TagHandlerFactory Lookup(const wxString &tag)
Definition: ProjectFileIORegistry.cpp:35
Track::SubstitutePendingChangedTrack
std::shared_ptr< Track > SubstitutePendingChangedTrack()
Definition: Track.cpp:1186
ProjectFileIO::SetError
void SetError(const TranslatableString &msg, const TranslatableString &libraryError={}, int errorCode={})
Just set stored errors.
Definition: ProjectFileIO.cpp:2338
XMLFileReader::GetErrorStr
const TranslatableString & GetErrorStr() const
Definition: XMLFileReader.cpp:178
ProjectFramePlacement
std::unique_ptr< const BasicUI::WindowPlacement > ProjectFramePlacement(AudacityProject *project)
Make a WindowPlacement object suitable for project (which may be null)
Definition: Project.cpp:205
ProgressDialog
ProgressDialog Class.
Definition: ProgressDialog.h:51
ProjectFileIO::SaveProject
bool SaveProject(const FilePath &fileName, const TrackList *lastSaved)
Definition: ProjectFileIO.cpp:2002
ProjectSerializer::GetDict
const wxMemoryBuffer & GetDict() const
Definition: ProjectSerializer.cpp:365
ProjectFileIO::UpdatePrefs
void UpdatePrefs() override
Definition: ProjectFileIO.cpp:1436
ProjectFileIO::CheckVersion
bool CheckVersion()
Definition: ProjectFileIO.cpp:618
ProjectFileIO::InSet
static void InSet(sqlite3_context *context, int argc, sqlite3_value **argv)
Definition: ProjectFileIO.cpp:721
BasicUI::WindowPlacement
Subclasses may hold information such as a parent window pointer for a dialog.
Definition: BasicUI.h:29
ProjectFileIO::UseConnection
void UseConnection(Connection &&conn, const FilePath &filePath)
Definition: ProjectFileIO.cpp:487
ProjectFileIO::DeleteBlocks
bool DeleteBlocks(const BlockIDs &blockids, bool complement)
Definition: ProjectFileIO.cpp:729
ProjectFileIO::AutoSaveDelete
bool AutoSaveDelete(sqlite3 *db=nullptr)
Definition: ProjectFileIO.cpp:1757
GetProjectFrame
AUDACITY_DLL_API wxFrame & GetProjectFrame(AudacityProject &project)
Get the top-level window associated with the project (as a wxFrame only, when you do not need to use ...
Definition: Project.cpp:188
AUDACITY_FILE_FORMAT_VERSION
#define AUDACITY_FILE_FORMAT_VERSION
Definition: ProjectFileIO.cpp:43
ProjectFileIO::DiscardConnection
void DiscardConnection()
Definition: ProjectFileIO.cpp:432
XMLFileReader::ParseString
bool ParseString(XMLTagHandler *baseHandler, const wxString &xmldata)
Definition: XMLFileReader.cpp:136
AudacityProject::GetProjectNumber
int GetProjectNumber()
Definition: Project.h:129
TitleRestorer::~TitleRestorer
~TitleRestorer()
Definition: ProjectFileIO.cpp:257
ProjectFileIO::ReopenProject
bool ReopenProject()
Definition: ProjectFileIO.cpp:2253
InvisibleTemporaryProject::~InvisibleTemporaryProject
~InvisibleTemporaryProject()
Definition: ProjectFileIO.cpp:2680
name
const TranslatableString name
Definition: Distortion.cpp:98
ProjectFileIO::WriteXMLHeader
void WriteXMLHeader(XMLWriter &xmlFile) const
Definition: ProjectFileIO.cpp:1670
NumericConverter::TIME
@ TIME
Definition: NumericTextCtrl.h:52
ProjectFileIO::WasCompacted
bool WasCompacted()
Definition: ProjectFileIO.cpp:1426
RefreshAllTitles
static void RefreshAllTitles(bool bShowProjectNumbers)
Definition: ProjectFileIO.cpp:220
ProgressDialog::Update
ProgressResult Update(int value, const TranslatableString &message={})
Definition: ProgressDialog.cpp:1327
DBConnection::GetRootPage
@ GetRootPage
Definition: DBConnection.h:79
PACK
#define PACK(b1, b2, b3, b4)
Definition: ProjectFileIO.cpp:59
ProjectFileIO::SaveCopy
bool SaveCopy(const FilePath &fileName)
Definition: ProjectFileIO.cpp:2210
ProjectFileIO::OpenConnection
bool OpenConnection(FilePath fileName={})
Definition: ProjectFileIO.cpp:342
THROW_INCONSISTENCY_EXCEPTION
#define THROW_INCONSISTENCY_EXCEPTION
Throw InconsistencyException, using C++ preprocessor to identify the source code location.
Definition: InconsistencyException.h:79
sFileIOKey
static const AudacityProject::AttachedObjects::RegisteredFactory sFileIOKey
Definition: ProjectFileIO.cpp:262
ProjectFileIO::GetTotalUsage
int64_t GetTotalUsage()
Definition: ProjectFileIO.cpp:2412
ProjectFileIO::BackupProject::Discard
void Discard()
if !IsOk() do nothing; else remove backup files
Definition: ProjectFileIO.cpp:1267
ProjectFileIO::UpdateSaved
bool UpdateSaved(const TrackList *tracks=nullptr)
Definition: ProjectFileIO.cpp:1979
ProjectFileIO::mPrevFileName
FilePath mPrevFileName
Definition: ProjectFileIO.h:310
ProjectFileIO::OnCheckpointFailure
void OnCheckpointFailure()
Definition: ProjectFileIO.cpp:1664
XMLFileReader.h
TempDirectory.h
ProjectFileIORegistry.h
TitleRestorer::sProjNumber
wxString sProjNumber
Definition: ProjectFileIO.h:321
ProjectFileIO::InitializeSQL
static bool InitializeSQL()
Definition: ProjectFileIO.cpp:214
ViewInfo.h
ProjectFileIO::GetLastError
const TranslatableString & GetLastError() const
Definition: ProjectFileIO.cpp:2318
BasicUI::ErrorDialogOptions
Options for variations of error dialogs; the default is for modal dialogs.
Definition: BasicUI.h:49
FileNames::IsOnFATFileSystem
AUDACITY_DLL_API bool IsOnFATFileSystem(const FilePath &path)
NumericConverter::FREQUENCY
@ FREQUENCY
Definition: NumericTextCtrl.h:54
XMLTagHandler
This class is an interface which should be implemented by classes which wish to be able to load and s...
Definition: XMLTagHandler.h:80
ProjectFileIO::GetValue
bool GetValue(const char *sql, wxString &value)
Definition: ProjectFileIO.cpp:546
ProjectFileIO::BackupProject::BackupProject
BackupProject(ProjectFileIO &projectFileIO, const FilePath &path)
Rename project file at path, and any auxiliary files, to backup path names.
Definition: ProjectFileIO.cpp:1256
BasicUI.h
Toolkit-neutral facade for basic user interface services.
ProjectFileIO::get2
static unsigned int get2(const unsigned char *ptr)
Definition: ProjectFileIO.cpp:2637
BlockIDs
std::unordered_set< SampleBlockID > BlockIDs
Definition: ProjectFileIO.h:43
min
int min(int a, int b)
Definition: CompareAudioCommand.cpp:106
TitleRestorer::UnnamedCount
size_t UnnamedCount
Definition: ProjectFileIO.h:323
ProjectFileIO::ProjectFileIO
ProjectFileIO(AudacityProject &project)
Definition: ProjectFileIO.cpp:280
ProjectSerializer::GetData
const wxMemoryBuffer & GetData() const
Definition: ProjectSerializer.cpp:370
ProjectFileIO::HandleXMLTag
bool HandleXMLTag(const wxChar *tag, const wxChar **attrs) override
Definition: ProjectFileIO.cpp:1520
TrackId
An in-session identifier of track objects across undo states. It does not persist between sessions.
Definition: Track.h:165
BasicUI
Definition: Export.h:39
TrackList::Get
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:495
XMLFileReader
Reads a file and passes the results through an XMLTagHandler.
Definition: XMLFileReader.h:20
BasicUI::MakeGenericProgress
std::unique_ptr< GenericProgressDialog > MakeGenericProgress(const WindowPlacement &placement, const TranslatableString &title, const TranslatableString &message)
Create and display a progress dialog (return nullptr if Services not installed)
Definition: BasicUI.h:281
Track
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:239
XMLWriter::Write
virtual void Write(const wxString &data)=0
names
static TranslatableStrings names
Definition: Tags.cpp:744
ProjectFileIO::GetFreeDiskSpace
wxLongLong GetFreeDiskSpace() const
Definition: ProjectFileIO.cpp:2279
_
#define _(s)
Definition: Internat.h:75
TitleRestorer::TitleRestorer
TitleRestorer(wxTopLevelWindow &window, AudacityProject &project)
Definition: ProjectFileIO.cpp:230
ConnectionPtr::Get
static ConnectionPtr & Get(AudacityProject &project)
Definition: DBConnection.cpp:693
WaveTrackFactory::GetSampleBlockFactory
const SampleBlockFactoryPtr & GetSampleBlockFactory() const
Definition: WaveTrack.h:717
AudacityProject
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:113
TempDirectory::UnsavedProjectFileName
AUDACITY_DLL_API wxString UnsavedProjectFileName()
Definition: TempDirectory.cpp:107
ProjectFileIO::AutoSave
bool AutoSave(bool recording=false)
Definition: ProjectFileIO.cpp:1742
wxDEFINE_EVENT
wxDEFINE_EVENT(EVT_PROJECT_TITLE_CHANGE, wxCommandEvent)
AudacityMessageBox.h
ProjectFileIO::ShowError
void ShowError(const BasicUI::WindowPlacement &placement, const TranslatableString &dlogTitle, const TranslatableString &message, const wxString &helpPage)
Displays an error dialog with a button that offers help.
Definition: ProjectFileIO.cpp:2306
ProjectFileIO::mWasCompacted
bool mWasCompacted
Definition: ProjectFileIO.h:304
ProjectFileIO::Query
bool Query(const char *sql, const ExecCB &callback)
Definition: ProjectFileIO.cpp:533
ProjectFileIO::RenameOrWarn
bool RenameOrWarn(const FilePath &src, const FilePath &dst)
Rename a file or put up appropriate warning message.
Definition: ProjectFileIO.cpp:1164
AllProjects
an object of class AllProjects acts like a standard library container, but refers to a global array o...
Definition: Project.h:39
XMLWriter::WriteAttr
void WriteAttr(const wxString &name, const Identifier &value)
Definition: XMLWriter.h:34
Verbatim
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
Definition: TranslatableString.h:321
NumericTextCtrl.h
ExceptionType::Internal
@ Internal
Indicates internal failure from Audacity.
TrackList::Create
static std::shared_ptr< TrackList > Create(AudacityProject *pOwner)
Definition: Track.cpp:512
ProjectFileIO::GetBlockUsage
int64_t GetBlockUsage(SampleBlockID blockid)
Definition: ProjectFileIO.cpp:2388
ProjectFileIO::GetLastLog
const wxString & GetLastLog() const
Definition: ProjectFileIO.cpp:2333
ProjectFileIO::MoveProject
bool MoveProject(const FilePath &src, const FilePath &dst)
Definition: ProjectFileIO.cpp:1204
FileException::WriteFailureMessage
static TranslatableString WriteFailureMessage(const wxFileName &fileName)
Definition: FileException.cpp:61
ProjectFileIO::GetBlob
bool GetBlob(const char *sql, wxMemoryBuffer &buffer)
Definition: ProjectFileIO.cpp:560
ProjectFileIO::get4
static unsigned int get4(const unsigned char *ptr)
Definition: ProjectFileIO.cpp:2643
audacity::ToWString
std::wstring ToWString(const std::string &str)
Definition: CodeConversions.cpp:34
InvisibleTemporaryProject::Project
AudacityProject & Project()
Definition: ProjectFileIO.h:337
SQLiteIniter
Definition: ProjectFileIO.cpp:169
ProjectFileIO::IsModified
bool IsModified() const
Definition: ProjectFileIO.cpp:2264
ProjectFileIO::GetDiskUsage
static int64_t GetDiskUsage(DBConnection &conn, SampleBlockID blockid)
Definition: ProjectFileIO.cpp:2426
CodeConversions.h
Declare functions to preform UTF-8 to std::wstring conversions.
ActiveProjects::Remove
void Remove(const FilePath &path)
ProjectFileIO::get_varint
static int get_varint(const unsigned char *ptr, int64_t *out)
Definition: ProjectFileIO.cpp:2654
ProjectFileIO::mPrevConn
Connection mPrevConn
Definition: ProjectFileIO.h:309
safenew
#define safenew
Definition: MemoryX.h:10
settings
static Settings & settings()
Definition: TrackInfo.cpp:86
ADD_EXCEPTION_CONTEXT
#define ADD_EXCEPTION_CONTEXT(name, value)
Definition: SentryHelper.h:21
ActiveProjects::Add
void Add(const FilePath &path)
BlockSpaceUsageAccumulator
std::function< void(const SampleBlock &) > BlockSpaceUsageAccumulator(unsigned long long &total)
Definition: SampleBlock.h:97
ProjectSerializer
a class used to (de)serialize the project catalog
Definition: ProjectSerializer.h:34
ProjectFileIO::SetDBError
void SetDBError(const TranslatableString &msg, const TranslatableString &libraryError={}, int errorCode=-1)
Set stored errors and write to log; and default libraryError to what database library reports.
Definition: ProjectFileIO.cpp:2346
SimpleMessageBoxException
A MessageBoxException that shows a given, unvarying string.
Definition: AudacityException.h:95
ProjectFileIO::Exec
int Exec(const char *query, const ExecCB &callback)
Definition: ProjectFileIO.cpp:506
InvisibleTemporaryProject::mpProject
std::shared_ptr< AudacityProject > mpProject
Definition: ProjectFileIO.h:342
ProjectFileIO::mpErrors
std::shared_ptr< DBConnectionErrors > mpErrors
Definition: ProjectFileIO.h:289
AudacityProject::GetProjectName
const wxString & GetProjectName() const
Definition: Project.cpp:165
ProjectFileIO::SetProjectTitle
void SetProjectTitle(int number=-1)
Definition: ProjectFileIO.cpp:1442
DBConnection::Prepare
sqlite3_stmt * Prepare(enum StatementID id, const char *sql)
Definition: DBConnection.cpp:392
ProjectFileIO::mProject
AudacityProject & mProject
Definition: ProjectFileIO.h:287
XMLWriter::StartTag
virtual void StartTag(const wxString &name)
Definition: XMLWriter.cpp:77
AllProjects::value_type
Container::value_type value_type
Definition: Project.h:59
ProjectFileIO::GenerateDoc
wxString GenerateDoc()
Return a strings representation of the active project XML doc.
Definition: ProjectFileIO.cpp:323
ProjectFileIO::mHadUnused
bool mHadUnused
Definition: ProjectFileIO.h:307
ProjectFileIO::mTemporary
bool mTemporary
Definition: ProjectFileIO.h:301
ProjectFileIO::HasConnection
bool HasConnection() const
Return true if a connection is now open.
Definition: ProjectFileIO.cpp:297
Internat::CompatibleToDouble
static bool CompatibleToDouble(const wxString &stringToConvert, double *result)
Convert a string to a number.
Definition: Internat.cpp:134
ProjectFileIO::BackupProject::~BackupProject
~BackupProject()
if !IsOk() do nothing; else if Discard() was not called, undo the renaming
Definition: ProjectFileIO.cpp:1276
ProjectFileIO::CloseProject
bool CloseProject()
Definition: ProjectFileIO.cpp:2221
TitleRestorer::sProjName
wxString sProjName
Definition: ProjectFileIO.h:322
ProjectFileIO
Object associated with a project that manages reading and writing of Audacity project file formats,...
Definition: ProjectFileIO.h:62
TempDirectory::TempDir
AUDACITY_DLL_API wxString TempDir()
Definition: TempDirectory.cpp:26
ProjectFileIO::RestoreConnection
void RestoreConnection()
Definition: ProjectFileIO.cpp:466