Audacity 3.2.0
Classes | Public Types | Public Member Functions | Static Public Member Functions | Private Member Functions | Static Private Member Functions | Private Attributes | List of all members
ProjectFileIO Class Referencefinal

Object associated with a project that manages reading and writing of Audacity project file formats, and autosave. More...

#include <ProjectFileIO.h>

Inheritance diagram for ProjectFileIO:
[legend]
Collaboration diagram for ProjectFileIO:
[legend]

Classes

class  BackupProject
 
struct  TentativeConnection
 

Public Types

using ExecCB = std::function< int(int cols, char **vals, char **names)>
 
- Public Types inherited from Observer::Publisher< ProjectFileIOMessage >
using message_type = ProjectFileIOMessage
 
using CallbackReturn = std::conditional_t< true, void, bool >
 
using Callback = std::function< CallbackReturn(const ProjectFileIOMessage &) >
 Type of functions that can be connected to the Publisher. More...
 

Public Member Functions

 ProjectFileIO (AudacityProject &project)
 
 ProjectFileIO (const ProjectFileIO &)=delete
 
ProjectFileIOoperator= (const ProjectFileIO &)=delete
 
 ~ProjectFileIO ()
 
const wxString & GetProjectTitle () const
 
void SetProjectTitle (int number=-1)
 
const FilePathGetFileName () const
 
void SetFileName (const FilePath &fileName)
 
bool IsModified () const
 
bool IsTemporary () const
 
bool IsRecovered () const
 
void MarkTemporary ()
 
bool AutoSave (bool recording=false)
 
bool AutoSaveDelete (sqlite3 *db=nullptr)
 
bool OpenProject ()
 
void CloseProject ()
 
bool ReopenProject ()
 
std::optional< TentativeConnectionLoadProject (const FilePath &fileName, bool ignoreAutosave)
 
bool UpdateSaved (const TrackList *tracks=nullptr)
 
bool SaveProject (const FilePath &fileName, const TrackList *lastSaved)
 
bool SaveCopy (const FilePath &fileName)
 
wxLongLong GetFreeDiskSpace () const
 
int64_t GetBlockUsage (SampleBlockID blockid)
 
int64_t GetCurrentUsage (const std::vector< const TrackList * > &trackLists) const
 
int64_t GetTotalUsage ()
 
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. More...
 
const TranslatableStringGetLastError () const
 
const TranslatableStringGetLibraryError () const
 
int GetLastErrorCode () const
 
const wxString & GetLastLog () const
 
void SetBypass ()
 
void Compact (const std::vector< const TrackList * > &tracks, bool force=false)
 
bool WasCompacted ()
 
bool HadUnused ()
 
bool DeleteBlocks (const BlockIDs &blockids, bool complement)
 
bool HasConnection () const
 Return true if a connection is now open. More...
 
DBConnectionGetConnection ()
 Return a reference to a connection, creating it as needed on demand; throw on failure. More...
 
wxString GenerateDoc ()
 Return a strings representation of the active project XML doc. More...
 
- Public Member Functions inherited from ClientData::Base
virtual ~Base ()
 
- Public Member Functions inherited from XMLTagHandler
 XMLTagHandler ()
 
virtual ~XMLTagHandler ()
 
virtual bool HandleXMLTag (const std::string_view &tag, const AttributesList &attrs)=0
 
virtual void HandleXMLEndTag (const std::string_view &WXUNUSED(tag))
 
virtual void HandleXMLContent (const std::string_view &WXUNUSED(content))
 
virtual XMLTagHandlerHandleXMLChild (const std::string_view &tag)=0
 
void ReadXMLEndTag (const char *tag)
 
void ReadXMLContent (const char *s, int len)
 
XMLTagHandlerReadXMLChild (const char *tag)
 
- Public Member Functions inherited from Observer::Publisher< ProjectFileIOMessage >
 Publisher (ExceptionPolicy *pPolicy=nullptr, Alloc a={})
 Constructor supporting type-erased custom allocation/deletion. More...
 
 Publisher (Publisher &&)=default
 
Publisheroperator= (Publisher &&)=default
 
Subscription Subscribe (Callback callback)
 Connect a callback to the Publisher; later-connected are called earlier. More...
 
Subscription Subscribe (Object &obj, Return(Object::*callback)(Args...))
 Overload of Subscribe takes an object and pointer-to-member-function. More...
 

Static Public Member Functions

static bool InitializeSQL ()
 
static ProjectFileIOGet (AudacityProject &project)
 
static const ProjectFileIOGet (const AudacityProject &project)
 
static int64_t GetDiskUsage (DBConnection &conn, SampleBlockID blockid)
 
static bool RemoveProject (const FilePath &filename)
 Remove any files associated with a project at given path; return true if successful. More...
 

Private Member Functions

bool RenameOrWarn (const FilePath &src, const FilePath &dst)
 Rename a file or put up appropriate warning message. More...
 
bool MoveProject (const FilePath &src, const FilePath &dst)
 
void OnCheckpointFailure ()
 
void WriteXMLHeader (XMLWriter &xmlFile) const
 
void WriteXML (XMLWriter &xmlFile, bool recording=false, const TrackList *tracks=nullptr)
 
bool HandleXMLTag (const std::string_view &tag, const AttributesList &attrs) override
 
XMLTagHandlerHandleXMLChild (const std::string_view &tag) override
 
void UpdatePrefs () override
 
int Exec (const char *query, const ExecCB &callback, bool silent=false)
 
sqlite3 * DB ()
 
bool OpenConnection (FilePath fileName={})
 
bool CloseConnection ()
 
void SaveConnection ()
 
void DiscardConnection ()
 
void RestoreConnection ()
 
void UseConnection (Connection &&conn, const FilePath &filePath)
 
bool Query (const char *sql, const ExecCB &callback, bool silent=false)
 
bool GetValue (const char *sql, wxString &value, bool silent=false)
 
bool GetValue (const char *sql, int64_t &value, bool silent=false)
 
bool CheckVersion ()
 
bool InstallSchema (sqlite3 *db, const char *schema="main")
 
bool WriteDoc (const char *table, const ProjectSerializer &autosave, const char *schema="main")
 
bool CopyTo (const FilePath &destpath, const TranslatableString &msg, bool isTemporary, bool prune=false, const std::vector< const TrackList * > &tracks={})
 
void SetError (const TranslatableString &msg, const TranslatableString &libraryError={}, int errorCode={})
 Just set stored errors. More...
 
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. More...
 
bool ShouldCompact (const std::vector< const TrackList * > &tracks)
 
ConnectionCurrConn ()
 
- Private Member Functions inherited from PrefsListener
 PrefsListener ()
 
virtual ~PrefsListener ()
 
virtual void UpdatePrefs ()=0
 
virtual void UpdateSelectedPrefs (int id)
 

Static Private Member Functions

static const std::vector< wxString > & AuxiliaryFileSuffixes ()
 
static FilePath SafetyFileName (const FilePath &src)
 Generate a name for short-lived backup project files from an existing project. More...
 
static void InSet (sqlite3_context *context, int argc, sqlite3_value **argv)
 
- Static Private Member Functions inherited from PrefsListener
static void Broadcast (int id=0)
 Call this static function to notify all PrefsListener objects. More...
 

Private Attributes

AudacityProjectmProject
 
wxString mTitle
 
std::shared_ptr< DBConnectionErrorsmpErrors
 
FilePath mFileName
 
bool mRecovered
 
bool mModified
 
bool mTemporary
 
bool mWasCompacted
 
bool mHadUnused
 
Connection mPrevConn
 
FilePath mPrevFileName
 
bool mPrevTemporary
 

Additional Inherited Members

- Static Public Attributes inherited from Observer::Publisher< ProjectFileIOMessage >
static constexpr bool notifies_all
 
- Protected Member Functions inherited from Observer::Publisher< ProjectFileIOMessage >
CallbackReturn Publish (const ProjectFileIOMessage &message)
 Send a message to connected callbacks. More...
 

Detailed Description

Object associated with a project that manages reading and writing of Audacity project file formats, and autosave.

Definition at line 60 of file ProjectFileIO.h.

Member Typedef Documentation

◆ ExecCB

using ProjectFileIO::ExecCB = std::function<int(int cols, char **vals, char **names)>

Definition at line 220 of file ProjectFileIO.h.

Constructor & Destructor Documentation

◆ ProjectFileIO() [1/2]

ProjectFileIO::ProjectFileIO ( AudacityProject project)
explicit

Definition at line 407 of file ProjectFileIO.cpp.

408 : mProject{ project }
409 , mpErrors{ std::make_shared<DBConnectionErrors>() }
410{
411 mPrevConn = nullptr;
412
413 mRecovered = false;
414 mModified = false;
415 mTemporary = true;
416
418
419 // Make sure there is plenty of space for Sqlite files
420 wxLongLong freeSpace = 0;
421
422 auto path = TempDirectory::TempDir();
423 if (wxGetDiskSpace(path, NULL, &freeSpace)) {
424 if (freeSpace < wxLongLong(wxLL(100 * 1048576))) {
425 auto volume = FileNames::AbbreviatePath( path );
426 /* i18n-hint: %s will be replaced by the drive letter (on Windows) */
428 XO("Warning"),
429 XO("There is very little free disk space left on %s\n"
430 "Please select a bigger temporary directory location in\n"
431 "Directories Preferences.").Format( volume ),
432 "Error:_Disk_full_or_not_writable"
433 );
434 }
435 }
436}
XO("Cut/Copy/Paste")
const auto project
AudacityProject & mProject
void SetProjectTitle(int number=-1)
Connection mPrevConn
std::shared_ptr< DBConnectionErrors > mpErrors
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:264
FILES_API wxString AbbreviatePath(const wxFileName &fileName)
Give enough of the path to identify the device. (On Windows, drive letter plus ':')
FILES_API wxString TempDir()

References FileNames::AbbreviatePath(), mModified, mPrevConn, mRecovered, mTemporary, SetProjectTitle(), BasicUI::ShowErrorDialog(), TempDirectory::TempDir(), and XO().

Here is the call graph for this function:

◆ ProjectFileIO() [2/2]

ProjectFileIO::ProjectFileIO ( const ProjectFileIO )
delete

◆ ~ProjectFileIO()

ProjectFileIO::~ProjectFileIO ( )

Definition at line 438 of file ProjectFileIO.cpp.

439{
440}

Member Function Documentation

◆ AutoSave()

bool ProjectFileIO::AutoSave ( bool  recording = false)

Definition at line 1788 of file ProjectFileIO.cpp.

1789{
1790 ProjectSerializer autosave;
1791 WriteXMLHeader(autosave);
1792 WriteXML(autosave, recording);
1793
1794 if (WriteDoc("autosave", autosave))
1795 {
1796 mModified = true;
1797 return true;
1798 }
1799
1800 return false;
1801}
void WriteXMLHeader(XMLWriter &xmlFile) const
void WriteXML(XMLWriter &xmlFile, bool recording=false, const TrackList *tracks=nullptr)
bool WriteDoc(const char *table, const ProjectSerializer &autosave, const char *schema="main")
a class used to (de)serialize the project catalog

References mModified, WriteDoc(), WriteXML(), and WriteXMLHeader().

Here is the call graph for this function:

◆ AutoSaveDelete()

bool ProjectFileIO::AutoSaveDelete ( sqlite3 *  db = nullptr)

Definition at line 1803 of file ProjectFileIO.cpp.

1804{
1805 int rc;
1806
1807 if (!db)
1808 {
1809 db = DB();
1810 }
1811
1812 rc = sqlite3_exec(db, "DELETE FROM autosave;", nullptr, nullptr, nullptr);
1813 if (rc != SQLITE_OK)
1814 {
1815 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
1816 ADD_EXCEPTION_CONTEXT("sqlite3.context", "ProjectGileIO::AutoSaveDelete");
1817
1818 SetDBError(
1819 XO("Failed to remove the autosave information from the project file.")
1820 );
1821 return false;
1822 }
1823
1824 mModified = false;
1825
1826 return true;
1827}
#define ADD_EXCEPTION_CONTEXT(name, value)
Definition: SentryHelper.h:21
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.
sqlite3 * DB()

References ADD_EXCEPTION_CONTEXT, DB(), mModified, SetDBError(), and XO().

Referenced by Compact(), SaveProject(), and UpdateSaved().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ AuxiliaryFileSuffixes()

const std::vector< wxString > & ProjectFileIO::AuxiliaryFileSuffixes ( )
staticprivate

Strings like -wal that may be appended to main project name to get other files created by the database system

Definition at line 1232 of file ProjectFileIO.cpp.

1233{
1234 static const std::vector<wxString> strings {
1235 "-wal",
1236#ifndef NO_SHM
1237 "-shm",
1238#endif
1239 };
1240 return strings;
1241}

Referenced by MoveProject(), RemoveProject(), SafetyFileName(), and ProjectFileIO::BackupProject::~BackupProject().

Here is the caller graph for this function:

◆ CheckVersion()

bool ProjectFileIO::CheckVersion ( )
private

Definition at line 727 of file ProjectFileIO.cpp.

