Audacity 3.2.0
RemoteProjectSnapshot.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 RemoteProjectSnapshot.cpp
7
8 Dmitry Vedenko
9
10**********************************************************************/
12
13#include <algorithm>
14#include <unordered_set>
15
16#include <wx/datetime.h>
17
19
20#include "CodeConversions.h"
21#include "Internat.h"
22#include "MemoryX.h"
23#include "StringUtils.h"
24
25#include "IResponse.h"
26#include "NetworkManager.h"
27#include "Request.h"
28
29#include "WavPackCompressor.h"
30
32{
33namespace
34{
35std::vector<std::string> ListAttachedDatabases()
36{
38 auto statement = db->CreateStatement("PRAGMA database_list");
39 auto result = statement->Prepare().Run();
40
41 std::vector<std::string> attachedDBs;
42
43 for (auto row : result)
44 {
45 std::string dbName;
46
47 if (!row.Get(1, dbName))
48 continue;
49
50 if (dbName == "main" || dbName == "temp")
51 continue;
52
53 attachedDBs.push_back(std::move(dbName));
54 }
55
56 return attachedDBs;
57}
58} // namespace
59
61 Tag, ProjectInfo projectInfo, SnapshotInfo snapshotInfo, std::string path,
62 RemoteProjectSnapshotStateCallback callback, bool downloadDetached)
63 : mSnapshotDBName { std::string("s_") + projectInfo.Id }
64 , mProjectInfo { std::move(projectInfo) }
65 , mSnapshotInfo { std::move(snapshotInfo) }
66 , mPath { std::move(path) }
67 , mCallback { std::move(callback) }
68 , mDownloadDetached { downloadDetached }
69{
71 // RemoteProjectSnapshot always receives a path to the database
72 // that has AudacityProject schema installed, even if it's a detached
73 // or was deleted from the disk before
74 auto attachStmt = db->CreateStatement("ATTACH DATABASE ? AS ?");
75 auto result = attachStmt->Prepare(mPath, mSnapshotDBName).Run();
76
77 if (!result.IsOk())
78 return;
79
81
82 auto blocksSource = mSnapshotDBName;
83
85 {
86 if (auto name = AttachOriginalDB(); !name.empty())
87 blocksSource = name;
88 }
89
90 // This would return and empty set when the project
91 // is detached
92 auto knownBlocks = CalculateKnownBlocks(blocksSource);
93
95 {
96 // We can assume, that if the known blocks are present,
97 // they come from the "original" database
98 SetupBlocksCopy(blocksSource, knownBlocks);
99 }
100 else if (knownBlocks.size() == mSnapshotInfo.Blocks.size())
101 {
102 auto syncInfo =
104
105 if (
106 syncInfo && syncInfo->SnapshotId == mSnapshotInfo.Id &&
107 syncInfo->SyncStatus == DBProjectData::SyncStatusSynced)
108 {
109 mCallback({ {}, 0, 0, true });
110 mNothingToDo = true;
111 return;
112 }
113 }
114
117
118 MarkProjectInDB(false);
119
121 mSnapshotInfo.Blocks.size() :
122 mSnapshotInfo.Blocks.size() - knownBlocks.size();
123
124 mRequests.reserve(1 + mMissingBlocks);
125
126 mRequests.push_back(std::make_pair(
128 [this](auto response) { OnProjectBlobDownloaded(response); }));
129
130 for (auto& block : mSnapshotInfo.Blocks)
131 {
132 if (knownBlocks.find(ToUpper(block.Hash)) != knownBlocks.end())
133 continue;
134
135 mRequests.push_back(std::make_pair(
136 block.Url, [this, hash = ToUpper(block.Hash)](auto response)
137 { OnBlockDownloaded(std::move(hash), response); }));
138 }
139
141 std::thread { &RemoteProjectSnapshot::RequestsThread, this };
142}
143
145{
146 DoCancel();
147
148 if (mRequestsThread.joinable())
149 mRequestsThread.join();
150
151 if (mCopyBlocksFuture.has_value())
152 mCopyBlocksFuture->wait();
153
154 {
155 auto lock = std::unique_lock { mResponsesMutex };
156 mResponsesEmptyCV.wait(lock, [this] { return mResponses.empty(); });
157 }
158
160
161 for (const auto& dbName : ListAttachedDatabases())
162 {
163 auto detachStmt = db->CreateStatement("DETACH DATABASE ?");
164 detachStmt->Prepare(dbName).Run();
165 }
166}
167
168std::shared_ptr<RemoteProjectSnapshot> RemoteProjectSnapshot::Sync(
169 ProjectInfo projectInfo, SnapshotInfo snapshotInfo, std::string path,
170 RemoteProjectSnapshotStateCallback callback, bool downloadDetached)
171{
172 auto snapshot = std::make_shared<RemoteProjectSnapshot>(
173 Tag {}, std::move(projectInfo), std::move(snapshotInfo), std::move(path),
174 std::move(callback), downloadDetached);
175
176 if (snapshot->mAttachedDBNames.empty())
177 {
178 snapshot->mCallback(
181 XO("Failed to attach to the Cloud project database")
182 .Translation()) },
183 0,
184 0,
185 false });
186
187 return {};
188 }
189
190 if (snapshot->mNothingToDo)
191 return {};
192
193 return snapshot;
194}
195
197{
198 DoCancel();
199
201 mDownloadedBlocks.load() + mCopiedBlocks.load(),
203 mProjectDownloaded.load() });
204}
205
207{
208 const auto duration =
209 mState.load(std::memory_order_acquire) == State::Downloading ?
210 Clock::now() - mStartTime :
212
213 return TransferStats {}
218 std::chrono::duration_cast<TransferStats::Duration>(duration));
219}
220
222{
223 return mProjectInfo.Id;
224}
225
227{
228 const std::string dbName = "o_" + mProjectInfo.Id;
229
230 const auto projectData =
232
233 if (!projectData)
234 return {};
235
237 // RemoteProjectSnapshot always receives a path to the database
238 // that has AudacityProject schema installed, even if it's a detached
239 // or was deleted from the disk before
240 auto attachStmt = db->CreateStatement("ATTACH DATABASE ? AS ?");
241 auto result = attachStmt->Prepare(projectData->LocalPath, dbName).Run();
242
243 if (!result.IsOk())
244 return {};
245
246 mAttachedDBNames.push_back(dbName);
247
248 return dbName;
249}
250
252 const std::string& dbName, std::unordered_set<std::string> blocks)
253{
254 // Still, better be safe than sorry
255 if (dbName == mSnapshotDBName)
256 return;
257
258 if (blocks.empty())
259 return;
260
261 mCopyBlocksFuture = std::async(
262 std::launch::async,
263 [this, dbName = dbName, blocks = std::move(blocks)]()
264 {
265 const auto queryString =
266 "INSERT INTO " + mSnapshotDBName +
267 ".sampleblocks "
268 "SELECT * FROM " +
269 dbName +
270 ".sampleblocks WHERE blockid IN (SELECT block_id FROM block_hashes WHERE hash = ?)";
271
272 // Only lock DB for one block a time so the download thread can
273 // continue to work
274 for (const auto& block : blocks)
275 {
276 if (!InProgress())
277 return false;
278
280
281 auto copyBlocksStatement = db->CreateStatement(queryString);
282
283 if (!copyBlocksStatement)
284 {
286 audacity::ToUTF8(copyBlocksStatement.GetError()
287 .GetErrorString()
288 .Translation()) });
289
290 return false;
291 }
292
293 auto result = copyBlocksStatement->Prepare(block).Run();
294
295 if (!result.IsOk())
296 {
298 audacity::ToUTF8(result.GetErrors()
299 .front()
300 .GetErrorString()
301 .Translation()) });
302 return false;
303 }
304
305 const auto rowsUpdated = result.GetModifiedRowsCount();
306 mCopiedBlocks.fetch_add(rowsUpdated, std::memory_order_acq_rel);
307
309 }
310
311 return true;
312 });
313}
314
315std::unordered_set<std::string> RemoteProjectSnapshot::CalculateKnownBlocks(
316 const std::string& attachedDbName) const
317{
318 std::unordered_set<std::string> remoteBlocks;
319
320 for (const auto& block : mSnapshotInfo.Blocks)
321 remoteBlocks.insert(ToUpper(block.Hash));
322
324
325 auto fn = db->CreateScalarFunction(
326 "inRemoteBlocks", [&remoteBlocks](const std::string& hash)
327 { return remoteBlocks.find(hash) != remoteBlocks.end(); });
328
329 auto statement = db->CreateStatement(
330 "SELECT hash FROM block_hashes WHERE project_id = ? AND inRemoteBlocks(hash) AND block_id IN (SELECT blockid FROM " +
331 attachedDbName + ".sampleblocks)");
332
333 if (!statement)
334 return {};
335
336 auto result = statement->Prepare(mProjectInfo.Id).Run();
337
338 std::unordered_set<std::string> knownBlocks;
339
340 for (auto row : result)
341 {
342 std::string hash;
343
344 if (!row.Get(0, hash))
345 continue;
346
347 knownBlocks.insert(hash);
348 }
349
350 return knownBlocks;
351}
352
354{
355 if (mState.load(std::memory_order_acquire) != State::Downloading)
356 return;
357
359
360 mRequestsCV.notify_one();
361
362 {
363 auto responsesLock = std::lock_guard { mResponsesMutex };
364 for (auto& response : mResponses)
365 response->abort();
366 }
367}
368
370 std::string url, SuccessHandler onSuccess, int retries)
371{
372 using namespace audacity::network_manager;
373
374 auto request = Request(url);
375
376 auto response = NetworkManager::GetInstance().doGet(request);
377
378 {
379 auto responsesLock = std::lock_guard { mResponsesMutex };
380 mResponses.push_back(response);
381 }
382
383 response->setRequestFinishedCallback(
384 [this, onSuccess = std::move(onSuccess), retries, response](auto)
385 {
386 mDownloadedBytes.fetch_add(
387 response->getBytesAvailable(), std::memory_order_acq_rel);
388
389 RemoveResponse(response.get());
390
391 auto responseResult = GetResponseResult(*response, false);
392
393 if (responseResult.Code == SyncResultCode::Cancelled)
394 return;
395
396 if (
397 responseResult.Code != SyncResultCode::Success &&
398 responseResult.Code != SyncResultCode::ConnectionFailed)
399 {
400 OnFailure(std::move(responseResult));
401 return;
402 }
403
404 if (responseResult.Code == SyncResultCode::ConnectionFailed)
405 {
406 if (retries <= 0)
407 {
408 OnFailure(std::move(responseResult));
409 return;
410 }
411
413 response->getRequest().getURL(), std::move(onSuccess),
414 retries - 1);
415
416 return;
417 }
418
419 onSuccess(response);
420 });
421}
422
423namespace
424{
425std::vector<uint8_t>
427{
428 const auto size = response.getBytesAvailable();
429
430 if (size == 0)
431 return response.readAll<std::vector<uint8_t>>();
432
433 std::vector<uint8_t> data(size);
434 response.readData(data.data(), size);
435
436 return data;
437}
438} // namespace
439
440
441void RemoteProjectSnapshot::OnProjectBlobDownloaded(
443{
444 const std::vector<uint8_t> data = ReadResponseData(*response);
445 uint64_t dictSize = 0;
446
447 if (data.size() < sizeof(uint64_t))
448 {
449 OnFailure({ SyncResultCode::UnexpectedResponse, {} });
450 return;
451 }
452
453 std::memcpy(&dictSize, data.data(), sizeof(uint64_t));
454
455 if (!IsLittleEndian())
456 dictSize = SwapIntBytes(dictSize);
457
458 if (data.size() < sizeof(uint64_t) + dictSize)
459 {
460 OnFailure({ SyncResultCode::UnexpectedResponse, {} });
461 return;
462 }
463
464 auto db = CloudProjectsDatabase::Get().GetConnection();
465 auto transaction = db->BeginTransaction("p_" + mProjectInfo.Id);
466
467 auto updateProjectStatement = db->CreateStatement(
468 "INSERT INTO " + mSnapshotDBName +
469 ".project (id, dict, doc) VALUES (1, ?1, ?2) "
470 "ON CONFLICT(id) DO UPDATE SET dict = ?1, doc = ?2");
471
472 if (!updateProjectStatement)
473 {
475 audacity::ToUTF8(updateProjectStatement.GetError()
476 .GetErrorString()
477 .Translation()) });
478 return;
479 }
480
481 auto& preparedUpdateProjectStatement = updateProjectStatement->Prepare();
482
483 preparedUpdateProjectStatement.Bind(
484 1, data.data() + sizeof(uint64_t), dictSize, false);
485
486 preparedUpdateProjectStatement.Bind(
487 2, data.data() + sizeof(uint64_t) + dictSize,
488 data.size() - sizeof(uint64_t) - dictSize, false);
489
490 auto result = preparedUpdateProjectStatement.Run();
491
492 if (!result.IsOk())
493 {
494 OnFailure(
497 result.GetErrors().front().GetErrorString().Translation()) });
498
499 return;
500 }
501
502 auto deleteAutosaveStatement = db->CreateStatement(
503 "DELETE FROM " + mSnapshotDBName + ".autosave WHERE id = 1");
504
505 if (!deleteAutosaveStatement)
506 {
508 audacity::ToUTF8(deleteAutosaveStatement.GetError()
509 .GetErrorString()
510 .Translation()) });
511 return;
512 }
513
514 result = deleteAutosaveStatement->Prepare().Run();
515
516 if (!result.IsOk())
517 {
518 OnFailure(
521 result.GetErrors().front().GetErrorString().Translation()) });
522 return;
523 }
524
525 if (auto error = transaction.Commit(); error.IsError())
526 {
528 audacity::ToUTF8(error.GetErrorString().Translation()) });
529 return;
530 }
531
532 mProjectDownloaded.store(true, std::memory_order_release);
533 ReportProgress();
534}
535
536void RemoteProjectSnapshot::OnBlockDownloaded(
537 std::string blockHash, audacity::network_manager::ResponsePtr response)
538{
539 const auto compressedData = ReadResponseData(*response);
540
541 const auto blockData =
542 DecompressBlock(compressedData.data(), compressedData.size());
543
544 if (!blockData)
545 {
546 OnFailure(
548 audacity::ToUTF8(XO("Failed to decompress the Cloud project block")
549 .Translation()) });
550 return;
551 }
552
553 auto db = CloudProjectsDatabase::Get().GetConnection();
554 auto transaction = db->BeginTransaction("b_" + blockHash);
555
556 auto hashesStatement = db->CreateStatement(
557 "INSERT INTO block_hashes (project_id, block_id, hash) VALUES (?1, ?2, ?3) "
558 "ON CONFLICT(project_id, block_id) DO UPDATE SET hash = ?3");
559
560 auto result =
561 hashesStatement->Prepare(mProjectInfo.Id, blockData->BlockId, blockHash)
562 .Run();
563
564 if (!result.IsOk())
565 {
566 OnFailure(
569 result.GetErrors().front().GetErrorString().Translation()) });
570 return;
571 }
572
573 auto blockStatement = db->CreateStatement(
574 "INSERT INTO " + mSnapshotDBName +
575 ".sampleblocks (blockid, sampleformat, summin, summax, sumrms, summary256, summary64k, samples) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8) "
576 "ON CONFLICT(blockid) DO UPDATE SET sampleformat = ?2, summin = ?3, summax = ?4, sumrms = ?5, summary256 = ?6, summary64k = ?7, samples = ?8");
577
578 if (!blockStatement)
579 {
580 OnFailure(
583 blockStatement.GetError().GetErrorString().Translation()) });
584 return;
585 }
586
587 auto& preparedStatement = blockStatement->Prepare();
588
589 preparedStatement.Bind(1, blockData->BlockId);
590 preparedStatement.Bind(2, static_cast<int64_t>(blockData->Format));
591 preparedStatement.Bind(3, blockData->BlockMinMaxRMS.Min);
592 preparedStatement.Bind(4, blockData->BlockMinMaxRMS.Max);
593 preparedStatement.Bind(5, blockData->BlockMinMaxRMS.RMS);
594 preparedStatement.Bind(
595 6, blockData->Summary256.data(),
596 blockData->Summary256.size() * sizeof(MinMaxRMS), false);
597 preparedStatement.Bind(
598 7, blockData->Summary64k.data(),
599 blockData->Summary64k.size() * sizeof(MinMaxRMS), false);
600 preparedStatement.Bind(
601 8, blockData->Data.data(), blockData->Data.size(), false);
602
603 result = preparedStatement.Run();
604
605 if (!result.IsOk())
606 {
607 OnFailure(
610 result.GetErrors().front().GetErrorString().Translation()) });
611 return;
612 }
613
614 if (auto error = transaction.Commit(); error.IsError())
615 {
617 audacity::ToUTF8(error.GetErrorString().Translation()) });
618 return;
619 }
620
621 mDownloadedBlocks.fetch_add(1, std::memory_order_acq_rel);
622
623 ReportProgress();
624}
625
626void RemoteProjectSnapshot::OnFailure(ResponseResult result)
627{
628 SetState(State::Failed);
629 mCallback({ result,
630 mDownloadedBlocks.load(std::memory_order_acquire) +
631 mCopiedBlocks.load(std::memory_order_acquire),
632 mMissingBlocks,
633 mProjectDownloaded.load(std::memory_order_acquire) });
634}
635
636void RemoteProjectSnapshot::RemoveResponse(
638{
639 {
640 auto lock = std::lock_guard { mResponsesMutex };
641 mResponses.erase(
642 std::remove_if(
643 mResponses.begin(), mResponses.end(),
644 [response](auto& r) { return r.get() == response; }),
645 mResponses.end());
646
647 if (mResponses.empty())
648 mResponsesEmptyCV.notify_all();
649 }
650 {
651 auto lock = std::lock_guard { mRequestsMutex };
652 mRequestsInProgress--;
653 mRequestsCV.notify_one();
654 }
655}
656
657void RemoteProjectSnapshot::MarkProjectInDB(bool successfulDownload)
658{
659 if (mDownloadDetached)
660 return;
661
662 auto& db = CloudProjectsDatabase::Get();
663 auto currentData = db.GetProjectData(mProjectInfo.Id);
664
665 auto data = currentData ? *currentData : DBProjectData {};
666
667 data.ProjectId = mProjectInfo.Id;
668 data.SnapshotId = mSnapshotInfo.Id;
669 data.SyncStatus = successfulDownload ? DBProjectData::SyncStatusSynced :
670 DBProjectData::SyncStatusDownloading;
671 data.LastRead = wxDateTime::Now().GetTicks();
672 data.LocalPath = mPath;
673
674 if (data.SavesCount == 0)
675 data.SavesCount = 1;
676
677 // For the downloaded projects - we don't need to show the dialog
678 data.FirstSyncDialogShown = true;
679
680 db.UpdateProjectData(data);
681
682 if (successfulDownload)
683 db.SetProjectUserSlug(mProjectInfo.Id, mProjectInfo.Username);
684}
685
686void RemoteProjectSnapshot::ReportProgress()
687{
688 if (mState.load(std::memory_order_acquire) != State::Downloading)
689 return;
690
691 const auto projectDownloaded =
692 mProjectDownloaded.load(std::memory_order_acquire);
693 const auto blocksDownloaded =
694 mDownloadedBlocks.load(std::memory_order_acquire);
695
696 const auto blockCopied = mCopiedBlocks.load(std::memory_order_acquire);
697
698 const auto processedBlocks = blocksDownloaded + blockCopied;
699
700 const auto completed =
701 processedBlocks == mMissingBlocks && projectDownloaded;
702
703 if (completed)
704 {
705 CleanupOrphanBlocks();
706 SetState(State::Succeeded);
707 MarkProjectInDB(true);
708 }
709
710 mCallback({ {}, processedBlocks, mMissingBlocks, projectDownloaded });
711}
712
713bool RemoteProjectSnapshot::InProgress() const
714{
715 return mState.load(std::memory_order_acquire) == State::Downloading;
716}
717
718void RemoteProjectSnapshot::RequestsThread()
719{
720 constexpr auto MAX_CONCURRENT_REQUESTS = 6;
721
722 while (InProgress())
723 {
724 std::pair<std::string, SuccessHandler> request;
725
726 {
727 auto lock = std::unique_lock { mRequestsMutex };
728
729 if (mRequestsInProgress >= MAX_CONCURRENT_REQUESTS)
730 {
731 mRequestsCV.wait(
732 lock,
733 [this, MAX_CONCURRENT_REQUESTS] {
734 return mRequestsInProgress < MAX_CONCURRENT_REQUESTS ||
735 !InProgress();
736 });
737 }
738
739 if (!InProgress())
740 return;
741
742 if (mNextRequestIndex >= mRequests.size())
743 return;
744
745 request = mRequests[mNextRequestIndex++];
746 mRequestsInProgress++;
747 }
748
749 DownloadBlob(std::move(request.first), std::move(request.second), 3);
750
751 // TODO: Random sleep to avoid overloading the server
752 std::this_thread::sleep_for(std::chrono::milliseconds(50));
753 }
754}
755
756void RemoteProjectSnapshot::SetState(State state)
757{
758 if (state != State::Downloading)
759 mEndTime = Clock::now();
760
761 mState.exchange(state);
762}
763
765{
766 auto db = CloudProjectsDatabase::Get().GetConnection();
767
768 auto transaction = db->BeginTransaction("d_" + mProjectInfo.Id);
769
770 std::unordered_set<std::string> snaphotBlockHashes;
771
772 for (const auto& block : mSnapshotInfo.Blocks)
773 snaphotBlockHashes.insert(ToUpper(block.Hash));
774
775 auto inSnaphotFunction = db->CreateScalarFunction(
776 "inSnapshot", [&snaphotBlockHashes](const std::string& hash)
777 { return snaphotBlockHashes.find(hash) != snaphotBlockHashes.end(); });
778
779 // Delete blocks not in the snapshot
780 auto deleteBlocksStatement = db->CreateStatement(
781 "DELETE FROM " + mSnapshotDBName +
782 ".sampleblocks WHERE blockid NOT IN (SELECT block_id FROM block_hashes WHERE project_id = ? AND inSnapshot(hash))");
783
784 if (!deleteBlocksStatement)
785 return;
786
787 auto result = deleteBlocksStatement->Prepare(mProjectInfo.Id).Run();
788
789 if (!result.IsOk())
790 return;
791
792 auto deleteHashesStatement = db->CreateStatement(
793 "DELETE FROM block_hashes WHERE project_id = ? AND NOT inSnapshot(hash)");
794
795 if (!deleteHashesStatement)
796 return;
797
798 result = deleteHashesStatement->Prepare(mProjectInfo.Id).Run();
799
800 if (!result.IsOk())
801 return;
802
803 transaction.Commit();
804}
805
806bool RemoteProjectSnapshotState::IsComplete() const noexcept
807{
808 return (BlocksDownloaded == BlocksTotal && ProjectDownloaded) ||
810}
811
812} // namespace audacity::cloud::audiocom::sync
Declare functions to perform UTF-8 to std::wstring conversions.
const TranslatableString name
Definition: Distortion.cpp:76
struct State mState
XO("Cut/Copy/Paste")
Declare an interface for HTTP response.
constexpr IntType SwapIntBytes(IntType value) noexcept
Swap bytes in an integer.
Definition: MemoryX.h:377
bool IsLittleEndian() noexcept
Check that machine is little-endian.
Definition: MemoryX.h:368
Declare a class for performing HTTP requests.
Declare a class for constructing HTTP requests.
std::string ToUpper(const std::string &str)
Definition: StringUtils.cpp:46
static const auto fn
std::optional< DBProjectData > GetProjectData(std::string_view projectId) const
std::unordered_set< std::string > CalculateKnownBlocks(const std::string &attachedDbName) const
void DownloadBlob(std::string url, SuccessHandler onSuccess, int retries=3)
void RemoveResponse(audacity::network_manager::IResponse *response)
static std::shared_ptr< RemoteProjectSnapshot > Sync(ProjectInfo projectInfo, SnapshotInfo snapshotInfo, std::string path, RemoteProjectSnapshotStateCallback callback, bool downloadDetached)
RemoteProjectSnapshot(Tag, ProjectInfo projectInfo, SnapshotInfo snapshotInfo, std::string path, RemoteProjectSnapshotStateCallback callback, bool downloadDetached)
std::vector< std::shared_ptr< audacity::network_manager::IResponse > > mResponses
std::function< void(audacity::network_manager::ResponsePtr)> SuccessHandler
std::vector< std::pair< std::string, SuccessHandler > > mRequests
void SetupBlocksCopy(const std::string &dbName, std::unordered_set< std::string > blocks)
Interface, that provides access to the data from the HTTP response.
Definition: IResponse.h:113
virtual uint64_t getBytesAvailable() const noexcept=0
virtual uint64_t readData(void *buffer, uint64_t maxBytesCount)=0
ResponsePtr doGet(const Request &request)
Result< Statement > CreateStatement(std::string_view sql) const
Prepares the given SQL statement for execution.
Definition: Connection.cpp:253
Services * Get()
Fetch the global instance, or nullptr if none is yet installed.
Definition: BasicUI.cpp:200
std::vector< uint8_t > ReadResponseData(audacity::network_manager::IResponse &response)
std::optional< DecompressedBlock > DecompressBlock(const void *data, const std::size_t size)
std::function< void(RemoteProjectSnapshotState)> RemoteProjectSnapshotStateCallback
ResponseResult GetResponseResult(IResponse &response, bool readBody)
std::shared_ptr< IResponse > ResponsePtr
std::string ToUTF8(const std::wstring &wstr)
STL namespace.
Definition: Dither.cpp:67
TransferStats & SetBytesTransferred(int64_t bytesTransferred)
TransferStats & SetProjectFilesTransferred(int64_t projectFilesTransferred)
TransferStats & SetBlocksTransferred(int64_t blocksTransferred)
TransferStats & SetTransferDuration(Duration transferDuration)
std::vector< SnapshotBlockInfo > Blocks
Definition: CloudSyncDTO.h:89