Audacity 3.2.0
MixdownUploader.cpp
Go to the documentation of this file.
1/* SPDX-License-Identifier: GPL-2.0-or-later */
2/*!********************************************************************
3
4 Audacity: A Digital Audio Editor
5
6 MixdownUploader.cpp
7
8 Dmitry Vedenko
9
10**********************************************************************/
11
12#include "MixdownUploader.h"
13
14#include <rapidjson/document.h>
15
16#include "WaveTrack.h"
17
18#include "CodeConversions.h"
19
20#include "ServiceConfig.h"
21
22#include "Export.h"
24
25#include "ProjectRate.h"
26
27#include "DataUploader.h"
28#include "UploadService.h"
29
30#include "BasicUI.h"
31
33{
34namespace
35{
37{
38 const auto tempPath = GetUploadTempPath();
39
40 wxFileName fileName(
41 tempPath,
42 wxString::Format(
43 "%lld", std::chrono::system_clock::now().time_since_epoch().count()),
44 extension);
45
46 fileName.Mkdir(0700, wxPATH_MKDIR_FULL);
47
48 if (fileName.Exists())
49 {
50 if (!wxRemoveFile(fileName.GetFullPath()))
51 return {};
52 }
53
54 return audacity::ToUTF8(fileName.GetFullPath());
55}
56
57int CalculateChannels(const TrackList& trackList)
58{
59 auto range = trackList.Any<const WaveTrack>();
60 return std::all_of(
61 range.begin(), range.end(),
62 [](const WaveTrack* track)
63 { return IsMono(*track) && track->GetPan() == 0; }) ?
64 1 :
65 2;
66}
67
69{
70 // Why Any requires non const ref???
71 for (const auto& track :
73 .Any<PlayableTrack>())
74 {
75 if (track->GetStartTime() != track->GetEndTime())
76 return true;
77 }
78
79 return false;
80}
81
82} // namespace
83
85{
86public:
88 : mParent { parent }
89 , mTask { std::move(task) }
90 , mExportThread { [this] { ExportTread(); } }
91 {
92 }
93
94 ~DataExporter() override
95 {
96 mExportThread.join();
97 }
98
99 void Cancel()
100 {
101 mCancelled.store(true, std::memory_order_release);
102 }
103
105 {
106 return mResult;
107 }
108
110 {
111 mResult = result;
112
113 if (result == ExportResult::Success)
114 {
115 mParent.UploadMixdown();
116 }
117 else
118 {
119 mParent.ReportProgress(
122 1.0, {});
123 }
124 }
125
127 {
128 }
129
130 bool IsCancelled() const override
131 {
132 return mCancelled.load(std::memory_order_acquire);
133 }
134
135 bool IsStopped() const override
136 {
137 return false;
138 }
139
140 void OnProgress(double value) override
141 {
142 mParent.ReportProgress(MixdownState::Exporting, value, {});
143 }
144
146 {
147 try
148 {
149 auto future = mTask.get_future();
150 mTask(*this);
151 const auto result = future.get();
152
153 BasicUI::CallAfter([this, result] { OnComplete(result); });
154 }
155 catch (const ExportDiskFullError& error)
156 {
157 HandleExportDiskFullError(error);
158 }
159 catch (const ExportErrorException& error)
160 {
161 HandleExportError(error);
162 }
163 catch (const ExportException& error)
164 {
165 HandleExportException(error);
166 }
167 catch (...)
168 {
169 HandleUnkonwnException();
170 }
171 }
172
174 {
175 mParent.ReportProgress(MixdownState::Failed, 1.0, {});
176
178 [this, fileName = error.GetFileName()]
179 {
180 ShowDiskFullExportErrorDialog(fileName);
181 });
182 }
183
185 {
186 mParent.ReportProgress(MixdownState::Failed, 1.0, {});
187
189 [this, message = error.GetMessage(), helpPage = error.GetHelpPageId()]
190 {
191 ShowExportErrorDialog(message, XO("Export failed"), helpPage, true);
192 });
193 }
194
196 {
197 mParent.ReportProgress(MixdownState::Failed, 1.0, {});
198
200 [this, message = error.What()]
201 {
202 ShowExportErrorDialog(Verbatim(message), XO("Export failed"), true);
203 });
204 }
205
207 {
208 mParent.ReportProgress(MixdownState::Failed, 1.0, {});
209 BasicUI::CallAfter([] { BasicUI::ShowMessageBox(XO("Export error")); });
210 }
211
212private:
215 std::thread mExportThread;
216
217 std::atomic<bool> mCancelled { false };
219};
220
221MixdownUploader::MixdownUploader(
222 Tag, CancellationContextPtr cancellationContext, const ServiceConfig& config,
223 const AudacityProject& project, MixdownProgressCallback progressCallback)
224 : mServiceConfig { config }
225 , mProject { project }
226 , mProgressCallback { std::move(progressCallback) }
227 , mCancellationContext { std::move(cancellationContext) }
228{
230}
231
233{
234 if (wxFileExists(mExportedFilePath))
235 wxRemoveFile(mExportedFilePath);
236}
237
238std::shared_ptr<MixdownUploader> MixdownUploader::Upload(
239 CancellationContextPtr cancellationContext, const ServiceConfig& config,
240 const AudacityProject& project, MixdownProgressCallback progressCallback)
241{
242 if (!progressCallback)
243 progressCallback = [](auto...) { return true; };
244
245 if (!cancellationContext)
246 cancellationContext =
248
249 auto uploader = std::make_shared<MixdownUploader>(
250 Tag {}, cancellationContext, config, project, std::move(progressCallback));
251
252 cancellationContext->OnCancelled(uploader);
253
254 return uploader;
255}
256
258{
259 auto lock = std::lock_guard { mUploadUrlsMutex };
260
261 assert(!mUploadUrls);
262 mUploadUrls = urls;
263
264 mUploadUrlsSet.notify_one();
265}
266
268{
269 if (!mDataExporter)
270 return;
271
272 if (mUploadCancelled.exchange(true, std::memory_order_acq_rel))
273 return;
274
275 // To be on a safe side, we cancel both operations
276 mDataExporter->Cancel();
277 // And ensure that WaitingForUrls is interrupted too
278 mUploadUrlsSet.notify_all();
279}
280
281std::future<MixdownResult> MixdownUploader::GetResultFuture()
282{
283 return mPromise.get_future();
284}
285
287 MixdownState state, double progress, ResponseResult uploadResult)
288{
289 mProgress.store(progress);
290
292 {
293 mProgressUpdateQueued = false;
294 mProgressCallback(progress);
295 }
296 else if (!mProgressUpdateQueued)
297 {
299
301 [weakThis = weak_from_this(), this]
302 {
303 auto lock = weakThis.lock();
304
305 if (!lock)
306 return;
307
308 if (mFinished.load())
309 return;
310
312
313 mProgressUpdateQueued = false;
314 });
315 }
316
317 if (
318 state == MixdownState::Succeeded || state == MixdownState::Failed ||
320 {
321 mFinished.store(true);
322 mPromise.set_value({ state, uploadResult });
323 }
324}
325
327{
329 {
330 mFinished.store(true);
331 mPromise.set_value({ MixdownState::Empty });
332 return;
333 }
334
336
337 const double t0 = 0.0;
338 const double t1 = tracks.GetEndTime();
339
340 const int nChannels = CalculateChannels(tracks);
341
342 auto hasMimeType = [](const auto&& mimeTypes, const std::string& mimeType)
343 {
344 return std::find(mimeTypes.begin(), mimeTypes.end(), mimeType) !=
345 mimeTypes.end();
346 };
347
348 const auto& registry = ExportPluginRegistry::Get();
349
350 for (const auto& preferredMimeType :
351 GetServiceConfig().GetPreferredAudioFormats(false))
352 {
353 auto config = GetServiceConfig().GetExportConfig(preferredMimeType);
354
356 auto pluginIt = std::find_if(
357 registry.begin(), registry.end(),
358 [&](auto t)
359 {
360 auto [plugin, formatIndex] = t;
361 parameters.clear();
362 return hasMimeType(
363 plugin->GetMimeTypes(formatIndex), preferredMimeType) &&
364 plugin->ParseConfig(formatIndex, config, parameters);
365 });
366
367 if (pluginIt == registry.end())
368 continue;
369
370 const auto [plugin, formatIndex] = *pluginIt;
371
372 const auto formatInfo = plugin->GetFormatInfo(formatIndex);
373 const auto path = GenerateTempPath(formatInfo.extensions[0]);
374
375 if (path.empty())
376 continue;
377
378 auto builder = ExportTaskBuilder {}
379 .SetParameters(parameters)
380 .SetNumChannels(nChannels)
382 .SetPlugin(plugin)
384 .SetRange(t0, t1, false);
385
386 mExportedFilePath = path;
387
388 mDataExporter = std::make_unique<DataExporter>(
389 *this, builder.Build(const_cast<AudacityProject&>(mProject)));
390
391 return;
392 }
393
394 if (!mDataExporter)
395 {
396 mFinished.store(true);
397 mPromise.set_value({ MixdownState::Failed });
398 }
399}
400
402{
404
405 {
406 auto lock = std::unique_lock { mUploadUrlsMutex };
407 mUploadUrlsSet.wait(
408 lock, [this] { return mUploadCancelled.load() || !!mUploadUrls; });
409 }
410
411 if (mUploadCancelled.load(std::memory_order_acquire))
412 {
414 return;
415 }
416
418
421 [this, strongThis = shared_from_this()](ResponseResult result)
422 {
423 const auto state = [code = result.Code]
424 {
425 if (code == SyncResultCode::Success)
427 else if (code == SyncResultCode::Cancelled)
429 else
431 }();
432
433 ReportProgress(state, 1.0, result);
434 },
435 [this, strongThis = shared_from_this()](double progress)
436 { ReportProgress(MixdownState::Uploading, progress, {}); });
437}
438} // namespace audacity::cloud::audiocom::sync
Toolkit-neutral facade for basic user interface services.
Declare functions to perform UTF-8 to std::wstring conversions.
#define str(a)
std::packaged_task< ExportResult(ExportProcessorDelegate &)> ExportTask
Definition: ExportTypes.h:31
ExportResult
Definition: ExportTypes.h:24
XO("Cut/Copy/Paste")
wxString FileExtension
File extension, not including any leading dot.
Definition: Identifier.h:224
an object holding per-project preferred sample rate
const auto tracks
const auto project
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
const wxFileNameWrapper & GetFileName() const noexcept
const TranslatableString & GetMessage() const noexcept
const wxString & GetHelpPageId() const noexcept
const wxString & What() const noexcept
static ExportPluginRegistry & Get()
std::vector< std::tuple< ExportOptionID, ExportValue > > Parameters
Definition: ExportPlugin.h:93
ExportTaskBuilder & SetPlugin(const ExportPlugin *plugin, int format=0) noexcept
Definition: Export.cpp:59
ExportTaskBuilder & SetParameters(ExportProcessor::Parameters parameters) noexcept
Definition: Export.cpp:47
ExportTaskBuilder & SetNumChannels(unsigned numChannels) noexcept
Definition: Export.cpp:53
ExportTaskBuilder & SetSampleRate(double sampleRate) noexcept
Definition: Export.cpp:72
ExportTaskBuilder & SetFileName(const wxFileName &filename)
Definition: Export.cpp:33
ExportTaskBuilder & SetRange(double t0, double t1, bool selectedOnly=false) noexcept
Definition: Export.cpp:39
static ProjectRate & Get(AudacityProject &project)
Definition: ProjectRate.cpp:28
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:850
auto Any() -> TrackIterRange< TrackType >
Definition: Track.h:950
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:314
Holds a msgid for the translation catalog; may also bind format arguments.
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
Configuration for the audio.com.
Definition: ServiceConfig.h:23
rapidjson::Document GetExportConfig(const std::string &exporterName) const
Export configuration suitable for the mime type provided.
void Upload(CancellationContextPtr cancellationContex, const ServiceConfig &config, const UploadUrls &target, std::vector< uint8_t > data, std::function< void(ResponseResult)> callback, std::function< void(double)> progressCallback={})
void SetStatusString(const TranslatableString &str) override
void HandleExportDiskFullError(const ExportDiskFullError &error)
DataExporter(MixdownUploader &parent, ExportTask task)
void ReportProgress(MixdownState state, double progress, ResponseResult uploadResult)
std::unique_ptr< DataExporter > mDataExporter
static std::shared_ptr< MixdownUploader > Upload(concurrency::CancellationContextPtr cancellationContext, const ServiceConfig &config, const AudacityProject &project, MixdownProgressCallback progressCallback)
concurrency::CancellationContextPtr mCancellationContext
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
Definition: BasicUI.cpp:214
bool IsUiThread()
Whether the current thread is the UI thread.
Definition: BasicUI.h:399
MessageBoxResult ShowMessageBox(const TranslatableString &message, MessageBoxOptions options={})
Show a modal message box with either Ok or Yes and No, and optionally Cancel.
Definition: BasicUI.h:279
double GetRate(const Track &track)
Definition: TimeTrack.cpp:182
std::function< void(double progress)> MixdownProgressCallback
const ServiceConfig & GetServiceConfig()
Returns the instance of the ServiceConfig.
std::shared_ptr< CancellationContext > CancellationContextPtr
std::string ToUTF8(const std::wstring &wstr)
wxString ToWXString(const std::string &str)
STL namespace.