728{
729 auto db = DB();
730 int rc;
731
732 // Install our schema if this is an empty DB
733 wxString result;
734 if (!GetValue("SELECT Count(*) FROM sqlite_master WHERE type='table';", result))
735 {
736 // Bug 2718 workaround for a better error message:
737 // If at this point we get SQLITE_CANTOPEN, then the directory is read-only
738 if (GetLastErrorCode() == SQLITE_CANTOPEN)
739 {
740 SetError(
741 /* i18n-hint: An error message. */
742 XO("Project is in a read only directory\n(Unable to create the required temporary files)"),
744 );
745 }
746
747 return false;
748 }
749
750 // If the return count is zero, then there are no tables defined, so this
751 // must be a new project file.
752 if (wxStrtol<char **>(result, nullptr, 10) == 0)
753 {
754 return InstallSchema(db);
755 }
756
757 // Check for our application ID
758 if (!GetValue("PRAGMA application_ID;", result))
759 {
760 return false;
761 }
762
763 // It's a database that SQLite recognizes, but it's not one of ours
764 if (wxStrtoul<char **>(result, nullptr, 10) != ProjectFileID)
765 {
766 SetError(XO("This is not an Audacity project file"));
767 return false;
768 }
769
770 // Get the project file version
771 if (!GetValue("PRAGMA user_version;", result))
772 {
773 return false;
774 }
775
776 const ProjectFormatVersion version =
777 ProjectFormatVersion::FromPacked(wxStrtoul<char**>(result, nullptr, 10));
778
779 // Project file version is higher than ours. We will refuse to
780 // process it since we can't trust anything about it.
781 if (SupportedProjectFormatVersion < version)
782 {
783 SetError(
784 XO("This project was created with a newer version of Audacity.\n\nYou will need to upgrade to open it.")
785 );
786 return false;
787 }
788
789 return true;
790}
static const int ProjectFileID
const ProjectFormatVersion SupportedProjectFormatVersion
This constant represents the current version of Audacity.
const TranslatableString & GetLibraryError() const
bool GetValue(const char *sql, wxString &value, bool silent=false)
bool InstallSchema(sqlite3 *db, const char *schema="main")
int GetLastErrorCode() const
void SetError(const TranslatableString &msg, const TranslatableString &libraryError={}, int errorCode={})
Just set stored errors.
A structure that holds the project version.
static ProjectFormatVersion FromPacked(uint32_t) noexcept

References DB(), ProjectFormatVersion::FromPacked(), GetLastErrorCode(), GetLibraryError(), GetValue(), InstallSchema(), ProjectFileID, SetError(), SupportedProjectFormatVersion, and XO().

Referenced by OpenConnection().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ CloseConnection()

bool ProjectFileIO::CloseConnection ( )
private

Definition at line 545 of file ProjectFileIO.cpp.

546{
547 auto &curConn = CurrConn();
548 if (!curConn)
549 return false;
550
551 if (!curConn->Close())
552 {
553 return false;
554 }
555 curConn.reset();
556
557 SetFileName({});
558
559 return true;
560}
void SetFileName(const FilePath &fileName)
Connection & CurrConn()

References CurrConn(), and SetFileName().

Referenced by CloseProject(), Compact(), OpenConnection(), ReopenProject(), and SaveProject().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ CloseProject()

void ProjectFileIO::CloseProject ( )

Definition at line 2371 of file ProjectFileIO.cpp.

2372{
2373 auto &currConn = CurrConn();
2374 if (!currConn)
2375 {
2376 wxLogDebug("Closing project with no database connection");
2377 return;
2378 }
2379
2380 // Save the filename since CloseConnection() will clear it
2381 wxString filename = mFileName;
2382
2383 // Not much we can do if this fails. The user will simply get
2384 // the recovery dialog upon next restart.
2385 if (CloseConnection())
2386 {
2387 // If this is a temporary project, we no longer want to keep the
2388 // project file.
2389 if (IsTemporary())
2390 {
2391 // This is just a safety check.
2392 wxFileName temp(TempDirectory::TempDir(), wxT(""));
2393 wxFileName file(filename);
2394 file.SetFullName(wxT(""));
2395 if (file == temp)
2396 RemoveProject(filename);
2397 }
2398 }
2399}
wxT("CloseDown"))
static bool RemoveProject(const FilePath &filename)
Remove any files associated with a project at given path; return true if successful.
FilePath mFileName
bool CloseConnection()
bool IsTemporary() const

References CloseConnection(), CurrConn(), IsTemporary(), mFileName, RemoveProject(), TempDirectory::TempDir(), and wxT().

Referenced by SaveProject().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ Compact()

void ProjectFileIO::Compact ( const std::vector< const TrackList * > &  tracks,
bool  force = false 
)

Definition at line 1408 of file ProjectFileIO.cpp.

1410{
1411 // Haven't compacted yet
1412 mWasCompacted = false;
1413
1414 // Assume we have unused blocks until we find out otherwise. That way cleanup
1415 // at project close time will still occur.
1416 mHadUnused = true;
1417
1418 // If forcing compaction, bypass inspection.
1419 if (!force)
1420 {
1421 // Don't compact if this is a temporary project or if it's determined there are not
1422 // enough unused blocks to make it worthwhile.
1423 if (IsTemporary() || !ShouldCompact(tracks))
1424 {
1425 // Delete the AutoSave doc it if exists
1426 if (IsModified())
1427 {
1428 // PRL: not clear what to do if the following fails, but the worst should
1429 // be, the project may reopen in its present state as a recovery file, not
1430 // at the last saved state.
1431 // REVIEW: Could the autosave file be corrupt though at that point, and so
1432 // prevent recovery?
1433 // LLL: I believe Paul is correct since it's deleted with a single SQLite
1434 // transaction. The next time the file opens will just invoke recovery.
1435 (void) AutoSaveDelete();
1436 }
1437
1438 return;
1439 }
1440 }
1441
1442 wxString origName = mFileName;
1443 wxString backName = origName + "_compact_back";
1444 wxString tempName = origName + "_compact_temp";
1445
1446 // Copy the original database to a new database. Only prune sample blocks if
1447 // we have a tracklist.
1448 // REVIEW: Compact can fail on the CopyTo with no error messages. That's OK?
1449 // LLL: We could display an error message or just ignore the failure and allow
1450 // the file to be compacted the next time it's saved.
1451 if (CopyTo(tempName, XO("Compacting project"), IsTemporary(), !tracks.empty(), tracks))
1452 {
1453 // Must close the database to rename it
1454 if (CloseConnection())
1455 {
1456 // Only use the new file if it is actually smaller than the original.
1457 //
1458 // If the original file doesn't have anything to compact (original and new
1459 // are basically identical), the file could grow by a few pages because of
1460 // differences in how SQLite constructs the b-tree.
1461 //
1462 // In this case, just toss the new file and continue to use the original.
1463 //
1464 // Also, do this after closing the connection so that the -wal file
1465 // gets cleaned up.
1466 if (wxFileName::GetSize(tempName) < wxFileName::GetSize(origName))
1467 {
1468 // Rename the original to backup
1469 if (wxRenameFile(origName, backName))
1470 {
1471 // Rename the temporary to original
1472 if (wxRenameFile(tempName, origName))
1473 {
1474 // Open the newly compacted original file
1475 if (OpenConnection(origName))
1476 {
1477 // Remove the old original file
1478 if (!wxRemoveFile(backName))
1479 {
1480 // Just log the error, nothing can be done to correct it
1481 // and WX should have logged another message showing the
1482 // system error code.
1483 wxLogWarning(wxT("Compaction failed to delete backup %s"), backName);
1484 }
1485
1486 // Remember that we compacted
1487 mWasCompacted = true;
1488
1489 return;
1490 }
1491 else
1492 {
1493 wxLogWarning(wxT("Compaction failed to open new project %s"), origName);
1494 }
1495
1496 if (!wxRenameFile(origName, tempName))
1497 {
1498 wxLogWarning(wxT("Compaction failed to rename original %s to temp %s"),
1499 origName, tempName);
1500 }
1501 }
1502 else
1503 {
1504 wxLogWarning(wxT("Compaction failed to rename temp %s to orig %s"),
1505 origName, tempName);
1506 }
1507
1508 if (!wxRenameFile(backName, origName))
1509 {
1510 wxLogWarning(wxT("Compaction failed to rename back %s to orig %s"),
1511 backName, origName);
1512 }
1513 }
1514 else
1515 {
1516 wxLogWarning(wxT("Compaction failed to rename orig %s to back %s"),
1517 backName, origName);
1518 }
1519 }
1520
1521 if (!OpenConnection(origName))
1522 {
1523 wxLogWarning(wxT("Compaction failed to reopen %s"), origName);
1524 }
1525 }
1526
1527 // Did not achieve any real compaction
1528 // RemoveProject not needed for what was an attached database
1529 if (!wxRemoveFile(tempName))
1530 {
1531 // Just log the error, nothing can be done to correct it
1532 // and WX should have logged another message showing the
1533 // system error code.
1534 wxLogWarning(wxT("Failed to delete temporary file...ignoring"));
1535 }
1536 }
1537
1538 return;
1539}
const auto tracks
bool CopyTo(const FilePath &destpath, const TranslatableString &msg, bool isTemporary, bool prune=false, const std::vector< const TrackList * > &tracks={})
bool OpenConnection(FilePath fileName={})
bool ShouldCompact(const std::vector< const TrackList * > &tracks)
bool AutoSaveDelete(sqlite3 *db=nullptr)
bool IsModified() const

References AutoSaveDelete(), CloseConnection(), CopyTo(), IsModified(), IsTemporary(), mFileName, mHadUnused, mWasCompacted, OpenConnection(), ShouldCompact(), tracks, wxT(), and XO().

Referenced by SaveProject().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ CopyTo()

