Audacity 3.2.0
ShareAudioDialog.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 ShareAudioDialog.cpp
7
8 Dmitry Vedenko
9
10**********************************************************************/
11#include "ShareAudioDialog.h"
12
13#include <cassert>
14#include <rapidjson/document.h>
15
16#include <wx/bmpbuttn.h>
17#include <wx/button.h>
18#include <wx/clipbrd.h>
19#include <wx/gauge.h>
20#include <wx/stattext.h>
21#include <wx/statline.h>
22#include <wx/textctrl.h>
23#include <wx/radiobut.h>
24
25#include "AllThemeResources.h"
26#include "BasicUI.h"
27#include "MemoryX.h"
28#include "Project.h"
29#include "ShuttleGui.h"
30#include "Theme.h"
31#include "Track.h"
32#include "WaveTrack.h"
33
34#include "ServiceConfig.h"
35#include "OAuthService.h"
36#include "UploadService.h"
37#include "UserService.h"
38
40#include "LinkAccountDialog.h"
41#include "UserImage.h"
42
43#include "CodeConversions.h"
44
45#include "Export.h"
47#include "ExportUtils.h"
50
51#include "WindowAccessible.h"
52#include "HelpSystem.h"
53#include "ProjectRate.h"
54
55#ifdef HAS_CUSTOM_URL_HANDLING
56#include "URLSchemesRegistry.h"
57#endif
58
59namespace cloud::audiocom
60{
61namespace
62{
63const wxSize avatarSize = { 32, 32 };
64
66{
67 const auto tempPath = GetUploadTempPath();
68
69 wxFileName fileName(
70 tempPath,
71 wxString::Format(
72 "%lld", std::chrono::system_clock::now().time_since_epoch().count()),
73 extension);
74
75 fileName.Mkdir(0700, wxPATH_MKDIR_FULL);
76
77 if (fileName.Exists())
78 {
79 if (!wxRemoveFile(fileName.GetFullPath()))
80 return {};
81 }
82
83 return fileName.GetFullPath();
84}
85
86const auto publicLabelText = XO("Public");
88 XO("Anyone will be able to listen to this audio.");
89
90const auto unlistedLabelText = XO("Unlisted");
92 "Only you and people you share a link with will be able to listen to this audio.");
93
94}
95
96// A helper structures holds UploadService and UploadPromise
98{
100
102
105 {
106 }
107};
108
110{
111public:
113 : mParent(parent)
114 {
115
116 }
117
119
120 void Cancel()
121 {
122 mCancelled.store(true, std::memory_order_release);
123 }
124
126 {
127 return mResult;
128 }
129
131 {
132 mResult = result;
133 }
134
136 {
137 }
138
139 bool IsCancelled() const override
140 {
141 return mCancelled.load(std::memory_order_acquire);
142 }
143
144 bool IsStopped() const override
145 {
146 return false;
147 }
148
149 void OnProgress(double value) override
150 {
151 mProgress.store(value, std::memory_order_release);
152 }
153
154 void UpdateUI()
155 {
156 constexpr auto ProgressSteps = 1000ull;
157
158 mParent.UpdateProgress(mProgress.load(std::memory_order_acquire) * ProgressSteps, ProgressSteps);
159 }
160
161private:
162
164
165 std::atomic<bool> mCancelled{false};
166 std::atomic<double> mProgress;
168};
169
172 parent, wxID_ANY, XO("Share Audio"), wxDefaultPosition, { 480, 250 },
173 wxDEFAULT_DIALOG_STYLE)
174 , mProject(project)
175 , mInitialStatePanel(*this)
176 , mServices(std::make_unique<Services>())
177{
179
180 ShuttleGui s(this, eIsCreating);
181
182 s.StartVerticalLay();
183 {
184 Populate(s);
185 }
186 s.EndVerticalLay();
187
188 Layout();
189 Fit();
190 Centre();
191
192 const auto size = GetSize();
193
194 SetMinSize({ size.x, std::min(250, size.y) });
195 SetMaxSize({ size.x, -1 });
196
197 mContinueAction = [this]()
198 {
199 if (mInitialStatePanel.root->IsShown())
200 StartUploadProcess();
201 };
202
203 Bind(
204 wxEVT_CHAR_HOOK,
205 [this](auto& evt)
206 {
207 if (!IsEscapeKey(evt))
208 {
209 evt.Skip();
210 return;
211 }
212
213 OnCancel();
214 });
215}
216
218{
220 // Clean up the temp file when the dialog is closed
221 if (!mFilePath.empty() && wxFileExists(mFilePath))
222 wxRemoveFile(mFilePath);
223}
224
226{
229
230 s.StartHorizontalLay(wxEXPAND, 0);
231 {
233 {
234 s.SetBorder(2);
235 s.StartHorizontalLay(wxEXPAND, 0);
236 {
237 s.AddSpace(0, 0, 1);
238
239 mCancelButton = s.AddButton(XXO("&Cancel"));
240 mCancelButton->Bind(wxEVT_BUTTON, [this](auto) { OnCancel(); });
241
242 s.AddSpace(4, 0, 0);
243
244 mContinueButton = s.AddButton(XXO("C&ontinue"));
245 mContinueButton->Bind(wxEVT_BUTTON, [this](auto) { OnContinue(); });
246 }
248 }
250 }
252
253 const auto title = mProject.GetProjectName();
254
255 if (!title.empty())
256 {
258 mInitialStatePanel.trackTitle->SetInsertionPoint(title.length());
259 }
260
262
264 wxEVT_TEXT,
265 [this](auto&) {
266 mContinueButton->Enable(
268 });
269}
270
272{
273 if (mInProgress)
274 {
275 AudacityMessageDialog dlgMessage(
276 this, XO("Are you sure you want to cancel?"), XO("Cancel upload to Audio.com"),
277 wxYES_NO | wxICON_QUESTION | wxNO_DEFAULT | wxSTAY_ON_TOP);
278
279 const auto result = dlgMessage.ShowModal();
280
281 if (result != wxID_YES)
282 return;
283
284 // If export has started, notify it that it should be canceled
286 mExportProgressUpdater->Cancel();
287 }
288
289
290 // If upload was started - ask it to discard the result.
291 // The result should be discarded even after the upload has finished
292 if (mServices->uploadPromise)
293 mServices->uploadPromise->DiscardResult();
294
295 EndModal(wxID_CANCEL);
296}
297
299{
301}
302
303namespace
304{
305int CalculateChannels(const TrackList& trackList)
306{
307 auto range = trackList.Any<const WaveTrack>();
308 return std::all_of(range.begin(), range.end(), [](const WaveTrack *track){
309 return IsMono(*track) && track->GetPan() == 0;
310 }) ? 1 : 2;
311}
312}
313
315{
317
318 const double t0 = 0.0;
319 const double t1 = tracks.GetEndTime();
320
321 const int nChannels = CalculateChannels(tracks);
322
323 auto hasMimeType = [](const auto&& mimeTypes, const std::string& mimeType)
324 {
325 return std::find(mimeTypes.begin(), mimeTypes.end(), mimeType) != mimeTypes.end();
326 };
327
328 const auto& registry = ExportPluginRegistry::Get();
329
330 for(const auto& preferredMimeType : GetServiceConfig().GetPreferredAudioFormats())
331 {
332 auto config = GetServiceConfig().GetExportConfig(preferredMimeType);
334 auto pluginIt = std::find_if(registry.begin(), registry.end(), [&](auto t)
335 {
336 auto [plugin, formatIndex] = t;
337 parameters.clear();
338 return hasMimeType(plugin->GetMimeTypes(formatIndex), preferredMimeType) &&
339 plugin->ParseConfig(formatIndex, config, parameters);
340 });
341
342 if(pluginIt == registry.end())
343 continue;
344
345 const auto [plugin, formatIndex] = *pluginIt;
346
347 const auto formatInfo = plugin->GetFormatInfo(formatIndex);
348 const auto path = GenerateTempPath(formatInfo.extensions[0]);
349
350 if(path.empty())
351 continue;
352
353 mExportProgressUpdater = std::make_unique<ExportProgressUpdater>(*this);
354
355 auto builder = ExportTaskBuilder{}
356 .SetParameters(parameters)
357 .SetNumChannels(nChannels)
359 .SetPlugin(plugin)
360 .SetFileName(path)
361 .SetRange(t0, t1, false);
362
363 auto result = ExportResult::Error;
365 {
366 auto exportTask = builder.Build(mProject);
367
368 auto f = exportTask.get_future();
369 std::thread(std::move(exportTask), std::ref(*mExportProgressUpdater)).detach();
370
372 {
373 if(f.wait_for(std::chrono::milliseconds(50)) != std::future_status::ready)
374 mExportProgressUpdater->UpdateUI();
375 result = f.get();
376 });
377 });
378
379 mExportProgressUpdater->SetResult(result);
380 const auto success = result == ExportResult::Success;
381 if(!success && wxFileExists(path))
382 wxRemoveFile(path);
383 if(success)
384 return path;
385 }
386 return {};
387}
388
390{
391 mInProgress = true;
392
393 mInitialStatePanel.root->Hide();
394 mProgressPanel.root->Show();
395
396 mProgressPanel.info->Hide();
397
398 mContinueButton->Hide();
399
400 Layout();
401 Fit();
402
404
406
407 if(mFilePath.empty())
408 {
411 {
413 }
414
415 return;
416 }
417
418 mProgressPanel.title->SetLabel(XO("Uploading audio...").Translation());
420
421 mServices->uploadPromise = mServices->uploadService.Upload(
422 mFilePath,
424 false,
425 [this](const auto& result)
426 {
427 CallAfter(
428 [this, result]()
429 {
430 mInProgress = false;
431
432 if (result.result == UploadOperationCompleted::Result::Success)
433 {
434 // Success indicates that UploadSuccessfulPayload is in the payload
435 assert(std::holds_alternative<UploadSuccessfulPayload>(result.payload));
436
437 if (
438 auto payload =
439 std::get_if<UploadSuccessfulPayload>(&result.payload))
440 HandleUploadSucceeded(*payload);
441 else
442 HandleUploadSucceeded({});
443
444 }
445 else if (
446 result.result != UploadOperationCompleted::Result::Aborted)
447 {
448 if (
449 auto payload =
450 std::get_if<UploadFailedPayload>(&result.payload))
451 HandleUploadFailed(*payload);
452 else
453 HandleUploadFailed({});
454 }
455 });
456 },
457 [this](auto current, auto total)
458 {
459 CallAfter(
460 [this, current, total]()
461 {
462 UpdateProgress(current, total);
463 });
464 });
465}
466
467void ShareAudioDialog::HandleUploadSucceeded(
468 const UploadSuccessfulPayload& payload)
469{
470 EndModal(wxID_CLOSE);
471 OpenInDefaultBrowser(wxString { payload.audioUrl });
472}
473
474void ShareAudioDialog::HandleUploadFailed(const UploadFailedPayload& payload)
475{
476 EndModal(wxID_ABORT);
477
478 TranslatableString message;
479
480 if (!payload.message.empty())
481 {
482 auto details = payload.message;
483
484 for (auto& err : payload.additionalErrors)
485 details += " " + err.second;
486
487 message = XO("Error: %s").Format(details);
488 }
489 else
490 {
491 message = XO(
492 "We are unable to upload this file. Please try again and make sure to link to your audio.com account before uploading.");
493 }
494
496 {}, XO("Upload error"),
497 message,
498 {},
500
501}
502
503void ShareAudioDialog::HandleExportFailure()
504{
505 EndModal(wxID_ABORT);
506
508 {}, XO("Export error"),
509 XO("We are unable to prepare this file for uploading."), {},
511}
512
513void ShareAudioDialog::ResetProgress()
514{
515 mStageStartTime = Clock::now();
516 mLastUIUpdateTime = mStageStartTime;
517
518 mProgressPanel.elapsedTime->SetLabel(" 00:00:00");
519 mProgressPanel.remainingTime->SetLabel(" 00:00:00");
520 mProgressPanel.progress->SetValue(0);
521
522 mLastProgressValue = 0;
523
524 mExportProgressUpdater.reset();
525
527}
528
529namespace
530{
531void SetTimeLabel(wxStaticText* label, std::chrono::milliseconds time)
532{
533 wxTimeSpan tsElapsed(0, 0, 0, time.count());
534
535 label->SetLabel(tsElapsed.Format(wxT(" %H:%M:%S")));
536 label->SetName(label->GetLabel());
537 label->Update();
538}
539}
540
541void ShareAudioDialog::UpdateProgress(uint64_t current, uint64_t total)
542{
543 using namespace std::chrono;
544
545 const auto now = Clock::now();
546
547 if (current == 0)
548 return;
549
550 if (current > total)
551 current = total;
552
553 if (mLastProgressValue != current)
554 {
555 constexpr int scale = 10000;
556
557 mLastProgressValue = static_cast<int>(current);
558
559 mProgressPanel.progress->SetRange(scale);
560 mProgressPanel.progress->SetValue((current * scale) / total);
561
562 if (current == total && mServices->uploadPromise)
563 {
564 mProgressPanel.timePanel->Hide();
565 mProgressPanel.title->SetLabel(XO("Finalizing upload...").Translation());
566 }
567 }
568
569 const auto elapsedSinceUIUpdate = now - mLastUIUpdateTime;
570
571 constexpr auto uiUpdateTimeout = 500ms;
572
573 if (elapsedSinceUIUpdate < uiUpdateTimeout && current < total)
574 return;
575
576 mLastUIUpdateTime = now;
577
578 const auto elapsed = duration_cast<milliseconds>(now - mStageStartTime);
579
580 SetTimeLabel(mProgressPanel.elapsedTime, elapsed);
581
582 const auto estimate = elapsed * total / current;
583 const auto remains = estimate - elapsed;
584
586 mProgressPanel.remainingTime,
587 std::chrono::duration_cast<std::chrono::milliseconds>(remains));
588}
589
590ShareAudioDialog::InitialStatePanel::InitialStatePanel(ShareAudioDialog& parent)
591 : parent { parent }
592 , mUserDataChangedSubscription(
593 GetUserService().Subscribe([this](const auto&) { UpdateUserData(); }))
594{
595}
596
598 ShuttleGui& s)
599{
600 root = s.StartInvisiblePanel();
601 s.StartVerticalLay(wxEXPAND, 1);
602 {
603 s.SetBorder(16);
604
605 s.StartHorizontalLay(wxEXPAND, 0);
606 {
607 avatar = safenew UserImage(s.GetParent(), avatarSize);
608
609 s.AddWindow(avatar);
610
611 s.StartVerticalLay(wxEXPAND, 1);
612 {
613 s.SetBorder(0);
614 s.AddSpace(0, 0, 1);
615 name = s.AddVariableText(XO("Anonymous"));
616 s.AddSpace(0, 0, 1);
617 }
618 s.EndVerticalLay();
619
620 s.AddSpace(0, 0, 1);
621
622 s.StartVerticalLay(wxEXPAND, 1);
623 {
624 s.AddSpace(0, 0, 1);
625
626 s.SetBorder(16);
627 oauthButton = s.AddButton(XXO("&Link Account"));
628 oauthButton->Bind(
629 wxEVT_BUTTON, [this](auto) { OnLinkButtonPressed(); });
630 s.AddSpace(0, 0, 1);
631 }
632 s.EndVerticalLay();
633 }
635
636 s.SetBorder(0);
637
638 s.AddWindow(safenew wxStaticLine { s.GetParent() }, wxEXPAND);
639
641 {
643 {
644 s.AddFixedText(XO("Track Title"));
645 s.AddSpace(8);
646 trackTitle = s.AddTextBox({}, {}, 60);
647 trackTitle->SetName(XO("Track Title").Translation());
648 trackTitle->SetFocus();
649 trackTitle->SetMaxLength(100);
650 s.AddSpace(16);
651
652 anonInfoPanel = s.StartInvisiblePanel();
653 {
654 AccessibleLinksFormatter privacyPolicy(XO(
655 "Your audio will be uploaded to our sharing service: %s,%%which requires a free account to use."));
656
657 privacyPolicy.FormatLink(
658 L"%s", XO("audio.com"), "https://audio.com");
659
660 privacyPolicy.FormatLink(
661 L"%%", TranslatableString {},
663
664 privacyPolicy.Populate(s);
665 }
667
668 authorizedInfoPanel = s.StartInvisiblePanel();
669 s.StartHorizontalLay(wxEXPAND, 1);
670 {
671 s.AddFixedText(XO("Press \"Continue\" to upload to audio.com"));
672 }
675 }
677 }
679 }
680 s.EndVerticalLay();
682
683 UpdateUserData();
684}
685
687{
688 auto rootParent = root->GetParent();
689 rootParent->Freeze();
690
691 auto layoutUpdater = finally(
692 [rootParent = root->GetParent(), this]()
693 {
694 oauthButton->Fit();
695 rootParent->Fit();
696 rootParent->Layout();
697
698 rootParent->Thaw();
699
700 rootParent->Refresh();
701 });
702
703 auto& oauthService = GetOAuthService();
704
705 if (!oauthService.HasRefreshToken())
706 {
707 SetAnonymousState();
708 return;
709 }
710
711 if (!oauthService.HasAccessToken())
712 oauthService.ValidateAuth({});
713
714 auto& userService = GetUserService();
715
716 if (userService.GetUserSlug().empty())
717 {
718 SetAnonymousState();
719 return;
720 }
721
722 const auto displayName = userService.GetDisplayName();
723
724 if (!displayName.empty())
725 name->SetLabel(displayName);
726
727 const auto avatarPath = userService.GetAvatarPath();
728
729 if (!avatarPath.empty())
730 avatar->SetBitmap(avatarPath);
731 else
732 avatar->SetBitmap(theTheme.Bitmap(bmpAnonymousUser));
733
734 oauthButton->SetLabel(XXO("&Unlink Account").Translation());
735
736 parent.mIsAuthorised = true;
737
738 anonInfoPanel->Hide();
739 authorizedInfoPanel->Show();
740
741 if (parent.mContinueButton != nullptr)
742 parent.mContinueButton->Enable(!trackTitle->GetValue().empty());
743}
744
746{
747 auto& oauthService = GetOAuthService();
748
749 if (oauthService.HasAccessToken())
750 oauthService.UnlinkAccount();
751 else
752 {
754 { audacity::ToWXString(GetServiceConfig().GetOAuthLoginPage()) });
755
756#ifdef HAS_CUSTOM_URL_HANDLING
758#endif
759 {
760 LinkAccountDialog dlg(root);
761 dlg.ShowModal();
762 }
763 }
764}
765
767{
768 parent.mIsAuthorised = false;
769
770 name->SetLabel(XO("Anonymous").Translation());
771 avatar->SetBitmap(theTheme.Bitmap(bmpAnonymousUser));
772 oauthButton->SetLabel(XXO("&Link Account").Translation());
773
774 anonInfoPanel->Show();
775 authorizedInfoPanel->Hide();
776
777 if (parent.mContinueButton != nullptr)
778 parent.mContinueButton->Enable(false);
779}
780
782{
783 wxString ret { trackTitle->GetValue() };
784 ret.Trim(true).Trim(false);
785 return ret;
786}
787
789{
790 return !GetTrackTitle().empty();
791}
792
794{
795 root = s.StartInvisiblePanel(16);
796 root->Hide();
797 s.StartVerticalLay(wxEXPAND, 1);
798 {
799 s.SetBorder(0);
800
801 title = s.AddVariableText(XO("Preparing audio..."));
802 s.AddSpace(0, 16, 0);
803
804 progress = safenew wxGauge { s.GetParent(), wxID_ANY, 100 };
805 s.AddWindow(progress, wxEXPAND);
806
807 timePanel = s.StartInvisiblePanel();
808 {
809 s.AddSpace(0, 16, 0);
810
811 s.StartWrapLay();
812 {
813 s.AddFixedText(XO("Elapsed Time:"));
814 elapsedTime = s.AddVariableText(Verbatim(" 00:00:00"));
815 }
816 s.EndWrapLay();
817
818 s.StartWrapLay();
819 {
820 s.AddFixedText(XO("Remaining Time:"));
821 remainingTime = s.AddVariableText(Verbatim(" 00:00:00"));
822 }
823 s.EndWrapLay();
824 }
826
827 s.AddSpace(0, 16, 0);
828
830 }
831
832 s.EndVerticalLay();
834
835 wxFont font = elapsedTime->GetFont();
836 font.MakeBold();
837
838 elapsedTime->SetFont(font);
839 remainingTime->SetFont(font);
840}
841} // namespace cloud::audiocom
wxT("CloseDown"))
Toolkit-neutral facade for basic user interface services.
Declare functions to perform UTF-8 to std::wstring conversions.
int min(int a, int b)
#define str(a)
const TranslatableString name
Definition: Distortion.cpp:76
ExportResult
Definition: ExportTypes.h:24
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
wxString FileExtension
File extension, not including any leading dot.
Definition: Identifier.h:224
#define safenew
Definition: MemoryX.h:10
static const auto title
an object holding per-project preferred sample rate
@ eIsCreating
Definition: ShuttleGui.h:37
TranslatableString label
Definition: TagsEditor.cpp:165
const auto tracks
const auto project
THEME_API Theme theTheme
Definition: Theme.cpp:82
declares abstract base class Track, TrackList, and iterators over TrackList
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
Wrap wxMessageDialog so that caption IS translatable.
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 wxString & GetProjectName() const
Definition: Project.cpp:100
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
void SetBorder(int Border)
Definition: ShuttleGui.h:488
void EndVerticalLay()
void EndInvisiblePanel()
wxPanel * StartInvisiblePanel(int border=0)
wxWindow * GetParent()
Definition: ShuttleGui.h:495
wxTextCtrl * AddTextBox(const TranslatableString &Caption, const wxString &Value, const int nChars)
Definition: ShuttleGui.cpp:639
void StartVerticalLay(int iProp=1)
wxButton * AddButton(const TranslatableString &Text, int PositionFlags=wxALIGN_CENTRE, bool setDefault=false)
Definition: ShuttleGui.cpp:361
void EndHorizontalLay()
void StartWrapLay(int PositionFlags=wxEXPAND, int iProp=0)
void StartHorizontalLay(int PositionFlags=wxALIGN_CENTRE, int iProp=1)
wxWindow * AddWindow(wxWindow *pWindow, int PositionFlags=wxALIGN_CENTRE)
Definition: ShuttleGui.cpp:300
void AddFixedText(const TranslatableString &Str, bool bCenter=false, int wrapWidth=0)
Definition: ShuttleGui.cpp:441
wxStaticText * AddVariableText(const TranslatableString &Str, bool bCenter=false, int PositionFlags=0, int wrapWidth=0)
Definition: ShuttleGui.cpp:464
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:630
wxSizerItem * AddSpace(int width, int height, int prop=0)
wxBitmap & Bitmap(int iIndex)
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:987
auto Any() -> TrackIterRange< TrackType >
Definition: Track.h:1091
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:354
Holds a msgid for the translation catalog; may also bind format arguments.
bool IsURLHandlingSupported() const noexcept
Returns true, if Audacity can handle custom URLs.
static URLSchemesRegistry & Get()
Retrieves the registry instance.
A Track that contains audio waveform data.
Definition: WaveTrack.h:220
rapidjson::Document GetExportConfig(const std::string &exporterName) const
Export configuration suitable for the mime type provided.
void SetStatusString(const TranslatableString &str) override
std::unique_ptr< ExportProgressUpdater > mExportProgressUpdater
void UpdateProgress(uint64_t current, uint64_t total)
struct cloud::audiocom::ShareAudioDialog::InitialStatePanel mInitialStatePanel
ShareAudioDialog(AudacityProject &project, wxWindow *parent=nullptr)
struct cloud::audiocom::ShareAudioDialog::ProgressPanel mProgressPanel
std::function< void()> mContinueAction
std::unique_ptr< Services > mServices
A unique_ptr like class that holds a pointer to UploadOperation.
Service, responsible for uploading audio files to audio.com.
bool OpenInDefaultBrowser(const wxString &url)
Open an URL in default browser.
Definition: BasicUI.cpp:240
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
Definition: BasicUI.cpp:208
void ShowErrorDialog(const WindowPlacement &placement, const TranslatableString &dlogTitle, const TranslatableString &message, const ManualPageID &helpPage, const ErrorDialogOptions &options={})
Show an error dialog with a link to the manual for further help.
Definition: BasicUI.h:259
void Yield()
Dispatch waiting events, including actions enqueued by CallAfter.
Definition: BasicUI.cpp:219
void ExceptionWrappedCall(Callable callable)
double GetRate(const Track &track)
Definition: TimeTrack.cpp:196
wxString ToWXString(const std::string &str)
void SetTimeLabel(wxStaticText *label, std::chrono::milliseconds time)
AuthorizationHandler & GetAuthorizationHandler()
wxString GetUploadTempPath()
const ServiceConfig & GetServiceConfig()
Returns the instance of the ServiceConfig.
OAuthService & GetOAuthService()
Returns the instance of the OAuthService.
UserService & GetUserService()
Options for variations of error dialogs; the default is for modal dialogs.
Definition: BasicUI.h:51
This structure represents an upload error as returned by the server.
Definition: UploadService.h:28
std::vector< AdditionalError > additionalErrors
Definition: UploadService.h:36
This structure represents the payload associated with successful upload.
Definition: UploadService.h:41
std::string audioUrl
URL to the uploaded audio.
Definition: UploadService.h:49