Skip to content

Commit

Permalink
Interrupted uploads will now resume when the project is opened
Browse files Browse the repository at this point in the history
  • Loading branch information
crsib committed Feb 26, 2024
1 parent 93174e2 commit f1c6c0b
Show file tree
Hide file tree
Showing 13 changed files with 436 additions and 66 deletions.
2 changes: 2 additions & 0 deletions libraries/lib-cloud-audiocom/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ set( SOURCES
sync/ProjectUploadOperation.h
sync/RemoteProjectSnapshot.cpp
sync/RemoteProjectSnapshot.h
sync/ResumedSnaphotUploadOperation.cpp
sync/ResumedSnaphotUploadOperation.h
sync/WavPackCompressor.cpp
sync/WavPackCompressor.h
)
Expand Down
49 changes: 40 additions & 9 deletions libraries/lib-cloud-audiocom/CloudSyncService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -273,8 +273,7 @@ CloudSyncService& CloudSyncService::Get()
}

CloudSyncService::GetProjectsFuture CloudSyncService::GetProjects(
concurrency::CancellationContextPtr context, int page,
int pageSize,
concurrency::CancellationContextPtr context, int page, int pageSize,
std::string searchString)
{
using namespace audacity::network_manager;
Expand Down Expand Up @@ -340,6 +339,12 @@ CloudSyncService::SyncFuture CloudSyncService::OpenFromCloud(
return mSyncPromise.get_future();
}

if (projectId.empty())
{
FailSync({ ResponseResultCode::InternalClientError, "Empty projectId" });
return mSyncPromise.get_future();
}

if (!callback)
mProgressCallback = [](auto...) { return true; };
else
Expand Down Expand Up @@ -403,8 +408,7 @@ CloudSyncService::SyncFuture CloudSyncService::SyncProject(
return;
}

auto& projectFileIO = ProjectFileIO::Get(project);

// Do not perform the snapshot request if the project is up to date
if (remoteInfo.HeadSnapshot.Id == projectInfo.SnapshotId)
{
CompleteSync({ sync::ProjectSyncResult::StatusCode::Succeeded });
Expand Down Expand Up @@ -490,15 +494,42 @@ void CloudSyncService::SyncCloudSnapshot(
InvisibleTemporaryProject project;
ProjectFileIO::Get(project.Project()).LoadProject(wxPath, true);
}
else if (HasAutosave(utf8Path))
else
{
if (mode == SyncMode::Normal)
assert(localProjectInfo.has_value());
assert(mode != SyncMode::ForceNew);
// The project exists on the disk. Depending on how we got here, we might
// different scenarios:
// 1. Local snapshot ID matches the remote snapshot ID. Just complete the
// sync right away. If the project was modified locally, but not saved,
// the user will be prompted about the autosave.
if (localProjectInfo->SnapshotId == snapshotInfo.Id)
{
FailSync({ ResponseResultCode::Conflict });
CompleteSync(
{ sync::ProjectSyncResult::StatusCode::Succeeded, {}, utf8Path });
return;
}

DropAutosave(utf8Path);
// 2. Project sync was interrupted.
if (
mode == SyncMode::Normal &&
localProjectInfo->SyncStatus != sync::DBProjectData::SyncStatusSynced)
{
// There is not enough information to decide if the project has
// diverged. Just open it, so the sync can resume. If the project has
// diverged, the user will be prompted to resolve the conflict on the
// next save.
CompleteSync(
{ sync::ProjectSyncResult::StatusCode::Succeeded, {}, utf8Path });
return;
}
// 3. Project was modified locally, but not saved.
if (HasAutosave(utf8Path))
{
if (mode == SyncMode::Normal)
FailSync({ ResponseResultCode::Conflict });
else
DropAutosave(utf8Path);
}
}

mRemoteSnapshot = sync::RemoteProjectSnapshot::Sync(
Expand Down
35 changes: 35 additions & 0 deletions libraries/lib-cloud-audiocom/sync/CloudSyncError.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,39 @@ CloudSyncError MakeClientFailure(const char* message)
return { CloudSyncError::ClientFailure, message };
}

CloudSyncError::ErrorType DeduceError(ResponseResultCode code)
{
switch (code)
{
case ResponseResultCode::Success:
return CloudSyncError::None;
case ResponseResultCode::Cancelled:
return CloudSyncError::Cancelled;
case ResponseResultCode::Expired:
return CloudSyncError::DataUploadFailed;
case ResponseResultCode::Conflict:
return CloudSyncError::ProjectVersionConflict;
case ResponseResultCode::ConnectionFailed:
return CloudSyncError::Network;
case ResponseResultCode::PaymentRequired:
return CloudSyncError::ProjectStorageLimitReached;
case ResponseResultCode::TooLarge:
return CloudSyncError::ProjectStorageLimitReached;
case ResponseResultCode::Unauthorized:
return CloudSyncError::Authorization;
case ResponseResultCode::Forbidden:
return CloudSyncError::Authorization;
case ResponseResultCode::NotFound:
return CloudSyncError::ProjectNotFound;
case ResponseResultCode::UnexpectedResponse:
return CloudSyncError::Server;
case ResponseResultCode::InternalClientError:
return CloudSyncError::ClientFailure;
case ResponseResultCode::UnknownError:
return CloudSyncError::DataUploadFailed;
}

return CloudSyncError::DataUploadFailed;
}

} // namespace audacity::cloud::audiocom::sync
5 changes: 5 additions & 0 deletions libraries/lib-cloud-audiocom/sync/CloudSyncError.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

#include <string>

#include "NetworkUtils.h"

class TranslatableString;

namespace audacity::network_manager
Expand Down Expand Up @@ -54,4 +56,7 @@ CloudSyncError MakeClientFailure(const std::string& message);
CLOUD_AUDIOCOM_API
CloudSyncError MakeClientFailure(const char* message);

CLOUD_AUDIOCOM_API CloudSyncError::ErrorType
DeduceError(ResponseResultCode code);

} // namespace audacity::cloud::audiocom::sync
3 changes: 3 additions & 0 deletions libraries/lib-cloud-audiocom/sync/CloudSyncUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ bool Deserialize(const rapidjson::Value& value, SnapshotInfo& urls)
if (!Deserialize(value, "date_updated", tempSnapshot.Updated))
return {};