bool ProjectFileIO::CopyTo ( const FilePath destpath,
const TranslatableString msg,
bool  isTemporary,
bool  prune = false,
const std::vector< const TrackList * > &  tracks = {} 
)
private
Parameters
tracksFirst track list (or if none, then the project's track list) are tracks to write into document blob; That list, plus any others, contain tracks whose sample blocks must be kept

Definition at line 916 of file ProjectFileIO.cpp.

921{
922 using namespace BasicUI;
923
924 auto pConn = CurrConn().get();
925 if (!pConn)
926 return false;
927
928 // Get access to the active tracklist
929 auto pProject = &mProject;
930
932
933 // Collect all active blockids
934 if (prune)
935 {
936 for (auto trackList : tracks)
937 if (trackList)
938 WaveTrackUtilities::InspectBlocks(*trackList, {}, &blockids);
939 }
940 // Collect ALL blockids
941 else
942 {
943 auto cb = [&blockids](int cols, char **vals, char **){
944 SampleBlockID blockid;
945 wxString{ vals[0] }.ToLongLong(&blockid);
946 blockids.insert(blockid);
947 return 0;
948 };
949
950 if (!Query("SELECT blockid FROM sampleblocks;", cb))
951 {
952 // Error message already captured.
953 return false;
954 }
955 }
956
957 // Create the project doc
959 WriteXMLHeader(doc);
960 WriteXML(doc, false, tracks.empty() ? nullptr : tracks[0]);
961
962 auto db = DB();
963 Connection destConn = nullptr;
964 bool success = false;
965 int rc = SQLITE_OK;
967
968 // Cleanup in case things go awry
969 auto cleanup = finally([&]
970 {
971 if (!success)
972 {
973 if (destConn)
974 {
975 destConn->Close();
976 destConn = nullptr;
977 }
978
979 // Rollback transaction in case one was active.
980 // If this fails (probably due to memory or disk space), the transaction will
981 // (presumably) still be active, so further updates to the project file will
982 // fail as well. Not really much we can do about it except tell the user.
983 auto result = sqlite3_exec(db, "ROLLBACK;", nullptr, nullptr, nullptr);
984
985 // Only capture the error if there wasn't a previous error
986 if (result != SQLITE_OK && (rc == SQLITE_DONE || rc == SQLITE_OK))
987 {
988 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
990 "sqlite3.context", "ProjectGileIO::CopyTo.cleanup");
991
993 XO("Failed to rollback transaction during import")
994 );
995 }
996
997 // And detach the outbound DB in case (if it's attached). Don't check for
998 // errors since it may not be attached. But, if it is and the DETACH fails,
999 // subsequent CopyTo() actions will fail until Audacity is relaunched.
1000 sqlite3_exec(db, "DETACH DATABASE outbound;", nullptr, nullptr, nullptr);
1001
1002 // RemoveProject not necessary to clean up attached database
1003 wxRemoveFile(destpath);
1004 }
1005 });
1006
1007 // Attach the destination database
1008 wxString sql;
1009 wxString dbName = destpath;
1010 // Bug 2793: Quotes in name need escaping for sqlite3.
1011 dbName.Replace( "'", "''");
1012 sql.Printf("ATTACH DATABASE '%s' AS outbound;", dbName.ToUTF8());
1013
1014 rc = sqlite3_exec(db, sql, nullptr, nullptr, nullptr);
1015 if (rc != SQLITE_OK)
1016 {
1017 SetDBError(
1018 XO("Unable to attach destination database")
1019 );
1020 return false;
1021 }
1022
1023 // Ensure attached DB connection gets configured
1024 //
1025 // NOTE: Between the above attach and setting the mode here, a normal DELETE
1026 // mode journal will be used and will briefly appear in the filesystem.
1027 if ( pConn->FastMode("outbound") != SQLITE_OK)
1028 {
1029 SetDBError(
1030 XO("Unable to switch to fast journaling mode")
1031 );
1032
1033 return false;
1034 }
1035
1036 // Install our schema into the new database
1037 if (!InstallSchema(db, "outbound"))
1038 {
1039 // Message already set
1040 return false;
1041 }
1042
1043 {
1044 // Ensure statement gets cleaned up
1045 sqlite3_stmt *stmt = nullptr;
1046 auto cleanup = finally([&]
1047 {
1048 if (stmt)
1049 {
1050 // No need to check return code
1051 sqlite3_finalize(stmt);
1052 }
1053 });
1054
1055 // Prepare the statement only once
1056 rc = sqlite3_prepare_v2(db,
1057 "INSERT INTO outbound.sampleblocks"
1058 " SELECT * FROM main.sampleblocks"
1059 " WHERE blockid = ?;",
1060 -1,
1061 &stmt,
1062 nullptr);
1063 if (rc != SQLITE_OK)
1064 {
1065 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
1067 "sqlite3.context", "ProjectGileIO::CopyTo.prepare");
1068
1069 SetDBError(
1070 XO("Unable to prepare project file command:\n\n%s").Format(sql)
1071 );
1072 return false;
1073 }
1074
1075 /* i18n-hint: This title appears on a dialog that indicates the progress
1076 in doing something.*/
1077 auto progress =
1078 BasicUI::MakeProgress(XO("Progress"), msg, ProgressShowCancel);
1080
1081 wxLongLong_t count = 0;
1082 wxLongLong_t total = blockids.size();
1083
1084 // Start a transaction. Since we're running without a journal,
1085 // this really doesn't provide rollback. It just prevents SQLite
1086 // from auto committing after each step through the loop.
1087 //
1088 // Also note that we will have an open transaction if we fail
1089 // while copying the blocks. This is fine since we're just going
1090 // to delete the database anyway.
1091 sqlite3_exec(db, "BEGIN;", nullptr, nullptr, nullptr);
1092
1093 // Copy sample blocks from the main DB to the outbound DB
1094 for (auto blockid : blockids)
1095 {
1096 // Bind statement parameters
1097 rc = sqlite3_bind_int64(stmt, 1, blockid);
1098 if (rc != SQLITE_OK)
1099 {
1100 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
1102 "sqlite3.context", "ProjectGileIO::CopyTo.bind");
1103
1104 SetDBError(
1105 XO("Failed to bind SQL parameter")
1106 );
1107
1108 return false;
1109 }
1110
1111 // Process it
1112 rc = sqlite3_step(stmt);
1113 if (rc != SQLITE_DONE)
1114 {
1115 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
1117 "sqlite3.context", "ProjectGileIO::CopyTo.step");
1118
1119 SetDBError(
1120 XO("Failed to update the project file.\nThe following command failed:\n\n%s").Format(sql)
1121 );
1122 return false;
1123 }
1124
1125 // Reset statement to beginning
1126 if (sqlite3_reset(stmt) != SQLITE_OK)
1127 {
1128 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
1130 "sqlite3.context", "ProjectGileIO::CopyTo.reset");
1131
1133 }
1134
1135 result = progress->Poll(++count, total);
1136 if (result != ProgressResult::Success)
1137 {
1138 // Note that we're not setting success, so the finally
1139 // block above will take care of cleaning up
1140 return false;
1141 }
1142 }
1143
1144 // Write the doc.
1145 //
1146 // If we're compacting a temporary project (user initiated from the File
1147 // menu), then write the doc to the "autosave" table since temporary
1148 // projects do not have a "project" doc.
1149 if (!WriteDoc(isTemporary ? "autosave" : "project", doc, "outbound"))
1150 {
1151 return false;
1152 }
1153
1154 // See BEGIN above...
1155 sqlite3_exec(db, "COMMIT;", nullptr, nullptr, nullptr);
1156 }
1157
1158 // Detach the destination database
1159 rc = sqlite3_exec(db, "DETACH DATABASE outbound;", nullptr, nullptr, nullptr);
1160 if (rc != SQLITE_OK)
1161 {
1162 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
1163 ADD_EXCEPTION_CONTEXT("sqlite3.context", "ProjectGileIO::CopyTo::detach");
1164
1165 SetDBError(
1166 XO("Destination project could not be detached")
1167 );
1168
1169 return false;
1170 }
1171
1172 // Tell cleanup everything is good to go
1173 success = true;
1174
1175 return true;
1176}
long long SampleBlockID
Definition: CloudSyncDTO.h:26
std::unique_ptr< DBConnection > Connection
Definition: DBConnection.h:132
#define THROW_INCONSISTENCY_EXCEPTION
Throw InconsistencyException, using C++ preprocessor to identify the source code location.
Abstract base class used in importing a file.
bool Query(const char *sql, const ExecCB &callback, bool silent=false)
ProgressResult
Definition: BasicUI.h:148
@ ProgressShowCancel
Definition: BasicUI.h:142
std::unique_ptr< ProgressDialog > MakeProgress(const TranslatableString &title, const TranslatableString &message, unsigned flags=(ProgressShowStop|ProgressShowCancel), const TranslatableString &remainingLabelText={})
Create and display a progress dialog.
Definition: BasicUI.h:294
WAVE_TRACK_API void InspectBlocks(const TrackList &tracks, BlockInspector inspector, SampleBlockIDSet *pIDs=nullptr)
std::unordered_set< SampleBlockID > SampleBlockIDSet

References ADD_EXCEPTION_CONTEXT, CurrConn(), DB(), WaveTrackUtilities::InspectBlocks(), InstallSchema(), BasicUI::MakeProgress(), mProject, BasicUI::ProgressShowCancel, Query(), SetDBError(), BasicUI::Success, THROW_INCONSISTENCY_EXCEPTION, tracks, WriteDoc(), WriteXML(), WriteXMLHeader(), and XO().

Referenced by Compact(), SaveCopy(), and SaveProject().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ CurrConn()

Connection & ProjectFileIO::CurrConn ( )
private

Definition at line 1226 of file ProjectFileIO.cpp.

1227{
1228 auto &connectionPtr = ConnectionPtr::Get( mProject );
1229 return connectionPtr.mpConnection;
1230}
static ConnectionPtr & Get(AudacityProject &project)

References ConnectionPtr::Get(), and mProject.

Referenced by CloseConnection(), CloseProject(), CopyTo(), GetBlockUsage(), GetConnection(), GetTotalUsage(), OpenConnection(), RestoreConnection(), SaveConnection(), SetBypass(), SetDBError(), SetError(), and UseConnection().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ DB()

sqlite3 * ProjectFileIO::DB ( )
private

Definition at line 478 of file ProjectFileIO.cpp.

479{
480 return GetConnection().DB();
481}
sqlite3 * DB()
DBConnection & GetConnection()
Return a reference to a connection, creating it as needed on demand; throw on failure.

References DBConnection::DB(), and GetConnection().

Referenced by AutoSaveDelete(), CheckVersion(), CopyTo(), DeleteBlocks(), Exec(), LoadProject(), and WriteDoc().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ DeleteBlocks()

bool ProjectFileIO::DeleteBlocks ( const BlockIDs blockids,
bool  complement 
)

Definition at line 839 of file ProjectFileIO.cpp.

840{
841 auto db = DB();
842 int rc;
843
844 ContextData contextData{ mProject, blockids };
845
846 auto cleanup = finally([&]
847 {
848 // Remove our function, whether it was successfully defined or not.
849 sqlite3_create_function(db, "inset", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr, nullptr, nullptr, nullptr);
850 });
851
852 // Add the function used to verify each row's blockid against the set of active blockids
853 rc = sqlite3_create_function(db, "inset", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC, &contextData, InSet, nullptr, nullptr);
854 if (rc != SQLITE_OK)
855 {
856 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
857 ADD_EXCEPTION_CONTEXT("sqlite3.context", "ProjectGileIO::DeleteBlocks::create_function");
858
859 /* i18n-hint: An error message. Don't translate inset or blockids.*/
860 SetDBError(XO("Unable to add 'inset' function (can't verify blockids)"));
861 return false;
862 }
863
864 // Delete all rows in the set, or not in it
865 // This is the first command that writes to the database, and so we
866 // do more informative error reporting than usual, if it fails.
867 auto sql = wxString::Format(
868 "DELETE FROM sampleblocks WHERE %sinset(blockid);",
869 complement ? "NOT " : "" );
870 rc = sqlite3_exec(db, sql, nullptr, nullptr, nullptr);
871 if (rc != SQLITE_OK)
872 {
873 ADD_EXCEPTION_CONTEXT("sqlite3.query", sql.ToStdString());
874 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
875 ADD_EXCEPTION_CONTEXT("sqlite3.context", "ProjectGileIO::GetBlob");
876
877 if( rc==SQLITE_READONLY)
878 /* i18n-hint: An error message. Don't translate blockfiles.*/
879 SetDBError(XO("Project is read only\n(Unable to work with the blockfiles)"));
880 else if( rc==SQLITE_LOCKED)
881 /* i18n-hint: An error message. Don't translate blockfiles.*/
882 SetDBError(XO("Project is locked\n(Unable to work with the blockfiles)"));
883 else if( rc==SQLITE_BUSY)
884 /* i18n-hint: An error message. Don't translate blockfiles.*/
885 SetDBError(XO("Project is busy\n(Unable to work with the blockfiles)"));
886 else if( rc==SQLITE_CORRUPT)
887 /* i18n-hint: An error message. Don't translate blockfiles.*/
888 SetDBError(XO("Project is corrupt\n(Unable to work with the blockfiles)"));
889 else if( rc==SQLITE_PERM)
890 /* i18n-hint: An error message. Don't translate blockfiles.*/
891 SetDBError(XO("Some permissions issue\n(Unable to work with the blockfiles)"));
892 else if( rc==SQLITE_IOERR)
893 /* i18n-hint: An error message. Don't translate blockfiles.*/
894 SetDBError(XO("A disk I/O error\n(Unable to work with the blockfiles)"));
895 else if( rc==SQLITE_AUTH)
896 /* i18n-hint: An error message. Don't translate blockfiles.*/
897 SetDBError(XO("Not authorized\n(Unable to work with the blockfiles)"));
898 else
899 /* i18n-hint: An error message. Don't translate blockfiles.*/
900 SetDBError(XO("Unable to work with the blockfiles"));
901
902 return false;
903 }
904
905 // Mark the project recovered if we deleted any rows
906 int changes = sqlite3_changes(db);
907 if (changes > 0)
908 {
909 wxLogInfo(XO("Total orphan blocks deleted %d").Translation(), changes);
910 mRecovered = true;
911 }
912
913 return true;
914}
static void InSet(sqlite3_context *context, int argc, sqlite3_value **argv)

References ADD_EXCEPTION_CONTEXT, DB(), InSet(), mProject, mRecovered, SetDBError(), and XO().

Referenced by LoadProject(), and SaveProject().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ DiscardConnection()

void ProjectFileIO::DiscardConnection ( )
private

Definition at line 577 of file ProjectFileIO.cpp.

578{
579 if (mPrevConn)
580 {
581 if (!mPrevConn->Close())
582 {
583 // Store an error message
585 XO("Failed to discard connection")
586 );
587 }
588
589 // If this is a temporary project, we no longer want to keep the
590 // project file.
591 if (mPrevTemporary)
592 {
593 // This is just a safety check.
594 wxFileName temp(TempDirectory::TempDir(), wxT(""));
595 wxFileName file(mPrevFileName);
596 file.SetFullName(wxT(""));
597 if (file == temp)
598 {
600 {
601 wxLogMessage("Failed to remove temporary project %s", mPrevFileName);
602 }
603 }
604 }
605 mPrevConn = nullptr;
606 mPrevFileName.clear();
607 }
608}
FilePath mPrevFileName

References mPrevConn, mPrevFileName, mPrevTemporary, RemoveProject(), SetDBError(), TempDirectory::TempDir(), wxT(), and XO().

Referenced by SaveConnection().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ Exec()

int ProjectFileIO::Exec ( const char *  query,
const ExecCB callback,
bool  silent = false 
)
private

Definition at line 651 of file ProjectFileIO.cpp.

652{
653 char *errmsg = nullptr;
654
655 const void *ptr = &callback;
656 int rc = sqlite3_exec(DB(), query, ExecCallback,
657 const_cast<void*>(ptr), &errmsg);
658
659 if (rc != SQLITE_ABORT && errmsg && !silent)
660 {
661 ADD_EXCEPTION_CONTEXT("sqlite3.query", query);
662 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
663
665 XO("Failed to execute a project file command:\n\n%s").Format(query),
666 Verbatim(errmsg),
667 rc
668 );
669 }
670 if (errmsg)
671 {
672 sqlite3_free(errmsg);
673 }
674
675 return rc;
676}
static int ExecCallback(void *data, int cols, char **vals, char **names)
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.

References ADD_EXCEPTION_CONTEXT, DB(), ExecCallback(), SetDBError(), Verbatim(), and XO().

Referenced by Query().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ GenerateDoc()

wxString ProjectFileIO::GenerateDoc ( )

Return a strings representation of the active project XML doc.

Definition at line 468 of file ProjectFileIO.cpp.

469{
470 auto &trackList = TrackList::Get( mProject );
471
472 XMLStringWriter doc;
473 WriteXMLHeader(doc);
474 WriteXML(doc, false, trackList.empty() ? nullptr : &trackList);
475 return doc;
476}
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:314
Wrapper to output XML data to strings.
Definition: XMLWriter.h:139

References TrackList::Get(), mProject, WriteXML(), and WriteXMLHeader().

Here is the call graph for this function:

