47 {
return std::make_shared<ProjectCloudExtension>(
project); }
72 if (
Queue.empty() || !canMerge)
73 Queue.push_back({ message, canMerge });
74 else if (
Queue.back().second)
75 Queue.back().first = message;
77 Queue.push_back({ message, canMerge });
90 for (
const auto& [message,
_] : queue)
101 using QueueType = std::vector<std::pair<CloudStatusChangedMessage, bool>>;
116 , mAsyncStateNotifier { std::make_unique<CloudStatusChangedNotifier>() }
117 , mUIStateNotifier { std::make_unique<CloudStatusChangedNotifier>() }
120 UpdateIdFromDatabase();
123ProjectCloudExtension::~ProjectCloudExtension() =
default;
136bool ProjectCloudExtension::IsCloudProject()
const
138 auto lock = std::lock_guard { mIdentifiersMutex };
139 return !mProjectId.empty();
142void ProjectCloudExtension::OnLoad()
144 UpdateIdFromDatabase();
147void ProjectCloudExtension::OnSyncStarted()
149 auto element = std::make_shared<UploadQueueElement>();
155 if (pTrack->GetId() ==
TrackId {})
158 element->Data.Tracks->Add(pTrack->Duplicate());
161 auto lock = std::lock_guard { mUploadQueueMutex };
162 mUploadQueue.push_back(std::move(element));
166 std::shared_ptr<ProjectUploadOperation> uploadOperation,
167 int64_t missingBlocksCount,
bool needsProjectUpload)
169 auto element = std::make_shared<UploadQueueElement>();
170 element->Operation = uploadOperation;
171 element->BlocksTotal = missingBlocksCount;
172 element->ProjectDataUploaded = !needsProjectUpload;
175 auto lock = std::lock_guard { mUploadQueueMutex };
176 mUploadQueue.push_back(element);
179 uploadOperation->Start();
180 UnsafeUpdateProgress();
183void ProjectCloudExtension::OnUploadOperationCreated(
184 std::shared_ptr<ProjectUploadOperation> uploadOperation)
186 auto lock = std::lock_guard { mUploadQueueMutex };
187 assert(mUploadQueue.size() > 0);
189 if (mUploadQueue.empty())
192 mUploadQueue.back()->Operation = uploadOperation;
193 UnsafeUpdateProgress();
196void ProjectCloudExtension::OnBlocksHashed(
199 auto lock = std::lock_guard { mUploadQueueMutex };
200 auto element = UnsafeFindUploadQueueElement(uploadOperation);
205 element->ReadyForUpload =
true;
206 uploadOperation.
Start();
209void ProjectCloudExtension::OnSnapshotCreated(
215 const auto projectFilePath =
218 auto previousDbData = cloudDatabase.GetProjectDataForPath(projectFilePath);
223 dbData = *previousDbData;
230 dbData.
SyncStatus = DBProjectData::SyncStatusUploading;
233 cloudDatabase.UpdateProjectData(dbData);
234 cloudDatabase.SetProjectUserSlug(
238 auto lock = std::lock_guard { mIdentifiersMutex };
244 auto lock = std::lock_guard { mUploadQueueMutex };
245 auto element = UnsafeFindUploadQueueElement(uploadOperation);
251 element->SnapshotResponse = response;
253 UnsafeUpdateProgress();
256void ProjectCloudExtension::OnProjectDataUploaded(
259 auto lock = std::lock_guard { mUploadQueueMutex };
260 auto element = UnsafeFindUploadQueueElement(uploadOperation);
265 element->ProjectDataUploaded =
true;
267 UnsafeUpdateProgress();
270void ProjectCloudExtension::OnBlockUploaded(
274 auto lock = std::lock_guard { mUploadQueueMutex };
275 auto element = UnsafeFindUploadQueueElement(uploadOperation);
280 element->BlocksHandled++;
282 UnsafeUpdateProgress();
285void ProjectCloudExtension::OnSyncCompleted(
289 auto lock = std::lock_guard { mUploadQueueMutex };
291 auto element = uploadOperation !=
nullptr ?
292 UnsafeFindUploadQueueElement(*uploadOperation) :
295 if (element !=
nullptr)
297 element->Synced =
true;
300 error.has_value() && error->Type == CloudSyncError::Aborted &&
301 element->SnapshotResponse)
305 const auto projectFilePath =
308 auto previousDbData =
309 cloudDatabase.GetProjectDataForPath(projectFilePath);
314 dbData = *previousDbData;
316 const auto parentId = element->SnapshotResponse->Snapshot.ParentId;
320 cloudDatabase.UpdateProjectData(dbData);
323 auto lock = std::lock_guard { mIdentifiersMutex };
324 mSnapshotId = parentId;
331 mUploadQueue.begin(), mUploadQueue.end(),
332 [](
auto& operation) { return operation->Synced; }))
334 UnsafeUpdateProgress();
338 mUploadQueue.clear();
340 MarkProjectSynced(!error.has_value());
344 error.has_value() ? ProjectSyncStatus::Failed :
345 ProjectSyncStatus::Synced,
350 if (!IsCloudProject())
353 ProjectSyncStatus::Local },
357void ProjectCloudExtension::CancelSync()
359 std::vector<std::shared_ptr<UploadQueueElement>> queue;
362 auto lock = std::lock_guard { mUploadQueueMutex };
369 for (
auto& item : queue)
371 if (!item->Operation)
374 item->Operation->Cancel();
378bool ProjectCloudExtension::IsSyncing()
const
380 auto lock = std::lock_guard { mStatusMutex };
382 return mLastStatus.Status == ProjectSyncStatus::Syncing;
385std::string ProjectCloudExtension::GetCloudProjectId()
const
387 auto lock = std::lock_guard { mIdentifiersMutex };
392std::string ProjectCloudExtension::GetSnapshotId()
const
394 auto lock = std::lock_guard { mIdentifiersMutex };
401 auto lock = std::lock_guard { mUploadQueueMutex };
403 if (mUploadQueue.empty())
409 std::vector<uint8_t> data;
410 data.resize(projectSize + dictSize +
sizeof(uint64_t));
412 const uint64_t dictSizeData =
415 std::memcpy(data.data(), &dictSizeData,
sizeof(uint64_t));
417 uint64_t offset =
sizeof(uint64_t);
419 for (
const auto [chunkData,
size] : serializer.
GetDict())
421 std::memcpy(data.data() + offset, chunkData,
size);
425 for (
const auto [chunkData,
size] : serializer.
GetData())
427 std::memcpy(data.data() + offset, chunkData,
size);
431 mUploadQueue.back()->Data.ProjectSnapshot = std::move(data);
433 if (mUploadQueue.back()->Operation)
435 mUploadQueue.back()->Operation->SetUploadData(mUploadQueue.back()->Data);
449 ProjectSyncStatus::Failed },
455 return mProject.weak_from_this();
458int64_t ProjectCloudExtension::GetSavesCount()
const
460 auto lock = std::lock_guard { mIdentifiersMutex };
462 if (mProjectId.empty())
466 auto dbData = cloudDatabase.GetProjectData(mProjectId);
471 return dbData->SavesCount;
478 return onUIThread ? mUIStateNotifier->SubscribeSafe(std::move(callback)) :
479 mAsyncStateNotifier->SubscribeSafe(std::move(callback));
482void ProjectCloudExtension::UpdateIdFromDatabase()
487 auto projectData = cloudDatabase.GetProjectDataForPath(
494 auto lock = std::lock_guard { mIdentifiersMutex };
496 mProjectId = projectData->ProjectId;
497 mSnapshotId = projectData->SnapshotId;
504 projectData->SyncStatus ==
505 DBProjectData::SyncStatusType::SyncStatusSynced ?
506 ProjectSyncStatus::Synced :
507 ProjectSyncStatus::Unsynced,
512void ProjectCloudExtension::UnsafeUpdateProgress()
514 if (mUploadQueue.empty())
517 int64_t handledElements = 0;
518 int64_t totalElements = 0;
520 for (
auto& element : mUploadQueue)
523 element->BlocksHandled + int(element->ProjectDataUploaded);
524 totalElements += element->BlocksTotal + 1;
527 assert(totalElements > 0);
531 ProjectSyncStatus::Syncing,
532 double(handledElements) / double(totalElements) },
536void ProjectCloudExtension::Publish(
540 auto lock = std::lock_guard { mStatusMutex };
541 mLastStatus = cloudStatus;
544 mAsyncStateNotifier->Enqueue(cloudStatus, canMerge);
545 mUIStateNotifier->Enqueue(cloudStatus, canMerge);
547 mAsyncStateNotifier->PublishSafe();
551 mUINotificationPending.store(
false);
552 mUIStateNotifier->PublishSafe();
554 else if (!mUINotificationPending.exchange(
true))
559 if (mUINotificationPending.exchange(
false))
560 mUIStateNotifier->PublishSafe();
565void ProjectCloudExtension::MarkProjectSynced(
bool success)
569 const auto projectFilePath =
572 auto previousDbData = cloudDatabase.GetProjectDataForPath(projectFilePath);
577 dbData = *previousDbData;
581 dbData.
SyncStatus = success ? DBProjectData::SyncStatusSynced :
582 DBProjectData::SyncStatusUploading;
584 cloudDatabase.UpdateProjectData(dbData);
588ProjectCloudExtension::UnsafeFindUploadQueueElement(
591 auto it = std::find_if(
592 mUploadQueue.begin(), mUploadQueue.end(),
593 [&](
const auto& element)
594 { return element->Operation.get() == &uploadOperation; });
596 return it != mUploadQueue.end() ? it->get() :
nullptr;
600ProjectCloudExtension::UnsafeFindUploadQueueElement(
604 ->UnsafeFindUploadQueueElement(uploadOperation);
607void ProjectCloudExtension::OnProjectPathChanged()
609 bool wasCloudProject =
false;
612 auto lock = std::lock_guard { mIdentifiersMutex };
614 wasCloudProject = !mProjectId.empty();
621 UpdateIdFromDatabase();
623 if (mProjectId.empty() && wasCloudProject)
635 const auto projectId =
638 const auto userSlug =
641 auto projectPage = serviceConfig.GetProjectPagePath(userSlug, projectId, trace);
642 auto url = oauthService.MakeAudioComAuthorizeURL(
userId, projectPage);
646bool ProjectCloudExtension::IsBlockLocked(int64_t blockID)
const
652 const auto projectId = GetCloudProjectId();
654 if (projectId.empty())
662 auto lock = std::lock_guard {
666 return mLastStatus.Status;
669bool ProjectCloudExtension::IsFirstSyncDialogShown()
const
671 auto lock = std::lock_guard { mIdentifiersMutex };
673 if (mProjectId.empty())
679void ProjectCloudExtension::SetFirstSyncDialogShown(
bool shown)
681 auto lock = std::lock_guard { mIdentifiersMutex };
683 if (mProjectId.empty())
689bool CloudStatusChangedMessage::IsSyncing() const noexcept
691 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)
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
UserService & GetUserService()
OAuthService & GetOAuthService()
Returns the instance of the OAuthService.
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