if (!Deserialize(value, "date_synced", tempSnapshot.Synced))
return {};

if (!Deserialize(value, "file_size", tempSnapshot.FileSize))
return {};

Expand Down
1 change: 1 addition & 0 deletions libraries/lib-cloud-audiocom/sync/CloudSyncUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ struct SnapshotInfo final

int64_t Created {};
int64_t Updated {};
int64_t Synced {};

int64_t FileSize;
int64_t BlocksSize;
Expand Down
57 changes: 6 additions & 51 deletions libraries/lib-cloud-audiocom/sync/LocalProjectSnapshot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,44 +291,6 @@ void LocalProjectSnapshot::UploadFailed(CloudSyncError error)
mProjectCloudExtension.OnSyncCompleted(this, std::make_optional(error));
}

namespace
{
CloudSyncError::ErrorType DeduceError(ResponseResultCode code)
{
switch (code)
{
case ResponseResultCode::Success:
return CloudSyncError::None;
case ResponseResultCode::Cancelled:
return CloudSyncError::Cancelled;
case ResponseResultCode::Expired:
return CloudSyncError::DataUploadFailed;
case ResponseResultCode::Conflict:
return CloudSyncError::ProjectVersionConflict;
case ResponseResultCode::ConnectionFailed:
return CloudSyncError::Network;
case ResponseResultCode::PaymentRequired:
return CloudSyncError::ProjectStorageLimitReached;
case ResponseResultCode::TooLarge:
return CloudSyncError::ProjectStorageLimitReached;
case ResponseResultCode::Unauthorized:
return CloudSyncError::Authorization;
case ResponseResultCode::Forbidden:
return CloudSyncError::Authorization;
case ResponseResultCode::NotFound:
return CloudSyncError::ProjectNotFound;
case ResponseResultCode::UnexpectedResponse:
return CloudSyncError::Server;
case ResponseResultCode::InternalClientError:
return CloudSyncError::ClientFailure;
case ResponseResultCode::UnknownError:
return CloudSyncError::DataUploadFailed;
}

return CloudSyncError::DataUploadFailed;
}
} // namespace