◆ Get() [1/2]

ProjectFileIO & ProjectFileIO::Get ( AudacityProject project)
static

Definition at line 396 of file ProjectFileIO.cpp.

397{
398 auto &result = project.AttachedObjects::Get< ProjectFileIO >( sFileIOKey );
399 return result;
400}
static const AudacityProject::AttachedObjects::RegisteredFactory sFileIOKey
Object associated with a project that manages reading and writing of Audacity project file formats,...
Definition: ProjectFileIO.h:66

References project, and sFileIOKey.

Referenced by ProjectFileManager::AddImportedTracks(), OpenProjectCommand::Apply(), ProjectFileManager::CloseProject(), ProjectFileManager::Compact(), ProjectFileManager::CompactProjectOnClose(), anonymous_namespace{ImportMIDI.cpp}::DoImportMIDI(), ProjectFileManager::DoSave(), Get(), ProjectManager::GetEstimatedRecordingMinsLeftOnDisk(), ProjectFileManager::Import(), anonymous_namespace{ProjectFileManager.cpp}::ImportProject(), ProjectFileManager::IsAlreadyOpen(), audacity::cloud::audiocom::sync::ProjectCloudExtension::MarkProjectSynced(), ProjectAudioManager::OnAudioIONewBlocks(), ProjectAudioManager::OnAudioIOStopRecording(), TimerRecordDialog::OnAutoSavePathButton_Click(), ProjectManager::OnCloseWindow(), HistoryDialog::OnCompact(), ContrastDialog::OnExport(), ProjectWindow::OnProjectTitleChange(), anonymous_namespace{CloudProjectFileIOExtensions.cpp}::IOExtension::OnSave(), audacity::cloud::audiocom::sync::ProjectCloudExtension::OnSnapshotCreated(), audacity::cloud::audiocom::sync::ProjectCloudExtension::OnSyncCompleted(), ProjectManager::OnTimer(), anonymous_namespace{TimerRecordDialog.cpp}::OnTimerRecord(), audacity::cloud::audiocom::sync::ProjectCloudExtension::OnUpdateSaved(), ProjectFileManager::OpenNewProject(), ProjectFileManager::OpenProject(), ProjectManager::OpenProject(), ProjectFileManager::OpenProjectFile(), audacity::cloud::audiocom::sync::OpenProjectFromCloud(), AutoRecoveryDialog::PopulateList(), TimerRecordDialog::PopulateOrExchange(), ProjectAudioManager::ProjectAudioManager(), ProjectManager::ProjectManager(), ProjectWindow::ProjectWindow(), ProjectFileManager::ReadProjectFile(), RefreshAllTitles(), ProjectManager::ResetProjectToEmpty(), ProjectFileManager::Save(), ProjectFileManager::SaveAs(), anonymous_namespace{CloudProjectFileIOExtensions.cpp}::IOExtension::SaveCloudProject(), ProjectFileManager::SaveCopy(), ProjectFileManager::SaveFromTimerRecording(), audacity::cloud::audiocom::CloudSyncService::SyncCloudSnapshot(), and audacity::cloud::audiocom::sync::ProjectCloudExtension::UpdateIdFromDatabase().

Here is the caller graph for this function:

◆ Get() [2/2]

const ProjectFileIO & ProjectFileIO::Get ( const AudacityProject project)
static

Definition at line 402 of file ProjectFileIO.cpp.

403{
404 return Get( const_cast< AudacityProject & >( project ) );
405}
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
static ProjectFileIO & Get(AudacityProject &project)

References Get(), and project.

Here is the call graph for this function:

◆ GetBlockUsage()

int64_t ProjectFileIO::GetBlockUsage ( SampleBlockID  blockid)

Definition at line 2541 of file ProjectFileIO.cpp.

2542{
2543 auto pConn = CurrConn().get();
2544 if (!pConn)
2545 return 0;
2546 return GetDiskUsage(*pConn, blockid);
2547}
static int64_t GetDiskUsage(DBConnection &conn, SampleBlockID blockid)

References CurrConn(), and GetDiskUsage().

Here is the call graph for this function:

◆ GetConnection()

DBConnection & ProjectFileIO::GetConnection ( )

Return a reference to a connection, creating it as needed on demand; throw on failure.

Definition at line 448 of file ProjectFileIO.cpp.

449{
450 auto &curConn = CurrConn();
451 if (!curConn)
452 {
453 if (!OpenConnection())
454 {
456 {
458 XO("Failed to open the project's database"),
459 XO("Warning"),
460 "Error:_Disk_full_or_not_writable"
461 };
462 }
463 }
464
465 return *curConn;
466}
@ Internal
Indicates internal failure from Audacity.
A MessageBoxException that shows a given, unvarying string.

References CurrConn(), Internal, OpenConnection(), and XO().

Referenced by DB().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ GetCurrentUsage()

int64_t ProjectFileIO::GetCurrentUsage ( const std::vector< const TrackList * > &  trackLists) const

Definition at line 2549 of file ProjectFileIO.cpp.

2551{
2552 using namespace WaveTrackUtilities;
2553 unsigned long long current = 0;
2554 const auto fn = BlockSpaceUsageAccumulator(current);
2555
2556 // Must pass address of this set, even if not otherwise used, to avoid
2557 // possible multiple count of shared blocks
2558 SampleBlockIDSet seen;
2559 for (auto pTracks: trackLists)
2560 if (pTracks)
2561 InspectBlocks(*pTracks, fn, &seen);
2562
2563 return current;
2564}
std::unordered_set< SampleBlockID > SampleBlockIDSet
Definition: CloudSyncDTO.h:27
std::function< void(SampleBlockConstPtr) > BlockSpaceUsageAccumulator(unsigned long long &total)
Definition: SampleBlock.h:108
static const auto fn

References BlockSpaceUsageAccumulator(), fn, and WaveTrackUtilities::InspectBlocks().

Here is the call graph for this function:

◆ GetDiskUsage()

int64_t ProjectFileIO::GetDiskUsage ( DBConnection conn,
SampleBlockID  blockid 
)
static

Definition at line 2579 of file ProjectFileIO.cpp.

2580{
2581 sqlite3_stmt* stmt = nullptr;
2582
2583 if (blockid == 0)
2584 {
2585 static const char* statement =
2586R"(SELECT
2587 sum(length(blockid) + length(sampleformat) +
2588 length(summin) + length(summax) + length(sumrms) +
2589 length(summary256) + length(summary64k) +
2590 length(samples))
2591FROM sampleblocks;)";
2592
2593 stmt = conn.Prepare(DBConnection::GetAllSampleBlocksSize, statement);
2594 }
2595 else
2596 {
2597 static const char* statement =
2598R"(SELECT
2599 length(blockid) + length(sampleformat) +
2600 length(summin) + length(summax) + length(sumrms) +
2601 length(summary256) + length(summary64k) +
2602 length(samples)
2603FROM sampleblocks WHERE blockid = ?1;)";
2604
2605 stmt = conn.Prepare(DBConnection::GetSampleBlockSize, statement);
2606 }
2607
2608 auto cleanup = finally(
2609 [stmt]() {
2610 // Clear statement bindings and rewind statement
2611 if (stmt != nullptr)
2612 {
2613 sqlite3_clear_bindings(stmt);
2614 sqlite3_reset(stmt);
2615 }
2616 });
2617
2618 if (blockid != 0)
2619 {
2620 int rc = sqlite3_bind_int64(stmt, 1, blockid);
2621
2622 if (rc != SQLITE_OK)
2623 {
2625 "sqlite3.rc", std::to_string(rc));
2626
2628 "sqlite3.context", "ProjectFileIO::GetDiskUsage::bind");
2629
2630 conn.ThrowException(false);
2631 }
2632 }
2633
2634 int rc = sqlite3_step(stmt);
2635
2636 if (rc != SQLITE_ROW)
2637 {
2638 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
2639
void ThrowException(bool write) const
throw and show appropriate message box
@ GetAllSampleBlocksSize
Definition: DBConnection.h:83
sqlite3_stmt * Prepare(enum StatementID id, const char *sql)

References ADD_EXCEPTION_CONTEXT, DBConnection::GetAllSampleBlocksSize, DBConnection::GetSampleBlockSize, DBConnection::Prepare(), and DBConnection::ThrowException().

Referenced by GetBlockUsage(), SqliteSampleBlock::GetSpaceUsage(), and GetTotalUsage().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ GetFileName()

const FilePath & ProjectFileIO::GetFileName ( ) const

Definition at line 1595 of file ProjectFileIO.cpp.

1596{
1597 return mFileName;
1598}

References mFileName.

Referenced by ProjectFileManager::IsAlreadyOpen(), ContrastDialog::OnExport(), OpenConnection(), TimerRecordDialog::PopulateOrExchange(), ProjectFileManager::SaveAs(), and anonymous_namespace{CloudProjectFileIOExtensions.cpp}::IOExtension::SaveCloudProject().

Here is the caller graph for this function:

◆ GetFreeDiskSpace()

wxLongLong ProjectFileIO::GetFreeDiskSpace ( ) const

Definition at line 2432 of file ProjectFileIO.cpp.

2433{
2434 wxLongLong freeSpace;
2435 if (wxGetDiskSpace(wxPathOnly(mFileName), NULL, &freeSpace))
2436 {
2438 // 4 GiB per-file maximum
2439 constexpr auto limit = 1ll << 32;
2440
2441 // Opening a file only to find its length looks wasteful but
2442 // seems to be necessary at least on Windows with FAT filesystems.
2443 // I don't know if that is only a wxWidgets bug.
2444 auto length = wxFile{mFileName}.Length();
2445 // auto length = wxFileName::GetSize(mFileName);
2446
2447 if (length == wxInvalidSize)
2448 length = 0;
2449 auto free = std::max<wxLongLong>(0, limit - length);
2450 freeSpace = std::min(freeSpace, free);
2451 }
2452 return freeSpace;
2453 }
2454
2455 return -1;
2456}
int min(int a, int b)
FILES_API bool IsOnFATFileSystem(const FilePath &path)
void free(void *ptr)
Definition: VectorOps.h:34

References staffpad::vo::free(), FileNames::IsOnFATFileSystem(), mFileName, and min().

Referenced by ProjectManager::GetEstimatedRecordingMinsLeftOnDisk(), and ProjectManager::OnTimer().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ GetLastError()

const TranslatableString & ProjectFileIO::GetLastError ( ) const

Definition at line 2471 of file ProjectFileIO.cpp.

2472{
2473 return mpErrors->mLastError;
2474}

References mpErrors.

Referenced by SaveProject().

Here is the caller graph for this function:

◆ GetLastErrorCode()

int ProjectFileIO::GetLastErrorCode ( ) const

Definition at line 2481 of file ProjectFileIO.cpp.

2482{
2483 return mpErrors->mErrorCode;
2484}

References mpErrors.

Referenced by CheckVersion().

Here is the caller graph for this function:

◆ GetLastLog()

const wxString & ProjectFileIO::GetLastLog ( ) const

Definition at line 2486 of file ProjectFileIO.cpp.

2487{
2488 return mpErrors->mLog;
2489}

References mpErrors.

Referenced by ShowError().

Here is the caller graph for this function:

◆ GetLibraryError()

const TranslatableString & ProjectFileIO::GetLibraryError ( ) const

Definition at line 2476 of file ProjectFileIO.cpp.

2477{
2478 return mpErrors->mLibraryError;
2479}

References mpErrors.

Referenced by CheckVersion().

Here is the caller graph for this function:

◆ GetProjectTitle()

const wxString & ProjectFileIO::GetProjectTitle ( ) const
inline

Definition at line 96 of file ProjectFileIO.h.

96{ return mTitle; }
wxString mTitle

Referenced by ProjectWindow::OnProjectTitleChange().

Here is the caller graph for this function:

◆ GetTotalUsage()

int64_t ProjectFileIO::GetTotalUsage ( )

Definition at line 2566 of file ProjectFileIO.cpp.

2567{
2568 auto pConn = CurrConn().get();
2569 if (!pConn)
2570 return 0;
2571 return GetDiskUsage(*pConn, 0);
2572}

References CurrConn(), and GetDiskUsage().

Referenced by ShouldCompact().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ GetValue() [1/2]

bool ProjectFileIO::GetValue ( const char *  sql,
int64_t &  value,
bool  silent = false 
)
private

Definition at line 705 of file ProjectFileIO.cpp.

706{
707 bool success = false;
708 auto cb = [&value, &success](int cols, char** vals, char**)
709 {
710 if (cols > 0)
711 {
712 const std::string_view valueString = vals[0];
713
714 success = std::errc() ==
715 FromChars(
716 valueString.data(), valueString.data() + valueString.length(),
717 value)
718 .ec;
719 }
720 // Stop after one row
721 return 1;
722 };
723
724 return Query(sql, cb, silent) && success;
725}
FromCharsResult FromChars(const char *buffer, const char *last, float &value) noexcept
Parse a string into a single precision floating point value, always uses the dot as decimal.
Definition: FromChars.cpp:153
std::errc ec
A pointer to the first character not matching the pattern.
Definition: FromChars.h:23

References FromCharsResult::ec, FromChars(), and Query().

Here is the call graph for this function:

◆ GetValue() [2/2]

bool ProjectFileIO::GetValue ( const char *  sql,
wxString &  value,
bool  silent = false 
)
private

Definition at line 691 of file ProjectFileIO.cpp.

