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) 2024 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 "extensionsprovider.h"
23 :
24 : #include "global/io/path.h"
25 :
26 : #include "extensionsession.h"
27 : #include "../extensionbundle.h"
28 : #include "legacy/extpluginrunner.h"
29 :
30 : #include "../extensionserrors.h"
31 :
32 : #include "log.h"
33 :
34 : using namespace muse;
35 : using namespace muse::extensions;
36 :
37 : static Uri VIEWER_URI = Uri("muse://extensions/viewer");
38 :
39 0 : void ExtensionsProvider::reloadExtensions()
40 : {
41 : //! NOTE Without this, reloading picks up a plugin's manifest but keeps
42 : //! running the code the engine compiled the first time, so edits to a
43 : //! plugin only take effect after a restart.
44 0 : extensionsUiEngine()->clearComponentCache();
45 :
46 0 : extensionsRegister()->reload();
47 0 : }
48 :
49 0 : ManifestList ExtensionsProvider::manifestList(Filter filter) const
50 : {
51 0 : return extensionsRegister()->manifestList(filter);
52 : }
53 :
54 0 : muse::async::Notification ExtensionsProvider::manifestListChanged() const
55 : {
56 0 : return extensionsRegister()->manifestListChanged();
57 : }
58 :
59 0 : bool ExtensionsProvider::exists(const ExtensionUri& uri) const
60 : {
61 0 : return extensionsRegister()->exists(uri);
62 : }
63 :
64 0 : const Manifest& ExtensionsProvider::manifest(const ExtensionUri& uri) const
65 : {
66 0 : return extensionsRegister()->manifest(uri);
67 : }
68 :
69 0 : void ExtensionsProvider::setEnabled(const ExtensionUri& uri, bool enabled)
70 : {
71 0 : extensionsRegister()->setEnabled(uri, enabled);
72 0 : }
73 :
74 0 : bool ExtensionsProvider::isEnabled(const ExtensionUri& uri) const
75 : {
76 0 : return extensionsRegister()->isEnabled(uri);
77 : }
78 :
79 0 : async::Channel<ExtensionUri> ExtensionsProvider::enabledChanged() const
80 : {
81 0 : return extensionsRegister()->enabledChanged();
82 : }
83 :
84 0 : async::Channel<ExtensionUri, ExtensionActionCode> ExtensionsProvider::dockRequested() const
85 : {
86 0 : return m_dockRequested;
87 : }
88 :
89 0 : muse::Ret ExtensionsProvider::perform(const ExtensionUri& uri, const ExtensionActionCode& actionCode)
90 : {
91 0 : const Manifest& m = manifest(uri);
92 0 : IF_ASSERT_FAILED(m.actions.size() > 0) {
93 0 : return make_ret(Ret::Code::UnknownError);
94 : }
95 :
96 0 : Action a = m.action(actionCode);
97 0 : IF_ASSERT_FAILED(a.isValid()) {
98 0 : return make_ret(Ret::Code::UnknownError);
99 : }
100 :
101 0 : switch (a.type) {
102 0 : case Type::Form: {
103 0 : if (a.docked) {
104 0 : m_dockRequested.send(uri, actionCode);
105 0 : return muse::make_ok();
106 : }
107 0 : UriQuery q = UriQuery(VIEWER_URI);
108 0 : q.addParam("extension", Val(uri.toString()));
109 0 : q.addParam("action", Val(actionCode));
110 0 : q.addParam("modal", Val(a.modal));
111 0 : return interactive()->openSync(q).ret;
112 0 : } break;
113 0 : case Type::Macros:
114 0 : return run(a, m);
115 0 : default:
116 0 : break;
117 : }
118 :
119 0 : return make_ret(Ret::Code::UnknownError);
120 0 : }
121 :
122 0 : muse::Ret ExtensionsProvider::run(const ExtensionUri& uri, const ExtensionActionCode& actionCode)
123 : {
124 0 : const Manifest& m = manifest(uri);
125 0 : IF_ASSERT_FAILED(m.actions.size() > 0) {
126 0 : return make_ret(Ret::Code::UnknownError);
127 : }
128 :
129 0 : Action a = m.action(actionCode);
130 0 : IF_ASSERT_FAILED(a.isValid()) {
131 0 : return make_ret(Ret::Code::UnknownError);
132 : }
133 :
134 0 : return run(a, m);
135 0 : }
136 :
137 0 : std::unique_ptr<IExtensionSession> ExtensionsProvider::newSession(const ExtensionUri& uri, const io::path_t& relativeScriptPath) const
138 : {
139 0 : const Manifest& manifest = this->manifest(uri);
140 0 : if (!manifest.isValid()) {
141 0 : LOGE() << "manifest not found: " << uri;
142 0 : return {};
143 : }
144 0 : const auto scriptPath = resolveBundleFile(io::dirpath(manifest.path), relativeScriptPath);
145 0 : if (!scriptPath) {
146 0 : LOGE() << "extension script not found in bundle: " << relativeScriptPath << ", manifest: " << manifest.path;
147 0 : return {};
148 : }
149 :
150 0 : return newSession(manifest, *scriptPath);
151 0 : }
152 :
153 0 : muse::Ret ExtensionsProvider::run(const Action& a, const Manifest& manifest)
154 : {
155 0 : if (!a.isValid()) {
156 0 : return make_ret(Err::ExtNotFound);
157 : }
158 :
159 : //! TODO Add check of type
160 :
161 0 : Ret ret;
162 0 : if (a.legacyPlugin) {
163 0 : legacy::ExtPluginRunner runner(iocContext());
164 0 : ret = runner.run(a);
165 0 : } else {
166 0 : auto session = newSession(manifest, a.path);
167 0 : if (!session) {
168 0 : return make_ret(Err::ExtLoadError);
169 : }
170 0 : ret = session->evaluate();
171 0 : if (!ret) {
172 0 : LOGE() << "failed evaluate extension script: " << a.path << ", err: " << ret.toString();
173 0 : return make_ret(Err::ExtLoadError);
174 : }
175 0 : ret = session->call(QString::fromStdString(a.func));
176 0 : if (!ret) {
177 0 : LOGE() << "failed call extension function: " << a.func << ", script: " << a.path << ", err: " << ret.toString();
178 0 : return make_ret(Err::ExtBadFormat);
179 : }
180 0 : }
181 :
182 0 : return ret;
183 0 : }
184 :
185 0 : std::unique_ptr<IExtensionSession> ExtensionsProvider::newSession(const Manifest& extensionManifest, const io::path_t& scriptPath) const
186 : {
187 0 : return std::make_unique<ExtensionSession>(iocContext(), extensionManifest, scriptPath);
188 : }
|