Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 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 : #include "workspacemanager.h"
23 :
24 : #include "types/uri.h"
25 :
26 : #include "log.h"
27 :
28 : using namespace muse;
29 : using namespace muse::workspace;
30 :
31 : static const std::string WORKSPACE_EXT(".mws");
32 :
33 0 : static bool containsWorkspace(const IWorkspacePtrList& list, const IWorkspacePtr& workspace)
34 : {
35 0 : return std::find(list.cbegin(), list.cend(), workspace) != list.cend();
36 : }
37 :
38 3 : void WorkspaceManager::init()
39 : {
40 3 : configuration()->currentWorkspaceNameChanged().onReceive(this, [this](const std::string&) {
41 0 : setupCurrentWorkspace();
42 0 : });
43 :
44 3 : load();
45 3 : }
46 :
47 3 : void WorkspaceManager::deinit()
48 : {
49 3 : saveCurrentWorkspace();
50 :
51 3 : m_currentWorkspace = nullptr;
52 3 : m_defaultWorkspace = nullptr;
53 3 : m_workspaces.clear();
54 3 : }
55 :
56 0 : bool WorkspaceManager::isInited() const
57 : {
58 0 : return m_defaultWorkspace != nullptr;
59 : }
60 :
61 4 : IWorkspacePtr WorkspaceManager::defaultWorkspace() const
62 : {
63 4 : return m_defaultWorkspace;
64 : }
65 :
66 5 : IWorkspacePtr WorkspaceManager::currentWorkspace() const
67 : {
68 5 : return m_currentWorkspace;
69 : }
70 :
71 0 : async::Notification WorkspaceManager::currentWorkspaceAboutToBeChanged() const
72 : {
73 0 : return m_currentWorkspaceAboutToBeChanged;
74 : }
75 :
76 0 : async::Notification WorkspaceManager::currentWorkspaceChanged() const
77 : {
78 0 : return m_currentWorkspaceChanged;
79 : }
80 :
81 2 : IWorkspacePtrList WorkspaceManager::workspaces() const
82 : {
83 2 : IWorkspacePtrList iworkspaces;
84 2 : iworkspaces.reserve(m_workspaces.size());
85 4 : for (const WorkspacePtr& w : m_workspaces) {
86 2 : iworkspaces.push_back(w);
87 : }
88 2 : return iworkspaces;
89 0 : }
90 :
91 0 : Ret WorkspaceManager::setWorkspaces(const IWorkspacePtrList& workspaces)
92 : {
93 0 : Ret ret = removeMissingWorkspaces(workspaces);
94 :
95 0 : if (ret) {
96 0 : ret = addNonExistentWorkspaces(workspaces);
97 : }
98 :
99 0 : m_workspacesListChanged.notify();
100 :
101 0 : return ret;
102 0 : }
103 :
104 0 : async::Notification WorkspaceManager::workspacesListChanged() const
105 : {
106 0 : return m_workspacesListChanged;
107 : }
108 :
109 0 : IWorkspacePtr WorkspaceManager:: cloneWorkspace(const IWorkspacePtr& workspace, const std::string& newWorkspaceName) const
110 : {
111 0 : return std::make_shared<Workspace>(makeNewWorkspacePath(newWorkspaceName), dynamic_cast<Workspace*>(workspace.get()), iocContext());
112 : }
113 :
114 0 : void WorkspaceManager::changeCurrentWorkspace(const std::string& newWorkspaceName)
115 : {
116 0 : prepareCurrentWorkspaceForChange();
117 0 : doChangeCurrentWorkspace(newWorkspaceName);
118 0 : }
119 :
120 0 : void WorkspaceManager::doChangeCurrentWorkspace(const std::string& newWorkspaceName)
121 : {
122 0 : if (configuration()->currentWorkspaceName() == newWorkspaceName || newWorkspaceName.empty()) {
123 0 : return;
124 : }
125 :
126 0 : configuration()->setCurrentWorkspaceName(newWorkspaceName);
127 : }
128 :
129 0 : void WorkspaceManager::createAndAppendNewWorkspace()
130 : {
131 0 : prepareCurrentWorkspaceForChange();
132 :
133 0 : IWorkspacePtrList workspaces = this->workspaces();
134 :
135 0 : QStringList workspaceNames;
136 0 : for (const IWorkspacePtr& workspace: workspaces) {
137 0 : workspaceNames << QString::fromStdString(workspace->name());
138 : }
139 :
140 0 : UriQuery uri("muse://workspace/create");
141 0 : uri.addParam("sync", Val(true));
142 0 : uri.addParam("workspaceNames", Val(workspaceNames.join(',')));
143 :
144 0 : auto i = modularity::ioc(iocContext())->resolve<IInteractive>("workspace");
145 0 : RetVal<Val> obj = i->openSync(uri);
146 0 : if (!obj.ret) {
147 0 : return;
148 : }
149 :
150 0 : QVariantMap meta = obj.val.toQVariant().toMap();
151 0 : QString name = meta.value("name").toString();
152 0 : IF_ASSERT_FAILED(!name.isEmpty()) {
153 0 : return;
154 : }
155 :
156 0 : IWorkspacePtr newWorkspace = cloneWorkspace(currentWorkspace(), name.toStdString());
157 0 : if (!newWorkspace) {
158 0 : return;
159 : }
160 :
161 0 : workspaces.emplace_back(newWorkspace);
162 :
163 0 : setWorkspaces(workspaces);
164 :
165 0 : configuration()->setCurrentWorkspaceName(name.toStdString());
166 0 : }
167 :
168 0 : void WorkspaceManager::openConfigureWorkspacesDialog()
169 : {
170 0 : prepareCurrentWorkspaceForChange();
171 :
172 0 : RetVal<Val> result = interactive()->openSync("muse://workspace/select");
173 0 : if (!result.ret) {
174 0 : return;
175 : }
176 :
177 0 : std::string selectedWorkspace = result.val.toString();
178 0 : doChangeCurrentWorkspace(selectedWorkspace);
179 0 : }
180 :
181 0 : WorkspacePtr WorkspaceManager::doNewWorkspace(const std::string& workspaceName) const
182 : {
183 0 : return std::make_shared<Workspace>(makeNewWorkspacePath(workspaceName), iocContext());
184 : }
185 :
186 0 : io::path_t WorkspaceManager::makeNewWorkspacePath(const std::string& workspaceName) const
187 : {
188 0 : return configuration()->userWorkspacesPath() + "/" + workspaceName + WORKSPACE_EXT;
189 : }
190 :
191 4 : void WorkspaceManager::appendNewWorkspace(WorkspacePtr workspace)
192 : {
193 4 : setupConnectionsToNewWorkspace(workspace);
194 4 : m_workspaces.push_back(workspace);
195 4 : }
196 :
197 4 : void WorkspaceManager::setupConnectionsToNewWorkspace(const IWorkspacePtr workspace)
198 : {
199 4 : std::string newWorkspaceName = workspace->name();
200 4 : workspace->reloadNotification().onNotify(this, [this, newWorkspaceName](){
201 0 : if (m_currentWorkspace->name() == newWorkspaceName) {
202 0 : m_currentWorkspaceChanged.notify();
203 : }
204 0 : });
205 4 : }
206 :
207 0 : Ret WorkspaceManager::removeMissingWorkspaces(const IWorkspacePtrList& newWorkspaceList)
208 : {
209 0 : IWorkspacePtrList oldWorkspaceList = workspaces();
210 0 : for (const IWorkspacePtr& oldWorkspace : oldWorkspaceList) {
211 0 : if (containsWorkspace(newWorkspaceList, oldWorkspace)) {
212 0 : continue;
213 : }
214 :
215 0 : Ret ret = removeWorkspace(oldWorkspace);
216 0 : if (!ret) {
217 0 : return ret;
218 : }
219 0 : }
220 :
221 0 : if (!containsWorkspace(newWorkspaceList, m_currentWorkspace)) {
222 0 : m_currentWorkspace = nullptr;
223 : }
224 :
225 0 : return make_ret(Ret::Code::Ok);
226 0 : }
227 :
228 0 : Ret WorkspaceManager::removeWorkspace(const IWorkspacePtr& workspace)
229 : {
230 0 : if (!canRemoveWorkspace(workspace)) {
231 0 : return make_ret(Ret::Code::Ok);
232 : }
233 :
234 0 : std::string workspaceName = workspace->name();
235 0 : for (auto it = m_workspaces.begin(); it != m_workspaces.end(); ++it) {
236 0 : if (it->get()->name() == workspaceName) {
237 0 : Ret ret = fileSystem()->remove(it->get()->filePath());
238 0 : if (ret) {
239 0 : m_workspaces.erase(it);
240 : }
241 :
242 0 : return ret;
243 0 : }
244 : }
245 :
246 0 : return make_ret(Ret::Code::Ok);
247 0 : }
248 :
249 0 : bool WorkspaceManager::canRemoveWorkspace(const IWorkspacePtr& workspace) const
250 : {
251 0 : return !workspace->isBuiltin();
252 : }
253 :
254 0 : Ret WorkspaceManager::addNonExistentWorkspaces(const IWorkspacePtrList& newWorkspaceList)
255 : {
256 0 : IWorkspacePtrList existentWorkspaces = workspaces();
257 0 : for (const IWorkspacePtr& workspace : newWorkspaceList) {
258 0 : if (containsWorkspace(existentWorkspaces, workspace)) {
259 0 : continue;
260 : }
261 :
262 0 : Ret ret = addWorkspace(workspace);
263 0 : if (!ret) {
264 0 : return ret;
265 : }
266 0 : }
267 :
268 0 : return make_ret(Ret::Code::Ok);
269 0 : }
270 :
271 0 : Ret WorkspaceManager::addWorkspace(IWorkspacePtr workspace)
272 : {
273 0 : auto writable = std::dynamic_pointer_cast<Workspace>(workspace);
274 0 : if (!writable) {
275 0 : return make_ret(Ret::Code::Ok);
276 : }
277 :
278 0 : Ret ret = writable->save();
279 :
280 0 : if (ret) {
281 0 : appendNewWorkspace(writable);
282 : }
283 :
284 0 : return ret;
285 0 : }
286 :
287 3 : void WorkspaceManager::load()
288 : {
289 3 : reloadWorkspaceFiles();
290 :
291 3 : setupDefaultWorkspace();
292 3 : setupCurrentWorkspace();
293 3 : }
294 :
295 4 : void WorkspaceManager::reloadWorkspaceFiles()
296 : {
297 4 : m_workspaces.clear();
298 :
299 4 : io::paths_t files = findWorkspaceFiles();
300 8 : for (const io::path_t& file : files) {
301 4 : auto workspace = std::make_shared<Workspace>(file, iocContext());
302 4 : appendNewWorkspace(workspace);
303 4 : }
304 :
305 4 : m_workspacesListChanged.notify();
306 4 : }
307 :
308 4 : io::paths_t WorkspaceManager::findWorkspaceFiles() const
309 : {
310 4 : auto findFiles = [this](const io::path_t& dirPath) {
311 4 : std::string filter = "*" + WORKSPACE_EXT;
312 16 : RetVal<io::paths_t> files = fileSystem()->scanFiles(dirPath, { filter });
313 4 : if (!files.ret) {
314 0 : LOGE() << files.ret.toString();
315 : }
316 :
317 8 : return files.val;
318 8 : };
319 :
320 4 : io::paths_t builtinWorkspacesPaths = configuration()->builtinWorkspacesFilePaths();
321 :
322 4 : io::paths_t userWorkspacesPaths = findFiles(configuration()->userWorkspacesPath());
323 4 : std::set<io::path_t> userWorkspacesFileNames;
324 4 : for (const io::path_t& dirPath : userWorkspacesPaths) {
325 0 : userWorkspacesFileNames.insert(io::filename(dirPath));
326 : }
327 :
328 4 : muse::remove_if(builtinWorkspacesPaths, [=](const io::path_t& path) -> bool {
329 4 : io::path_t fileName = io::filename(path);
330 8 : return muse::contains(userWorkspacesFileNames, fileName);
331 4 : });
332 :
333 4 : io::paths_t result;
334 4 : muse::join(result, builtinWorkspacesPaths);
335 4 : muse::join(result, userWorkspacesPaths);
336 :
337 4 : return result;
338 4 : }
339 :
340 3 : void WorkspaceManager::setupDefaultWorkspace()
341 : {
342 3 : std::string defaultWorkspaceName = configuration()->defaultWorkspaceName();
343 3 : WorkspacePtr workspace = findAndInit(defaultWorkspaceName);
344 3 : if (workspace) {
345 3 : m_defaultWorkspace = workspace;
346 3 : return;
347 : }
348 :
349 0 : LOGW() << "not found default workspace, will be created new";
350 :
351 0 : m_defaultWorkspace = doNewWorkspace(defaultWorkspaceName);
352 0 : m_workspaces.push_back(m_defaultWorkspace);
353 :
354 0 : Ret ret = fileSystem()->makePath(configuration()->userWorkspacesPath());
355 0 : if (!ret) {
356 0 : LOGE() << ret.toString();
357 0 : return;
358 : }
359 :
360 0 : ret = m_defaultWorkspace->load();
361 0 : if (!ret) {
362 0 : LOGE() << ret.toString();
363 0 : return;
364 : }
365 6 : }
366 :
367 3 : void WorkspaceManager::setupCurrentWorkspace()
368 : {
369 3 : std::string workspaceName = configuration()->currentWorkspaceName();
370 3 : if (m_currentWorkspace && m_currentWorkspace->isLoaded()) {
371 0 : if (m_currentWorkspace->name() == workspaceName) {
372 0 : return;
373 : }
374 :
375 0 : saveCurrentWorkspace();
376 :
377 : //! NOTE Perhaps we need to unload the current workspace (clear memory)
378 : }
379 :
380 3 : WorkspacePtr workspace = findAndInit(workspaceName);
381 3 : if (!workspace) {
382 : //! NOTE The workspace may have been created by another context/process
383 : //! and saved to disk. Try reloading the file list before falling back.
384 1 : reloadWorkspaceFiles();
385 1 : workspace = findAndInit(workspaceName);
386 : }
387 :
388 3 : if (!workspace) {
389 1 : std::string defaultWorkspaceName = configuration()->defaultWorkspaceName();
390 1 : LOGW() << "failed get workspace: " << workspaceName << ", will use " << defaultWorkspaceName;
391 :
392 : //! NOTE Already should be inited
393 1 : IF_ASSERT_FAILED(m_defaultWorkspace) {
394 0 : setupDefaultWorkspace();
395 : }
396 :
397 1 : workspace = m_defaultWorkspace;
398 1 : configuration()->setCurrentWorkspaceName(defaultWorkspaceName);
399 1 : }
400 :
401 3 : m_currentWorkspace = workspace;
402 3 : m_currentWorkspaceChanged.notify();
403 3 : }
404 :
405 7 : WorkspacePtr WorkspaceManager::findByName(const std::string& name) const
406 : {
407 9 : for (auto workspace : m_workspaces) {
408 7 : if (workspace->name() == name) {
409 5 : return workspace;
410 : }
411 2 : }
412 :
413 2 : return nullptr;
414 : }
415 :
416 7 : WorkspacePtr WorkspaceManager::findAndInit(const std::string& name) const
417 : {
418 7 : WorkspacePtr workspace = findByName(name);
419 7 : if (!workspace) {
420 2 : return nullptr;
421 : }
422 :
423 5 : if (!workspace->isLoaded()) {
424 3 : Ret ret = workspace->load();
425 3 : if (!ret) {
426 0 : LOGE() << "failed load workspace: " << name;
427 0 : return nullptr;
428 : }
429 3 : }
430 :
431 5 : return workspace;
432 7 : }
433 :
434 3 : void WorkspaceManager::saveCurrentWorkspace()
435 : {
436 3 : if (!m_currentWorkspace) {
437 0 : return;
438 : }
439 :
440 3 : Ret ret = m_currentWorkspace->save();
441 3 : if (!ret) {
442 0 : LOGE() << "failed save current workspace, err: " << ret.toString();
443 : }
444 3 : }
445 :
446 0 : void WorkspaceManager::prepareCurrentWorkspaceForChange()
447 : {
448 0 : m_currentWorkspaceAboutToBeChanged.notify();
449 0 : }
|