Audacity 3.2.0
CloudProjectsDatabase.cpp
Go to the documentation of this file.
1/* SPDX-License-Identifier: GPL-2.0-or-later */
2/*!********************************************************************
3
4 Audacity: A Digital Audio Editor
5
6 CloudProjectsDatabase.cpp
7
8 Dmitry Vedenko
9
10**********************************************************************/
12
13#include <cassert>
14
15#include "CodeConversions.h"
16#include "FileNames.h"
17
19{
20namespace
21{
22const char* createTableQuery = R"(
23CREATE TABLE IF NOT EXISTS projects
24(
25 project_id TEXT,
26 snapshot_id TEXT,
27 saves_count INTEGER,
28 last_audio_preview_save INTEGER,
29 local_path TEXT,
30 last_modified INTEGER,
31 last_read INTEGER,
32 sync_status INTEGER,
33 PRIMARY KEY (project_id)
34);
35
36CREATE INDEX IF NOT EXISTS local_path_index ON projects (local_path);
37
38CREATE TABLE IF NOT EXISTS block_hashes
39(
40 project_id INTEGER,
41 block_id INTEGER,
42 hash TEXT,
43 PRIMARY KEY (project_id, block_id)
44);
45
46CREATE INDEX IF NOT EXISTS block_hashes_index ON block_hashes (hash);
47
48CREATE TABLE IF NOT EXISTS pending_snapshots
49(
50 project_id TEXT,
51 snapshot_id TEXT,
52 confirm_url TEXT,
53 PRIMARY KEY (project_id, snapshot_id)
54);
55
56CREATE TABLE IF NOT EXISTS pending_project_blobs
57(
58 project_id TEXT,
59 snapshot_id TEXT,
60
61 upload_url TEXT,
62 confirm_url TEXT,
63 fail_url TEXT,
64
65 blob BLOB,
66 PRIMARY KEY (project_id, snapshot_id)
67);
68
69CREATE TABLE IF NOT EXISTS pending_project_blocks
70(
71 project_id TEXT,
72 snapshot_id TEXT,
73
74 upload_url TEXT,
75 confirm_url TEXT,
76 fail_url TEXT,
77
78 block_id INTEGER,
79 block_sample_format INTEGER,
80 block_hash TEXT,
81 PRIMARY KEY (project_id, snapshot_id, block_id)
82);
83
84CREATE TABLE IF NOT EXISTS project_users
85(
86 project_id INTEGER,
87 user_name TEXT,
88 PRIMARY KEY (project_id)
89);
90
91CREATE TABLE IF NOT EXISTS migration
92(
93 version INTEGER
94);
95
96INSERT INTO migration (version) VALUES (0);
97
98DELETE FROM migration WHERE ROWID != 1;
99)";
100
102ALTER TABLE projects ADD COLUMN synced_dialog_shown INTEGER DEFAULT 1;
103)";
104
105const char* migrations[] = {
107};
108} // namespace
109
111{
112 static CloudProjectsDatabase instance;
113 return instance;
114}
115
117{
118 auto lock = std::lock_guard { mConnectionMutex };
119
120 if (!mConnection)
122
123 // It is safe to call lock on a null connection
125}
126
128{
129 return const_cast<CloudProjectsDatabase*>(this)->GetConnection();
130}
131
132std::optional<DBProjectData>
133CloudProjectsDatabase::GetProjectData(std::string_view projectId) const
134{
135 auto connection = GetConnection();
136
137 if (!connection)
138 return {};
139
140 auto statement = connection->CreateStatement(
141 "SELECT project_id, snapshot_id, saves_count, last_audio_preview_save, local_path, last_modified, last_read, sync_status, synced_dialog_shown FROM projects WHERE project_id = ? LIMIT 1");
142
143 if (!statement)
144 return {};
145
146 return DoGetProjectData(statement->Prepare(projectId).Run());
147}
148
150 const std::string& projectFilePath) const
151{
152 auto connection = GetConnection();
153
154 if (!connection)
155 return {};
156
157 auto statement = connection->CreateStatement(
158 "SELECT project_id, snapshot_id, saves_count, last_audio_preview_save, local_path, last_modified, last_read, sync_status, synced_dialog_shown FROM projects WHERE local_path = ? LIMIT 1");
159
160 if (!statement)
161 return {};
162
163 return DoGetProjectData(statement->Prepare(projectFilePath).Run());
164}
165
166std::vector<DBProjectData>
168{
169 std::vector<DBProjectData> result;
170
171 auto connection = GetConnection();
172
173 if (!connection)
174 return result;
175
176 auto statement = connection->CreateStatement(
177 "SELECT project_id, snapshot_id, saves_count, last_audio_preview_save, local_path, last_modified, last_read, sync_status, synced_dialog_shown FROM projects");
178
179 if (!statement)
180 return result;
181
182 auto runResult = statement->Prepare().Run();
183
184 for (auto row : runResult)
185 {
186 auto data = DoGetProjectData(row);
187
188 if (data)
189 result.push_back(*data);
190 }
191
192 return result;
193}
194
196 std::string_view projectId)
197{
198 auto connection = GetConnection();
199
200 if (!connection)
201 return;
202
203 auto tx = connection->BeginTransaction("DeleteProject");
204 if (DeleteProject(connection, projectId))
205 tx.Commit();
206}
207
209 std::string_view projectId, std::string_view snapshotId)
210{
211 auto connection = GetConnection();
212
213 if (!connection)
214 return false;
215
216 auto statement = connection->CreateStatement(
217 "UPDATE projects SET sync_status = ? WHERE project_id = ? AND snapshot_id = ?");
218
219 if (!statement)
220 return false;
221
222 auto result = statement
223 ->Prepare(
224 static_cast<int>(DBProjectData::SyncStatusSynced),
225 projectId, snapshotId)
226 .Run();
227
228 if (!result.IsOk())
229 return false;
230
231 return result.GetModifiedRowsCount() > 0;
232}
233
235 std::string_view projectId, const SampleBlockIDSet& blockSet)
236{
237 auto connection = GetConnection();
238
239 if (!connection)
240 return;
241
242 auto inProjectSet = connection->CreateScalarFunction(
243 "inProjectSet", [&blockSet](int64_t blockIndex)
244 { return blockSet.find(blockIndex) != blockSet.end(); });
245
246 auto statement = connection->CreateStatement(
247 "DELETE FROM block_hashes WHERE project_id = ? AND NOT inProjectSet(block_id)");
248
249 auto result = statement->Prepare(projectId).Run();
250
251 if (!result.IsOk())
252 {
253 assert(false);
254 }
255}
256
257std::optional<std::string> CloudProjectsDatabase::GetBlockHash(
258 std::string_view projectId, int64_t blockId) const
259{
260 auto connection = GetConnection();
261
262 if (!connection)
263 return {};
264
265 auto statement = connection->CreateStatement(
266 "SELECT hash FROM block_hashes WHERE project_id = ? AND block_id = ? LIMIT 1");
267
268 if (!statement)
269 return {};
270
271 auto result = statement->Prepare(projectId, blockId).Run();
272
273 for (auto row : result)
274 {
275 std::string hash;
276
277 if (!row.Get(0, hash))
278 return {};
279
280 return hash;
281 }
282
283 return {};
284}
285
287 std::string_view projectId,
288 const std::vector<std::pair<int64_t, std::string>>& hashes)
289{
290 auto connection = GetConnection();
291
292 if (!connection)
293 return;
294
295 const int localVar {};
296 auto transaction = connection->BeginTransaction(
297 std::string("UpdateBlockHashes_") +
298 std::to_string(reinterpret_cast<size_t>(&localVar)));
299
300 auto statement = connection->CreateStatement(
301 "INSERT OR REPLACE INTO block_hashes (project_id, block_id, hash) VALUES (?, ?, ?)");
302
303 for (const auto& [blockId, hash] : hashes)
304 statement->Prepare(projectId, blockId, hash).Run();
305
306 transaction.Commit();
307}
308
310{
311 auto connection = GetConnection();
312
313 if (!connection)
314 return false;
315
316 auto tx = connection->BeginTransaction("UpdateProjectData");
317
318 auto updateProjectData = connection->CreateStatement(
319 "INSERT OR REPLACE INTO projects (project_id, snapshot_id, saves_count, last_audio_preview_save, local_path, last_modified, last_read, sync_status, synced_dialog_shown) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
320
321 if (!updateProjectData)
322 return false;
323
324 auto result = updateProjectData
325 ->Prepare(
326 projectData.ProjectId, projectData.SnapshotId,
327 projectData.SavesCount, projectData.LastAudioPreview,
328 projectData.LocalPath, projectData.LastModified,
329 projectData.LastRead, projectData.SyncStatus,
330 projectData.FirstSyncDialogShown)
331 .Run();
332
333 if (!result.IsOk())
334 return false;
335
336 auto listMissingProjects = connection->CreateStatement (
337 "SELECT project_id FROM projects WHERE project_id != ? AND local_path = ?");
338
339 if (!listMissingProjects)
340 return false;
341
342 auto missingProjects = listMissingProjects->Prepare(
343 projectData.ProjectId, projectData.LocalPath).Run();
344
345 for (auto row : missingProjects)
346 {
347 std::string missingProjectId;
348
349 if (!row.Get(0, missingProjectId))
350 return false;
351
352 if (!DeleteProject(connection, missingProjectId))
353 return false;
354 }
355
356 return tx.Commit().IsOk();
357}
358
360 std::string_view projectId) const
361{
362 auto connection = GetConnection();
363
364 if (!connection)
365 return false;
366
367 auto statement = connection->CreateStatement (
368 "SELECT synced_dialog_shown FROM projects WHERE project_id = ? LIMIT 1");
369
370 if (!statement)
371 return false;
372
373 auto result = statement->Prepare(projectId).Run();
374
375 for (auto row : result)
376 {
377 bool shown;
378
379 if (!row.Get(0, shown))
380 return false;
381
382 return shown;
383 }
384
385 return false;
386}
387
389 std::string_view projectId, bool shown)
390{
391 auto connection = GetConnection();
392
393 if (!connection)
394 return;
395
396 auto statement = connection->CreateStatement (
397 "UPDATE projects SET synced_dialog_shown = ? WHERE project_id = ?");
398
399 if (!statement)
400 return;
401
402 statement->Prepare(shown, projectId).Run();
403}
404
405std::string
407{
408 auto connection = GetConnection();
409
410 if (!connection)
411 return {};
412
413 auto statement = connection->CreateStatement(
414 "SELECT user_name FROM project_users WHERE project_id = ? LIMIT 1");
415
416 if (!statement)
417 return {};
418
419 auto result = statement->Prepare(projectId).Run();
420
421 for (auto row : result)
422 {
423 std::string slug;
424
425 if (!row.Get(0, slug))
426 return {};
427
428 return slug;
429 }
430
431 return {};
432}
433
435 std::string_view projectId, std::string_view slug)
436{
437 auto connection = GetConnection();
438
439 if (!connection)
440 return;
441
442 auto statement = connection->CreateStatement(
443 "INSERT OR REPLACE INTO project_users (project_id, user_name) VALUES (?, ?)");
444
445 if (!statement)
446 return;
447
448 statement->Prepare(projectId, slug).Run();
449}
450
452 std::string_view projectId, int64_t blockId) const
453{
454 auto connection = GetConnection();
455
456 if (!connection)
457 return false;
458
459 auto statement = connection->CreateStatement(
460 "SELECT 1 FROM pending_project_blocks WHERE project_id = ? AND block_id = ? LIMIT 1");
461
462 if (!statement)
463 return false;
464
465 auto result = statement->Prepare(projectId, blockId).Run();
466
467 for (auto row : result)
468 return true;
469
470 return false;
471}
472
474 const PendingSnapshotData& snapshotData)
475{
476 auto connection = GetConnection();
477
478 if (!connection)
479 return;
480
481 auto statement = connection->CreateStatement(
482 "INSERT OR REPLACE INTO pending_snapshots (project_id, snapshot_id, confirm_url) VALUES (?, ?, ?)");
483
484 if (!statement)
485 return;
486
487 statement
488 ->Prepare(
489 snapshotData.ProjectId, snapshotData.SnapshotId,
490 snapshotData.ConfirmUrl)
491 .Run();
492}
493
495 std::string_view projectId, std::string_view snapshotId)
496{
497 auto connection = GetConnection();
498
499 if (!connection)
500 return;
501
502 static const char* queries[] = {
503 "DELETE FROM pending_snapshots WHERE project_id = ? AND snapshot_id = ?",
504 "DELETE FROM pending_project_blobs WHERE project_id = ? AND snapshot_id = ?",
505 "DELETE FROM pending_project_blocks WHERE project_id = ? AND snapshot_id = ?",
506 };
507
508 auto tx = connection->BeginTransaction("RemovePendingSnapshot");
509
510 for (auto query : queries)
511 {
512 auto statement = connection->CreateStatement(query);
513
514 if (!statement)
515 return;
516
517 if (!statement->Prepare(projectId, snapshotId).Run().IsOk())
518 return;
519 }
520
521 tx.Commit();
522}
523
524std::vector<PendingSnapshotData>
525CloudProjectsDatabase::GetPendingSnapshots(std::string_view projectId) const
526{
527 auto connection = GetConnection();
528
529 if (!connection)
530 return {};
531
532 auto statement = connection->CreateStatement(
533 "SELECT project_id, snapshot_id, confirm_url FROM pending_snapshots WHERE project_id = ?");
534
535 if (!statement)
536 return {};
537
538 auto result = statement->Prepare(projectId).Run();
539
540 std::vector<PendingSnapshotData> snapshots;
541
542 for (auto row : result)
543 {
545
546 if (!row.Get(0, data.ProjectId))
547 return {};
548
549 if (!row.Get(1, data.SnapshotId))
550 return {};
551
552 if (!row.Get(2, data.ConfirmUrl))
553 return {};
554
555 snapshots.push_back(data);
556 }
557
558 return snapshots;
559}
560
562 const PendingProjectBlobData& blobData)
563{
564 auto connection = GetConnection();
565
566 if (!connection)
567 return;
568
569 auto statement = connection->CreateStatement(
570 "INSERT OR REPLACE INTO pending_project_blobs (project_id, snapshot_id, upload_url, confirm_url, fail_url, blob) VALUES (?, ?, ?, ?, ?, ?)");
571
572 if (!statement)
573 return;
574
575 statement->Prepare()
576 .Bind(1, blobData.ProjectId)
577 .Bind(2, blobData.SnapshotId)
578 .Bind(3, blobData.UploadUrl)
579 .Bind(4, blobData.ConfirmUrl)
580 .Bind(5, blobData.FailUrl)
581 .Bind(6, blobData.BlobData.data(), blobData.BlobData.size(), false)
582 .Run();
583}
584
586 std::string_view projectId, std::string_view snapshotId)
587{
588 auto connection = GetConnection();
589
590 if (!connection)
591 return;
592
593 auto statement = connection->CreateStatement(
594 "DELETE FROM pending_project_blobs WHERE project_id = ? AND snapshot_id = ?");
595
596 if (!statement)
597 return;
598
599 statement->Prepare(projectId, snapshotId).Run();
600}
601
602std::optional<PendingProjectBlobData>
604 std::string_view projectId, std::string_view snapshotId) const
605{
606 auto connection = GetConnection();
607
608 if (!connection)
609 return {};
610
611 auto statement = connection->CreateStatement(
612 "SELECT project_id, snapshot_id, upload_url, confirm_url, fail_url, blob FROM pending_project_blobs WHERE project_id = ? AND snapshot_id = ?");
613
614 if (!statement)
615 return {};
616
617 auto result = statement->Prepare(projectId, snapshotId).Run();
618
619 for (auto row : result)
620 {
622
623 if (!row.Get(0, data.ProjectId))
624 return {};
625
626 if (!row.Get(1, data.SnapshotId))
627 return {};
628
629 if (!row.Get(2, data.UploadUrl))
630 return {};
631
632 if (!row.Get(3, data.ConfirmUrl))
633 return {};
634
635 if (!row.Get(4, data.FailUrl))
636 return {};
637
638 const auto size = row.GetColumnBytes(6);
639 data.BlobData.resize(size);
640
641 if (size != row.ReadData(6, data.BlobData.data(), size))
642 return {};
643
644 return data;
645 }
646
647 return {};
648}
649
651 const std::vector<PendingProjectBlockData>& blockData)
652{
653 auto connection = GetConnection();
654
655 if (!connection)
656 return;
657
658 auto tx = connection->BeginTransaction("AddPendingProjectBlocks");
659
660 auto statement = connection->CreateStatement(
661 "INSERT OR REPLACE INTO pending_project_blocks (project_id, snapshot_id, upload_url, confirm_url, fail_url, block_id, block_sample_format, block_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
662
663 if (!statement)
664 return;
665
666 for (const auto& data : blockData)
667 statement->Prepare()
668 .Bind(1, data.ProjectId)
669 .Bind(2, data.SnapshotId)
670 .Bind(3, data.UploadUrl)
671 .Bind(4, data.ConfirmUrl)
672 .Bind(5, data.FailUrl)
673 .Bind(6, data.BlockId)
674 .Bind(7, data.BlockSampleFormat)
675 .Bind(8, data.BlockHash)
676 .Run();
677
678 tx.Commit();
679}
680
682 std::string_view projectId, int64_t blockId)
683{
684 auto connection = GetConnection();
685
686 if (!connection)
687 return;
688
689 auto statement = connection->CreateStatement(
690 "DELETE FROM pending_project_blocks WHERE project_id = ? AND block_id = ?");
691
692 if (!statement)
693 return;
694
695 statement->Prepare(projectId, blockId).Run();
696}
697
699 std::string_view projectId, std::string_view snapshotId)
700{
701 auto connection = GetConnection();
702
703 if (!connection)
704 return;
705
706 auto statement = connection->CreateStatement(
707 "DELETE FROM pending_project_blocks WHERE project_id = ? AND snapshot_id = ?");
708
709 if (!statement)
710 return;
711
712 statement->Prepare(projectId, snapshotId).Run();
713}
714
715std::vector<PendingProjectBlockData>
717 std::string_view projectId, std::string_view snapshotId)
718{
719 auto connection = GetConnection();
720
721 if (!connection)
722 return {};
723
724 auto statement = connection->CreateStatement(
725 "SELECT project_id, snapshot_id, upload_url, confirm_url, fail_url, block_id, block_sample_format, block_hash FROM pending_project_blocks WHERE project_id = ? AND snapshot_id = ?");
726
727 if (!statement)
728 return {};
729
730 auto result = statement->Prepare(projectId, snapshotId).Run();
731
732 std::vector<PendingProjectBlockData> blocks;
733
734 for (auto row : result)
735 {
737
738 if (!row.Get(0, data.ProjectId))
739 return {};
740
741 if (!row.Get(1, data.SnapshotId))
742 return {};
743
744 if (!row.Get(2, data.UploadUrl))
745 return {};
746
747 if (!row.Get(3, data.ConfirmUrl))
748 return {};
749
750 if (!row.Get(4, data.FailUrl))
751 return {};
752
753 if (!row.Get(5, data.BlockId))
754 return {};
755
756 if (!row.Get(6, data.BlockSampleFormat))
757 return {};
758
759 if (!row.Get(7, data.BlockHash))
760 return {};
761
762 blocks.push_back(data);
763 }
764
765 return blocks;
766}
767
768std::optional<DBProjectData>
770{
771 DBProjectData data;
772
773 if (!row.Get(0, data.ProjectId))
774 return {};
775
776 if (!row.Get(1, data.SnapshotId))
777 return {};
778
779 if (!row.Get(2, data.SavesCount))
780 return {};
781
782 if (!row.Get(3, data.LastAudioPreview))
783 return {};
784
785 if (!row.Get(4, data.LocalPath))
786 return {};
787
788 if (!row.Get(5, data.LastModified))
789 return {};
790
791 if (!row.Get(6, data.LastRead))
792 return {};
793
794 int status;
795 if (!row.Get(7, status))
796 return {};
797
798 data.SyncStatus = static_cast<DBProjectData::SyncStatusType>(status);
799
800 if (!row.Get(8, data.FirstSyncDialogShown))
801 return {};
802
803 return data;
804}
805
806std::optional<DBProjectData>
808 sqlite::RunResult result) const
809{
810 for (auto row : result)
811 {
812 if (auto data = DoGetProjectData(row))
813 return data;
814 }
815
816 return {};
817}
818
820{
821 if (mConnection)
822 return true;
823
824 const auto configDir = FileNames::ConfigDir();
825 const auto configPath = configDir + "/audiocom_sync.db";
826
828
829 if (!mConnection)
830 return false;
831
832 auto connection = mConnection->Acquire();
833
834 auto result = connection->Execute(createTableQuery);
835
836 if (!result)
837 {
838 mConnection = {};
839 return false;
840 }
841
842 return RunMigrations();
843}
844
846{
847 auto connection = mConnection->Acquire();
848
849 auto getMigrationVersion =
850 connection->CreateStatement("SELECT version FROM migration LIMIT 1");
851
852 if (!getMigrationVersion)
853 return false;
854
855 auto versionResult = getMigrationVersion->Prepare().Run();
856
857 if (!versionResult.IsOk())
858 return false;
859
860 int version = 0;
861
862 for (auto row : versionResult)
863 {
864 if (!row.Get(0, version))
865 return false;
866 break;
867 }
868
869 const auto migrationsCount = std::size(migrations);
870
871 if (version >= migrationsCount)
872 return true;
873
874 auto tx = connection->BeginTransaction("RunMigrations");
875
876 for (int i = version; i < migrationsCount; ++i)
877 {
878 auto result = connection->Execute(migrations[i]);
879
880 if (!result)
881 return false;
882 }
883
884 auto updateVersion =
885 connection->CreateStatement("UPDATE migration SET version = ?");
886
887 if (!updateVersion)
888 return false;
889
890 if (!updateVersion->Prepare(migrationsCount).Run().IsOk())
891 return false;
892
893 return tx.Commit().IsOk();
894}
895
897 sqlite::SafeConnection::Lock& connection, std::string_view projectId)
898{
899 static const char* queries[] = {
900 "DELETE FROM projects WHERE project_id = ?",
901 "DELETE FROM block_hashes WHERE project_id = ?",
902 "DELETE FROM pending_snapshots WHERE project_id = ?",
903 "DELETE FROM pending_project_blobs WHERE project_id = ?",
904 "DELETE FROM pending_project_blocks WHERE project_id = ?",
905 "DELETE FROM project_users WHERE project_id = ?",
906 };
907
908 for (auto query : queries)
909 {
910 auto statement = connection->CreateStatement(query);
911
912 if (!statement)
913 return false;
914
915 if (!statement->Prepare(projectId).Run().IsOk())
916 return false;
917 }
918
919 return true;
920}
921
922} // namespace audacity::cloud::audiocom::sync
std::unordered_set< SampleBlockID > SampleBlockIDSet
Definition: CloudSyncDTO.h:27
Declare functions to perform UTF-8 to std::wstring conversions.
std::optional< DBProjectData > DoGetProjectData(const sqlite::Row &result) const
void UpdateBlockHashes(std::string_view projectId, const std::vector< std::pair< int64_t, std::string > > &hashes)
std::optional< std::string > GetBlockHash(std::string_view projectId, int64_t blockId) const
bool UpdateProjectData(const DBProjectData &projectData)
void AddPendingSnapshot(const PendingSnapshotData &snapshotData)
void UpdateProjectBlockList(std::string_view projectId, const SampleBlockIDSet &blockSet)
bool MarkProjectAsSynced(std::string_view projectId, std::string_view snapshotId)
void SetProjectUserSlug(std::string_view projectId, std::string_view slug)
std::vector< PendingProjectBlockData > GetPendingProjectBlocks(std::string_view projectId, std::string_view snapshotId)
std::optional< DBProjectData > GetProjectData(std::string_view projectId) const
std::shared_ptr< sqlite::SafeConnection > mConnection
void SetFirstSyncDialogShown(std::string_view projectId, bool shown=true)
void RemovePendingProjectBlock(std::string_view projectId, int64_t blockId)
bool IsFirstSyncDialogShown(std::string_view projectId) const
void AddPendingProjectBlocks(const std::vector< PendingProjectBlockData > &blockData)
void AddPendingProjectBlob(const PendingProjectBlobData &blobData)
void RemovePendingSnapshot(std::string_view projectId, std::string_view snapshotId)
bool IsProjectBlockLocked(std::string_view projectId, int64_t blockId) const
std::vector< PendingSnapshotData > GetPendingSnapshots(std::string_view projectId) const
std::optional< DBProjectData > GetProjectDataForPath(const std::string &projectPath) const
void RemovePendingProjectBlob(std::string_view projectId, std::string_view snapshotId)
void RemovePendingProjectBlocks(std::string_view projectId, std::string_view snapshotId)
std::string GetProjectUserSlug(std::string_view projectId)
std::optional< PendingProjectBlobData > GetPendingProjectBlob(std::string_view projectId, std::string_view snapshotId) const
Result< Statement > CreateStatement(std::string_view sql) const
Prepares the given SQL statement for execution.
Definition: Connection.cpp:253
A class representing a row in a result set.
Definition: Statement.h:32
bool Get(int columnIndex, bool &value) const
Definition: Statement.cpp:374
A class representing a result of a run operation.
Definition: Statement.h:99
static std::shared_ptr< SafeConnection > Open(std::string_view path, OpenMode mode=OpenMode::ReadWriteCreate, ThreadMode threadMode=ThreadMode::Serialized, Error *openError=nullptr)
FILES_API FilePath ConfigDir()
Audacity user config directory.
std::string ToUTF8(const std::wstring &wstr)
enum audacity::cloud::audiocom::sync::DBProjectData::SyncStatusType SyncStatus