Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2026 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 :
23 : #pragma once
24 :
25 : #include "isaveprojectscenario.h"
26 :
27 : #include <QIODevice>
28 : #include <QString>
29 : #include <QUrl>
30 :
31 : #include "modularity/ioc.h"
32 : #include "async/asyncable.h"
33 : #include "async/promise.h"
34 : #include "interactive/iinteractive.h"
35 : #include "interactive/iplatforminteractive.h"
36 : #include "project/iprojectsession.h"
37 : #include "cloud/musescorecom/imusescorecomservice.h"
38 : #include "cloud/audiocom/iaudiocomservice.h"
39 : #include "io/ifilesystem.h"
40 : #include "notation/inotationconfiguration.h"
41 : #include "progress.h"
42 :
43 : #include "iexportprojectscenario.h"
44 : #include "iprojectconfiguration.h"
45 : #include "irecentfilescontroller.h"
46 : #include "iopenprojectscenario.h"
47 :
48 : namespace mu::project {
49 : class SaveProjectScenario : public ISaveProjectScenario, public muse::Contextable, public muse::async::Asyncable
50 : {
51 : friend class SaveProjectScenarioTests;
52 :
53 : public:
54 : muse::GlobalInject<IProjectConfiguration> configuration;
55 : muse::GlobalInject<notation::INotationConfiguration> notationConfiguration;
56 : muse::GlobalInject<muse::io::IFileSystem> fileSystem;
57 : muse::GlobalInject<muse::cloud::IMuseScoreComService> museScoreComService;
58 : muse::GlobalInject<muse::cloud::IAudioComService> audioComService;
59 : muse::GlobalInject<muse::IPlatformInteractive> platformInteractive;
60 : muse::ContextInject<IRecentFilesController> recentFilesController = { this };
61 : muse::ContextInject<IExportProjectScenario> exportProjectScenario = { this };
62 : muse::ContextInject<muse::IInteractive> interactive = { this };
63 : muse::ContextInject<project::IProjectSession> projectSession = { this };
64 : muse::ContextInject<IOpenProjectScenario> openProjectScenario = { this };
65 :
66 61 : SaveProjectScenario(const muse::modularity::ContextPtr& iocCtx)
67 61 : : muse::Contextable(iocCtx) {}
68 :
69 : muse::async::Promise<muse::Ret> saveProject(SaveMode saveMode, SaveLocationType saveLocationType = SaveLocationType::Undefined,
70 : bool force = false) override;
71 : muse::async::Promise<muse::Ret> saveProject(const muse::io::path_t& path = muse::io::path_t()) override;
72 : muse::async::Promise<muse::Ret> saveProjectAt(const muse::rcommand::Params& params) override;
73 :
74 : muse::async::Promise<muse::Ret> publish() override;
75 : muse::async::Promise<muse::Ret> shareAudio() override;
76 :
77 : bool isBusy(BusyStatus status) const override;
78 : muse::async::Notification busyChanged() const override;
79 :
80 : private:
81 : static constexpr int RET_CODE_CHANGE_SAVE_LOCATION_TYPE = 1234;
82 : static constexpr int RET_CODE_CONFLICT_RESPONSE_SAVE_AS = 1235;
83 : static constexpr int RET_CODE_CONFLICT_RESPONSE_PUBLISH_AS_NEW_SCORE = 1236;
84 : static constexpr int RET_CODE_CONFLICT_RESPONSE_REPLACE = 1237;
85 :
86 : struct AudioFile {
87 : QString format;
88 : std::shared_ptr<QIODevice> device = nullptr;
89 :
90 25 : AudioFile() {}
91 :
92 10 : bool isValid() const
93 : {
94 10 : return !format.isEmpty() && device != nullptr;
95 : }
96 : };
97 :
98 : INotationProjectPtr currentNotationProject() const;
99 : notation::INotationInteractionPtr currentInteraction() const;
100 :
101 : void setBusy(BusyStatus status, bool isBusy);
102 : muse::async::Promise<muse::Ret> runIfNotBusy(BusyStatus status, const std::function<muse::async::Promise<muse::Ret>()>& flow);
103 :
104 : muse::async::Promise<muse::RetVal<muse::Val> > openDialog(const muse::UriQuery& query) const;
105 :
106 : muse::async::Promise<bool> refuseSaveAfter(muse::async::Promise<muse::Ret> shown);
107 :
108 : muse::async::Promise<muse::RetVal<SaveLocation> > askSaveLocation(INotationProjectPtr project, SaveMode mode,
109 : SaveLocationType preselectedType = SaveLocationType::Undefined) const;
110 : muse::async::Promise<muse::RetVal<SaveLocation> > askSaveLocationOfType(INotationProjectPtr project, SaveMode mode,
111 : SaveLocationType type) const;
112 : muse::async::Promise<muse::RetVal<muse::io::path_t> > askLocalPath(INotationProjectPtr project, SaveMode mode) const;
113 : muse::async::Promise<muse::Ret> saveProjectLocallyInstead(const INotationProjectPtr& project, SaveMode saveMode);
114 : muse::async::Promise<muse::RetVal<SaveLocationType> > saveLocationType() const;
115 : muse::async::Promise<muse::RetVal<SaveLocationType> > askSaveLocationType() const;
116 : muse::async::Promise<muse::RetVal<CloudProjectInfo> > askCloudLocation(INotationProjectPtr project, SaveMode mode) const;
117 : muse::async::Promise<muse::RetVal<CloudProjectInfo> > askPublishLocation(INotationProjectPtr project) const;
118 : muse::async::Promise<muse::RetVal<CloudAudioInfo> > askShareAudioLocation(INotationProjectPtr project) const;
119 : muse::async::Promise<muse::RetVal<CloudAudioInfo> > doAskShareAudioLocation(INotationProjectPtr project) const;
120 : muse::async::Promise<muse::RetVal<CloudProjectInfo> > doAskCloudLocation(INotationProjectPtr project, SaveMode mode,
121 : bool isPublishShare) const;
122 : muse::async::Promise<muse::RetVal<CloudProjectInfo> > doAskCloudLocationAuthorized(INotationProjectPtr project, SaveMode mode,
123 : bool isPublishShare) const;
124 : muse::async::Promise<muse::RetVal<CloudProjectInfo> > askCloudProjectInfo(INotationProjectPtr project, SaveMode mode,
125 : bool isPublishShare) const;
126 : muse::async::Promise<muse::RetVal<CloudProjectInfo> > askCloudProjectInfo(INotationProjectPtr project, SaveMode mode,
127 : bool isPublishShare, const QString& defaultName,
128 : muse::cloud::Visibility defaultVisibility,
129 : const QUrl& existingScoreUrl) const;
130 : muse::async::Promise<bool> warnBeforePublishing(bool isPublishShare, muse::cloud::Visibility visibility) const;
131 : muse::async::Promise<bool> warnBeforeSavingToExistingPubliclyVisibleCloudProject() const;
132 : muse::async::Promise<muse::Ret> warnCloudNotAvailableForUploading(bool isPublishShare) const;
133 : muse::async::Promise<muse::Ret> warnCloudNotAvailableForSharingAudio() const;
134 : muse::async::Promise<muse::RetVal<muse::Val> > ensureAuthorization(const QString& cloudCode, bool publishingScore,
135 : const std::string& text) const;
136 : muse::async::Promise<muse::Ret> showCloudSaveError(const muse::Ret& ret, const CloudProjectInfo& info, bool isPublishShare,
137 : bool alreadyAttempted) const;
138 : muse::async::Promise<muse::Ret> showAudioCloudShareError(const muse::Ret& ret) const;
139 :
140 : muse::Ret canSaveProject() const;
141 : muse::async::Promise<muse::Ret> saveProjectAt(const SaveLocation& saveLocation, SaveMode saveMode = SaveMode::Save, bool force = false);
142 : muse::async::Promise<muse::Ret> doSaveProjectAt(const SaveLocation& saveLocation, SaveMode saveMode);
143 : muse::async::Promise<muse::Ret> saveProjectToCloud(CloudProjectInfo info, SaveMode saveMode = SaveMode::Save);
144 : muse::async::Promise<muse::Ret> doSaveProjectToCloud(const CloudProjectInfo& info, SaveMode saveMode);
145 : muse::async::Promise<muse::Ret> saveAndUploadProject(const INotationProjectPtr& project, CloudProjectInfo info, SaveMode saveMode);
146 : muse::async::Promise<muse::Ret> doSaveAndUploadProject(const INotationProjectPtr& project, const CloudProjectInfo& info,
147 : SaveMode saveMode, bool isPublic);
148 : muse::async::Promise<muse::Ret> saveProjectLocally(const muse::io::path_t& path, SaveMode saveMode = SaveMode::Save,
149 : bool createBackup = true);
150 : muse::async::Promise<muse::Ret> saveCloudProjectLocally(const INotationProjectPtr& project, const CloudProjectInfo& info,
151 : SaveMode saveMode);
152 :
153 : muse::async::Promise<muse::Ret> shareAudio(const AudioFile& existingAudio);
154 : muse::async::Promise<muse::Ret> uploadAudioToAudioCom(const AudioFile& audio, const INotationProjectPtr& project,
155 : const CloudAudioInfo& info);
156 : muse::async::Promise<muse::Ret> alsoShareAudioCom(const AudioFile& audio);
157 :
158 : muse::async::Promise<muse::Ret> askAudioGenerationSettings() const;
159 : muse::async::Promise<muse::RetVal<bool> > needGenerateAudio(bool isPublic) const;
160 : bool needGenerateAudioAccordingToSettings() const;
161 : AudioFile exportMp3(const notation::INotationPtr notation) const;
162 :
163 : void showUploadProgressDialog();
164 : void closeUploadProgressDialog();
165 :
166 : muse::async::Promise<muse::Ret> uploadProject(const CloudProjectInfo& info, const AudioFile& audio, bool openEditUrl, bool publishMode);
167 : muse::async::Promise<muse::Ret> uploadAudioToMuseScoreCom(const AudioFile& audio, const QUrl& sourceUrl, const QUrl& urlToOpen,
168 : bool isFirstSave, bool publishMode);
169 :
170 : muse::async::Promise<muse::Ret> onUploadFinished(const QUrl& urlToOpen, bool isFirstSave, const AudioFile& audio, bool publishMode);
171 : muse::async::Promise<muse::Ret> onProjectSuccessfullyUploaded(const QUrl& urlToOpen = QUrl(), bool isFirstSave = true);
172 : muse::async::Promise<muse::Ret> onProjectUploadFailed(const muse::Ret& ret, const CloudProjectInfo& info, const AudioFile& audio,
173 : bool openEditUrl, bool publishMode);
174 :
175 : void onAudioSuccessfullyUploaded(const QUrl& urlToOpen);
176 : muse::async::Promise<muse::Ret> onAudioUploadFailed(const muse::Ret& ret);
177 :
178 : muse::async::Promise<muse::Ret> warnCloudIsNotAvailable();
179 :
180 : muse::async::Promise<bool> askIfUserAgreesToSaveProjectWithErrors(const muse::Ret& ret, const SaveLocation& location);
181 : muse::async::Promise<bool> askIfUserAgreesToSaveCorruptedScore(const SaveLocation& location, const std::string& errorText,
182 : bool newlyCreated);
183 : muse::async::Promise<muse::Ret> warnCorruptedScoreCannotBeSavedOnCloud(const std::string& errorText, bool canRevert);
184 : muse::async::Promise<bool> askIfUserAgreesToSaveCorruptedScoreLocally(const std::string& errorText, bool canRevert);
185 : muse::async::Promise<bool> askIfUserAgreesToSaveCorruptedScoreUponOpenning(const SaveLocation& location, const std::string& errorText);
186 : muse::async::Promise<muse::Ret> showErrCorruptedScoreCannotBeSaved(const SaveLocation& location, const std::string& errorText);
187 :
188 : muse::async::Promise<muse::Ret> warnScoreCouldnotBeSaved(const muse::Ret& ret);
189 : muse::async::Promise<muse::Ret> warnScoreCouldnotBeSaved(const std::string& errorText);
190 : muse::async::Promise<int> warnScoreHasBecomeCorruptedAfterSave(const muse::Ret& ret);
191 :
192 : muse::async::Promise<muse::Ret> askToRevertCorruptedScoreToLastSaved();
193 :
194 : RecentFile makeRecentFile(INotationProjectPtr project);
195 : void moveProject(INotationProjectPtr project, const muse::io::path_t& newPath, bool replace);
196 :
197 : QUrl scoreManagerUrl() const;
198 :
199 : std::set<BusyStatus> m_busyStatuses;
200 : muse::async::Notification m_busyChanged;
201 :
202 : muse::ProgressPtr m_uploadingProjectProgress = nullptr;
203 : muse::ProgressPtr m_uploadingAudioProgress = nullptr;
204 :
205 : int m_numberOfSavesToCloud = 0;
206 : };
207 : }
|