45 {
return std::make_shared<ProjectCloudExtension>(
project); }
70 if (
Queue.empty() || !canMerge)
71 Queue.push_back({ message, canMerge });
72 else if (
Queue.back().second)
73 Queue.back().first = message;
75 Queue.push_back({ message, canMerge });
88 for (
const auto& [message,
_] : queue)
99 using QueueType = std::vector<std::pair<CloudStatusChangedMessage, bool>>;
114 , mAsyncStateNotifier { std::make_unique<CloudStatusChangedNotifier>() }
115 , mUIStateNotifier { std::make_unique<CloudStatusChangedNotifier>() }
118 UpdateIdFromDatabase();
121ProjectCloudExtension::~ProjectCloudExtension() =
default;
134bool ProjectCloudExtension::IsCloudProject()
const
136 auto lock = std::lock_guard { mIdentifiersMutex };
137 return !mProjectId.empty();
140void ProjectCloudExtension::OnLoad()
142 UpdateIdFromDatabase();
145void ProjectCloudExtension::OnSyncStarted()
147 auto element = std::make_shared<UploadQueueElement>();
153 if (pTrack->GetId() ==
TrackId {})
156 element->Data.Tracks->Add(pTrack->Duplicate());
159 auto lock = std::lock_guard { mUploadQueueMutex };
160 mUploadQueue.push_back(std::move(element));
164 std::shared_ptr<ProjectUploadOperation> uploadOperation,
165 int64_t missingBlocksCount,
bool needsProjectUpload)
167 auto element = std::make_shared<UploadQueueElement>();
168 element->Operation = uploadOperation;
169 element->BlocksTotal = missingBlocksCount;
170 element->ProjectDataUploaded = !needsProjectUpload;
173 auto lock = std::lock_guard { mUploadQueueMutex };
174 mUploadQueue.push_back(element);
177 uploadOperation->Start();
178 UnsafeUpdateProgress();
181void ProjectCloudExtension::OnUploadOperationCreated(
182 std::shared_ptr<ProjectUploadOperation> uploadOperation)
184 auto lock = std::lock_guard { mUploadQueueMutex };
185 assert(mUploadQueue.size() > 0);
187 if (mUploadQueue.empty())
190 mUploadQueue.back()->Operation = uploadOperation;
191 UnsafeUpdateProgress();
194void ProjectCloudExtension::OnBlocksHashed(
197 auto lock = std::lock_guard { mUploadQueueMutex };
198 auto element = UnsafeFindUploadQueueElement(uploadOperation);
203 element->ReadyForUpload =
true;
204 uploadOperation.
Start();
207void ProjectCloudExtension::OnSnapshotCreated(
213 const auto projectFilePath =
216 auto previousDbData = cloudDatabase.GetProjectDataForPath(projectFilePath);
221 dbData = *previousDbData;
228 dbData.
SyncStatus = DBProjectData::SyncStatusUploading;
231 cloudDatabase.UpdateProjectData(dbData);
232 cloudDatabase.SetProjectUserSlug(
236 auto lock = std::lock_guard { mIdentifiersMutex };
242 auto lock = std::lock_guard { mUploadQueueMutex };
243 auto element = UnsafeFindUploadQueueElement(uploadOperation);
249 element->SnapshotResponse = response;
251 UnsafeUpdateProgress();
254void ProjectCloudExtension::OnProjectDataUploaded(
257 auto lock = std::lock_guard { mUploadQueueMutex };
258 auto element = UnsafeFindUploadQueueElement(uploadOperation);
263 element->ProjectDataUploaded =
true;
265 UnsafeUpdateProgress();
268void ProjectCloudExtension::OnBlockUploaded(
272 auto lock = std::lock_guard { mUploadQueueMutex };
273 auto element = UnsafeFindUploadQueueElement(uploadOperation);
278 element->BlocksHandled++;
280 UnsafeUpdateProgress();
283void ProjectCloudExtension::OnSyncCompleted(
287 auto lock = std::lock_guard { mUploadQueueMutex };
289 auto element = uploadOperation !=
nullptr ?
290 UnsafeFindUploadQueueElement(*uploadOperation) :
293 if (element !=
nullptr)
295 element->Synced =
true;
298 error.has_value() && error->Type == CloudSyncError::Aborted &&
299 element->SnapshotResponse)
303 const auto projectFilePath =
306 auto previousDbData =
307 cloudDatabase.GetProjectDataForPath(projectFilePath);
312 dbData = *previousDbData;
314 const auto parentId = element->SnapshotResponse->Snapshot.ParentId;
318 cloudDatabase.UpdateProjectData(dbData);
321 auto lock = std::lock_guard { mIdentifiersMutex };
322 mSnapshotId = parentId;
329 mUploadQueue.begin(), mUploadQueue.end(),
330 [](
auto& operation) { return operation->Synced; }))
332 UnsafeUpdateProgress();
336 mUploadQueue.clear();
338 MarkProjectSynced(!error.has_value());
342 error.has_value() ? ProjectSyncStatus::Failed :
343 ProjectSyncStatus::Synced,
348 if (!IsCloudProject())
351 ProjectSyncStatus::Local },
355void ProjectCloudExtension::CancelSync()
357 std::vector<std::shared_ptr<UploadQueueElement>> queue;
360 auto lock = std::lock_guard { mUploadQueueMutex };
367 for (
auto& item : queue)
369 if (!item->Operation)
372 item->Operation->Cancel();
376bool ProjectCloudExtension::IsSyncing()
const
378 auto lock = std::lock_guard { mStatusMutex };
380 return mLastStatus.Status == ProjectSyncStatus::Syncing;
383std::string ProjectCloudExtension::GetCloudProjectId()
const
385 auto lock = std::lock_guard { mIdentifiersMutex };
390std::string ProjectCloudExtension::GetSnapshotId()
const
392 auto lock = std::lock_guard { mIdentifiersMutex };
399 auto lock = std::lock_guard { mUploadQueueMutex };
401 if (mUploadQueue.empty())
407 std::vector<uint8_t> data;
408 data.resize(projectSize + dictSize +
sizeof(uint64_t));
410 const uint64_t dictSizeData =
413 std::memcpy(data.data(), &dictSizeData,
sizeof(uint64_t));
415 uint64_t offset =
sizeof(dictSize);
417 for (
const auto [chunkData,
size] : serializer.
GetDict())
419 std::memcpy(data.data() + offset, chunkData,
size);
423 for (
const auto [chunkData,
size] : serializer.
GetData())
425 std::memcpy(data.data() + offset, chunkData,
size);
429 mUploadQueue.back()->Data.ProjectSnapshot = std::move(data);
431 if (mUploadQueue.back()->Operation)
433 mUploadQueue.back()->Operation->SetUploadData(mUploadQueue.back()->Data);
447 ProjectSyncStatus::Failed },
453 return mProject.weak_from_this();
456int64_t ProjectCloudExtension::GetSavesCount()
const
458 auto lock = std::lock_guard { mIdentifiersMutex };
460 if (mProjectId.empty())
464 auto dbData = cloudDatabase.GetProjectData(mProjectId);
469 return dbData->SavesCount;
476 return onUIThread ? mUIStateNotifier->SubscribeSafe(std::move(callback)) :
477 mAsyncStateNotifier->SubscribeSafe(std::move(callback));
480void ProjectCloudExtension::UpdateIdFromDatabase()
485 auto projectData = cloudDatabase.GetProjectDataForPath(
492 auto lock = std::lock_guard { mIdentifiersMutex };
494 mProjectId = projectData->ProjectId;
495 mSnapshotId = projectData->SnapshotId;
502 projectData->SyncStatus ==
503 DBProjectData::SyncStatusType::SyncStatusSynced ?
504 ProjectSyncStatus::Synced :
505 ProjectSyncStatus::Unsynced,
510void ProjectCloudExtension::UnsafeUpdateProgress()
512 if (mUploadQueue.empty())
515 int64_t handledElements = 0;
516 int64_t totalElements = 0;
518 for (
auto& element : mUploadQueue)
521 element->BlocksHandled + int(element->ProjectDataUploaded);
522 totalElements += element->BlocksTotal + 1;
525 assert(totalElements > 0);
529 ProjectSyncStatus::Syncing,
530 double(handledElements) / double(totalElements) },
534void ProjectCloudExtension::Publish(
538 auto lock = std::lock_guard { mStatusMutex };
539 mLastStatus = cloudStatus;
542 mAsyncStateNotifier->Enqueue(cloudStatus, canMerge);
543 mUIStateNotifier->Enqueue(cloudStatus, canMerge);
545 mAsyncStateNotifier->PublishSafe();
549 mUINotificationPending.store(
false);
550 mUIStateNotifier->PublishSafe();
552 else if (!mUINotificationPending.exchange(
true))
557 if (mUINotificationPending.exchange(
false))
558 mUIStateNotifier->PublishSafe();
563void ProjectCloudExtension::MarkProjectSynced(
bool success)
567 const auto projectFilePath =
570 auto previousDbData = cloudDatabase.GetProjectDataForPath(projectFilePath);
575 dbData = *previousDbData;
579 dbData.
SyncStatus = success ? DBProjectData::SyncStatusSynced :
580 DBProjectData::SyncStatusUploading;
582 cloudDatabase.UpdateProjectData(dbData);
586ProjectCloudExtension::UnsafeFindUploadQueueElement(
589 auto it = std::find_if(
590 mUploadQueue.begin(), mUploadQueue.end(),
591 [&](
const auto& element)
592 { return element->Operation.get() == &uploadOperation; });
594 return it != mUploadQueue.end() ? it->get() :
nullptr;
598ProjectCloudExtension::UnsafeFindUploadQueueElement(
602 ->UnsafeFindUploadQueueElement(uploadOperation);
605void ProjectCloudExtension::OnProjectPathChanged()
607 bool wasCloudProject =
false;
610 auto lock = std::lock_guard { mIdentifiersMutex };
612 wasCloudProject = !mProjectId.empty();
619 UpdateIdFromDatabase();
621 if (mProjectId.empty() && wasCloudProject)
628 const auto projectId =
631 const auto userSlug =
637bool ProjectCloudExtension::IsBlockLocked(int64_t blockID)
const
643 const auto projectId = GetCloudProjectId();
645 if (projectId.empty())
653 auto lock = std::lock_guard {
657 return mLastStatus.Status;
660bool ProjectCloudExtension::IsFirstSyncDialogShown()
const
662 auto lock = std::lock_guard { mIdentifiersMutex };
664 if (mProjectId.empty())
670void ProjectCloudExtension::SetFirstSyncDialogShown(
bool shown)
672 auto lock = std::lock_guard { mIdentifiersMutex };
674 if (mProjectId.empty())
680bool CloudStatusChangedMessage::IsSyncing() const noexcept
682 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, AudiocomTrace) 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