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