2022-09-01 12:44:33 +02:00
|
|
|
import produce from 'immer';
|
|
|
|
import Setting from '../../../models/Setting';
|
|
|
|
import shim from '../../../shim';
|
2023-12-22 13:31:57 +02:00
|
|
|
import PluginService, { defaultPluginSetting, DefaultPluginsInfo, Plugins, PluginSettings } from '../PluginService';
|
2023-07-27 17:05:56 +02:00
|
|
|
import Logger from '@joplin/utils/Logger';
|
2023-12-22 13:31:57 +02:00
|
|
|
import { join } from 'path';
|
2022-09-01 12:44:33 +02:00
|
|
|
|
2022-09-12 17:08:06 +02:00
|
|
|
const logger = Logger.create('defaultPluginsUtils');
|
|
|
|
|
2022-09-01 12:44:33 +02:00
|
|
|
|
2023-12-22 13:31:57 +02:00
|
|
|
// Use loadAndRunDefaultPlugins
|
|
|
|
// Exported for testing.
|
|
|
|
export const getDefaultPluginPathsAndSettings = async (
|
2024-02-03 21:28:47 +02:00
|
|
|
defaultPluginsDir: string,
|
|
|
|
defaultPluginsInfo: DefaultPluginsInfo,
|
|
|
|
pluginSettings: PluginSettings,
|
|
|
|
pluginService: PluginService,
|
2023-12-22 13:31:57 +02:00
|
|
|
) => {
|
|
|
|
const pluginPaths: string[] = [];
|
|
|
|
|
2022-09-12 17:08:06 +02:00
|
|
|
if (!await shim.fsDriver().exists(defaultPluginsDir)) {
|
|
|
|
logger.info(`Could not find default plugins' directory: ${defaultPluginsDir} - skipping installation.`);
|
2023-12-22 13:31:57 +02:00
|
|
|
return { pluginPaths, pluginSettings };
|
2022-09-12 17:08:06 +02:00
|
|
|
}
|
2023-12-22 13:31:57 +02:00
|
|
|
|
2022-09-12 17:08:06 +02:00
|
|
|
const defaultPluginsPaths = await shim.fsDriver().readDirStats(defaultPluginsDir);
|
|
|
|
if (defaultPluginsPaths.length <= 0) {
|
2023-12-22 13:31:57 +02:00
|
|
|
logger.info(`Default plugins' directory is empty: ${defaultPluginsDir} - no default plugins will be installed.`);
|
2022-09-12 17:08:06 +02:00
|
|
|
}
|
|
|
|
|
2023-12-06 21:24:00 +02:00
|
|
|
for (const pluginStat of defaultPluginsPaths) {
|
2023-12-22 13:31:57 +02:00
|
|
|
// Each plugin should be within a folder with the same ID as the plugin
|
2024-01-26 12:33:48 +02:00
|
|
|
const pluginFileName = pluginStat.path;
|
|
|
|
const pluginIdMatch = pluginFileName.match(/^(.*)+\.jpl$/);
|
|
|
|
|
|
|
|
// Previously, default plugins were stored as
|
|
|
|
// default-plugin-id/plugin.jpl
|
|
|
|
// We handle this case by skipping files that don't match the format
|
|
|
|
// default-plugin-id.jpl
|
|
|
|
if (!pluginIdMatch) {
|
|
|
|
logger.warn(`Default plugin filename ${pluginFileName} is not a .JPL file. Skipping.`);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const pluginId = pluginIdMatch[1];
|
2022-09-01 12:44:33 +02:00
|
|
|
|
2023-12-22 13:31:57 +02:00
|
|
|
if (!defaultPluginsInfo.hasOwnProperty(pluginId)) {
|
|
|
|
logger.warn(`Default plugin ${pluginId} is missing in defaultPluginsInfo. Not loading.`);
|
2023-12-11 15:58:45 +02:00
|
|
|
continue;
|
|
|
|
}
|
2023-12-22 13:31:57 +02:00
|
|
|
|
2024-02-03 21:28:47 +02:00
|
|
|
// We skip plugins that are already loaded -- attempting to unpack a different version of a plugin
|
|
|
|
// that has already been loaded causes errors (see #9832).
|
|
|
|
if (pluginService.isPluginLoaded(pluginId)) {
|
|
|
|
logger.info(`Not loading default plugin ${pluginId} -- a plugin with the same ID is already loaded.`);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-01-26 12:33:48 +02:00
|
|
|
pluginPaths.push(join(defaultPluginsDir, pluginFileName));
|
2022-09-01 12:44:33 +02:00
|
|
|
|
|
|
|
pluginSettings = produce(pluginSettings, (draft: PluginSettings) => {
|
2023-12-22 13:31:57 +02:00
|
|
|
// Default plugins can be overridden but not uninstalled (as they're part of
|
|
|
|
// the app bundle). When overriding and unoverriding a default plugin, the plugin's
|
|
|
|
// state may be deleted.
|
|
|
|
// As such, we recreate the plugin state if necessary.
|
|
|
|
if (!draft[pluginId]) {
|
|
|
|
draft[pluginId] = defaultPluginSetting();
|
2024-02-09 00:48:39 +02:00
|
|
|
|
|
|
|
const enabledByDefault = defaultPluginsInfo[pluginId].enabled;
|
|
|
|
if (typeof enabledByDefault === 'boolean') {
|
|
|
|
draft[pluginId].enabled = enabledByDefault;
|
|
|
|
}
|
2023-12-22 13:31:57 +02:00
|
|
|
}
|
2022-09-01 12:44:33 +02:00
|
|
|
});
|
|
|
|
}
|
2023-12-22 13:31:57 +02:00
|
|
|
|
|
|
|
return { pluginSettings, pluginPaths };
|
|
|
|
};
|
|
|
|
|
|
|
|
export const loadAndRunDefaultPlugins = async (
|
|
|
|
service: PluginService,
|
|
|
|
defaultPluginsDir: string,
|
|
|
|
defaultPluginsInfo: DefaultPluginsInfo,
|
|
|
|
originalPluginSettings: PluginSettings,
|
|
|
|
): Promise<PluginSettings> => {
|
|
|
|
const { pluginPaths, pluginSettings } = await getDefaultPluginPathsAndSettings(
|
2024-02-03 21:28:47 +02:00
|
|
|
defaultPluginsDir, defaultPluginsInfo, originalPluginSettings, service,
|
2023-12-22 13:31:57 +02:00
|
|
|
) ?? { pluginPaths: [], pluginSettings: originalPluginSettings };
|
|
|
|
|
|
|
|
await service.loadAndRunPlugins(pluginPaths, pluginSettings, { builtIn: true, devMode: false });
|
2022-09-01 12:44:33 +02:00
|
|
|
return pluginSettings;
|
2023-12-22 13:31:57 +02:00
|
|
|
};
|
2022-09-01 12:44:33 +02:00
|
|
|
|
2023-12-22 13:31:57 +02:00
|
|
|
// Applies setting overrides and marks default plugins as installed.
|
|
|
|
// Should be called after plugins have finished loading.
|
|
|
|
export const afterDefaultPluginsLoaded = async (
|
|
|
|
allLoadedPlugins: Plugins,
|
|
|
|
defaultPluginsInfo: DefaultPluginsInfo,
|
|
|
|
pluginSettings: PluginSettings,
|
|
|
|
) => {
|
|
|
|
const installedDefaultPlugins: string[] = Setting.value('installedDefaultPlugins');
|
|
|
|
const allDefaultPlugins = Object.keys(defaultPluginsInfo);
|
2022-09-01 12:44:33 +02:00
|
|
|
|
2023-12-22 13:31:57 +02:00
|
|
|
const isFirstLoadOfDefaultPlugin = (pluginId: string) => {
|
|
|
|
// Not installed?
|
|
|
|
if (!pluginSettings[pluginId]) {
|
|
|
|
return false;
|
2022-09-01 12:44:33 +02:00
|
|
|
}
|
2023-12-22 13:31:57 +02:00
|
|
|
|
|
|
|
// Not the first load
|
|
|
|
if (installedDefaultPlugins.includes(pluginId)) {
|
|
|
|
return false;
|
2022-09-01 12:44:33 +02:00
|
|
|
}
|
2023-12-22 13:31:57 +02:00
|
|
|
|
|
|
|
// Return true only if the plugin is built-in (and not a user-installed
|
|
|
|
// copy).
|
|
|
|
//
|
|
|
|
// This avoids overriding existing user-set settings.
|
|
|
|
return allLoadedPlugins[pluginId]?.builtIn ?? false;
|
2022-09-01 12:44:33 +02:00
|
|
|
};
|
|
|
|
|
2023-12-22 13:31:57 +02:00
|
|
|
for (const pluginId of allDefaultPlugins) {
|
|
|
|
// if pluginId is present in pluginSettings and not in installedDefaultPlugins array,
|
|
|
|
// then it's a new default plugin and needs overrides applied.
|
|
|
|
if (isFirstLoadOfDefaultPlugin(pluginId)) {
|
|
|
|
// Postprocess: Apply setting overrides
|
|
|
|
for (const settingName of Object.keys(defaultPluginsInfo[pluginId].settings ?? {})) {
|
|
|
|
if (!installedDefaultPlugins.includes(pluginId) && Setting.keyExists(`plugin-${pluginId}.${settingName}`)) {
|
|
|
|
Setting.setValue(`plugin-${pluginId}.${settingName}`, defaultPluginsInfo[pluginId].settings[settingName]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mark the plugin as installed so that postprocessing won't be done again.
|
|
|
|
Setting.setArrayValue('installedDefaultPlugins', pluginId);
|
|
|
|
}
|
2022-09-01 12:44:33 +02:00
|
|
|
}
|
2023-12-22 13:31:57 +02:00
|
|
|
};
|