Audacity 3.2.0
CloudProjectFileIOExtensions.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 CloudProjectFileIOExtensions.cpp
7
8 Dmitry Vedenko
9
10**********************************************************************/
11
13
17
18#include "ExportUtils.h"
19#include "OAuthService.h"
20#include "ServiceConfig.h"
21#include "UserService.h"
22
28
29#include "sync/CloudSyncDTO.h"
33
34#include "BasicUI.h"
35#include "CodeConversions.h"
36#include "Project.h"
37#include "ProjectFileIO.h"
39#include "ProjectFileManager.h"
40#include "ProjectWindow.h"
41
42namespace
43{
44using namespace audacity::cloud::audiocom;
46
48{
50 OnOpen(AudacityProject& project, const std::string& path) override
51 {
52 return SyncCloudProject(
56 }
57
59 {
60 auto& projectCloudExtenstion = ProjectCloudExtension::Get(project);
61 projectCloudExtenstion.OnLoad();
62
63 if (projectCloudExtenstion.IsCloudProject())
65 projectCloudExtenstion, [&project](AudiocomTrace trace) {
67 });
68 }
69
71 AudacityProject& project, std::string name, std::string filePath,
72 const ProjectSaveCallback& projectSaveCallback, bool fileRenamed,
73 AudiocomTrace trace)
74 {
75 auto& projectCloudExtension = ProjectCloudExtension::Get(project);
76
77 projectCloudExtension.OnSyncStarted();
78
79 auto future = LocalProjectSnapshot::Create(
80 GetServiceConfig(), GetOAuthService(), projectCloudExtension, name,
81 mUploadMode, trace);
82
83 mUploadMode = UploadMode::Normal;
84
85 // Do we need UI here?
86 // while (future.wait_for(std::chrono::milliseconds(50)) !=
87 // std::future_status::ready)
88 // BasicUI::Yield();
89
90 auto result = future.get();
91
92 if (!result.Response)
93 // Prevent any updates to the file to preserve the correct state.
94 // Errors would be handled by the UI extension
96
97 if (!projectSaveCallback(audacity::ToUTF8(filePath), fileRenamed))
98 {
99 if (result.Operation)
100 result.Operation->Abort();
101 else
102 projectCloudExtension.OnSyncCompleted(
103 nullptr, CloudSyncError { CloudSyncError::Aborted }, trace);
104 // Something has failed horrible during the save
106 }
107
108 if (mSnapshotCallback)
109 {
110 mSnapshotCallback(*result.Response);
111 mSnapshotCallback = {};
112 }
113
115 }
116
118 AudacityProject& project, const ProjectSaveCallback& projectSaveCallback,
119 AudiocomTrace trace)
120 {
121 auto authResult = PerformBlockingAuth(&project, trace);
122
123 if (authResult.Result == AuthResult::Status::Failure)
124 {
125 LinkFailedDialog dialog { &ProjectWindow::Get(project), trace };
126 dialog.ShowModal();
127 // Pretend we have canceled the save
129 }
130 else if (authResult.Result == AuthResult::Status::Cancelled)
131 {
133 }
134 else if (authResult.Result == AuthResult::Status::UseAlternative)
135 {
138 }
139
140 return PerformCloudSave(
141 project, audacity::ToUTF8(project.GetProjectName()),
143 projectSaveCallback, false, trace);
144 }
145
148 const ProjectSaveCallback& projectSaveCallback) override
149 {
150 auto& projectCloudExtension = ProjectCloudExtension::Get(project);
151 auto& projectFileIO = ProjectFileIO::Get(project);
152
153 const bool isTemporary = projectFileIO.IsTemporary();
154 const bool isCloudProject = projectCloudExtension.IsCloudProject();
155
156 const bool forceSaveToCloud =
157 mAudiocomTrace == AudiocomTrace::SaveToCloudMenu;
158
159 Finally finally { [&] {
161 } };
162
163 auto parent = &ProjectWindow::Get(project);
164
165 // Check location first
166 if (isTemporary && !forceSaveToCloud)
167 {
168 CloudLocationDialog cloudLocationDialog { parent,
169 LocationDialogType::Save };
170 const auto saveAction = cloudLocationDialog.ShowDialog();
171
172 // Not doing a cloud save
173 if (saveAction == LocationDialogResult::Local)
175 else if (saveAction == LocationDialogResult::Cancel)
177 }
178
179 // For regular projects - do nothing
180 if (!isTemporary && !forceSaveToCloud && !isCloudProject)
182
183 const auto trace = forceSaveToCloud ?
186
187 if (!isTemporary)
188 return SaveCloudProject(project, projectSaveCallback, trace);
189
192 project.GetProjectName(), parent, false, trace);
193
194 if (result.first == CloudProjectPropertiesDialog::Action::Cancel)
195 // Suppress the Save function completely
197 else if (
198 result.first == CloudProjectPropertiesDialog::Action::SaveLocally)
199 // Just let the things flow as usual
201
202 const auto dir = CloudProjectsSavePath.Read();
203 FileNames::MkDir(dir);
204
205 const auto filePath = sync::MakeSafeProjectPath(dir, result.second);
206
207 return PerformCloudSave(
208 project, result.second, audacity::ToUTF8(filePath),
209 projectSaveCallback, true, mAudiocomTrace);
210 }
211
213 {
214 auto& projectCloudExtension = ProjectCloudExtension::Get(project);
215
216 if (!projectCloudExtension.IsCloudProject())
218
219 return OnCloseHook::Call(project) ? OnCloseAction::Continue :
221 }
222
224 AudacityProject& project, const ProjectSerializer& serializer) override
225 {
226 auto& projectCloudExtension = ProjectCloudExtension::Get(project);
227
228 if (!projectCloudExtension.IsCloudProject())
229 return;
230
231 projectCloudExtension.OnUpdateSaved(serializer);
232
233 const int savesCount = projectCloudExtension.GetSavesCount();
234
235 if (savesCount < 2)
236 {
237 SyncInBackroundDialog { &project }.ShowDialog();
238 }
239
240 if (projectCloudExtension.IsFirstSyncDialogShown())
241 return;
242
244 [weakProject = project.weak_from_this()] {
245 auto project = weakProject.lock();
246
247 if (!project)
248 return true;
249
250 return ProjectCloudExtension::Get(*project)
251 .GetCurrentSyncStatus() == ProjectSyncStatus::Synced;
252 },
253 [weakProject = project.weak_from_this(), trace = mAudiocomTrace] {
254 auto project = weakProject.lock();
255
256 if (
257 !project ||
258 ProjectCloudExtension::Get(*project).IsFirstSyncDialogShown())
259 return;
260
261 ProjectCloudExtension::Get(*project).SetFirstSyncDialogShown(true);
262
263 const auto result =
264 SyncSucceededDialog { project.get() }.ShowDialog();
265
266 if (result == SyncSucceededDialog::ViewOnlineIdentifier())
268 ProjectCloudExtension::Get(*project).GetCloudProjectPage(
269 trace)));
270 });
271 }
272
273 bool
274 IsBlockLocked(const AudacityProject& project, int64_t blockId) const override
275 {
276 return ProjectCloudExtension::Get(project).IsBlockLocked(blockId);
277 }
278
279public:
281 {
282 mAudiocomTrace = AudiocomTrace::SaveToCloudMenu;
283 }
284
286 {
287 mUploadMode = mode;
288 }
289
291 {
292 mSnapshotCallback = std::move(callback);
293 }
294
295private:
296 // Snapshot callback for the next save
298 // Upload mode for the next save
299 UploadMode mUploadMode { UploadMode::Normal };
300 // Forces the next save to be a cloud save
302};
303
305{
306 static IOExtension extension;
307 return extension;
308}
309
311} // namespace
312
314{
317 CreateSnapshotCallback snapshotCallback)
318{
320
321 auto& projectCloudExtension = ProjectCloudExtension::Get(project);
322
323 auto& ioExtension = GetExtension();
324
325 ioExtension.ForceCloudSave();
326 ioExtension.SetUploadModeForNextSave(mode);
327 ioExtension.SetSnapshotCallbackForNextSave(std::move(snapshotCallback));
328
330}
331
333{
334 // TODO: Delete the old file immediately?
336}
337
338} // namespace audacity::cloud::audiocom::sync
Toolkit-neutral facade for basic user interface services.
#define ASSERT_MAIN_THREAD()
Definition: BasicUI.h:414
Declare functions to perform UTF-8 to std::wstring conversions.
AudiocomTrace
Definition: ExportUtils.h:27
@ SaveProjectSaveToCloudMenu
std::function< bool(const std::string &path, bool nameChanged)> ProjectSaveCallback
OnOpenAction
Action the ProjectManager should take after the open hooks were called.
@ Continue
ProjectManager should continue with the open.
@ Cancel
Open was cancelled by the extension.
OnSaveAction
Action the ProjectManager should take after the save hooks were called.
@ Handled
Save was handled by the extension.
@ Continue
Save was not handled by the extension.
@ Cancelled
Save was cancelled by the extension.
OnCloseAction
Action the ProjectManager should take after the close hooks were called.
@ Veto
Extension vetoed the close.
@ Continue
Extension did not veto the close.
wxString name
Definition: TagsEditor.cpp:166
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
Set of hooks for project file I/O.
static ProjectFileIO & Get(AudacityProject &project)
const FilePath & GetFileName() const
bool SaveAs(bool allowOverwrite=false)
static ProjectFileManager & Get(AudacityProject &project)
a class used to (de)serialize the project catalog
static ProjectWindow & Get(AudacityProject &project)
bool Read(T *pVar) const
overload of Read returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:207
OnSaveAction PerformCloudSave(AudacityProject &project, std::string name, std::string filePath, const ProjectSaveCallback &projectSaveCallback, bool fileRenamed, AudiocomTrace trace)
OnSaveAction OnSave(AudacityProject &project, const ProjectSaveCallback &projectSaveCallback) override
This hook is called before the project is saved.
OnSaveAction SaveCloudProject(AudacityProject &project, const ProjectSaveCallback &projectSaveCallback, AudiocomTrace trace)
bool IsBlockLocked(const AudacityProject &project, int64_t blockId) const override
This hook is called to check if a block is locked.
OnCloseAction OnClose(AudacityProject &project) override
This hook is called before the project is closed.
void OnLoad(AudacityProject &project) override
This hook is called after the project is loaded.
OnOpenAction OnOpen(AudacityProject &project, const std::string &path) override
This hook is called before the project is opened.
void OnUpdateSaved(AudacityProject &project, const ProjectSerializer &serializer) override
This hook is called after the project blob is saved.
static ProjectCloudExtension & Get(AudacityProject &project)
bool OpenInDefaultBrowser(const wxString &url)
Open an URL in default browser.
Definition: BasicUI.cpp:246
Services * Get()
Fetch the global instance, or nullptr if none is yet installed.
Definition: BasicUI.cpp:202
IMPORT_EXPORT_API ExportResult Show(ExportTask exportTask)
FILES_API wxString MkDir(const wxString &Str)
bool ResaveLocally(AudacityProject &project)
void SaveToCloud(AudacityProject &project, UploadMode mode, CreateSnapshotCallback snapshotCallback)
bool SyncCloudProject(AudacityProject &project, std::string_view path, AudiocomTrace trace, bool force)
wxString MakeSafeProjectPath(const wxString &rootDir, const wxString &projectName)
void ResumeProjectUpload(ProjectCloudExtension &projectCloudExtension, std::function< void(AudiocomTrace)> onBeforeUploadStarts)
std::function< void(const CreateSnapshotResponse &)> CreateSnapshotCallback
void ShowDialogOn(std::function< bool()> condition, std::function< void()> dialogFactory)
AuthResult PerformBlockingAuth(AudacityProject *project, AudiocomTrace trace, const TranslatableString &alternativeActionLabel)
UserService & GetUserService()
OAuthService & GetOAuthService()
Returns the instance of the OAuthService.
const ServiceConfig & GetServiceConfig()
Returns the instance of the ServiceConfig.
std::string ToUTF8(const std::wstring &wstr)
wxString ToWXString(const std::string &str)
"finally" as in The C++ Programming Language, 4th ed., p. 358 Useful for defining ad-hoc RAII actions...
Definition: MemoryX.h:175