692{
693 // Retrieve the first column in the first row, if any
694 result.clear();
695 auto cb = [&result](int cols, char **vals, char **){
696 if (cols > 0)
697 result = vals[0];
698 // Stop after one row
699 return 1;
700 };
701
702 return Query(sql, cb, silent);
703}

References Query().

Referenced by CheckVersion(), LoadProject(), and WriteDoc().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ HadUnused()

bool ProjectFileIO::HadUnused ( )

Definition at line 1546 of file ProjectFileIO.cpp.

1547{
1548 return mHadUnused;
1549}

References mHadUnused.

Referenced by SetBypass().

Here is the caller graph for this function:

◆ HandleXMLChild()

XMLTagHandler * ProjectFileIO::HandleXMLChild ( const std::string_view &  tag)
overrideprivatevirtual

Implements XMLTagHandler.

Definition at line 1717 of file ProjectFileIO.cpp.

1718{
1719 auto &project = mProject;
1721}
XMLTagHandler * CallObjectAccessor(const std::string_view &tag, Host &host)
static XMLMethodRegistry & Get()
Get the unique instance.

References XMLMethodRegistry< Host >::CallObjectAccessor(), XMLMethodRegistry< Host >::Get(), mProject, and project.

Here is the call graph for this function:

◆ HandleXMLTag()

bool ProjectFileIO::HandleXMLTag ( const std::string_view &  tag,
const AttributesList attrs 
)
overrideprivatevirtual

Implements XMLTagHandler.

Definition at line 1638 of file ProjectFileIO.cpp.

1639{
1640 auto &project = mProject;
1641
1642 wxString fileVersion;
1643 wxString audacityVersion;
1644 int requiredTags = 0;
1645
1646 // loop through attrs, which is a null-terminated list of
1647 // attribute-value pairs
1648 for (auto pair : attrs)
1649 {
1650 auto attr = pair.first;
1651 auto value = pair.second;
1652
1654 .CallAttributeHandler( attr, project, value ) )
1655 continue;
1656
1657 else if (attr == "version")
1658 {
1659 fileVersion = value.ToWString();
1660 requiredTags++;
1661 }
1662
1663 else if (attr == "audacityversion")
1664 {
1665 audacityVersion = value.ToWString();
1666 requiredTags++;
1667 }
1668 } // while
1669
1670 if (requiredTags < 2)
1671 {
1672 return false;
1673 }
1674
1675 // Parse the file version from the project
1676 int fver;
1677 int frel;
1678 int frev;
1679 if (!wxSscanf(fileVersion, wxT("%i.%i.%i"), &fver, &frel, &frev))
1680 {
1681 return false;
1682 }
1683
1684 // Parse the file version Audacity was build with
1685 int cver;
1686 int crel;
1687 int crev;
1688 wxSscanf(wxT(AUDACITY_FILE_FORMAT_VERSION), wxT("%i.%i.%i"), &cver, &crel, &crev);
1689
1690 int fileVer = ((fver *100)+frel)*100+frev;
1691 int codeVer = ((cver *100)+crel)*100+crev;
1692
1693 if (codeVer<fileVer)
1694 {
1695 /* i18n-hint: %s will be replaced by the version number.*/
1696 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.")
1697 .Format(audacityVersion, AUDACITY_VERSION_STRING);
1698
1700 XO("Can't open project file"),
1701 msg,
1702 "FAQ:Errors_opening_an_Audacity_project"
1703 );
1704
1705 return false;
1706 }
1707
1708 if (tag != "project")
1709 {
1710 return false;
1711 }
1712
1713 // All other tests passed, so we succeed
1714 return true;
1715}
std::unique_ptr< const BasicUI::WindowPlacement > ProjectFramePlacement(AudacityProject *project)
Make a WindowPlacement object suitable for project (which may be null)
Definition: Project.cpp:129
#define AUDACITY_FILE_FORMAT_VERSION
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.

References AUDACITY_FILE_FORMAT_VERSION, XMLMethodRegistry< Host >::Get(), mProject, project, ProjectFramePlacement(), ShowError(), wxT(), and XO().

Here is the call graph for this function:

◆ HasConnection()

bool ProjectFileIO::HasConnection ( ) const

Return true if a connection is now open.

Definition at line 442 of file ProjectFileIO.cpp.

443{
444 auto &connectionPtr = ConnectionPtr::Get( mProject );
445 return connectionPtr.mpConnection != nullptr;
446}

References ConnectionPtr::Get(), and mProject.

Here is the call graph for this function:

◆ InitializeSQL()

bool ProjectFileIO::InitializeSQL ( )
static

Definition at line 375 of file ProjectFileIO.cpp.

376{
377 if (audacity::sqlite::Initialize().IsError())
378 return false;
379
381 [](int code, std::string_view message) {
382 // message is forwarded from SQLite, so it is null-terminated
383 wxLogMessage("SQLite error (%d): %s", code, message.data());
384 });
385
386 return true;
387}
void SetLogCallback(LogCallback callback)
Definition: SQLiteUtils.cpp:97
Error Initialize() noexcept
Definition: SQLiteUtils.cpp:92

References audacity::sqlite::Initialize(), and audacity::sqlite::SetLogCallback().

Referenced by AudacityApp::OnInit0().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ InSet()

void ProjectFileIO::InSet ( sqlite3_context *  context,
int  argc,
sqlite3_value **  argv 
)
staticprivate

Definition at line 827 of file ProjectFileIO.cpp.

828{
829 auto contextData = reinterpret_cast<ContextData*>(sqlite3_user_data(context));
830 SampleBlockID blockid = sqlite3_value_int64(argv[0]);
831
832 sqlite3_result_int(
833 context,
834 contextData->blockids.find(blockid) != contextData->blockids.end() ||
836 contextData->project, blockid));
837}
UTILITY_API const char *const * argv
A copy of argv; responsibility of application startup to assign it.
static bool IsBlockLocked(const AudacityProject &project, int64_t blockId)

References CommandLineArgs::argv, and ProjectFileIOExtensionRegistry::IsBlockLocked().

Referenced by DeleteBlocks().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ InstallSchema()

bool ProjectFileIO::InstallSchema ( sqlite3 *  db,
const char *  schema = "main" 
)
private

Definition at line 792 of file ProjectFileIO.cpp.

793{
794 int rc;
795
796 wxString sql;
798 sql.Replace("<schema>", schema);
799
800 rc = sqlite3_exec(db, sql, nullptr, nullptr, nullptr);
801 if (rc != SQLITE_OK)
802 {
804 XO("Unable to initialize the project file")
805 );
806 return false;
807 }
808
809 return true;
810}
static const char * ProjectFileSchema
const ProjectFormatVersion BaseProjectFormatVersion
This is a helper constant for the "most compatible" project version with the value (3,...
uint32_t GetPacked() const noexcept
Returns a version packed to 32-bit integer.

References BaseProjectFormatVersion, ProjectFormatVersion::GetPacked(), ProjectFileID, ProjectFileSchema, SetDBError(), and XO().

Referenced by CheckVersion(), and CopyTo().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ IsModified()

bool ProjectFileIO::IsModified ( ) const

Definition at line 2412 of file ProjectFileIO.cpp.

2413{
2414 return mModified;
2415}

References mModified.

Referenced by Compact(), and anonymous_namespace{TimerRecordDialog.cpp}::OnTimerRecord().

Here is the caller graph for this function:

◆ IsRecovered()

bool ProjectFileIO::IsRecovered ( ) const

Definition at line 2422 of file ProjectFileIO.cpp.

2423{
2424 return mRecovered;
2425}

References mRecovered.

◆ IsTemporary()

bool ProjectFileIO::IsTemporary ( ) const

Definition at line 2417 of file ProjectFileIO.cpp.

2418{
2419 return mTemporary;
2420}

References mTemporary.

Referenced by CloseProject(), Compact(), SaveProject(), SetBypass(), and SetFileName().

Here is the caller graph for this function:

◆ LoadProject()

auto ProjectFileIO::LoadProject ( const FilePath fileName,
bool  ignoreAutosave 
)

If successful, return non-empty; the caller must commit to keep the association of the opened file with the project

Definition at line 2030 of file ProjectFileIO.cpp.

2032{
2033 auto now = std::chrono::high_resolution_clock::now();
2034
2035 std::optional<TentativeConnection> result{ *this };
2036
2037 bool success = false;
2038
2039 // Open the project file
2040 if (!OpenConnection(fileName))
2041 return {};
2042
2043 int64_t rowId = -1;
2044
2045 bool useAutosave =
2046 !ignoreAutosave &&
2047 GetValue("SELECT ROWID FROM main.autosave WHERE id = 1;", rowId, true);
2048
2049 int64_t rowsCount = 0;
2050 // If we didn't have an autosave doc, load the project doc instead
2051 if (
2052 !useAutosave &&
2053 (!GetValue("SELECT COUNT(1) FROM main.project;", rowsCount, true) || rowsCount == 0))
2054 {
2055 // Missing both the autosave and project docs. This can happen if the
2056 // system were to crash before the first autosave into a temporary file.
2057 // This should be a recoverable scenario.
2058 mRecovered = true;
2059 mModified = true;
2060
2061 return result;
2062 }
2063
2064 if (!useAutosave && !GetValue("SELECT ROWID FROM main.project WHERE id = 1;", rowId, false))
2065 return {};
2066 else
2067 {
2068 // Load 'er up
2070 DB(), "main", useAutosave ? "autosave" : "project", rowId);
2071
2072 success = ProjectSerializer::Decode(stream, this);
2073
2074 if (!success)
2075 {
2076 SetError(
2077 XO("Unable to parse project information.")
2078 );
2079 return {};
2080 }
2081
2082 // Check for orphans blocks...sets mRecovered if any were deleted
2083
2084 auto blockids = WaveTrackFactory::Get( mProject )
2086 ->GetActiveBlockIDs();
2087 if (blockids.size() > 0)
2088 {
2089 success = DeleteBlocks(blockids, true);
2090 if (!success)
2091 return {};
2092 }
2093
2094 // Remember if we used autosave or not
2095 if (useAutosave)
2096 {
2097 mRecovered = true;
2098 }
2099 }
2100
2101 // Mark the project modified if we recovered it
2102 if (mRecovered)
2103 {
2104 mModified = true;
2105 }
2106
2107 // A previously saved project will have a document in the project table, so
2108 // we use that knowledge to determine if this file is an unsaved/temporary
2109 // file or a permanent project file
2110 wxString queryResult;
2111 success = GetValue("SELECT Count(*) FROM project;", queryResult);
2112 if (!success)
2113 return {};
2114
2115 mTemporary = !queryResult.IsSameAs(wxT("1"));
2116
2117 result->SetFileName(fileName);
2118
2119 auto duration = std::chrono::high_resolution_clock::now() - now;
2120
2121 wxLogInfo(
2122 "Project loaded in %lld ms",
2123 std::chrono::duration_cast<std::chrono::milliseconds>(duration).count());
2124
2125 return result;
2126}
bool DeleteBlocks(const BlockIDs &blockids, bool complement)
static bool Decode(BufferedStreamReader &in, XMLTagHandler *handler)
static WaveTrackFactory & Get(AudacityProject &project)
Definition: WaveTrack.cpp:3349
const SampleBlockFactoryPtr & GetSampleBlockFactory() const
Definition: WaveTrack.h:887

References DB(), ProjectSerializer::Decode(), DeleteBlocks(), WaveTrackFactory::Get(), WaveTrackFactory::GetSampleBlockFactory(), GetValue(), mModified, mProject, mRecovered, mTemporary, OpenConnection(), SetError(), wxT(), and XO().

Referenced by audacity::cloud::audiocom::CloudSyncService::SyncCloudSnapshot().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ MarkTemporary()

void ProjectFileIO::MarkTemporary ( )

Definition at line 2427 of file ProjectFileIO.cpp.

2428{
2429 mTemporary = true;
2430}

References mTemporary.

Referenced by audacity::cloud::audiocom::sync::OpenProjectFromCloud(), and anonymous_namespace{CloudProjectFileIOExtensions.cpp}::IOExtension::SaveCloudProject().

Here is the caller graph for this function:

◆ MoveProject()

bool ProjectFileIO::MoveProject ( const FilePath src,
const FilePath dst 
)
private

Definition at line 1319 of file ProjectFileIO.cpp.

1320{
1321 // Assume the src database file is not busy.
1322 if (!RenameOrWarn(src, dst))
1323 return false;
1324
1325 // So far so good, but the separate -wal and -shm files might yet exist,
1326 // as when checkpointing failed for limited space on the drive.
1327 // If so move them too or else lose data.
1328
1329 std::vector< std::pair<FilePath, FilePath> > pairs{ { src, dst } };
1330 bool success = false;
1331 auto cleanup = finally([&]{
1332 if (!success) {
1333 // If any one of the renames failed, back out the previous ones.
1334 // This should be a no-fail recovery! Not clear what to do if any
1335 // of these renames fails.
1336 for (auto &pair : pairs) {
1337 if (!(pair.first.empty() && pair.second.empty()))
1338 wxRenameFile(pair.second, pair.first);
1339 }
1340 }
1341 });
1342
1343 for (const auto &suffix : AuxiliaryFileSuffixes()) {
1344 auto srcName = src + suffix;
1345 if (wxFileExists(srcName)) {
1346 auto dstName = dst + suffix;
1347 if (!RenameOrWarn(srcName, dstName))
1348 return false;
1349 pairs.push_back({ srcName, dstName });
1350 }
1351 }
1352
1353 return (success = true);
1354}
bool RenameOrWarn(const FilePath &src, const FilePath &dst)
Rename a file or put up appropriate warning message.
static const std::vector< wxString > & AuxiliaryFileSuffixes()

