20#include <wx/sstream.h>
48#define AUDACITY_FILE_FORMAT_VERSION "1.3.0"
51#if !defined(__WXMSW__)
60#define PACK(b1, b2, b3, b4) ((b1 << 24) | (b2 << 16) | (b3 << 8) | b4)
96 "PRAGMA <schema>.application_id = %d;"
97 "PRAGMA <schema>.user_version = %u;"
111 "CREATE TABLE IF NOT EXISTS <schema>.project"
113 " id INTEGER PRIMARY KEY,"
131 "CREATE TABLE IF NOT EXISTS <schema>.autosave"
133 " id INTEGER PRIMARY KEY,"
152 "CREATE TABLE IF NOT EXISTS <schema>.sampleblocks"
154 " blockid INTEGER PRIMARY KEY AUTOINCREMENT,"
155 " sampleformat INTEGER,"
176 mRc = sqlite3_config(SQLITE_CONFIG_URI, 1);
177 if (
mRc == SQLITE_OK)
180 if (
mRc == SQLITE_OK)
182 mRc = sqlite3_initialize();
187 if (
mRc == SQLITE_OK)
193 auto vfs = sqlite3_vfs_find(
"unix-excl");
196 sqlite3_vfs_register(vfs, 1);
205 (void) sqlite3_shutdown();
208 static void LogCallback(
void *WXUNUSED(arg),
int code,
const char *msg)
210 wxLogMessage(
"sqlite3 message: (%d) %s", code, msg);
219 static std::optional<SQLiteBlobStream>
Open(
220 sqlite3* db,
const char* schema,
const char* table,
const char* column,
221 int64_t rowID,
bool readOnly)
noexcept
226 sqlite3_blob* blob =
nullptr;
228 const int rc = sqlite3_blob_open(
229 db, schema, table, column, rowID, readOnly ? 0 : 1, &blob);
234 return std::make_optional<SQLiteBlobStream>(blob, readOnly);
246 *
this = std::move(rhs);
268 return mBlob !=
nullptr;
273 if (
mBlob ==
nullptr)
276 const int rc = sqlite3_blob_close(
mBlob);
289 return SQLITE_MISUSE;
301 if (!
IsOpen() || ptr ==
nullptr)
302 return SQLITE_MISUSE;
306 if (availableBytes == 0)
311 else if (availableBytes <
size)
313 size = availableBytes;
341 static constexpr std::array<const char*, 2>
Columns = {
"dict",
"doc" };
344 sqlite3* db,
const char* schema,
const char* table,
388 size_t ReadData(
void* buffer,
size_t maxBytes)
override
397 maxBytes = std::min<size_t>(maxBytes, std::numeric_limits<int>::max());
398 auto bytesRead =
static_cast<int>(maxBytes);
400 if (SQLITE_OK !=
mBlobStream->Read(buffer, bytesRead))
409 else if (bytesRead == 0)
414 return static_cast<size_t>(bytesRead);
423 return sqliteIniter.
mRc == SQLITE_OK;
428 auto result = std::make_shared< ProjectFileIO >( parent );
457 wxLongLong freeSpace = 0;
460 if (wxGetDiskSpace(path, NULL, &freeSpace)) {
461 if (freeSpace < wxLongLong(wxLL(100 * 1048576))) {
466 XO(
"There is very little free disk space left on %s\n"
467 "Please select a bigger temporary directory location in\n"
468 "Directories Preferences.").Format( volume ),
469 "Error:_Disk_full_or_not_writable"
482 return connectionPtr.mpConnection !=
nullptr;
495 XO(
"Failed to open the project's database"),
497 "Error:_Disk_full_or_not_writable"
511 WriteXML(doc,
false, trackList.empty() ?
nullptr : &trackList);
530 if (fileName.empty())
533 if (fileName.empty())
544 wxFileName file(fileName);
545 file.SetFullName(
wxT(
""));
553 curConn = std::make_unique<DBConnection>(
555 auto rc = curConn->Open(fileName);
560 XO(
"Failed to open database file:\n\n%s").
Format(fileName),
588 if (!curConn->Close())
622 XO(
"Failed to discard connection")
633 file.SetFullName(
wxT(
""));
638 wxLogMessage(
"Failed to remove temporary project %s",
mPrevFileName);
653 if (!curConn->Close())
657 XO(
"Failed to restore connection")
674 curConn = std::move(conn);
682 return GuardedCall<int>(
683 [&]{
return cb(cols, vals,
names); },
690 char *errmsg =
nullptr;
692 const void *ptr = &callback;
694 const_cast<void*
>(ptr), &errmsg);
696 if (rc != SQLITE_ABORT && errmsg && !silent)
702 XO(
"Failed to execute a project file command:\n\n%s").
Format(query),
709 sqlite3_free(errmsg);
717 int rc =
Exec(sql, callback, silent);
720 if ( !(rc == SQLITE_OK || rc == SQLITE_ABORT) )
732 auto cb = [&result](
int cols,
char **vals,
char **){
739 return Query(sql, cb, silent);
744 bool success =
false;
745 auto cb = [&value, &success](
int cols,
char** vals,
char**)
749 const std::string_view valueString = vals[0];
751 success = std::errc() ==
753 valueString.data(), valueString.data() + valueString.length(),
761 return Query(sql, cb, silent) && success;
771 if (!
GetValue(
"SELECT Count(*) FROM sqlite_master WHERE type='table';", result))
779 XO(
"Project is in a read only directory\n(Unable to create the required temporary files)"),
789 if (wxStrtol<char **>(result,
nullptr, 10) == 0)
795 if (!
GetValue(
"PRAGMA application_ID;", result))
801 if (wxStrtoul<char **>(result,
nullptr, 10) !=
ProjectFileID)
803 SetError(
XO(
"This is not an Audacity project file"));
808 if (!
GetValue(
"PRAGMA user_version;", result))
821 XO(
"This project was created with a newer version of Audacity.\n\nYou will need to upgrade to open it.")
835 sql.Replace(
"<schema>", schema);
837 rc = sqlite3_exec(db, sql,
nullptr,
nullptr,
nullptr);
841 XO(
"Unable to initialize the project file")
860 sqlite3_result_int(context, blockids->find(blockid) != blockids->end());
868 auto cleanup =
finally([&]
871 sqlite3_create_function(db,
"inset", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC,
nullptr,
nullptr,
nullptr,
nullptr);
875 const void *p = &blockids;
876 rc = sqlite3_create_function(db,
"inset", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC,
const_cast<void*
>(p),
InSet,
nullptr,
nullptr);
883 SetDBError(
XO(
"Unable to add 'inset' function (can't verify blockids)"));
890 auto sql = wxString::Format(
891 "DELETE FROM sampleblocks WHERE %sinset(blockid);",
892 complement ?
"NOT " :
"" );
893 rc = sqlite3_exec(db, sql,
nullptr,
nullptr,
nullptr);
900 if( rc==SQLITE_READONLY)
902 SetDBError(
XO(
"Project is read only\n(Unable to work with the blockfiles)"));
903 else if( rc==SQLITE_LOCKED)
905 SetDBError(
XO(
"Project is locked\n(Unable to work with the blockfiles)"));
906 else if( rc==SQLITE_BUSY)
908 SetDBError(
XO(
"Project is busy\n(Unable to work with the blockfiles)"));
909 else if( rc==SQLITE_CORRUPT)
911 SetDBError(
XO(
"Project is corrupt\n(Unable to work with the blockfiles)"));
912 else if( rc==SQLITE_PERM)
914 SetDBError(
XO(
"Some permissions issue\n(Unable to work with the blockfiles)"));
915 else if( rc==SQLITE_IOERR)
917 SetDBError(
XO(
"A disk I/O error\n(Unable to work with the blockfiles)"));
918 else if( rc==SQLITE_AUTH)
920 SetDBError(
XO(
"Not authorized\n(Unable to work with the blockfiles)"));
929 int changes = sqlite3_changes(db);
932 wxLogInfo(
XO(
"Total orphan blocks deleted %d").Translation(), changes);
943 const std::vector<const TrackList *> &
tracks )
959 for (
auto trackList :
tracks)
966 auto cb = [&blockids](
int cols,
char **vals,
char **){
968 wxString{ vals[0] }.ToLongLong(&blockid);
969 blockids.insert(blockid);
973 if (!
Query(
"SELECT blockid FROM sampleblocks;", cb))
987 bool success =
false;
992 auto cleanup =
finally([&]
1006 auto result = sqlite3_exec(db,
"ROLLBACK;",
nullptr,
nullptr,
nullptr);
1009 if (result != SQLITE_OK && (rc == SQLITE_DONE || rc == SQLITE_OK))
1013 "sqlite3.context",
"ProjectGileIO::CopyTo.cleanup");
1016 XO(
"Failed to rollback transaction during import")
1023 sqlite3_exec(db,
"DETACH DATABASE outbound;",
nullptr,
nullptr,
nullptr);
1026 wxRemoveFile(destpath);
1032 wxString dbName = destpath;
1034 dbName.Replace(
"'",
"''");
1035 sql.Printf(
"ATTACH DATABASE '%s' AS outbound;", dbName.ToUTF8());
1037 rc = sqlite3_exec(db, sql,
nullptr,
nullptr,
nullptr);
1038 if (rc != SQLITE_OK)
1041 XO(
"Unable to attach destination database")
1050 if ( pConn->FastMode(
"outbound") != SQLITE_OK)
1053 XO(
"Unable to switch to fast journaling mode")
1068 sqlite3_stmt *stmt =
nullptr;
1069 auto cleanup =
finally([&]
1074 sqlite3_finalize(stmt);
1079 rc = sqlite3_prepare_v2(db,
1080 "INSERT INTO outbound.sampleblocks"
1081 " SELECT * FROM main.sampleblocks"
1082 " WHERE blockid = ?;",
1086 if (rc != SQLITE_OK)
1090 "sqlite3.context",
"ProjectGileIO::CopyTo.prepare");
1093 XO(
"Unable to prepare project file command:\n\n%s").
Format(sql)
1104 wxLongLong_t count = 0;
1105 wxLongLong_t total = blockids.size();
1114 sqlite3_exec(db,
"BEGIN;",
nullptr,
nullptr,
nullptr);
1117 for (
auto blockid : blockids)
1120 rc = sqlite3_bind_int64(stmt, 1, blockid);
1121 if (rc != SQLITE_OK)
1125 "sqlite3.context",
"ProjectGileIO::CopyTo.bind");
1128 XO(
"Failed to bind SQL parameter")
1135 rc = sqlite3_step(stmt);
1136 if (rc != SQLITE_DONE)
1140 "sqlite3.context",
"ProjectGileIO::CopyTo.step");
1143 XO(
"Failed to update the project file.\nThe following command failed:\n\n%s").
Format(sql)
1149 if (sqlite3_reset(stmt) != SQLITE_OK)
1153 "sqlite3.context",
"ProjectGileIO::CopyTo.reset");
1158 result = progress->Poll(++count, total);
1172 if (!
WriteDoc(isTemporary ?
"autosave" :
"project", doc,
"outbound"))
1178 sqlite3_exec(db,
"COMMIT;",
nullptr,
nullptr,
nullptr);
1182 rc = sqlite3_exec(db,
"DETACH DATABASE outbound;",
nullptr,
nullptr,
nullptr);
1183 if (rc != SQLITE_OK)
1189 XO(
"Destination project could not be detached")
1204 unsigned long long current = 0;
1208 for (
auto pTracks :
tracks)
1217 unsigned long long blockcount = 0;
1219 auto cb = [&blockcount](
int cols,
char **vals,
char **)
1222 wxString(vals[0]).ToULongLong(&blockcount);
1226 if (!
Query(
"SELECT Count(*) FROM sampleblocks;", cb) || blockcount == 0)
1238 wxLogDebug(
wxT(
"used = %lld total = %lld %lld"), current, total, total ? current / total : 0);
1239 if (!total || current / total > 80)
1241 wxLogDebug(
wxT(
"not compacting"));
1244 wxLogDebug(
wxT(
"compacting"));
1252 return connectionPtr.mpConnection;
1257 static const std::vector<wxString> strings {
1280 auto numberString = [](
int num) -> wxString {
1281 return num == 1 ? wxString{} : wxString::Format(
".%d", num);
1285 suffixes.push_back({});
1288 const auto name =
fn.GetName();
1291 fn.SetName(
name + numberString(nn++) + extra );
1292 result =
fn.GetFullPath();
1294 while( std::any_of(suffixes.begin(), suffixes.end(), [&](
auto &suffix){
1295 return wxFileExists(result + suffix);
1303 std::atomic_bool done = {
false};
1304 bool success =
false;
1305 auto thread = std::thread([&]
1307 success = wxRenameFile(src, dst);
1314 XO(
"Copying Project"),
XO(
"This may take several seconds"));
1320 using namespace std::chrono;
1321 std::this_thread::sleep_for(50ms);
1329 XO(
"Error Writing to File"),
1330 XO(
"Audacity failed to write file %s.\n"
1331 "Perhaps disk is full or not writable.\n"
1332 "For tips on freeing up space, click the help button.")
1334 "Error:_Disk_full_or_not_writable"
1352 std::vector< std::pair<FilePath, FilePath> > pairs{ { src, dst } };
1353 bool success =
false;
1354 auto cleanup =
finally([&]{
1359 for (
auto &pair : pairs) {
1360 if (!(pair.first.empty() && pair.second.empty()))
1361 wxRenameFile(pair.second, pair.first);
1367 auto srcName = src + suffix;
1368 if (wxFileExists(srcName)) {
1369 auto dstName = dst + suffix;
1372 pairs.push_back({ srcName, dstName });
1376 return (success =
true);
1381 if (!wxFileExists(filename))
1384 bool success = wxRemoveFile(filename);
1386 for (
const auto &suffix : suffixes) {
1387 auto file = filename + suffix;
1388 if (wxFileExists(file))
1389 success = wxRemoveFile(file) && success;
1407 if (!mPath.empty()) {
1416 if (!mPath.empty()) {
1417 if (!mSafety.empty()) {
1420 suffixes.push_back({});
1421 for (
const auto &suffix : suffixes) {
1422 auto path = mPath + suffix;
1423 if (wxFileExists(path))
1425 wxRenameFile(mSafety + suffix, mPath + suffix);
1432 const std::vector<const TrackList *> &
tracks,
bool force)
1466 wxString backName = origName +
"_compact_back";
1467 wxString tempName = origName +
"_compact_temp";
1489 if (wxFileName::GetSize(tempName) < wxFileName::GetSize(origName))
1492 if (wxRenameFile(origName, backName))
1495 if (wxRenameFile(tempName, origName))
1501 if (!wxRemoveFile(backName))
1506 wxLogWarning(
wxT(
"Compaction failed to delete backup %s"), backName);
1516 wxLogWarning(
wxT(
"Compaction failed to open new project %s"), origName);
1519 if (!wxRenameFile(origName, tempName))
1521 wxLogWarning(
wxT(
"Compaction failed to rename original %s to temp %s"),
1522 origName, tempName);
1527 wxLogWarning(
wxT(
"Compaction failed to rename temp %s to orig %s"),
1528 origName, tempName);
1531 if (!wxRenameFile(backName, origName))
1533 wxLogWarning(
wxT(
"Compaction failed to rename back %s to orig %s"),
1534 backName, origName);
1539 wxLogWarning(
wxT(
"Compaction failed to rename orig %s to back %s"),
1540 backName, origName);
1546 wxLogWarning(
wxT(
"Compaction failed to reopen %s"), origName);
1552 if (!wxRemoveFile(tempName))
1557 wxLogWarning(
wxT(
"Failed to delete temporary file...ignoring"));
1591 XO(
"[Project %02i] Audacity \"%s\"")
1592 .Format( number + 1,
1606 name +=
_(
"(Recovered)");
1612 if (
auto pThis = wThis.lock())
1655 wxString fileVersion;
1656 wxString audacityVersion;
1657 int requiredTags = 0;
1661 for (
auto pair : attrs)
1663 auto attr = pair.first;
1664 auto value = pair.second;
1667 .CallAttributeHandler( attr,
project, value ) )
1670 else if (attr ==
"version")
1672 fileVersion = value.ToWString();
1676 else if (attr ==
"audacityversion")
1678 audacityVersion = value.ToWString();
1683 if (requiredTags < 2)
1692 if (!wxSscanf(fileVersion,
wxT(
"%i.%i.%i"), &fver, &frel, &frev))
1703 int fileVer = ((fver *100)+frel)*100+frev;
1704 int codeVer = ((cver *100)+crel)*100+crev;
1706 if (codeVer<fileVer)
1709 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.")
1710 .Format(audacityVersion, AUDACITY_VERSION_STRING);
1713 XO(
"Can't open project file"),
1715 "FAQ:Errors_opening_an_Audacity_project"
1721 if (tag !=
"project")
1746 xmlFile.
Write(
wxT(
"version=\"1.0\" "));
1747 xmlFile.
Write(
wxT(
"standalone=\"no\" "));
1753 xmlFile.
Write(
wxT(
"\"-//audacityproject-1.3.0//DTD//EN\" "));
1754 xmlFile.
Write(
wxT(
"\"http://audacity.sourceforge.net/xml/audacityproject-1.3.0.dtd\" "));
1769 xmlFile.
WriteAttr(
wxT(
"xmlns"),
wxT(
"http://audacity.sourceforge.net/xml/"));
1772 xmlFile.
WriteAttr(
wxT(
"audacityversion"), AUDACITY_VERSION_STRING);
1776 tracklist.Any().Visit([&](
const Track &t) {
1786 else if (useTrack->GetId() ==
TrackId{}) {
1792 useTrack->WriteXML(xmlFile);
1806 if (
WriteDoc(
"autosave", autosave))
1824 rc = sqlite3_exec(db,
"DELETE FROM autosave;",
nullptr,
nullptr,
nullptr);
1825 if (rc != SQLITE_OK)
1831 XO(
"Failed to remove the autosave information from the project file.")
1843 const char *schema )
1856 "INSERT INTO %s.%s(id, dict, doc) VALUES(1, ?1, ?2)"
1857 " ON CONFLICT(id) DO UPDATE SET dict = ?1, doc = ?2;",
1860 sqlite3_stmt *stmt =
nullptr;
1861 auto cleanup =
finally([&]
1865 sqlite3_finalize(stmt);
1869 rc = sqlite3_prepare_v2(db, sql, -1, &stmt,
nullptr);
1870 if (rc != SQLITE_OK)
1877 XO(
"Unable to prepare project file command:\n\n%s").
Format(sql)
1889 sqlite3_bind_zeroblob(stmt, 1, dict.
GetSize()) ||
1890 sqlite3_bind_zeroblob(stmt, 2, data.
GetSize()))
1900 const auto reportError = [
this](
auto sql) {
1902 XO(
"Failed to update the project file.\nThe following command failed:\n\n%s")
1906 rc = sqlite3_step(stmt);
1908 if (rc != SQLITE_DONE)
1919 sqlite3_finalize(stmt);
1926 const wxString rowIDSql =
1927 wxString::Format(
"SELECT ROWID FROM %s.%s WHERE id = 1;", schema, table);
1929 if (!
GetValue(rowIDSql, rowID,
true))
1934 reportError(rowIDSql);
1938 const auto writeStream = [db, schema, table, rowID,
this](
const char* column,
const MemoryStream& stream) {
1953 for (
auto chunk : stream)
1955 if (SQLITE_OK != blobStream->Write(chunk.first, chunk.second))
1966 if (blobStream->Close() != SQLITE_OK)
1969 "sqlite3.rc", std::to_string(sqlite3_errcode(db)));
1972 "sqlite3.context",
"ProjectGileIO::WriteDoc::writeBlobStream");
1982 if (!writeStream(
"dict", dict))
1985 if (!writeStream(
"doc", data))
1988 const auto requiredVersion =
1991 const wxString setVersionSql =
1992 wxString::Format(
"PRAGMA user_version = %u", requiredVersion.GetPacked());
1994 if (!
Query(setVersionSql.c_str(), [](
auto...) { return 0; }))
1999 reportError(setVersionSql);
2003 return transaction.
Commit();
2008 : mProjectFileIO{ projectFileIO }
2015 : mProjectFileIO{ other.mProjectFileIO }
2017 , mCommitted{ other.mCommitted }
2019 other.mCommitted =
true;
2025 mProjectFileIO.RestoreConnection();
2035 if (!mCommitted && !
mFileName.empty()) {
2037 mProjectFileIO.DiscardConnection();
2043 -> std::optional<TentativeConnection>
2045 auto now = std::chrono::high_resolution_clock::now();
2047 std::optional<TentativeConnection> result{ *
this };
2049 bool success =
false;
2059 GetValue(
"SELECT ROWID FROM main.autosave WHERE id = 1;", rowId,
true);
2061 int64_t rowsCount = 0;
2065 (!
GetValue(
"SELECT COUNT(1) FROM main.project;", rowsCount,
true) || rowsCount == 0))
2076 if (!useAutosave && !
GetValue(
"SELECT ROWID FROM main.project WHERE id = 1;", rowId,
false))
2082 DB(),
"main", useAutosave ?
"autosave" :
"project", rowId);
2089 XO(
"Unable to parse project information.")
2098 ->GetActiveBlockIDs();
2099 if (blockids.size() > 0)
2122 wxString queryResult;
2123 success =
GetValue(
"SELECT Count(*) FROM project;", queryResult);
2129 result->SetFileName(fileName);
2131 auto duration = std::chrono::high_resolution_clock::now() - now;
2134 "Project loaded in %lld ms",
2135 std::chrono::duration_cast<std::chrono::milliseconds>(duration).count());
2174 bool reopened =
false;
2176 if (
true == (moved =
MoveProject(savedName, fileName)))
2194 if (reopened && !moved) {
2203"The project's database failed to reopen, "
2204"possibly because of limited space on the storage device."),
2205 "Error:_Disk_full_or_not_writable"
2221 if (!
CopyTo(fileName,
XO(
"Saving project"),
false))
2224 XO(
"Error Saving Project"),
2226 "Error:_Disk_full_or_not_writable"
2232 Connection newConn = std::make_unique<DBConnection>(
2234 [
this]{ OnCheckpointFailure(); });
2247 std::atomic_bool done = {
false};
2248 bool success =
true;
2249 auto thread = std::thread([&]
2251 auto rc = newConn->Open(fileName);
2252 if (rc != SQLITE_OK)
2264 XO(
"Syncing"),
XO(
"This may take several seconds"));
2270 using namespace std::chrono;
2271 std::this_thread::sleep_for(50ms);
2280 XO(
"Error Saving Project"),
2281 XO(
"The project failed to open, possibly due to limited space\n"
2282 "on the storage device.\n\n%s").Format(
GetLastError()),
2283 "Error:_Disk_full_or_not_writable");
2288 if (!wxRemoveFile(fileName))
2290 wxLogMessage(
"Failed to remove destination project after open failure: %s", fileName);
2302 XO(
"Error Saving Project"),
2303 XO(
"Unable to remove autosave information, possibly due to limited space\n"
2304 "on the storage device.\n\n%s").Format(
GetLastError()),
2305 "Error:_Disk_full_or_not_writable");
2310 if (!wxRemoveFile(fileName))
2312 wxLogMessage(
"Failed to remove destination project after AutoSaveDelete failure: %s", fileName);
2331 Compact( { lastSaved ? lastSaved : empty.get() }, true );
2345 XO(
"Error Saving Project"),
2347 "Error:_Disk_full_or_not_writable"
2373 return CopyTo(fileName,
XO(
"Backing up project"),
false,
true,
2387 wxLogDebug(
"Closing project with no database connection");
2404 wxFileName file(filename);
2405 file.SetFullName(
wxT(
""));
2442 wxLongLong freeSpace;
2443 if (wxGetDiskSpace(wxPathOnly(
mFileName), NULL, &freeSpace))
2447 constexpr auto limit = 1ll << 32;
2452 auto length = wxFile{
mFileName}.Length();
2455 if (length == wxInvalidSize)
2457 auto free = std::max<wxLongLong>(0, limit - length);
2470 const wxString &helpPage)
2504 currConn->SetError(msg, libraryError, errorCode);
2512 currConn->SetDBError(msg, libraryError, errorCode);
2528 currConn->SetBypass(
true );
2542 currConn->SetBypass(
false );
2558 const std::vector<const TrackList*> &trackLists)
const
2560 unsigned long long current = 0;
2566 for (
auto pTracks: trackLists)
2588 sqlite3_stmt* stmt =
nullptr;
2592 static const char* statement =
2594 sum(length(blockid) + length(sampleformat) +
2595 length(summin) + length(summax) + length(sumrms) +
2596 length(summary256) + length(summary64k) +
2598FROM sampleblocks;)";
2604 static const char* statement =
2606 length(blockid) + length(sampleformat) +
2607 length(summin) + length(summax) + length(sumrms) +
2608 length(summary256) + length(summary64k) +
2610FROM sampleblocks WHERE blockid = ?1;)";
2615 auto cleanup =
finally(
2618 if (stmt !=
nullptr)
2620 sqlite3_clear_bindings(stmt);
2621 sqlite3_reset(stmt);
2627 int rc = sqlite3_bind_int64(stmt, 1, blockid);
2629 if (rc != SQLITE_OK)
2632 "sqlite3.rc", std::to_string(rc));
2635 "sqlite3.context",
"ProjectFileIO::GetDiskUsage::bind");
2641 int rc = sqlite3_step(stmt);
2643 if (rc != SQLITE_ROW)
2648 "sqlite3.context",
"ProjectFileIO::GetDiskUsage::step");
2653 const int64_t
size = sqlite3_column_int64(stmt, 0);
2666 projectFileIO.SetBypass();
2675 projectFileIO.CloseProject();
2684 if ( !projectFileIO.AutoSave() )
2687 XO(
"Automatic database backup failed."),
2689 "Error:_Disk_full_or_not_writable"
@ Internal
Indicates internal failure from Audacity.
SimpleGuard< R > MakeSimpleGuard(R value) noexcept(noexcept(SimpleGuard< R >{ value }))
Convert a value to a handler function returning that value, suitable for GuardedCall<R>
Toolkit-neutral facade for basic user interface services.
Declare functions to perform UTF-8 to std::wstring conversions.
Declare DBConnection, which maintains database connection and associated status and background thread...
std::unique_ptr< DBConnection > Connection
const TranslatableString name
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.
Declare functions to convert numeric types to string representation.
#define THROW_INCONSISTENCY_EXCEPTION
Throw InconsistencyException, using C++ preprocessor to identify the source code location.
std::unique_ptr< const BasicUI::WindowPlacement > ProjectFramePlacement(AudacityProject *project)
Make a WindowPlacement object suitable for project (which may be null)
#define AUDACITY_FILE_FORMAT_VERSION
static ProjectHistory::AutoSave::Scope scope
Install the callback from undo manager.
static const int ProjectFileID
static const AudacityProject::AttachedObjects::RegisteredFactory sFileIOKey
#define PACK(b1, b2, b3, b4)
static int ExecCallback(void *data, int cols, char **vals, char **names)
static const char * ProjectFileSchema
std::unordered_set< SampleBlockID > BlockIDs
@ CheckpointFailure
Failure happened in a worker thread.
@ ProjectTitleChange
A normal occurrence.
std::function< void(const SampleBlock &) > BlockSpaceUsageAccumulator(unsigned long long &total)
#define ADD_EXCEPTION_CONTEXT(name, value)
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
void InspectBlocks(const TrackList &tracks, BlockInspector inspector, SampleBlockIDSet *pIDs)
std::unordered_set< SampleBlockID > SampleBlockIDSet
std::vector< Attribute > AttributesList
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Subclasses may hold information such as a parent window pointer for a dialog.
BufferedProjectBlobStream(sqlite3 *db, const char *schema, const char *table, int64_t rowID)
static constexpr std::array< const char *, 2 > Columns
bool OpenBlob(size_t index)
bool HasMoreData() const override
size_t ReadData(void *buffer, size_t maxBytes) override
std::optional< SQLiteBlobStream > mBlobStream
A facade-like class, that implements buffered reading from the underlying data stream.
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
static ConnectionPtr & Get(AudacityProject &project)
void ThrowException(bool write) const
throw and show appropriate message box
sqlite3_stmt * Prepare(enum StatementID id, const char *sql)
static TranslatableString WriteFailureMessage(const wxFileName &fileName)
typename GlobalVariable< AutoSave, const std::function< void(AudacityProject &) >, nullptr, Options... >::Scope Scope
InvisibleTemporaryProject()
AudacityProject & Project()
std::shared_ptr< AudacityProject > mpProject
~InvisibleTemporaryProject()
A low overhead memory stream with O(1) append, low heap fragmentation and a linear memory view.
const size_t GetSize() const noexcept
CallbackReturn Publish(const ProjectFileIOMessage &message)
Send a message to connected callbacks.
BackupProject(ProjectFileIO &projectFileIO, const FilePath &path)
Rename project file at path, and any auxiliary files, to backup path names.
~BackupProject()
if !IsOk() do nothing; else if Discard() was not called, undo the renaming
void Discard()
if !IsOk() do nothing; else remove backup files
Object associated with a project that manages reading and writing of Audacity project file formats,...
AudacityProject & mProject
DBConnection & GetConnection()
Return a reference to a connection, creating it as needed on demand; throw on failure.
bool AutoSave(bool recording=false)
void OnCheckpointFailure()
bool MoveProject(const FilePath &src, const FilePath &dst)
void UpdatePrefs() override
static bool RemoveProject(const FilePath &filename)
Remove any files associated with a project at given path; return true if successful.
bool UpdateSaved(const TrackList *tracks=nullptr)
bool CopyTo(const FilePath &destpath, const TranslatableString &msg, bool isTemporary, bool prune=false, const std::vector< const TrackList * > &tracks={})
const TranslatableString & GetLibraryError() const
void SetProjectTitle(int number=-1)
void UseConnection(Connection &&conn, const FilePath &filePath)
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.
std::optional< TentativeConnection > LoadProject(const FilePath &fileName, bool ignoreAutosave)
bool GetValue(const char *sql, wxString &value, bool silent=false)
int64_t GetBlockUsage(SampleBlockID blockid)
static ProjectFileIO & Get(AudacityProject &project)
std::function< int(int cols, char **vals, char **names)> ExecCB
wxString GenerateDoc()
Return a strings representation of the active project XML doc.
void SetFileName(const FilePath &fileName)
const FilePath & GetFileName() const
bool OpenConnection(FilePath fileName={})
bool RenameOrWarn(const FilePath &src, const FilePath &dst)
Rename a file or put up appropriate warning message.
bool SaveProject(const FilePath &fileName, const TrackList *lastSaved)
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.
bool InstallSchema(sqlite3 *db, const char *schema="main")
void Compact(const std::vector< const TrackList * > &tracks, bool force=false)
int Exec(const char *query, const ExecCB &callback, bool silent=false)
int GetLastErrorCode() const
bool DeleteBlocks(const BlockIDs &blockids, bool complement)
bool SaveCopy(const FilePath &fileName)
bool ShouldCompact(const std::vector< const TrackList * > &tracks)
static FilePath SafetyFileName(const FilePath &src)
Generate a name for short-lived backup project files from an existing project.
bool Query(const char *sql, const ExecCB &callback, bool silent=false)
void WriteXMLHeader(XMLWriter &xmlFile) const
int64_t GetCurrentUsage(const std::vector< const TrackList * > &trackLists) const
XMLTagHandler * HandleXMLChild(const std::string_view &tag) override
const TranslatableString & GetLastError() const
void WriteXML(XMLWriter &xmlFile, bool recording=false, const TrackList *tracks=nullptr)
void SetError(const TranslatableString &msg, const TranslatableString &libraryError={}, int errorCode={})
Just set stored errors.
bool AutoSaveDelete(sqlite3 *db=nullptr)
static bool InitializeSQL()
static const std::vector< wxString > & AuxiliaryFileSuffixes()
static void InSet(sqlite3_context *context, int argc, sqlite3_value **argv)
std::shared_ptr< DBConnectionErrors > mpErrors
bool WriteDoc(const char *table, const ProjectSerializer &autosave, const char *schema="main")
ProjectFileIO(AudacityProject &project)
bool HandleXMLTag(const std::string_view &tag, const AttributesList &attrs) override
wxLongLong GetFreeDiskSpace() const
bool HasConnection() const
Return true if a connection is now open.
static int64_t GetDiskUsage(DBConnection &conn, SampleBlockID blockid)
const wxString & GetLastLog() const
a class used to (de)serialize the project catalog
static bool Decode(BufferedStreamReader &in, XMLTagHandler *handler)
const MemoryStream & GetData() const
const MemoryStream & GetDict() const
bool IsOpen() const noexcept
int Write(const void *ptr, int size) noexcept
~SQLiteBlobStream() noexcept
int Read(void *ptr, int &size) noexcept
SQLiteBlobStream(sqlite3_blob *blob, bool readOnly) noexcept
static std::optional< SQLiteBlobStream > Open(sqlite3 *db, const char *schema, const char *table, const char *column, int64_t rowID, bool readOnly) noexcept
SQLiteBlobStream & operator=(SQLiteBlobStream &&rhs) noexcept
bool IsEof() const noexcept
SQLiteBlobStream(SQLiteBlobStream &&rhs) noexcept
static void LogCallback(void *WXUNUSED(arg), int code, const char *msg)
A MessageBoxException that shows a given, unvarying string.
Abstract base class for an object holding data associated with points on a time axis.
std::shared_ptr< Track > SubstitutePendingChangedTrack()
An in-session identifier of track objects across undo states. It does not persist between sessions.
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
static TrackListHolder Create(AudacityProject *pOwner)
static TrackList & Get(AudacityProject &project)
RAII for a database transaction, possibly nested.
bool Commit()
Commit the transaction.
Holds a msgid for the translation catalog; may also bind format arguments.
static WaveTrackFactory & Get(AudacityProject &project)
const SampleBlockFactoryPtr & GetSampleBlockFactory() const
XMLTagHandler * CallObjectAccessor(const std::string_view &tag, Host &host)
static XMLMethodRegistry & Get()
Get the unique instance.
void CallWriters(const Host &host, XMLWriter &writer)
Wrapper to output XML data to strings.
This class is an interface which should be implemented by classes which wish to be able to load and s...
Base class for XMLFileWriter and XMLStringWriter that provides the general functionality for creating...
virtual void StartTag(const wxString &name)
void WriteAttr(const wxString &name, const Identifier &value)
virtual void EndTag(const wxString &name)
virtual void Write(const wxString &data)=0
PROJECT_FILE_IO_API void Remove(const FilePath &path)
PROJECT_FILE_IO_API void Add(const FilePath &path)
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)
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
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.
void Yield()
Dispatch waiting events, including actions enqueued by CallAfter.
std::unique_ptr< ProgressDialog > MakeProgress(const TranslatableString &title, const TranslatableString &message, unsigned flags=(ProgressShowStop|ProgressShowCancel), const TranslatableString &remainingLabelText={})
Create and display a progress dialog.
UTILITY_API const char *const * argv
A copy of argv; responsibility of application startup to assign it.
UTILITY_API int argc
A copy of argc; responsibility of application startup to assign it.
FILES_API bool IsOnFATFileSystem(const FilePath &path)
FILES_API wxString AbbreviatePath(const wxFileName &fileName)
Give enough of the path to identify the device. (On Windows, drive letter plus ':')
FILES_API wxString UnsavedProjectFileName()
FILES_API wxString TempDir()
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
std::wstring ToWString(const std::string &str)
Options for variations of error dialogs; the default is for modal dialogs.
ErrorDialogOptions && Log(std::wstring log_) &&
std::errc ec
A pointer to the first character not matching the pattern.
ProjectFileIO & mProjectFileIO
void SetFileName(const FilePath &fileName)
TentativeConnection(ProjectFileIO &projectFileIO)