18#include <wx/filename.h>
20#include <rapidjson/document.h>
21#include <rapidjson/writer.h>
46 return "audio/x-wavpack";
47 else if (ext ==
"flac")
48 return "audio/x-flac";
49 else if (ext ==
"mp3")
56 const wxString& filePath,
const wxString& projectName,
bool isPublic)
58 rapidjson::Document document;
61 const wxFileName fileName(filePath);
67 mimeType.data(), mimeType.length(), document.GetAllocator()),
68 document.GetAllocator());
72 if (!downloadMime.empty())
77 downloadMime.data(), downloadMime.length(),
78 document.GetAllocator()),
79 document.GetAllocator());
86 rapidjson::Value(
name.data(),
name.length(), document.GetAllocator()),
87 document.GetAllocator());
91 rapidjson::Value(
static_cast<int64_t
>(fileName.GetSize().GetValue())),
92 document.GetAllocator());
95 "public", rapidjson::Value(isPublic), document.GetAllocator());
97 rapidjson::StringBuffer buffer;
98 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
99 document.Accept(writer);
101 return std::string(buffer.GetString());
106 rapidjson::Document document;
107 document.SetObject();
110 "progress", rapidjson::Value(current /
static_cast<double>(total) * 100.0),
111 document.GetAllocator());
113 rapidjson::StringBuffer buffer;
114 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
115 document.Accept(writer);
117 return std::string(buffer.GetString());
122 rapidjson::StringStream stream(payloadText.c_str());
123 rapidjson::Document document;
125 document.ParseStream(stream);
127 if (!document.IsObject())
130 assert(document.IsObject());
136 auto readInt = [&document](
const char*
name) {
137 return document.HasMember(
name) && document[
name].IsInt() ?
138 document[
name].GetInt() :
142 auto readString = [&document](
const char*
name) ->
const char*
144 return document.HasMember(
name) && document[
name].IsString() ?
145 document[
name].GetString() :
149 payload.
code = readInt(
"code");
150 payload.
status = readInt(
"status");
152 payload.
name = readString(
"name");
153 payload.
message = readString(
"message");
155 if (document.HasMember(
"errors") && document[
"errors"].IsObject())
157 for (
auto& err : document[
"errors"].GetObject ())
159 if (!err.value.IsString())
163 err.name.GetString(), err.value.GetString());
176 std::enable_shared_from_this<UploadOperation>
180 wxString projectName,
bool isPublic,
183 : mServiceConfig(serviceConfig)
184 , mFileName(
std::move(fileName))
185 , mProjectName(
std::move(projectName))
186 , mIsPublic(isPublic)
187 , mAudiocomTrace(trace)
188 , mCompletedCallback(
std::move(completedCallback))
189 , mProgressCallback(
std::move(progressCallback))
216 using Clock = std::chrono::steady_clock;
229 if (!mAuthToken.empty())
235 if (!language.empty())
244 std::lock_guard<std::mutex> lock(mStatusMutex);
248 std::lock_guard<std::mutex> callbacksLock(mCallbacksMutex);
250 if (mCompletedCallback)
256 mProgressCallback = {};
257 mCompletedCallback = {};
263 std::lock_guard<std::mutex> lock(mStatusMutex);
267 std::lock_guard<std::mutex> callbacksLock(mCallbacksMutex);
269 if (mCompletedCallback)
271 const auto uploadURL = mAuthToken.empty() ?
273 mAudioID, mUploadToken, mAudiocomTrace) :
275 mUserName, mAudioSlug, mAudiocomTrace);
282 mProgressCallback = {};
283 mCompletedCallback = {};
298 mAuthToken = std::string(authToken);
299 SetRequiredHeaders(request);
303 std::lock_guard<std::mutex> lock(mStatusMutex);
310 request, payload.data(), payload.size());
312 mActiveResponse = response;
314 response->setRequestFinishedCallback(
315 [response, sharedThis = shared_from_this(),
this](
auto) {
316 auto responseCode = response->getHTTPCode();
318 if (responseCode == 201)
320 HandleUploadPolicy(response->readAll<std::string>());
322 else if (responseCode == 401)
326 response->readAll<std::string>());
328 else if (responseCode == 422)
332 response->readAll<std::string>());
338 response->readAll<std::string>());
347 rapidjson::Document document;
348 document.Parse(uploadPolicyJSON.data(), uploadPolicyJSON.length());
351 !document.HasMember(
"url") || !document.HasMember(
"success") ||
352 !document.HasMember(
"fail") || !document.HasMember(
"progress"))
361 auto form = std::make_unique<MultipartData>();
363 if (document.HasMember(
"fields"))
365 const auto& fields = document[
"fields"];
367 for (
auto it = fields.MemberBegin(); it != fields.MemberEnd(); ++it)
368 form->Add(it->name.GetString(), it->value.GetString());
371 const auto fileField =
372 document.HasMember(
"field") ? document[
"field"].GetString() :
"file";
374 const wxFileName
name { mFileName };
392 const auto url = document[
"url"].GetString();
394 mSuccessUrl = document[
"success"].GetString();
395 mFailureUrl = document[
"fail"].GetString();
396 mProgressUrl = document[
"progress"].GetString();
398 if (document.HasMember(
"extra"))
400 const auto& extra = document[
"extra"];
402 mAudioID = extra[
"audio"][
"id"].GetString();
403 mAudioSlug = extra[
"audio"][
"slug"].GetString();
405 if (extra.HasMember(
"token"))
406 mUploadToken = extra[
"token"].GetString();
408 mUserName = extra[
"audio"][
"username"].GetString();
411 const auto encType = document.HasMember(
"enctype") ?
412 document[
"enctype"].GetString() :
413 "multipart/form-data";
422 std::lock_guard<std::mutex> lock(mStatusMutex);
430 mActiveResponse = response;
432 response->setRequestFinishedCallback(
433 [response, sharedThis = shared_from_this(),
this](
auto)
435 HandleS3UploadCompleted(response);
438 response->setUploadProgressCallback(
439 [response, sharedThis = shared_from_this(),
440 this](
auto current,
auto total)
441 { HandleUploadProgress(current, total); });
447 std::lock_guard<std::mutex> callbacksLock(mCallbacksMutex);
449 if (mProgressCallback)
450 mProgressCallback(current, total);
453 const auto now = Clock::now();
457 mLastProgressReportTime = now;
469 std::lock_guard<std::mutex> lock(mStatusMutex);
475 request, payload.data(), payload.size());
477 response->setRequestFinishedCallback([response](
auto) {});
485 const auto responseCode = response->getHTTPCode();
488 responseCode == 200 || responseCode == 201 || responseCode == 204;
490 Request request(success ? mSuccessUrl : mFailureUrl);
491 SetRequiredHeaders(request);
493 std::lock_guard<std::mutex> lock(mStatusMutex);
501 mActiveResponse = finalResponse;
503 finalResponse->setRequestFinishedCallback(
504 [finalResponse, sharedThis = shared_from_this(),
this, success](
auto)
506 const auto httpCode = finalResponse->getHTTPCode();
507 if (success && httpCode >= 200 && httpCode < 300)
515 finalResponse->readAll<std::string>());
521 std::lock_guard<std::mutex> lock(mStatusMutex);
528 std::lock_guard<std::mutex> lock(mStatusMutex);
536 if (
auto activeResponse = mActiveResponse.lock())
537 activeResponse->abort();
540 std::lock_guard<std::mutex> callbacksLock(mCallbacksMutex);
542 if (mCompletedCallback)
545 mCompletedCallback = {};
546 mProgressCallback = {};
556 auto url = mServiceConfig.
GetAPIUrl(
"/audio");
557 url +=
"/" + mAudioID +
"?token=" + mUploadToken;
562 response->setRequestFinishedCallback(
572 : mServiceConfig(config), mOAuthService(service)
577 const wxString& fileName,
const wxString& projectName,
bool isPublic,
581 if (!wxFileExists(fileName))
583 if (completedCallback)
590 auto operation = std::make_shared<AudiocomUploadOperation>(
592 std::move(completedCallback), std::move(progressCallback), trace);
595 [operation,
this](std::string_view authToken) {
596 operation->InitiateUpload(authToken);
606 std::shared_ptr<UploadOperation> operation)
607 : mOperation(
std::move(operation))
618UploadOperationHandle::operator bool() const noexcept
620 return mOperation !=
nullptr;
632 if (!wxDirExists(tempPath))
641 tempPath,
XO(
"Cannot proceed to upload.")))
644 return tempPath +
"/cloud/";
652 if (!wxDirExists(tempPath))
657 wxDir::GetAllFiles(tempPath, &files, {}, wxDIR_FILES);
659 for (
const auto& file : files)
Declare abstract class AudacityException, some often-used subclasses, and GuardedCall.
Declare functions to perform UTF-8 to std::wstring conversions.
Declare an interface for HTTP response.
Declare a class for performing HTTP requests.
Declare a class for constructing HTTP requests.
Subscription Subscribe(Callback callback)
Connect a callback to the Publisher; later-connected are called earlier.
Service responsible for OAuth authentication against the audio.com service.
void ValidateAuth(AuthSuccessCallback completedHandler, AudiocomTrace, bool silent)
Attempt to authorize the user.
Configuration for the audio.com.
std::string GetAudioURL(std::string_view userSlug, std::string_view audioSlug, AudiocomTrace) const
Helper to construct the page URL for the authorised upload.
std::string GetFinishUploadPage(std::string_view audioID, std::string_view token, AudiocomTrace) const
Helper to construct the page URL for the anonymous upload last stage.
std::chrono::milliseconds GetProgressCallbackTimeout() const
Timeout between progress callbacks.
std::string GetDownloadMime() const
std::string GetAPIUrl(std::string_view apiURI) const
Helper to construct the full URLs for the API.
std::string GetAcceptLanguageValue() const
Returns the preferred language.
A unique_ptr like class that holds a pointer to UploadOperation.
std::shared_ptr< UploadOperation > mOperation
UploadOperation * operator->() const noexcept
UploadOperationHandle()=default
Class used to track the upload operation.
virtual ~UploadOperation()
UploadOperationHandle Upload(const wxString &fileName, const wxString &projectName, bool isPublic, CompletedCallback completedCallback, ProgressCallback progressCallback, AudiocomTrace)
Uploads the file to audio.com.
std::function< void(uint64_t current, uint64_t total)> ProgressCallback
const ServiceConfig & mServiceConfig
OAuthService & mOAuthService
UploadService(const ServiceConfig &config, OAuthService &service)
std::function< void(const UploadOperationCompleted &)> CompletedCallback
static NetworkManager & GetInstance()
ResponsePtr doPost(const Request &request, const void *data, size_t size)
ResponsePtr doDelete(const Request &request)
ResponsePtr doPatch(const Request &request, const void *data, size_t size)
Request & setHeader(const std::string &name, std::string value)
FILES_API bool WritableLocationCheck(const FilePath &path, const TranslatableString &message)
Check location on writable access and return true if checked successfully.
FILES_API const FilePath & DefaultTempDir()
FILES_API Observer::Publisher< FilePath > & GetTempPathObserver()
std::string GetUploadRequestPayload(const wxString &filePath, const wxString &projectName, bool isPublic)
std::string_view DeduceMimeType(const wxString &ext)
const auto tempChangedSubscription
std::string GetProgressPayload(uint64_t current, uint64_t total)
UploadFailedPayload ParseUploadFailedMessage(const std::string &payloadText)
wxString GetUploadTempPath()
const ServiceConfig & GetServiceConfig()
Returns the instance of the ServiceConfig.
const std::string ApplicationJson
std::string ToUTF8(const std::wstring &wstr)
This structure represents an upload error as returned by the server.
std::vector< AdditionalError > additionalErrors
Result
Result of the upload.
@ FileNotFound
Specified file is not found.
@ InvalidData
audio.com has failed to understand what Audacity wants
@ Success
Upload was successful.
@ Aborted
Upload was aborted by the user.
@ UnexpectedResponse
Audacity has failed to understand audio.com response.
@ UploadFailed
Upload failed for some other reason.
@ Unauthorized
Authorization is required.
This structure represents the payload associated with successful upload.
void DiscardResult() override
UploadService::CompletedCallback mCompletedCallback
const AudiocomTrace mAudiocomTrace
AudiocomUploadOperation(const ServiceConfig &serviceConfig, wxString fileName, wxString projectName, bool isPublic, UploadService::CompletedCallback completedCallback, UploadService::ProgressCallback progressCallback, AudiocomTrace trace)
void HandleS3UploadCompleted(std::shared_ptr< audacity::network_manager::IResponse > response)
std::mutex mCallbacksMutex
const ServiceConfig & mServiceConfig
const wxString mProjectName
std::chrono::steady_clock Clock
bool IsCompleted() override
Returns true if the upload is finished.
void FailPromise(UploadOperationCompleted::Result result, std::string errorMessage)
UploadService::ProgressCallback mProgressCallback
void HandleUploadProgress(uint64_t current, uint64_t total)
std::weak_ptr< audacity::network_manager::IResponse > mActiveResponse
void InitiateUpload(std::string_view authToken)
void Abort() override
Abort the upload, if running.
Clock::time_point mLastProgressReportTime
void HandleUploadPolicy(std::string uploadPolicyJSON)
void SetRequiredHeaders(audacity::network_manager::Request &request) const