17#include <wx/filename.h>
19#include <rapidjson/document.h>
20#include <rapidjson/writer.h>
44 return "audio/x-wavpack";
45 else if (ext ==
"flac")
46 return "audio/x-flac";
47 else if (ext ==
"mp3")
54 const wxString& filePath,
const wxString& projectName,
bool isPublic)
56 rapidjson::Document document;
59 const wxFileName fileName(filePath);
65 mimeType.data(), mimeType.length(), document.GetAllocator()),
66 document.GetAllocator());
70 if (!downloadMime.empty())
75 downloadMime.data(), downloadMime.length(),
76 document.GetAllocator()),
77 document.GetAllocator());
84 rapidjson::Value(
name.data(),
name.length(), document.GetAllocator()),
85 document.GetAllocator());
89 rapidjson::Value(
static_cast<int64_t
>(fileName.GetSize().GetValue())),
90 document.GetAllocator());
93 "public", rapidjson::Value(isPublic), document.GetAllocator());
95 rapidjson::StringBuffer buffer;
96 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
97 document.Accept(writer);
99 return std::string(buffer.GetString());
104 rapidjson::Document document;
105 document.SetObject();
108 "progress", rapidjson::Value(current /
static_cast<double>(total) * 100.0),
109 document.GetAllocator());
111 rapidjson::StringBuffer buffer;
112 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
113 document.Accept(writer);
115 return std::string(buffer.GetString());
124 std::enable_shared_from_this<UploadOperation>
128 wxString projectName,
bool isPublic,
131 : mServiceConfig(serviceConfig)
132 , mFileName(
std::move(fileName))
133 , mProjectName(
std::move(projectName))
134 , mIsPublic(isPublic)
135 , mCompletedCallback(
std::move(completedCallback))
136 , mProgressCallback(
std::move(progressCallback))
161 using Clock = std::chrono::steady_clock;
174 if (!mAuthToken.empty())
182 std::lock_guard<std::mutex> lock(mStatusMutex);
186 std::lock_guard<std::mutex> callbacksLock(mCallbacksMutex);
188 if (mCompletedCallback)
194 mProgressCallback = {};
195 mCompletedCallback = {};
201 std::lock_guard<std::mutex> lock(mStatusMutex);
205 std::lock_guard<std::mutex> callbacksLock(mCallbacksMutex);
207 if (mCompletedCallback)
217 mProgressCallback = {};
218 mCompletedCallback = {};
233 mAuthToken = std::string(authToken);
234 SetAuthHeader(request);
238 std::lock_guard<std::mutex> lock(mStatusMutex);
245 request, payload.data(), payload.size());
247 mActiveResponse = response;
249 response->setRequestFinishedCallback(
250 [response, sharedThis = shared_from_this(),
this](
auto) {
251 auto responseCode = response->getHTTPCode();
253 if (responseCode == 201)
255 HandleUploadPolicy(response->readAll<std::string>());
257 else if (responseCode == 401)
261 response->readAll<std::string>());
263 else if (responseCode == 422)
267 response->readAll<std::string>());
273 response->readAll<std::string>());
282 rapidjson::Document document;
283 document.Parse(uploadPolicyJSON.data(), uploadPolicyJSON.length());
286 !document.HasMember(
"url") || !document.HasMember(
"success") ||
287 !document.HasMember(
"fail") || !document.HasMember(
"progress"))
296 auto form = std::make_unique<MultipartData>();
298 if (document.HasMember(
"fields"))
300 const auto& fields = document[
"fields"];
302 for (
auto it = fields.MemberBegin(); it != fields.MemberEnd(); ++it)
303 form->Add(it->name.GetString(), it->value.GetString());
306 const auto fileField =
307 document.HasMember(
"field") ? document[
"field"].GetString() :
"file";
309 const wxFileName
name { mFileName };
327 const auto url = document[
"url"].GetString();
329 mSuccessUrl = document[
"success"].GetString();
330 mFailureUrl = document[
"fail"].GetString();
331 mProgressUrl = document[
"progress"].GetString();
333 if (document.HasMember(
"extra"))
335 const auto& extra = document[
"extra"];
337 mAudioID = extra[
"audio"][
"id"].GetString();
338 mAudioSlug = extra[
"audio"][
"slug"].GetString();
340 if (extra.HasMember(
"token"))
341 mUploadToken = extra[
"token"].GetString();
344 const auto encType = document.HasMember(
"enctype") ?
345 document[
"enctype"].GetString() :
346 "multipart/form-data";
355 std::lock_guard<std::mutex> lock(mStatusMutex);
363 mActiveResponse = response;
365 response->setRequestFinishedCallback(
366 [response, sharedThis = shared_from_this(),
this](
auto)
368 HandleS3UploadCompleted(response);
371 response->setUploadProgressCallback(
372 [response, sharedThis = shared_from_this(),
373 this](
auto current,
auto total)
374 { HandleUploadProgress(current, total); });
380 std::lock_guard<std::mutex> callbacksLock(mCallbacksMutex);
382 if (mProgressCallback)
383 mProgressCallback(current, total);
386 const auto now = Clock::now();
390 mLastProgressReportTime = now;
402 std::lock_guard<std::mutex> lock(mStatusMutex);
408 request, payload.data(), payload.size());
410 response->setRequestFinishedCallback([response](
auto) {});
418 const auto responseCode = response->getHTTPCode();
421 responseCode == 200 || responseCode == 201 || responseCode == 204;
423 Request request(success ? mSuccessUrl : mFailureUrl);
424 SetAuthHeader(request);
426 std::lock_guard<std::mutex> lock(mStatusMutex);
434 mActiveResponse = finalResponse;
436 finalResponse->setRequestFinishedCallback(
437 [finalResponse, sharedThis = shared_from_this(),
this, success](
auto)
439 const auto httpCode = finalResponse->getHTTPCode();
440 if (success && httpCode >= 200 && httpCode < 300)
448 finalResponse->readAll<std::string>());
454 std::lock_guard<std::mutex> lock(mStatusMutex);
461 std::lock_guard<std::mutex> lock(mStatusMutex);
469 if (
auto activeResponse = mActiveResponse.lock())
470 activeResponse->abort();
473 std::lock_guard<std::mutex> callbacksLock(mCallbacksMutex);
475 if (mCompletedCallback)
478 mCompletedCallback = {};
479 mProgressCallback = {};
489 auto url = mServiceConfig.
GetAPIUrl(
"/audio");
490 url +=
"/" + mAudioID +
"?token=" + mUploadToken;
495 response->setRequestFinishedCallback(
505 : mServiceConfig(config), mOAuthService(service)
510 const wxString& fileName,
const wxString& projectName,
bool isPublic,
513 if (!wxFileExists(fileName))
515 if (completedCallback)
522 auto operation = std::make_shared<AudiocomUploadOperation>(
524 std::move(completedCallback), std::move(progressCallback));
527 { operation->InitiateUpload(authToken); });
535 std::shared_ptr<UploadOperation> operation)
536 : mOperation(
std::move(operation))
547UploadOperationHandle::operator bool() const noexcept
549 return mOperation !=
nullptr;
561 if (!wxDirExists(tempPath))
570 tempPath,
XO(
"Cannot proceed to upload.")))
573 return tempPath +
"/cloud/";
581 if (!wxDirExists(tempPath))
586 wxDir::GetAllFiles(tempPath, &files, {}, wxDIR_FILES);
588 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.
const TranslatableString name
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.
Request & setHeader(const std::string &name, std::string value)
Service responsible for OAuth authentication against the audio.com service.
void ValidateAuth(std::function< void(std::string_view)> completedHandler)
Attempt to authorize the user.
Configuration for the audio.com.
std::string GetAPIUrl(std::string_view apiURI) const
Helper to construct the full URLs for the API.
std::chrono::milliseconds GetProgressCallbackTimeout() const
Timeout between progress callbacks.
MimeType GetDownloadMime() const
Return the mime type server should store the file. This is a requirement from audiocom.
std::string GetFinishUploadPage(std::string_view audioID, std::string_view token) const
Helper to construct the page URL for the anonymous upload last stage.
A unique_ptr like class that holds a pointer to UploadOperation.
UploadOperationHandle()=default
UploadOperation * operator->() const noexcept
std::shared_ptr< UploadOperation > mOperation
Class used to track the upload operation.
virtual ~UploadOperation()
std::function< void(uint64_t current, uint64_t total)> ProgressCallback
OAuthService & mOAuthService
UploadOperationHandle Upload(const wxString &fileName, const wxString &projectName, bool isPublic, CompletedCallback completedCallback, ProgressCallback progressCallback)
Uploads the file to audio.com.
const ServiceConfig & mServiceConfig
UploadService(const ServiceConfig &config, OAuthService &service)
std::function< void(const UploadOperationCompleted &)> CompletedCallback
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()
FrameStatistics & GetInstance() noexcept
const std::string ApplicationJson
std::string ToUTF8(const std::wstring &wstr)
std::string GetProgressPayload(uint64_t current, uint64_t total)
std::string_view DeduceMimeType(const wxString &ext)
const auto tempChangedSubscription
std::string GetUploadRequestPayload(const wxString &filePath, const wxString &projectName, bool isPublic)
wxString GetUploadTempPath()
const ServiceConfig & GetServiceConfig()
Returns the instance of the ServiceConfig.
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.
void HandleS3UploadCompleted(std::shared_ptr< audacity::network_manager::IResponse > response)
AudiocomUploadOperation(const ServiceConfig &serviceConfig, wxString fileName, wxString projectName, bool isPublic, UploadService::CompletedCallback completedCallback, UploadService::ProgressCallback progressCallback)
const ServiceConfig & mServiceConfig
void Abort() override
Abort the upload, if running.
UploadService::ProgressCallback mProgressCallback
UploadService::CompletedCallback mCompletedCallback
std::mutex mCallbacksMutex
void HandleUploadProgress(uint64_t current, uint64_t total)
const wxString mProjectName
void FailPromise(UploadOperationCompleted::Result result, std::string errorMessage)
void DiscardResult() override
Clock::time_point mLastProgressReportTime
bool IsCompleted() override
Returns true if the upload is finished.
std::chrono::steady_clock Clock
std::weak_ptr< audacity::network_manager::IResponse > mActiveResponse
void HandleUploadPolicy(std::string uploadPolicyJSON)
void InitiateUpload(std::string_view authToken)
void SetAuthHeader(audacity::network_manager::Request &request) const