References AuxiliaryFileSuffixes(), and RenameOrWarn().

Referenced by ProjectFileIO::BackupProject::BackupProject(), and SaveProject().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ OnCheckpointFailure()

void ProjectFileIO::OnCheckpointFailure ( )
private

Definition at line 1723 of file ProjectFileIO.cpp.

1724{
1725 // DBConnection promises to invoke this in main thread idle time
1726 // So we don't need a redundant CallAfter to satisfy our own promise
1728}
@ CheckpointFailure
Failure happened in a worker thread.
CallbackReturn Publish(const ProjectFileIOMessage &message)
Send a message to connected callbacks.
Definition: Observer.h:207

References CheckpointFailure, and Observer::Publisher< ProjectFileIOMessage >::Publish().

Here is the call graph for this function:

◆ OpenConnection()

bool ProjectFileIO::OpenConnection ( FilePath  fileName = {})
private
Precondition
*CurConn() does not exist
Postcondition
*CurConn() exists or return value is false

Definition at line 487 of file ProjectFileIO.cpp.

488{
489 auto &curConn = CurrConn();
490 wxASSERT(!curConn);
491 bool isTemp = false;
492
493 if (fileName.empty())
494 {
495 fileName = GetFileName();
496 if (fileName.empty())
497 {
499 isTemp = true;
500 }
501 }
502 else
503 {
504 // If this project resides in the temporary directory, then we'll mark it
505 // as temporary.
506 wxFileName temp(TempDirectory::TempDir(), wxT(""));
507 wxFileName file(fileName);
508 file.SetFullName(wxT(""));
509 if (file == temp)
510 {
511 isTemp = true;
512 }
513 }
514
515 // Pass weak_ptr to project into DBConnection constructor
516 curConn = std::make_unique<DBConnection>(
517 mProject.shared_from_this(), mpErrors, [this]{ OnCheckpointFailure(); } );
518 auto rc = curConn->Open(fileName);
519 if (rc != SQLITE_OK)
520 {
521 // Must use SetError() here since we do not have an active DB
522 SetError(
523 XO("Failed to open database file:\n\n%s").Format(fileName),
524 {},
525 rc
526 );
527 curConn.reset();
528 return false;
529 }
530
531 if (!CheckVersion())
532 {
534 curConn.reset();
535 return false;
536 }
537
538 mTemporary = isTemp;
539
540 SetFileName(fileName);
541
542 return true;
543}
const FilePath & GetFileName() const
FILES_API wxString UnsavedProjectFileName()

References CheckVersion(), CloseConnection(), CurrConn(), GetFileName(), mpErrors, mProject, mTemporary, SetError(), SetFileName(), TempDirectory::TempDir(), TempDirectory::UnsavedProjectFileName(), wxT(), and XO().

Referenced by Compact(), GetConnection(), LoadProject(), OpenProject(), ReopenProject(), and SaveProject().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ OpenProject()

bool ProjectFileIO::OpenProject ( )

Definition at line 2366 of file ProjectFileIO.cpp.

2367{
2368 return OpenConnection();
2369}

References OpenConnection().

Here is the call graph for this function:

◆ operator=()

ProjectFileIO & ProjectFileIO::operator= ( const ProjectFileIO )
delete

◆ Query()

bool ProjectFileIO::Query ( const char *  sql,
const ExecCB callback,
bool  silent = false 
)
private

Definition at line 678 of file ProjectFileIO.cpp.

679{
680 int rc = Exec(sql, callback, silent);
681 // SQLITE_ABORT is a non-error return only meaning the callback
682 // stopped the iteration of rows early
683 if ( !(rc == SQLITE_OK || rc == SQLITE_ABORT) )
684 {
685 return false;
686 }
687
688 return true;
689}
int Exec(const char *query, const ExecCB &callback, bool silent=false)

References Exec().

Referenced by CopyTo(), GetValue(), ShouldCompact(), and WriteDoc().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ RemoveProject()

bool ProjectFileIO::RemoveProject ( const FilePath filename)
static

Remove any files associated with a project at given path; return true if successful.

Definition at line 1356 of file ProjectFileIO.cpp.

1357{
1358 if (!wxFileExists(filename))
1359 return false;
1360
1361 bool success = wxRemoveFile(filename);
1362 auto &suffixes = AuxiliaryFileSuffixes();
1363 for (const auto &suffix : suffixes) {
1364 auto file = filename + suffix;
1365 if (wxFileExists(file))
1366 success = wxRemoveFile(file) && success;
1367 }
1368 return success;
1369}

References AuxiliaryFileSuffixes().

Referenced by CloseProject(), ProjectFileIO::BackupProject::Discard(), DiscardConnection(), and AutoRecoveryDialog::OnDiscardSelected().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ RenameOrWarn()

bool ProjectFileIO::RenameOrWarn ( const FilePath src,
const FilePath dst 
)
private

Rename a file or put up appropriate warning message.

Failure might happen when renaming onto another device, doing copy of contents

Definition at line 1278 of file ProjectFileIO.cpp.

1279{
1280 std::atomic_bool done = {false};
1281 bool success = false;
1282 auto thread = std::thread([&]
1283 {
1284 success = wxRenameFile(src, dst);
1285 done = true;
1286 });
1287
1288 // Provides a progress dialog with indeterminate mode
1289 using namespace BasicUI;
1291 XO("Copying Project"), XO("This may take several seconds"));
1292 wxASSERT(pd);
1293
1294 // Wait for the checkpoints to end
1295 while (!done)
1296 {
1297 using namespace std::chrono;
1298 std::this_thread::sleep_for(50ms);
1299 pd->Pulse();
1300 }
1301 thread.join();
1302
1303 if (!success)
1304 {
1306 XO("Error Writing to File"),
1307 XO("Audacity failed to write file %s.\n"
1308 "Perhaps disk is full or not writable.\n"
1309 "For tips on freeing up space, click the help button.")
1310 .Format(dst),
1311 "Error:_Disk_full_or_not_writable"
1312 );
1313 return false;
1314 }
1315
1316 return true;
1317}
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:312

References BasicUI::MakeGenericProgress(), mProject, ProjectFramePlacement(), ShowError(), and XO().

Referenced by MoveProject().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ ReopenProject()

bool ProjectFileIO::ReopenProject ( )

Definition at line 2401 of file ProjectFileIO.cpp.

2402{
2403 FilePath fileName = mFileName;
2404 if (!CloseConnection())
2405 {
2406 return false;
2407 }
2408
2409 return OpenConnection(fileName);
2410}
wxString FilePath
Definition: Project.h:21

References CloseConnection(), mFileName, and OpenConnection().

Here is the call graph for this function:

◆ RestoreConnection()

void ProjectFileIO::RestoreConnection ( )
private

Definition at line 611 of file ProjectFileIO.cpp.

612{
613 auto &curConn = CurrConn();
614 if (curConn)
615 {
616 if (!curConn->Close())
617 {
618 // Store an error message
620 XO("Failed to restore connection")
621 );
622 }
623 }
624
625 curConn = std::move(mPrevConn);
628
629 mPrevFileName.clear();
630}

References CurrConn(), mPrevConn, mPrevFileName, mPrevTemporary, mTemporary, SetDBError(), SetFileName(), and XO().

Here is the call graph for this function:

◆ SafetyFileName()

FilePath ProjectFileIO::SafetyFileName ( const FilePath src)
staticprivate

Generate a name for short-lived backup project files from an existing project.

Definition at line 1243 of file ProjectFileIO.cpp.

1244{
1245 wxFileNameWrapper fn{ src };
1246
1247 // Extra characters inserted into filename before extension
1248 wxString extra =
1249#ifdef __WXGTK__
1250 wxT("~")
1251#else
1252 wxT(".bak")
1253#endif
1254 ;
1255
1256 int nn = 1;
1257 auto numberString = [](int num) -> wxString {
1258 return num == 1 ? wxString{} : wxString::Format(".%d", num);
1259 };
1260
1261 auto suffixes = AuxiliaryFileSuffixes();
1262 suffixes.push_back({});
1263
1264 // Find backup paths not already occupied; check all auxiliary suffixes
1265 const auto name = fn.GetName();
1266 FilePath result;
1267 do {
1268 fn.SetName( name + numberString(nn++) + extra );
1269 result = fn.GetFullPath();
1270 }
1271 while( std::any_of(suffixes.begin(), suffixes.end(), [&](auto &suffix){
1272 return wxFileExists(result + suffix);
1273 }) );
1274
1275 return result;
1276}
const TranslatableString name
Definition: Distortion.cpp:76

References AuxiliaryFileSuffixes(), fn, name, and wxT().

Referenced by ProjectFileIO::BackupProject::BackupProject().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ SaveConnection()

void ProjectFileIO::SaveConnection ( )
private

Definition at line 564 of file ProjectFileIO.cpp.

565{
566 // Should do nothing in proper usage, but be sure not to leak a connection:
568
569 mPrevConn = std::move(CurrConn());
572
573 SetFileName({});
574}
void DiscardConnection()

References CurrConn(), DiscardConnection(), mFileName, mPrevConn, mPrevFileName, mPrevTemporary, mTemporary, and SetFileName().

Referenced by ProjectFileIO::TentativeConnection::TentativeConnection().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ SaveCopy()

bool ProjectFileIO::SaveCopy ( const FilePath fileName)

Definition at line 2360 of file ProjectFileIO.cpp.

