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 :
23 : #include "registeraudiopluginsscenario.h"
24 :
25 : #include <QCoreApplication>
26 : #include <algorithm>
27 : #include <atomic>
28 : #include <chrono>
29 : #include <condition_variable>
30 : #include <map>
31 : #include <memory>
32 : #include <mutex>
33 : #include <thread>
34 : #include <vector>
35 :
36 : #include "global/containers.h"
37 : #include "global/translation.h"
38 :
39 : #include "audiopluginserrors.h"
40 :
41 : #include "log.h"
42 :
43 : using namespace muse;
44 : using namespace muse::audioplugins;
45 :
46 : #ifdef MUSE_MODULE_AUDIOPLUGINS_SCAN_TRACE
47 : #define SCAN_TRACE() LOGI()
48 : #else
49 : #define SCAN_TRACE() LOGN()
50 : #endif
51 :
52 : namespace {
53 : // completeBasename() is empty for LV2 "<uri>@<bundle>/" composites and
54 : // load() rejects empty ids; fall back to the full path.
55 25 : std::string placeholderIdFromPath(const io::path_t& path)
56 : {
57 25 : std::string id = io::completeBasename(path).toStdString();
58 25 : if (id.empty()) {
59 1 : id = path.toStdString();
60 : }
61 25 : return id;
62 0 : }
63 :
64 6 : int64_t pluginScanConcurrency()
65 : {
66 6 : const unsigned int concurrency = std::thread::hardware_concurrency();
67 6 : return concurrency > 0 ? static_cast<int64_t>(concurrency) : 1;
68 : }
69 :
70 26 : void processProgressEvents()
71 : {
72 26 : if (QCoreApplication::instance()) {
73 26 : QCoreApplication::processEvents();
74 : }
75 26 : }
76 :
77 : constexpr int AUDIO_PLUGIN_REGISTRATION_TIMEOUT_MS = 15000;
78 : }
79 :
80 2 : void RegisterAudioPluginsScenario::init()
81 : {
82 3 : TRACEFUNC;
83 :
84 2 : m_progress.canceled().onNotify(this, [this]() {
85 : SCAN_TRACE() << "Audio plugin scan cancellation requested";
86 0 : m_aborted = true;
87 0 : });
88 :
89 2 : Ret ret = knownPluginsRegister()->load();
90 2 : if (!ret) {
91 0 : LOGE() << ret.toString();
92 : }
93 2 : }
94 :
95 2 : Ret RegisterAudioPluginsScenario::markCrashedPluginsAsBroken()
96 : {
97 3 : TRACEFUNC;
98 :
99 2 : const PluginResourceIdList crashedIds = loadGuard()->danglingLoads();
100 2 : if (crashedIds.empty()) {
101 1 : return make_ok();
102 : }
103 :
104 1 : AudioPluginInfoList brokenInfos;
105 :
106 3 : for (const AudioPluginInfo& info : knownPluginsRegister()->pluginInfoList()) {
107 2 : if (!muse::contains(crashedIds, info.meta.id)) {
108 1 : continue;
109 : }
110 :
111 1 : LOGI() << "Plugin crashed the application while loading in a previous run, marking as broken: " << info.meta.id;
112 :
113 1 : AudioPluginInfo broken = info;
114 1 : broken.state = AudioPluginState::Error;
115 1 : broken.errorCode = static_cast<int>(Err::PluginCrashedOnLoad);
116 1 : brokenInfos.push_back(std::move(broken));
117 2 : }
118 :
119 1 : Ret ret = make_ok();
120 :
121 1 : if (!brokenInfos.empty()) {
122 1 : PluginResourceIdList brokenIds;
123 1 : brokenIds.reserve(brokenInfos.size());
124 2 : for (const AudioPluginInfo& info : brokenInfos) {
125 1 : brokenIds.push_back(info.meta.id);
126 : }
127 :
128 1 : ret = knownPluginsRegister()->unregisterPlugins(brokenIds);
129 1 : if (ret) {
130 1 : ret = knownPluginsRegister()->registerPlugins(brokenInfos);
131 : }
132 :
133 1 : if (!ret) {
134 0 : LOGE() << "Failed to mark crashed plugins as broken: " << ret.toString();
135 0 : return ret;
136 : }
137 1 : }
138 :
139 1 : return loadGuard()->clearDanglingLoads();
140 2 : }
141 :
142 7 : PluginScanResult RegisterAudioPluginsScenario::scanPlugins(Progress* progress) const
143 : {
144 8 : TRACEFUNC;
145 :
146 7 : PluginScanResult result;
147 :
148 : // one binary path can host several plugin ids (shell bundles)
149 7 : std::map<io::path_t, std::vector<AudioPluginState> > registered;
150 24 : for (const auto& info : knownPluginsRegister()->pluginInfoList()) {
151 17 : registered[info.path].push_back(info.state);
152 7 : }
153 :
154 14 : for (const auto& scanner : scannerRegister()->scanners()) {
155 22 : for (const auto& path : scanner->scanPlugins(progress)) {
156 15 : auto it = registered.find(path);
157 15 : if (it == registered.end()) {
158 3 : result.newPluginPaths.push_back(path);
159 4 : continue;
160 : }
161 :
162 12 : const std::vector<AudioPluginState>& states = it->second;
163 :
164 : // Discovered placeholder: a prior run was interrupted, re-validate
165 12 : const bool hasDiscovered = std::any_of(states.cbegin(), states.cend(),
166 13 : [](AudioPluginState state) {
167 13 : return state == AudioPluginState::Discovered;
168 : });
169 12 : if (hasDiscovered) {
170 1 : result.newPluginPaths.push_back(path);
171 1 : registered.erase(it);
172 1 : continue;
173 : }
174 :
175 : // a Missing path is back: re-validate rather than trust the cache,
176 : // the binary may have changed
177 11 : const bool hasMissing = std::any_of(states.cbegin(), states.cend(),
178 11 : [](AudioPluginState state) {
179 11 : return state == AudioPluginState::Missing;
180 : });
181 11 : if (hasMissing) {
182 2 : result.newPluginPaths.push_back(path);
183 : }
184 11 : registered.erase(it);
185 7 : }
186 : }
187 :
188 : // paths no scanner reports anymore are missing
189 10 : for (const auto& [path, states] : registered) {
190 : // skip paths already fully Missing, nothing to transition
191 3 : const bool hasTransition = std::any_of(states.cbegin(), states.cend(),
192 3 : [](AudioPluginState state) {
193 3 : return state != AudioPluginState::Missing;
194 : });
195 3 : if (hasTransition) {
196 3 : result.missingPluginPaths.push_back(path);
197 : }
198 : }
199 :
200 7 : return result;
201 7 : }
202 :
203 0 : Ret RegisterAudioPluginsScenario::rescanAllPlugins()
204 : {
205 0 : TRACEFUNC;
206 :
207 0 : Ret ret = knownPluginsRegister()->clear();
208 0 : if (!ret) {
209 0 : LOGE() << "Failed to clear plugins registry: " << ret.toString();
210 0 : return ret;
211 : }
212 :
213 0 : return updatePluginsRegistry();
214 0 : }
215 :
216 5 : Ret RegisterAudioPluginsScenario::updatePluginsRegistry()
217 : {
218 6 : TRACEFUNC;
219 :
220 5 : PluginScanResult result = scanPlugins();
221 :
222 5 : Ret ret = knownPluginsRegister()->setPluginsState(result.missingPluginPaths, AudioPluginState::Missing);
223 5 : if (!ret) {
224 0 : LOGE() << "Failed to mark missing plugins: " << ret.toString();
225 0 : return ret;
226 : }
227 :
228 5 : ret = registerNewPlugins(result.newPluginPaths, /*validate*/ true);
229 5 : if (!ret) {
230 0 : LOGE() << "Failed to register new plugins: " << ret.toString();
231 0 : return ret;
232 : }
233 :
234 5 : return knownPluginsRegister()->load();
235 5 : }
236 :
237 11 : Ret RegisterAudioPluginsScenario::registerNewPlugins(const io::paths_t& pluginPaths, bool validate)
238 : {
239 12 : TRACEFUNC;
240 :
241 11 : if (pluginPaths.empty()) {
242 3 : return make_ok();
243 : }
244 :
245 8 : Ret ret = persistDiscoveredPlaceholders(pluginPaths);
246 8 : if (!ret) {
247 0 : return ret;
248 : }
249 :
250 8 : if (validate) {
251 : SCAN_TRACE() << "Starting audio plugin validation for " << pluginPaths.size() << " plugin paths";
252 6 : processPluginsRegistration(pluginPaths);
253 : }
254 :
255 8 : return knownPluginsRegister()->load();
256 11 : }
257 :
258 8 : Ret RegisterAudioPluginsScenario::persistDiscoveredPlaceholders(const io::paths_t& pluginPaths)
259 : {
260 : // Placeholders make scanPlugins() re-validate these paths next launch.
261 : // Clear any prior entry at the path first to avoid the same-id-same-path assert.
262 8 : AudioPluginInfoList placeholders;
263 8 : placeholders.reserve(pluginPaths.size());
264 21 : for (const io::path_t& path : pluginPaths) {
265 13 : Ret ret = knownPluginsRegister()->removePluginsAtPath(path);
266 13 : if (!ret) {
267 0 : return ret;
268 : }
269 :
270 13 : AudioPluginInfo info;
271 13 : info.meta.id = placeholderIdFromPath(path);
272 13 : info.meta.type = metaType(path);
273 13 : info.path = path;
274 13 : info.state = AudioPluginState::Discovered;
275 13 : placeholders.emplace_back(std::move(info));
276 26 : }
277 8 : return knownPluginsRegister()->registerPlugins(placeholders);
278 8 : }
279 :
280 0 : Ret RegisterAudioPluginsScenario::unregisterRemovedPlugins(const PluginResourceIdList& pluginIds)
281 : {
282 0 : TRACEFUNC;
283 :
284 0 : if (pluginIds.empty()) {
285 0 : return make_ok();
286 : }
287 :
288 0 : Ret ret = knownPluginsRegister()->unregisterPlugins(pluginIds);
289 0 : if (!ret) {
290 0 : LOGE() << "Failed to unregister removed plugins: " << ret.toString();
291 : }
292 :
293 0 : return ret;
294 0 : }
295 :
296 9 : AudioPluginInfoList RegisterAudioPluginsScenario::scanResult(const io::path_t& pluginPath, const io::path_t& resultFile, int code) const
297 : {
298 9 : if (code == 0) {
299 6 : RetVal<AudioPluginInfoList> res = knownPluginsRegister()->readPluginsFrom(resultFile);
300 6 : if (res.ret) {
301 6 : fileSystem()->remove(resultFile);
302 6 : return res.val;
303 : } else {
304 0 : LOGE() << "Could not read scan result for " << pluginPath.toStdString() << ": " << res.ret.toString();
305 : }
306 6 : } else {
307 3 : LOGE() << "Could not register plugin: " << pluginPath.toStdString() << "\n error code: " << code;
308 : }
309 :
310 3 : fileSystem()->remove(resultFile);
311 9 : return { makeFailedPluginInfo(pluginPath, code) };
312 3 : }
313 :
314 9 : static void appendPluginInfos(AudioPluginInfoList& destination, const AudioPluginInfoList& source)
315 : {
316 9 : destination.insert(destination.end(), source.cbegin(), source.cend());
317 9 : }
318 :
319 6 : void RegisterAudioPluginsScenario::processPluginsRegistration(const io::paths_t& pluginPaths)
320 : {
321 6 : interactive()->showProgress(muse::trc("audio", "Validating audio plugins"), m_progress);
322 :
323 6 : m_aborted = false;
324 6 : m_progress.start();
325 6 : processProgressEvents();
326 :
327 6 : const std::string appPath = globalConfiguration()->appBinPath().toStdString();
328 6 : const int64_t pluginCount = static_cast<int64_t>(pluginPaths.size());
329 6 : if (pluginCount == 0) {
330 0 : m_progress.finish(muse::make_ok());
331 0 : return;
332 : }
333 :
334 6 : const int64_t concurrency = pluginScanConcurrency();
335 6 : const size_t registryFlushSize = 64;
336 :
337 : SCAN_TRACE() << "Audio plugin scan process started: pluginCount=" << pluginCount
338 : << ", concurrency=" << concurrency
339 : << ", timeoutMs=" << AUDIO_PLUGIN_REGISTRATION_TIMEOUT_MS
340 : << ", appPath=" << appPath;
341 :
342 : struct CompletedScan {
343 : int64_t index = 0;
344 : io::path_t resultFile;
345 : int code = 0;
346 : };
347 :
348 6 : std::mutex completedMutex;
349 6 : std::condition_variable completedChanged;
350 6 : std::vector<CompletedScan> completedScans;
351 6 : std::atomic<int64_t> nextIndex { 0 };
352 6 : std::atomic<int64_t> activeWorkers { 0 };
353 6 : std::vector<std::thread> workers;
354 6 : PluginResourceIdList completedPlaceholderIds;
355 6 : AudioPluginInfoList completedPluginInfo;
356 6 : int64_t doneCount = 0;
357 :
358 6 : auto flushCompletedScanResults = [&]() {
359 6 : if (completedPlaceholderIds.empty() && completedPluginInfo.empty()) {
360 1 : return;
361 : }
362 :
363 : SCAN_TRACE() << "Flushing audio plugin scan results: placeholders=" << completedPlaceholderIds.size()
364 : << ", pluginInfos=" << completedPluginInfo.size();
365 :
366 5 : Ret ret = knownPluginsRegister()->unregisterPlugins(completedPlaceholderIds);
367 5 : if (!ret) {
368 0 : LOGE() << "Failed to remove completed plugin placeholders: " << ret.toString();
369 : }
370 :
371 5 : ret = knownPluginsRegister()->registerPlugins(completedPluginInfo);
372 5 : if (!ret) {
373 0 : LOGE() << "Failed to register scanned plugins: " << ret.toString();
374 : }
375 :
376 5 : completedPlaceholderIds.clear();
377 5 : completedPluginInfo.clear();
378 5 : };
379 :
380 10 : auto worker = [&](int64_t workerId) {
381 : SCAN_TRACE() << "Audio plugin scan worker started: workerId=" << workerId;
382 20 : while (!m_aborted.load() && !m_progress.isCanceled()) {
383 19 : const int64_t index = nextIndex.fetch_add(1);
384 19 : if (index >= pluginCount) {
385 9 : break;
386 : }
387 :
388 10 : const io::path_t resultFile = scanResultFilePath(index);
389 10 : const std::string pluginPathStr = pluginPaths[index].toStdString();
390 :
391 : SCAN_TRACE() << "Audio plugin scan worker " << workerId
392 : << " validating index=" << index << "/" << pluginCount
393 : << ", resultFile=" << resultFile.toStdString()
394 : << ", pluginPath=" << pluginPathStr;
395 :
396 : // clear leftovers from a previous run
397 10 : fileSystem()->remove(resultFile);
398 :
399 : std::vector<std::string> args { "--register-audio-plugin", pluginPathStr, "--register-audio-plugin-out",
400 70 : resultFile.toStdString() };
401 :
402 10 : const std::string serverUrl = MUSE_MODULE_AUDIOPLUGINS_CRASHREPORT_URL;
403 10 : if (!serverUrl.empty()) {
404 0 : const std::string dumpsDir = (globalConfiguration()->userAppDataPath() + "/logs/dumps-plugin-validation").toStdString();
405 0 : args.insert(args.end(), { "--crash-dumps-dir", dumpsDir });
406 0 : args.insert(args.end(), { "--crash-server-url", serverUrl });
407 0 : }
408 :
409 20 : const int code = process()->execute(appPath, args,
410 : AUDIO_PLUGIN_REGISTRATION_TIMEOUT_MS,
411 11 : [this]() { return m_aborted.load() || m_progress.isCanceled(); });
412 :
413 : SCAN_TRACE() << "Audio plugin scan worker " << workerId
414 : << " validation finished: index=" << index
415 : << ", code=" << code
416 : << ", aborted=" << m_aborted.load()
417 : << ", pluginPath=" << pluginPathStr;
418 :
419 : {
420 10 : std::lock_guard lock(completedMutex);
421 10 : completedScans.push_back({ index, resultFile, code });
422 10 : }
423 10 : completedChanged.notify_one();
424 10 : }
425 :
426 10 : const int64_t remainingWorkers = --activeWorkers;
427 : SCAN_TRACE() << "Audio plugin scan worker stopped: workerId=" << workerId
428 : << ", remainingWorkers=" << remainingWorkers
429 : << ", aborted=" << m_aborted.load();
430 10 : completedChanged.notify_one();
431 40 : };
432 :
433 6 : const int64_t workerCount = std::min(concurrency, pluginCount);
434 6 : activeWorkers = workerCount;
435 6 : workers.reserve(static_cast<size_t>(workerCount));
436 16 : for (int64_t i = 0; i < workerCount; ++i) {
437 10 : workers.emplace_back(worker, i);
438 : }
439 :
440 16 : while (doneCount < pluginCount) {
441 10 : CompletedScan scan;
442 : {
443 10 : std::unique_lock lock(completedMutex);
444 20 : while (completedScans.empty() && activeWorkers.load() > 0) {
445 5 : completedChanged.wait_for(lock, std::chrono::milliseconds(50));
446 5 : lock.unlock();
447 5 : processProgressEvents();
448 5 : lock.lock();
449 : }
450 :
451 10 : if (completedScans.empty()) {
452 : SCAN_TRACE() << "Audio plugin scan completion loop ended with no completed scans: doneCount=" << doneCount
453 : << ", pluginCount=" << pluginCount
454 : << ", activeWorkers=" << activeWorkers.load()
455 : << ", aborted=" << m_aborted.load();
456 0 : break;
457 : }
458 :
459 10 : scan = completedScans.back();
460 10 : completedScans.pop_back();
461 10 : }
462 :
463 10 : ++doneCount;
464 : SCAN_TRACE() << "Audio plugin scan result received: doneCount=" << doneCount << "/" << pluginCount
465 : << ", index=" << scan.index
466 : << ", code=" << scan.code
467 : << ", resultFile=" << scan.resultFile.toStdString()
468 : << ", pluginPath=" << pluginPaths[scan.index].toStdString();
469 :
470 10 : if (scan.code == IProcess::ExecuteCanceledCode) {
471 : SCAN_TRACE() << "Audio plugin scan result ignored after cancellation: index=" << scan.index
472 : << ", pluginPath=" << pluginPaths[scan.index].toStdString();
473 1 : continue;
474 : }
475 :
476 9 : completedPlaceholderIds.push_back(placeholderIdFromPath(pluginPaths[scan.index]));
477 9 : appendPluginInfos(completedPluginInfo, scanResult(pluginPaths[scan.index], scan.resultFile, scan.code));
478 :
479 9 : m_progress.progress(doneCount, pluginCount, io::filename(pluginPaths[scan.index]).toStdString());
480 9 : processProgressEvents();
481 :
482 9 : if (completedPlaceholderIds.size() >= registryFlushSize) {
483 0 : flushCompletedScanResults();
484 : }
485 :
486 9 : if (m_aborted.load() && activeWorkers.load() == 0) {
487 0 : break;
488 : }
489 10 : }
490 :
491 16 : for (std::thread& workerThread : workers) {
492 10 : if (workerThread.joinable()) {
493 : SCAN_TRACE() << "Joining audio plugin scan worker thread";
494 10 : workerThread.join();
495 : }
496 : }
497 :
498 6 : flushCompletedScanResults();
499 :
500 : SCAN_TRACE() << "Audio plugin scan process finished: doneCount=" << doneCount
501 : << ", pluginCount=" << pluginCount
502 : << ", activeWorkers=" << activeWorkers.load()
503 : << ", aborted=" << m_aborted.load();
504 :
505 6 : if (!m_aborted.load()) {
506 6 : m_progress.finish(muse::make_ok());
507 : }
508 6 : processProgressEvents();
509 6 : }
510 :
511 2 : RetVal<AudioPluginInfoList> RegisterAudioPluginsScenario::validatePluginInfo(const io::path_t& pluginPath) const
512 : {
513 : SCAN_TRACE() << "Validating audio plugin metadata: pluginPath=" << pluginPath.toStdString();
514 :
515 2 : const IAudioPluginMetaReaderPtr reader = metaReader(pluginPath);
516 2 : if (!reader) {
517 : SCAN_TRACE() << "No audio plugin meta reader found: pluginPath=" << pluginPath.toStdString();
518 0 : return RetVal<AudioPluginInfoList>(make_ret(Err::UnknownPluginType));
519 : }
520 :
521 2 : const RetVal<PluginMetaList> metaList = reader->readMeta(pluginPath);
522 2 : if (!metaList.ret) {
523 : SCAN_TRACE() << "Failed reading audio plugin metadata: pluginPath=" << pluginPath.toStdString()
524 : << ", ret=" << metaList.ret.toString();
525 0 : return RetVal<AudioPluginInfoList>(metaList.ret);
526 : }
527 :
528 : SCAN_TRACE() << "Audio plugin metadata read: pluginPath=" << pluginPath.toStdString()
529 : << ", metaCount=" << metaList.val.size();
530 :
531 2 : AudioPluginInfoList infoList;
532 2 : infoList.reserve(metaList.val.size());
533 :
534 5 : for (const PluginMeta& meta : metaList.val) {
535 3 : AudioPluginInfo info;
536 3 : info.meta = meta;
537 3 : info.path = pluginPath;
538 3 : info.state = AudioPluginState::Validated;
539 3 : infoList.emplace_back(std::move(info));
540 3 : }
541 :
542 2 : return RetVal<AudioPluginInfoList>::make_ok(infoList);
543 2 : }
544 :
545 1 : Ret RegisterAudioPluginsScenario::registerPlugin(const io::path_t& pluginPath)
546 : {
547 2 : TRACEFUNC;
548 :
549 1 : IF_ASSERT_FAILED(!pluginPath.empty()) {
550 0 : return false;
551 : }
552 :
553 1 : Ret ret = knownPluginsRegister()->removePluginsAtPath(pluginPath);
554 1 : if (!ret) {
555 0 : LOGE() << "Failed to clear existing entry at " << pluginPath.toStdString()
556 0 : << ": " << ret.toString();
557 0 : return ret;
558 : }
559 :
560 1 : RetVal<AudioPluginInfoList> infoList = validatePluginInfo(pluginPath);
561 1 : if (!infoList.ret) {
562 0 : LOGE() << infoList.ret.toString();
563 0 : return infoList.ret;
564 : }
565 :
566 1 : return knownPluginsRegister()->registerPlugins(infoList.val);
567 1 : }
568 :
569 1 : Ret RegisterAudioPluginsScenario::validatePlugin(const io::path_t& pluginPath, const io::path_t& outputFile)
570 : {
571 2 : TRACEFUNC;
572 :
573 1 : IF_ASSERT_FAILED(!pluginPath.empty()) {
574 0 : return false;
575 : }
576 :
577 : SCAN_TRACE() << "Audio plugin validation subprocess started: pluginPath=" << pluginPath.toStdString()
578 : << ", outputFile=" << outputFile.toStdString();
579 :
580 1 : RetVal<AudioPluginInfoList> infoList = validatePluginInfo(pluginPath);
581 1 : if (!infoList.ret) {
582 0 : LOGE() << infoList.ret.toString();
583 0 : return infoList.ret;
584 : }
585 :
586 1 : Ret ret = knownPluginsRegister()->writePluginsTo(outputFile, infoList.val);
587 : SCAN_TRACE() << "Audio plugin validation subprocess finished: pluginPath=" << pluginPath.toStdString()
588 : << ", outputFile=" << outputFile.toStdString()
589 : << ", pluginInfoCount=" << infoList.val.size()
590 : << ", ret=" << ret.toString();
591 1 : return ret;
592 1 : }
593 :
594 3 : AudioPluginInfo RegisterAudioPluginsScenario::makeFailedPluginInfo(const io::path_t& pluginPath, int failCode) const
595 : {
596 3 : AudioPluginInfo info;
597 3 : info.meta.id = placeholderIdFromPath(pluginPath);
598 3 : info.meta.type = metaType(pluginPath);
599 3 : info.path = pluginPath;
600 3 : info.state = AudioPluginState::Error;
601 3 : info.errorCode = failCode != 0 ? failCode : -1;
602 3 : return info;
603 0 : }
604 :
605 10 : io::path_t RegisterAudioPluginsScenario::scanResultFilePath(int64_t index) const
606 : {
607 10 : return fileSystem()->temporaryDirectoryPath() + "/muse_audioplugin_scan_" + std::to_string(index) + ".json";
608 : }
609 :
610 18 : IAudioPluginMetaReaderPtr RegisterAudioPluginsScenario::metaReader(const io::path_t& pluginPath) const
611 : {
612 18 : for (const IAudioPluginMetaReaderPtr& reader : metaReaderRegister()->readers()) {
613 18 : if (reader->canReadMeta(pluginPath)) {
614 18 : return reader;
615 : }
616 : }
617 :
618 0 : return nullptr;
619 : }
620 :
621 16 : audioplugins::PluginType RegisterAudioPluginsScenario::metaType(const io::path_t& pluginPath) const
622 : {
623 16 : const IAudioPluginMetaReaderPtr reader = metaReader(pluginPath);
624 32 : return reader ? reader->metaType() : audioplugins::PluginType();
625 16 : }
|