void LocalProjectSnapshot::DataUploadFailed(const ResponseResult& uploadResult)
{
UploadFailed({ DeduceError(uploadResult.Code), uploadResult.Content });
Expand Down Expand Up @@ -515,7 +477,7 @@ void LocalProjectSnapshot::OnSnapshotCreated(

if (mProjectBlocksLock->MissingBlocks.empty())
{
MarkSnapshotSynced(0);
MarkSnapshotSynced();
return;
}

Expand All @@ -541,7 +503,7 @@ void LocalProjectSnapshot::OnSnapshotCreated(

if (succeeded)
{
MarkSnapshotSynced(handledBlocks);
MarkSnapshotSynced();
return;
}

Expand Down Expand Up @@ -583,26 +545,19 @@ void LocalProjectSnapshot::StorePendingSnapshot(
CloudProjectsDatabase::Get().AddPendingProjectBlocks(pendingBlocks);
}

void LocalProjectSnapshot::MarkSnapshotSynced(int64_t blocksCount)
void LocalProjectSnapshot::MarkSnapshotSynced()
{
using namespace audacity::network_manager;
using namespace network_manager;
Request request(mServiceConfig.GetSnapshotSyncUrl(
mProjectCloudExtension.GetCloudProjectId(),
mProjectCloudExtension.GetSnapshotId()));

const auto language = mServiceConfig.GetAcceptLanguageValue();

if (!language.empty())
request.setHeader(
audacity::network_manager::common_headers::AcceptLanguage, language);

request.setHeader(
common_headers::Authorization, mOAuthService.GetAccessToken());
SetCommonHeaders(request);

auto response = NetworkManager::GetInstance().doPost(request, nullptr, 0);

response->setRequestFinishedCallback(
[this, response, blocksCount](auto)
[this, response](auto)
{
CloudProjectsDatabase::Get().RemovePendingSnapshot(
mCreateSnapshotResponse->Project.Id,
Expand Down
2 changes: 1 addition & 1 deletion libraries/lib-cloud-audiocom/sync/LocalProjectSnapshot.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class CLOUD_AUDIOCOM_API LocalProjectSnapshot final :
OnSnapshotCreated(const CreateSnapshotResponse& response, bool newProject);
void StorePendingSnapshot(
const CreateSnapshotResponse& response, const ProjectUploadData& data);
void MarkSnapshotSynced(int64_t blocksCount);
void MarkSnapshotSynced();


ProjectCloudExtension& mProjectCloudExtension;
Expand Down
23 changes: 21 additions & 2 deletions libraries/lib-cloud-audiocom/sync/ProjectCloudExtension.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ struct ProjectCloudExtension::UploadQueueElement final
int64_t BlocksHandled { 0 };
int64_t BlocksTotal { 0 };

bool SnapshotCreated { false };
bool ProjectDataUploaded { false };
bool ReadyForUpload { false };
bool Synced { false };
Expand Down Expand Up @@ -160,6 +159,24 @@ void ProjectCloudExtension::OnSyncStarted()
mUploadQueue.push_back(std::move(element));
}

void cloud::audiocom::sync::ProjectCloudExtension::OnSyncResumed(
std::shared_ptr<ProjectUploadOperation> uploadOperation,
int64_t missingBlocksCount, bool needsProjectUpload)
{
auto element = std::make_shared<UploadQueueElement>();
element->Operation = uploadOperation;
element->BlocksTotal = missingBlocksCount;
element->ProjectDataUploaded = !needsProjectUpload;

{
auto lock = std::lock_guard { mUploadQueueMutex };
mUploadQueue.push_back(element);
}

uploadOperation->Start(UploadMode::Normal);
UnsafeUpdateProgress();
}

void ProjectCloudExtension::OnUploadOperationCreated(
std::shared_ptr<ProjectUploadOperation> uploadOperation)
{
Expand Down Expand Up @@ -234,7 +251,6 @@ void ProjectCloudExtension::OnSnapshotCreated(
if (!element)
return;

element->SnapshotCreated = true;
element->BlocksTotal = response.SyncState.MissingBlocks.size();

UnsafeUpdateProgress();
Expand Down Expand Up @@ -275,6 +291,9 @@ void ProjectCloudExtension::OnSyncCompleted(
{
auto lock = std::lock_guard { mUploadQueueMutex };

if (mUploadQueue.empty())
return;

if (uploadOperation == nullptr)
{
mUploadQueue.pop_back();
Expand Down
7 changes: 5 additions & 2 deletions libraries/lib-cloud-audiocom/sync/ProjectCloudExtension.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,14 @@
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>

#include "ClientData.h"
#include "Observer.h"

#include "ProjectUploadOperation.h"
#include "CloudSyncError.h"
#include "CloudSyncUtils.h"
#include "ProjectUploadOperation.h"

class AudacityProject;
class ProjectSerializer;
Expand Down Expand Up @@ -69,6 +68,10 @@ class CLOUD_AUDIOCOM_API ProjectCloudExtension final : public ClientData::Base
//! This method is called from the UI thread
void OnSyncStarted();
//! This method is called from the UI thread
void OnSyncResumed(
std::shared_ptr<ProjectUploadOperation> uploadOperation,
int64_t missingBlocksCount, bool needsProjectUpload);
//! This method is called from the UI thread
void OnUploadOperationCreated(
std::shared_ptr<ProjectUploadOperation> uploadOperation);
//! This method is called not from the UI thread
Expand Down
Loading

0 comments on commit f1c6c0b

Please sign in to comment.