2361{
2362 return CopyTo(fileName, XO("Backing up project"), false, true,
2364}

References CopyTo(), TrackList::Get(), mProject, and XO().

Here is the call graph for this function:

◆ SaveProject()

bool ProjectFileIO::SaveProject ( const FilePath fileName,
const TrackList lastSaved 
)

Definition at line 2153 of file ProjectFileIO.cpp.

2155{
2156 // In the case where we're saving a temporary project to a permanent project,
2157 // we'll try to simply rename the project to save a bit of time. We then fall
2158 // through to the normal Save (not SaveAs) processing.
2159 if (IsTemporary() && mFileName != fileName)
2160 {
2161 FilePath savedName = mFileName;
2162 if (CloseConnection())
2163 {
2164 bool reopened = false;
2165 bool moved = false;
2166 if (true == (moved = MoveProject(savedName, fileName)))
2167 {
2168 if (OpenConnection(fileName))
2169 reopened = true;
2170 else {
2171 MoveProject(fileName, savedName);
2172 moved = false; // No longer moved
2173
2174 reopened = OpenConnection(savedName);
2175 }
2176 }
2177 else {
2178 // Rename can fail -- if it's to a different device, requiring
2179 // real copy of contents, which might exhaust space
2180 reopened = OpenConnection(savedName);
2181 }
2182
2183 // Warning issued in MoveProject()
2184 if (reopened && !moved) {
2185 return false;
2186 }
2187
2188 if (!reopened) {
2189 BasicUI::CallAfter([this]{
2190 ShowError( {},
2191 XO("Warning"),
2192 XO(
2193"The project's database failed to reopen, "
2194"possibly because of limited space on the storage device."),
2195 "Error:_Disk_full_or_not_writable"
2196 );
2198 });
2199
2200 return false;
2201 }
2202 }
2203 }
2204
2205 // If we're saving to a different file than the current one, then copy the
2206 // current to the new file and make it the active file.
2207 if (mFileName != fileName)
2208 {
2209 // Do NOT prune here since we need to retain the Undo history
2210 // after we switch to the new file.
2211 if (!CopyTo(fileName, XO("Saving project"), false))
2212 {
2213 ShowError( {},
2214 XO("Error Saving Project"),
2216 "Error:_Disk_full_or_not_writable"
2217 );
2218 return false;
2219 }
2220
2221 // Open the newly created database
2222 Connection newConn = std::make_unique<DBConnection>(
2223 mProject.shared_from_this(), mpErrors,
2224 [this]{ OnCheckpointFailure(); });
2225
2226 // NOTE: There is a noticeable delay here when dealing with large multi-hour
2227 // projects that we just created. The delay occurs in Open() when it
2228 // calls SafeMode() and is due to the switch from the NONE journal mode
2229 // to the WAL journal mode.
2230 //
2231 // So, we do the Open() in a thread and display a progress dialog. Since
2232 // this is currently the only known instance where this occurs, we do the
2233 // threading here. If more instances are identified, then the threading
2234 // should be moved to DBConnection::Open(), wrapping the SafeMode() call
2235 // there.
2236 {
2237 std::atomic_bool done = {false};
2238 bool success = true;
2239 auto thread = std::thread([&]
2240 {
2241 auto rc = newConn->Open(fileName);
2242 if (rc != SQLITE_OK)
2243 {
2244 // Capture the error string
2245 SetError(Verbatim(sqlite3_errstr(rc)));
2246 success = false;
2247 }
2248 done = true;
2249 });
2250
2251 // Provides a progress dialog with indeterminate mode
2252 using namespace BasicUI;
2253 auto pd = MakeGenericProgress({},
2254 XO("Syncing"), XO("This may take several seconds"));
2255 wxASSERT(pd);
2256
2257 // Wait for the checkpoints to end
2258 while (!done)
2259 {
2260 using namespace std::chrono;
2261 std::this_thread::sleep_for(50ms);
2262 pd->Pulse();
2263 }
2264 thread.join();
2265
2266 if (!success)
2267 {
2268 // Additional help via a Help button links to the manual.
2269 ShowError( {},
2270 XO("Error Saving Project"),
2271 XO("The project failed to open, possibly due to limited space\n"
2272 "on the storage device.\n\n%s").Format(GetLastError()),
2273 "Error:_Disk_full_or_not_writable");
2274
2275 newConn = nullptr;
2276
2277 // Clean up the destination project
2278 if (!wxRemoveFile(fileName))
2279 {
2280 wxLogMessage("Failed to remove destination project after open failure: %s", fileName);
2281 }
2282
2283 return false;
2284 }
2285 }
2286
2287 // Autosave no longer needed in original project file.
2288 if (!AutoSaveDelete())
2289 {
2290 // Additional help via a Help button links to the manual.
2291 ShowError( {},
2292 XO("Error Saving Project"),
2293 XO("Unable to remove autosave information, possibly due to limited space\n"
2294 "on the storage device.\n\n%s").Format(GetLastError()),
2295 "Error:_Disk_full_or_not_writable");
2296
2297 newConn = nullptr;
2298
2299 // Clean up the destination project
2300 if (!wxRemoveFile(fileName))
2301 {
2302 wxLogMessage("Failed to remove destination project after AutoSaveDelete failure: %s", fileName);
2303 }
2304
2305 return false;
2306 }
2307
2308 if (lastSaved) {
2309 using namespace WaveTrackUtilities;
2310 // Bug2605: Be sure not to save orphan blocks
2311 bool recovered = mRecovered;
2312 SampleBlockIDSet blockids;
2313 InspectBlocks(*lastSaved, {}, &blockids);
2314 // TODO: Not sure what to do if the deletion fails
2315 DeleteBlocks(blockids, true);
2316 // Don't set mRecovered if any were deleted
2317 mRecovered = recovered;
2318 }
2319
2320 // Try to compact the original project file.
2321 auto empty = TrackList::Create(&mProject);
2322 Compact( { lastSaved ? lastSaved : empty.get() }, true );
2323
2324 // Safe to close the original project file now. Not much we can do if this fails,
2325 // but we should still be in good shape since we'll be switching to the newly
2326 // saved database below.
2327 CloseProject();
2328
2329 // And make it the active project file
2330 UseConnection(std::move(newConn), fileName);
2331 }
2332
2333 if (!UpdateSaved(nullptr))
2334 {
2335 ShowError(
2336 {}, XO("Error Saving Project"),
2338 "Error:_Disk_full_or_not_writable");
2339 return false;
2340 }
2341
2342 // Reaching this point defines success and all the rest are no-fail
2343 // operations:
2344
2345 // No longer modified
2346 mModified = false;
2347
2348 // No longer recovered
2349 mRecovered = false;
2350
2351 // No longer a temporary project
2352 mTemporary = false;
2353
2354 // Adjust the title
2356
2357 return true;
2358}
static TranslatableString WriteFailureMessage(const wxFileName &fileName)
bool MoveProject(const FilePath &src, const FilePath &dst)
bool UpdateSaved(const TrackList *tracks=nullptr)
void UseConnection(Connection &&conn, const FilePath &filePath)
void Compact(const std::vector< const TrackList * > &tracks, bool force=false)
const TranslatableString & GetLastError() const
static TrackListHolder Create(AudacityProject *pOwner)
Definition: Track.cpp:330
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
Definition: BasicUI.cpp:208

References AutoSaveDelete(), BasicUI::CallAfter(), CloseConnection(), CloseProject(), Compact(), CopyTo(), TrackList::Create(), DeleteBlocks(), GetLastError(), WaveTrackUtilities::InspectBlocks(), IsTemporary(), BasicUI::MakeGenericProgress(), mFileName, mModified, MoveProject(), mpErrors, mProject, mRecovered, mTemporary, OpenConnection(), Observer::Publisher< ProjectFileIOMessage >::Publish(), ReconnectionFailure, SetError(), SetProjectTitle(), ShowError(), UpdateSaved(), UseConnection(), Verbatim(), FileException::WriteFailureMessage(), and XO().

Here is the call graph for this function:

◆ SetBypass()

void ProjectFileIO::SetBypass ( )

Definition at line 2507 of file ProjectFileIO.cpp.

2508{
2509 auto &currConn = CurrConn();
2510 if (!currConn)
2511 return;
2512
2513 // Determine if we can bypass sample block deletes during shutdown.
2514 //
2515 // IMPORTANT:
2516 // If the project was compacted, then we MUST bypass further
2517 // deletions since the new file doesn't have the blocks that the
2518 // Sequences expect to be there.
2519
2520 currConn->SetBypass( true );
2521
2522 // Only permanent project files need cleaning at shutdown
2523 if (!IsTemporary() && !WasCompacted())
2524 {
2525 // If we still have unused blocks, then we must not bypass deletions
2526 // during shutdown. Otherwise, we would have orphaned blocks the next time
2527 // the project is opened.
2528 //
2529 // An example of when dead blocks will exist is when a user opens a permanent
2530 // project, adds a track (with samples) to it, and chooses not to save the
2531 // changes.
2532 if (HadUnused())
2533 {
2534 currConn->SetBypass( false );
2535 }
2536 }
2537
2538 return;
2539}

References CurrConn(), HadUnused(), IsTemporary(), and WasCompacted().

Here is the call graph for this function:

◆ SetDBError()

void ProjectFileIO::SetDBError ( const TranslatableString msg,
const TranslatableString libraryError = {},
int  errorCode = -1 
)
private

Set stored errors and write to log; and default libraryError to what database library reports.

Definition at line 2499 of file ProjectFileIO.cpp.

2501{
2502 auto &currConn = CurrConn();
2503 if (currConn)
2504 currConn->SetDBError(msg, libraryError, errorCode);
2505}

References CurrConn().

Referenced by AutoSaveDelete(), CopyTo(), DeleteBlocks(), DiscardConnection(), Exec(), InstallSchema(), RestoreConnection(), and WriteDoc().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ SetError()

void ProjectFileIO::SetError ( const TranslatableString msg,
const TranslatableString libraryError = {},
int  errorCode = {} 
)
private

Just set stored errors.

Definition at line 2491 of file ProjectFileIO.cpp.

2493{
2494 auto &currConn = CurrConn();
2495 if (currConn)
2496 currConn->SetError(msg, libraryError, errorCode);
2497}

References CurrConn().

Referenced by CheckVersion(), LoadProject(), OpenConnection(), and SaveProject().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ SetFileName()

void ProjectFileIO::SetFileName ( const FilePath fileName)

Definition at line 1600 of file ProjectFileIO.cpp.

1601{
1602 auto &project = mProject;
1603
1604 if (!fileName.empty() && fileName != mFileName)
1605 {
1607 [wThis = weak_from_this()]
1608 {
1609 if (auto pThis = wThis.lock())
1611 });
1612 }
1613
1614 if (!mFileName.empty())
1615 {
1617 }
1618
1619 mFileName = fileName;
1620
1621 if (!mFileName.empty())
1622 {
1624 }
1625
1626 if (IsTemporary())
1627 {
1628 project.SetProjectName({});
1629 }
1630 else
1631 {
1632 project.SetProjectName(wxFileName(mFileName).GetName());
1633 }
1634
1636}
@ ProjectFilePathChange
A normal occurrence.
PROJECT_FILE_IO_API void Remove(const FilePath &path)
PROJECT_FILE_IO_API void Add(const FilePath &path)

References ActiveProjects::Add(), BasicUI::CallAfter(), IsTemporary(), mFileName, mProject, project, ProjectFilePathChange, ActiveProjects::Remove(), and SetProjectTitle().

Referenced by CloseConnection(), OpenConnection(), RestoreConnection(), SaveConnection(), and UseConnection().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ SetProjectTitle()

void ProjectFileIO::SetProjectTitle ( int  number = -1)

Definition at line 1557 of file ProjectFileIO.cpp.

1558{
1559 auto &project = mProject;
1560 wxString name = project.GetProjectName();
1561
1562 // If we are showing project numbers, then we also explicitly show "<untitled>" if there
1563 // is none.
1564 if (number >= 0)
1565 {
1566 name =
1567 /* i18n-hint: The %02i is the project number, the %s is the project name.*/
1568 XO("[Project %02i] Audacity \"%s\"")
1569 .Format( number + 1,
1570 name.empty() ? XO("<untitled>") : Verbatim((const char *)name))
1571 .Translation();
1572 }
1573 // If we are not showing numbers, then <untitled> shows as 'Audacity'.
1574 else if (name.empty())
1575 {
1576 name = _TS("Audacity");
1577 }
1578
1579 if (mRecovered)
1580 {
1581 name += wxT(" ");
1582 /* i18n-hint: E.g this is recovered audio that had been lost.*/
1583 name += _("(Recovered)");
1584 }
1585
1586 if (name != mTitle) {
1587 mTitle = name;
1588 BasicUI::CallAfter( [wThis = weak_from_this()]{
1589 if (auto pThis = wThis.lock())
1591 } );
1592 }
1593}
#define _TS(s)
Definition: Internat.h:27
#define _(s)
Definition: Internat.h:73
@ ProjectTitleChange
A normal occurrence.

References _, _TS, BasicUI::CallAfter(), TranslatableString::empty(), mProject, mRecovered, mTitle, name, project, ProjectTitleChange, Verbatim(), wxT(), and XO().

Referenced by ProjectFileIO(), RefreshAllTitles(), SaveProject(), SetFileName(), and UpdatePrefs().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ ShouldCompact()

bool ProjectFileIO::ShouldCompact ( const std::vector< const TrackList * > &  tracks)
private

Definition at line 1178 of file ProjectFileIO.cpp.

1179{
1181 unsigned long long current = 0;
1182
1183 {
1184 auto fn = BlockSpaceUsageAccumulator( current );
1185 for (auto pTracks : tracks)
1186 if (pTracks)
1188 &active // Visit unique blocks only
1189 );
1190 }
1191
1192 // Get the number of blocks and total length from the project file.
1193 unsigned long long total = GetTotalUsage();
1194 unsigned long long blockcount = 0;
1195
1196 auto cb = [&blockcount](int cols, char **vals, char **)
1197 {
1198 // Convert
1199 wxString(vals[0]).ToULongLong(&blockcount);
1200 return 0;
1201 };
1202
1203 if (!Query("SELECT Count(*) FROM sampleblocks;", cb) || blockcount == 0)
1204 {
1205 // Shouldn't compact since we don't have the full picture
1206 return false;
1207 }
1208
1209 // Remember if we had unused blocks in the project file
1210 mHadUnused = (blockcount > active.size());
1211
1212 // Let's make a percentage...should be plenty of head room
1213 current *= 100;
1214
1215 wxLogDebug(wxT("used = %lld total = %lld %lld"), current, total, total ? current / total : 0);
1216 if (!total || current / total > 80)
1217 {
1218 wxLogDebug(wxT("not compacting"));
1219 return false;
1220 }
1221 wxLogDebug(wxT("compacting"));
1222
1223 return true;
1224}
int64_t GetTotalUsage()

References BlockSpaceUsageAccumulator(), fn, GetTotalUsage(), WaveTrackUtilities::InspectBlocks(), mHadUnused, Query(), tracks, and wxT().

Referenced by Compact().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ ShowError()

void ProjectFileIO::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 at line 2459 of file ProjectFileIO.cpp.

2463{
2464 using namespace audacity;
2465 using namespace BasicUI;
2466 ShowErrorDialog( placement, dlogTitle, message, helpPage,
2467 ErrorDialogOptions{ ErrorDialogType::ModalErrorReport }
2468 .Log(ToWString(GetLastLog())));
2469}
const wxString & GetLastLog() const
std::wstring ToWString(const std::string &str)
Options for variations of error dialogs; the default is for modal dialogs.
Definition: BasicUI.h:52
ErrorDialogOptions && Log(std::wstring log_) &&
Definition: BasicUI.h:64

References GetLastLog(), BasicUI::ErrorDialogOptions::Log(), BasicUI::ShowErrorDialog(), and audacity::ToWString().

Referenced by HandleXMLTag(), RenameOrWarn(), and SaveProject().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ UpdatePrefs()

void ProjectFileIO::UpdatePrefs ( )
overrideprivatevirtual

Implements PrefsListener.

Definition at line 1551 of file ProjectFileIO.cpp.

1552{
1554}

References SetProjectTitle().

Here is the call graph for this function:

◆ UpdateSaved()

bool ProjectFileIO::UpdateSaved ( const TrackList tracks = nullptr)

Definition at line 2128 of file ProjectFileIO.cpp.

2129{
2131 WriteXMLHeader(doc);
2132 WriteXML(doc, false, tracks);
2133
2134 if (!WriteDoc("project", doc))
2135 {
2136 return false;
2137 }
2138
2139 // Autosave no longer needed
2140 if (!AutoSaveDelete())
2141 {
2142 return false;
2143 }
2144
2146
2147 return true;
2148}
static void OnUpdateSaved(AudacityProject &project, const ProjectSerializer &serializer)

References AutoSaveDelete(), mProject, ProjectFileIOExtensionRegistry::OnUpdateSaved(), tracks, WriteDoc(), WriteXML(), and WriteXMLHeader().

Referenced by SaveProject().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ UseConnection()

