44 {
return std::make_shared<ProjectCloudExtension>(
project); }
69 if (
Queue.empty() || !canMerge)
70 Queue.push_back({ message, canMerge });
71 else if (
Queue.back().second)
72 Queue.back().first = message;
74 Queue.push_back({ message, canMerge });
87 for (
const auto& [message,
_] : queue)
98 using QueueType = std::vector<std::pair<CloudStatusChangedMessage, bool>>;
113 , mAsyncStateNotifier { std::make_unique<CloudStatusChangedNotifier>() }
114 , mUIStateNotifier { std::make_unique<CloudStatusChangedNotifier>() }
117 UpdateIdFromDatabase();
120ProjectCloudExtension::~ProjectCloudExtension() =
default;
133bool ProjectCloudExtension::IsCloudProject()
const
135 auto lock = std::lock_guard { mIdentifiersMutex };
136 return !mProjectId.empty();
139void ProjectCloudExtension::OnLoad()
141 UpdateIdFromDatabase();
144void ProjectCloudExtension::OnSyncStarted()
146 auto element = std::make_shared<UploadQueueElement>();
152 if (pTrack->GetId() ==
TrackId {})
155 element->Data.Tracks->Add(pTrack->Duplicate());
158 auto lock = std::lock_guard { mUploadQueueMutex };
159 mUploadQueue.push_back(std::move(element));
163 std::shared_ptr<ProjectUploadOperation> uploadOperation,
164 int64_t missingBlocksCount,
bool needsProjectUpload)
166 auto element = std::make_shared<UploadQueueElement>();
167 element->Operation = uploadOperation;
168 element->BlocksTotal = missingBlocksCount;
169 element->ProjectDataUploaded = !needsProjectUpload;
172 auto lock = std::lock_guard { mUploadQueueMutex };
173 mUploadQueue.push_back(element);
176 uploadOperation->Start();
177 UnsafeUpdateProgress();
180void ProjectCloudExtension::OnUploadOperationCreated(
181 std::shared_ptr<ProjectUploadOperation> uploadOperation)
183 auto lock = std::lock_guard { mUploadQueueMutex };
184 assert(mUploadQueue.size() > 0);
186 if (mUploadQueue.empty())
189 mUploadQueue.back()->Operation = uploadOperation;
190 UnsafeUpdateProgress();
193void ProjectCloudExtension::OnBlocksHashed(
196 auto lock = std::lock_guard { mUploadQueueMutex };
197 auto element = UnsafeFindUploadQueueElement(uploadOperation);
202 element->ReadyForUpload =
true;
203 uploadOperation.
Start();
206void ProjectCloudExtension::OnSnapshotCreated(
212 const auto projectFilePath =
215 auto previousDbData = cloudDatabase.GetProjectDataForPath(projectFilePath);
220 dbData = *previousDbData;
227 dbData.
SyncStatus = DBProjectData::SyncStatusUploading;
230 cloudDatabase.UpdateProjectData(dbData);
231 cloudDatabase.SetProjectUserSlug(
235 auto lock = std::lock_guard { mIdentifiersMutex };
241 auto lock = std::lock_guard { mUploadQueueMutex };
242 auto element = UnsafeFindUploadQueueElement(uploadOperation);
248 element->SnapshotResponse = response;
250 UnsafeUpdateProgress();
253void ProjectCloudExtension::OnProjectDataUploaded(
256 auto lock = std::lock_guard { mUploadQueueMutex };
257 auto element = UnsafeFindUploadQueueElement(uploadOperation);
262 element->ProjectDataUploaded =
true;
264 UnsafeUpdateProgress();
267void ProjectCloudExtension::OnBlockUploaded(
271 auto lock = std::lock_guard { mUploadQueueMutex };
272 auto element = UnsafeFindUploadQueueElement(uploadOperation);
277 element->BlocksHandled++;
279 UnsafeUpdateProgress();
282void ProjectCloudExtension::OnSyncCompleted(
284 std::optional<CloudSyncError> error)
286 auto lock = std::lock_guard { mUploadQueueMutex };
288 auto element = uploadOperation !=
nullptr ?
289 UnsafeFindUploadQueueElement(*uploadOperation) :
292 if (element !=
nullptr)
294 element->Synced =
true;
297 error.has_value() && error->Type == CloudSyncError::Aborted &&
298 element->SnapshotResponse)
302 const auto projectFilePath =
305 auto previousDbData =
306 cloudDatabase.GetProjectDataForPath(projectFilePath);
311 dbData = *previousDbData;
313 const auto parentId = element->SnapshotResponse->Snapshot.ParentId;
317 cloudDatabase.UpdateProjectData(dbData);
320 auto lock = std::lock_guard { mIdentifiersMutex };
321 mSnapshotId = parentId;
328 mUploadQueue.begin(), mUploadQueue.end(),
329 [](
auto& operation) { return operation->Synced; }))
331 UnsafeUpdateProgress();
335 mUploadQueue.clear();
337 MarkProjectSynced(!error.has_value());
340 { error.has_value() ? ProjectSyncStatus::Failed :
341 ProjectSyncStatus::Synced,
346 if (!IsCloudProject())
347 Publish({ ProjectSyncStatus::Local },
false);
350void ProjectCloudExtension::CancelSync()
352 std::vector<std::shared_ptr<UploadQueueElement>> queue;
355 auto lock = std::lock_guard { mUploadQueueMutex };
362 for (
auto& item : queue)
364 if (!item->Operation)
367 item->Operation->Cancel();
371bool ProjectCloudExtension::IsSyncing()
const
373 auto lock = std::lock_guard { mStatusMutex };
375 return mLastStatus.Status == ProjectSyncStatus::Syncing;
378std::string ProjectCloudExtension::GetCloudProjectId()
const
380 auto lock = std::lock_guard { mIdentifiersMutex };
385std::string ProjectCloudExtension::GetSnapshotId()
const
387 auto lock = std::lock_guard { mIdentifiersMutex };
394 auto lock = std::lock_guard { mUploadQueueMutex };
396 if (mUploadQueue.empty())
402 std::vector<uint8_t> data;
403 data.resize(projectSize + dictSize +
sizeof(uint64_t));
405 const uint64_t dictSizeData =
408 std::memcpy(data.data(), &dictSizeData,
sizeof(uint64_t));
410 uint64_t offset =
sizeof(dictSize);
412 for (
const auto [chunkData,
size] : serializer.
GetDict())
414 std::memcpy(data.data() + offset, chunkData,
size);
418 for (
const auto [chunkData,
size] : serializer.
GetData())
420 std::memcpy(data.data() + offset, chunkData,
size);
424 mUploadQueue.back()->Data.ProjectSnapshot = std::move(data);
426 if (mUploadQueue.back()->Operation)
428 mUploadQueue.back()->Operation->SetUploadData(mUploadQueue.back()->Data);
440 Publish({ ProjectSyncStatus::Failed },
false);
445 return mProject.weak_from_this();
448int64_t ProjectCloudExtension::GetSavesCount()
const
450 auto lock = std::lock_guard { mIdentifiersMutex };
452 if (mProjectId.empty())
456 auto dbData = cloudDatabase.GetProjectData(mProjectId);
461 return dbData->SavesCount;
468 return onUIThread ? mUIStateNotifier->SubscribeSafe(std::move(callback)) :
469 mAsyncStateNotifier->SubscribeSafe(std::move(callback));
472void ProjectCloudExtension::UpdateIdFromDatabase()
477 auto projectData = cloudDatabase.GetProjectDataForPath(
484 auto lock = std::lock_guard { mIdentifiersMutex };
486 mProjectId = projectData->ProjectId;
487 mSnapshotId = projectData->SnapshotId;
492 projectData->SyncStatus ==
493 DBProjectData::SyncStatusType::SyncStatusSynced ?
494 ProjectSyncStatus::Synced :
495 ProjectSyncStatus::Unsynced,
500void ProjectCloudExtension::UnsafeUpdateProgress()
502 if (mUploadQueue.empty())
505 int64_t handledElements = 0;
506 int64_t totalElements = 0;
508 for (
auto& element : mUploadQueue)
511 element->BlocksHandled + int(element->ProjectDataUploaded);
512 totalElements += element->BlocksTotal + 1;
515 assert(totalElements > 0);
518 { ProjectSyncStatus::Syncing,
519 double(handledElements) / double(totalElements) },
523void ProjectCloudExtension::Publish(
527 auto lock = std::lock_guard { mStatusMutex };
528 mLastStatus = cloudStatus;
531 mAsyncStateNotifier->Enqueue(cloudStatus, canMerge);
532 mUIStateNotifier->Enqueue(cloudStatus, canMerge);
534 mAsyncStateNotifier->PublishSafe();
538 mUINotificationPending.store(
false);
539 mUIStateNotifier->PublishSafe();
541 else if (!mUINotificationPending.exchange(
true))
546 if (mUINotificationPending.exchange(
false))
547 mUIStateNotifier->PublishSafe();
552void ProjectCloudExtension::MarkProjectSynced(
bool success)
556 const auto projectFilePath =
559 auto previousDbData = cloudDatabase.GetProjectDataForPath(projectFilePath);
564 dbData = *previousDbData;
568 dbData.
SyncStatus = success ? DBProjectData::SyncStatusSynced :
569 DBProjectData::SyncStatusUploading;
571 cloudDatabase.UpdateProjectData(dbData);
575ProjectCloudExtension::UnsafeFindUploadQueueElement(
578 auto it = std::find_if(
579 mUploadQueue.begin(), mUploadQueue.end(),
580 [&](
const auto& element)
581 { return element->Operation.get() == &uploadOperation; });
583 return it != mUploadQueue.end() ? it->get() :
nullptr;
587ProjectCloudExtension::UnsafeFindUploadQueueElement(
591 ->UnsafeFindUploadQueueElement(uploadOperation);
594void ProjectCloudExtension::OnProjectPathChanged()
596 bool wasCloudProject =
false;
599 auto lock = std::lock_guard { mIdentifiersMutex };
601 wasCloudProject = !mProjectId.empty();
608 UpdateIdFromDatabase();
610 if (mProjectId.empty() && wasCloudProject)
611 Publish({ ProjectSyncStatus::Local },
false);
614std::string ProjectCloudExtension::GetCloudProjectPage()
const
616 const auto projectId =
619 const auto userSlug =
625bool ProjectCloudExtension::IsBlockLocked(int64_t blockID)
const
631 const auto projectId = GetCloudProjectId();
633 if (projectId.empty())
641 auto lock = std::lock_guard {
645 return mLastStatus.Status;
648bool ProjectCloudExtension::IsFirstSyncDialogShown()
const
650 auto lock = std::lock_guard { mIdentifiersMutex };
652 if (mProjectId.empty())
658void ProjectCloudExtension::SetFirstSyncDialogShown(
bool shown)
660 auto lock = std::lock_guard { mIdentifiersMutex };
662 if (mProjectId.empty())
668bool CloudStatusChangedMessage::IsSyncing() const noexcept
670 return Status == ProjectSyncStatus::Syncing;
Toolkit-neutral facade for basic user interface services.
Declare functions to perform UTF-8 to std::wstring conversions.
constexpr IntType SwapIntBytes(IntType value) noexcept
Swap bytes in an integer.
bool IsLittleEndian() noexcept
Check that machine is little-endian.
@ ProjectFilePathChange
A normal occurrence.
declares abstract base class Track, TrackList, and iterators over TrackList
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
const size_t GetSize() const noexcept
An object that sends messages to an open-ended list of subscribed callbacks.
Subscription Subscribe(Callback callback)
Connect a callback to the Publisher; later-connected are called earlier.
CallbackReturn Publish(const CloudStatusChangedMessage &message)
Send a message to connected callbacks.
A move-only handle representing a connection to a Publisher.
Object associated with a project that manages reading and writing of Audacity project file formats,...
static ProjectFileIO & Get(AudacityProject &project)
a class used to (de)serialize the project catalog
const MemoryStream & GetData() const
const MemoryStream & GetDict() const
An in-session identifier of track objects across undo states. It does not persist between sessions.
static TrackListHolder Create(AudacityProject *pOwner)
static TrackList & Get(AudacityProject &project)
std::string GetProjectPageUrl(std::string_view userId, std::string_view projectId) const
void OnSyncResumed(std::shared_ptr< ProjectUploadOperation > uploadOperation, int64_t missingBlocksCount, bool needsProjectUpload)
This method is called from the UI thread.
void OnProjectPathChanged()
ProjectCloudExtension(AudacityProject &project)
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
bool IsUiThread()
Whether the current thread is the UI thread.
Services * Get()
Fetch the global instance, or nullptr if none is yet installed.
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
const AudacityProject & GetProject(const Track &track)
const AttachedProjectObjects::RegisteredFactory key
const ServiceConfig & GetServiceConfig()
Returns the instance of the ServiceConfig.
std::string ToUTF8(const std::wstring &wstr)
ProjectSyncState SyncState
enum audacity::cloud::audiocom::sync::DBProjectData::SyncStatusType SyncStatus
Observer::Subscription SubscribeSafe(std::function< void(const CloudStatusChangedMessage &)> callback)
std::vector< std::pair< CloudStatusChangedMessage, bool > > QueueType
void Enqueue(CloudStatusChangedMessage message, bool canMerge)
std::recursive_mutex ObserverMutex
std::optional< CreateSnapshotResponse > SnapshotResponse
std::shared_ptr< ProjectUploadOperation > Operation
std::vector< UploadUrls > MissingBlocks