void ProjectFileIO::UseConnection ( Connection &&  conn,
const FilePath filePath 
)
private

Definition at line 632 of file ProjectFileIO.cpp.

633{
634 auto &curConn = CurrConn();
635 wxASSERT(!curConn);
636
637 curConn = std::move(conn);
638 SetFileName(filePath);
639}

References CurrConn(), and SetFileName().

Referenced by SaveProject().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ WasCompacted()

bool ProjectFileIO::WasCompacted ( )

Definition at line 1541 of file ProjectFileIO.cpp.

1542{
1543 return mWasCompacted;
1544}

References mWasCompacted.

Referenced by SetBypass().

Here is the caller graph for this function:

◆ WriteDoc()

bool ProjectFileIO::WriteDoc ( const char *  table,
const ProjectSerializer autosave,
const char *  schema = "main" 
)
private

Definition at line 1829 of file ProjectFileIO.cpp.

1832{
1833 auto db = DB();
1834
1835 TransactionScope transaction(mProject, "UpdateProject");
1836
1837 int rc;
1838
1839 // For now, we always use an ID of 1. This will replace the previously
1840 // written row every time.
1841 char sql[256];
1842 sqlite3_snprintf(
1843 sizeof(sql), sql,
1844 "INSERT INTO %s.%s(id, dict, doc) VALUES(1, ?1, ?2)"
1845 " ON CONFLICT(id) DO UPDATE SET dict = ?1, doc = ?2;",
1846 schema, table);
1847
1848 sqlite3_stmt *stmt = nullptr;
1849 auto cleanup = finally([&]
1850 {
1851 if (stmt)
1852 {
1853 sqlite3_finalize(stmt);
1854 }
1855 });
1856
1857 rc = sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr);
1858 if (rc != SQLITE_OK)
1859 {
1860 ADD_EXCEPTION_CONTEXT("sqlite3.query", sql);
1861 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
1862 ADD_EXCEPTION_CONTEXT("sqlite3.context", "ProjectGileIO::WriteDoc::prepare");
1863
1864 SetDBError(
1865 XO("Unable to prepare project file command:\n\n%s").Format(sql)
1866 );
1867 return false;
1868 }
1869
1870 const MemoryStream& dict = autosave.GetDict();
1871 const MemoryStream& data = autosave.GetData();
1872
1873 // Bind statement parameters
1874 // Might return SQL_MISUSE which means it's our mistake that we violated
1875 // preconditions; should return SQL_OK which is 0
1876 if (
1877 sqlite3_bind_zeroblob(stmt, 1, dict.GetSize()) ||
1878 sqlite3_bind_zeroblob(stmt, 2, data.GetSize()))
1879 {
1880 ADD_EXCEPTION_CONTEXT("sqlite3.query", sql);
1881 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
1882 ADD_EXCEPTION_CONTEXT("sqlite3.context", "ProjectGileIO::WriteDoc::bind");
1883
1884 SetDBError(XO("Unable to bind to blob"));
1885 return false;
1886 }
1887
1888 const auto reportError = [this](auto sql) {
1889 SetDBError(
1890 XO("Failed to update the project file.\nThe following command failed:\n\n%s")
1891 .Format(sql));
1892 };
1893
1894 rc = sqlite3_step(stmt);
1895
1896 if (rc != SQLITE_DONE)
1897 {
1898 ADD_EXCEPTION_CONTEXT("sqlite3.query", sql);
1899 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
1900 ADD_EXCEPTION_CONTEXT("sqlite3.context", "ProjectGileIO::WriteDoc::step");
1901
1902 reportError(sql);
1903 return false;
1904 }
1905
1906 // Finalize the statement before committing the transaction
1907 sqlite3_finalize(stmt);
1908 stmt = nullptr;
1909
1910 // Get rowid
1911
1912 int64_t rowID = 0;
1913
1914 const wxString rowIDSql =
1915 wxString::Format("SELECT ROWID FROM %s.%s WHERE id = 1;", schema, table);
1916
1917 if (!GetValue(rowIDSql, rowID, true))
1918 {
1919 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(sqlite3_errcode(db)));
1920 ADD_EXCEPTION_CONTEXT("sqlite3.context", "ProjectGileIO::WriteDoc::rowid");
1921
1922 reportError(rowIDSql);
1923 return false;
1924 }
1925
1926 const auto writeStream = [db, schema, table, rowID, this](const char* column, const MemoryStream& stream) {
1927
1928 auto blobStream =
1929 SQLiteBlobStream::Open(db, schema, table, column, rowID, false);
1930
1931 if (!blobStream)
1932 {
1933 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(sqlite3_errcode(db)));
1934 ADD_EXCEPTION_CONTEXT("sqlite3.col", column);
1935 ADD_EXCEPTION_CONTEXT("sqlite3.context", "ProjectGileIO::WriteDoc::openBlobStream");
1936
1937 SetDBError(XO("Unable to bind to blob"));
1938 return false;
1939 }
1940
1941 for (auto chunk : stream)
1942 {
1943 if (SQLITE_OK != blobStream->Write(chunk.first, chunk.second))
1944 {
1945 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(sqlite3_errcode(db)));
1946 ADD_EXCEPTION_CONTEXT("sqlite3.col", column);
1947 ADD_EXCEPTION_CONTEXT("sqlite3.context", "ProjectGileIO::WriteDoc::writeBlobStream");
1948 // The user visible message is not changed, so there is no need for new strings
1949 SetDBError(XO("Unable to bind to blob"));
1950 return false;
1951 }
1952 }
1953
1954 if (blobStream->Close() != SQLITE_OK)
1955 {
1957 "sqlite3.rc", std::to_string(sqlite3_errcode(db)));
1958 ADD_EXCEPTION_CONTEXT("sqlite3.col", column);
1960 "sqlite3.context", "ProjectGileIO::WriteDoc::writeBlobStream");
1961 // The user visible message is not changed, so there is no need for new
1962 // strings
1963 SetDBError(XO("Unable to bind to blob"));
1964 return false;
1965 }
1966
1967 return true;
1968 };
1969
1970 if (!writeStream("dict", dict))
1971 return false;
1972
1973 if (!writeStream("doc", data))
1974 return false;
1975
1976 const auto requiredVersion =
1978
1979 const wxString setVersionSql =
1980 wxString::Format("PRAGMA user_version = %u", requiredVersion.GetPacked());
1981
1982 if (!Query(setVersionSql.c_str(), [](auto...) { return 0; }))
1983 {
1984 // DV: Very unlikely case.
1985 // Since we need to improve the error messages in the future, let's use
1986 // the generic message for now, so no new strings are needed
1987 reportError(setVersionSql);
1988 return false;
1989 }
1990
1991 return transaction.Commit();
1992}
A low overhead memory stream with O(1) append, low heap fragmentation and a linear memory view.
const size_t GetSize() const noexcept
ProjectFormatVersion GetRequiredVersion(const AudacityProject &project) const
Returns the minimum possible version that can be used to save the project.
static const ProjectFormatExtensionsRegistry & Get()
const MemoryStream & GetData() const
const MemoryStream & GetDict() const
static std::optional< SQLiteBlobStream > Open(sqlite3 *db, const char *schema, const char *table, const char *column, int64_t rowID, bool readOnly) noexcept
RAII for a database transaction, possibly nested.

References ADD_EXCEPTION_CONTEXT, TransactionScope::Commit(), DB(), ProjectFormatExtensionsRegistry::Get(), ProjectSerializer::GetData(), ProjectSerializer::GetDict(), ProjectFormatExtensionsRegistry::GetRequiredVersion(), MemoryStream::GetSize(), GetValue(), mProject, SQLiteBlobStream::Open(), Query(), SetDBError(), and XO().

Referenced by AutoSave(), CopyTo(), and UpdateSaved().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ WriteXML()

void ProjectFileIO::WriteXML ( XMLWriter xmlFile,
bool  recording = false,
const TrackList tracks = nullptr 
)
private

Definition at line 1745 of file ProjectFileIO.cpp.

1749{
1750 auto &proj = mProject;
1751 auto &tracklist = tracks ? *tracks : TrackList::Get(proj);
1752
1753 //TIMER_START( "AudacityProject::WriteXML", xml_writer_timer );
1754
1755 xmlFile.StartTag(wxT("project"));
1756 xmlFile.WriteAttr(wxT("xmlns"), wxT("http://audacity.sourceforge.net/xml/"));
1757
1758 xmlFile.WriteAttr(wxT("version"), wxT(AUDACITY_FILE_FORMAT_VERSION));
1759 xmlFile.WriteAttr(wxT("audacityversion"), AUDACITY_VERSION_STRING);
1760
1761 ProjectFileIORegistry::Get().CallWriters(proj, xmlFile);
1762
1763 auto &pendingTracks = PendingTracks::Get(proj);
1764 tracklist.Any().Visit([&](const Track &t) {
1765 auto useTrack = &t;
1766 if (recording) {
1767 // When append-recording, there is a temporary "shadow" track accumulating
1768 // changes and displayed on the screen but it is not yet part of the
1769 // regular track list. That is the one that we want to back up.
1770 // SubstitutePendingChangedTrack() fetches the shadow, if the track has
1771 // one, else it gives the same track back.
1772 useTrack = &pendingTracks.SubstitutePendingChangedTrack(t);
1773 }
1774 else if (useTrack->GetId() == TrackId{}) {
1775 // This is a track added during a non-appending recording that is
1776 // not yet in the undo history. The UndoManager skips backing it up
1777 // when pushing. Don't auto-save it.
1778 return;
1779 }
1780 useTrack->WriteXML(xmlFile);
1781 });
1782
1783 xmlFile.EndTag(wxT("project"));
1784
1785 //TIMER_STOP( xml_writer_timer );
1786}
static PendingTracks & Get(AudacityProject &project)
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:110
virtual void WriteXML(XMLWriter &xmlFile) const =0
An in-session identifier of track objects across undo states. It does not persist between sessions.
Definition: Track.h:79
void CallWriters(const Host &host, XMLWriter &writer)
virtual void StartTag(const wxString &name)
Definition: XMLWriter.cpp:79
void WriteAttr(const wxString &name, const Identifier &value)
Definition: XMLWriter.h:36
virtual void EndTag(const wxString &name)
Definition: XMLWriter.cpp:102

References AUDACITY_FILE_FORMAT_VERSION, XMLMethodRegistry< Host >::CallWriters(), XMLWriter::EndTag(), XMLMethodRegistry< Host >::Get(), PendingTracks::Get(), TrackList::Get(), mProject, XMLWriter::StartTag(), tracks, XMLWriter::WriteAttr(), Track::WriteXML(), and wxT().

Referenced by AutoSave(), CopyTo(), GenerateDoc(), and UpdateSaved().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ WriteXMLHeader()

void ProjectFileIO::WriteXMLHeader ( XMLWriter xmlFile) const
private

Definition at line 1730 of file ProjectFileIO.cpp.

1731{
1732 xmlFile.Write(wxT("<?xml "));
1733 xmlFile.Write(wxT("version=\"1.0\" "));
1734 xmlFile.Write(wxT("standalone=\"no\" "));
1735 xmlFile.Write(wxT("?>\n"));
1736
1737 xmlFile.Write(wxT("<!DOCTYPE "));
1738 xmlFile.Write(wxT("project "));
1739 xmlFile.Write(wxT("PUBLIC "));
1740 xmlFile.Write(wxT("\"-//audacityproject-1.3.0//DTD//EN\" "));
1741 xmlFile.Write(wxT("\"http://audacity.sourceforge.net/xml/audacityproject-1.3.0.dtd\" "));
1742 xmlFile.Write(wxT(">\n"));
1743}
virtual void Write(const wxString &data)=0

References XMLWriter::Write(), and wxT().

Referenced by AutoSave(), CopyTo(), GenerateDoc(), and UpdateSaved().

Here is the call graph for this function:
Here is the caller graph for this function:

Member Data Documentation

◆ mFileName

FilePath ProjectFileIO::mFileName
private

◆ mHadUnused

bool ProjectFileIO::mHadUnused
private

Definition at line 330 of file ProjectFileIO.h.

Referenced by Compact(), HadUnused(), and ShouldCompact().

◆ mModified

bool ProjectFileIO::mModified
private

◆ mpErrors

std::shared_ptr<DBConnectionErrors> ProjectFileIO::mpErrors
private

◆ mPrevConn

Connection ProjectFileIO::mPrevConn
private

◆ mPrevFileName

FilePath ProjectFileIO::mPrevFileName
private

Definition at line 333 of file ProjectFileIO.h.

Referenced by DiscardConnection(), RestoreConnection(), and SaveConnection().

◆ mPrevTemporary

bool ProjectFileIO::mPrevTemporary
private

Definition at line 334 of file ProjectFileIO.h.

Referenced by DiscardConnection(), RestoreConnection(), and SaveConnection().

◆ mProject

AudacityProject& ProjectFileIO::mProject
private

◆ mRecovered

bool ProjectFileIO::mRecovered
private

◆ mTemporary

bool ProjectFileIO::mTemporary
private

◆ mTitle

wxString ProjectFileIO::mTitle
private

Definition at line 310 of file ProjectFileIO.h.

Referenced by SetProjectTitle().

◆ mWasCompacted

bool ProjectFileIO::mWasCompacted
private

Definition at line 327 of file ProjectFileIO.h.

Referenced by Compact(), and WasCompacted().


The documentation for this class was generated from the following files: