1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-07-13 00:10:37 +02:00

Desktop: Resolves #7934: Don't create an extra copy of default plugins (load directly from the app bundle) (#9508)

This commit is contained in:
Henry Heino
2023-12-22 03:31:57 -08:00
committed by GitHub
parent bf59b23efe
commit ee18271f9b
13 changed files with 242 additions and 258 deletions

View File

@ -1,90 +1,111 @@
import produce from 'immer';
import path = require('path');
import Setting from '../../../models/Setting';
import shim from '../../../shim';
import PluginService, { defaultPluginSetting, DefaultPluginsInfo, PluginSettings } from '../PluginService';
import PluginService, { defaultPluginSetting, DefaultPluginsInfo, Plugins, PluginSettings } from '../PluginService';
import Logger from '@joplin/utils/Logger';
import * as React from 'react';
const shared = require('../../../components/shared/config/config-shared.js');
import { join } from 'path';
const logger = Logger.create('defaultPluginsUtils');
export function checkPreInstalledDefaultPlugins(defaultPluginsId: string[], pluginSettings: PluginSettings) {
const installedDefaultPlugins: string[] = Setting.value('installedDefaultPlugins');
for (const pluginId of defaultPluginsId) {
// if pluginId is present in pluginSettings and not in installedDefaultPlugins array,
// then its either pre-installed by user or just uninstalled
if (pluginSettings[pluginId] && !installedDefaultPlugins.includes(pluginId)) Setting.setArrayValue('installedDefaultPlugins', pluginId);
}
}
export async function installDefaultPlugins(service: PluginService, defaultPluginsDir: string, defaultPluginsId: string[], pluginSettings: PluginSettings): Promise<PluginSettings> {
// Use loadAndRunDefaultPlugins
// Exported for testing.
export const getDefaultPluginPathsAndSettings = async (
defaultPluginsDir: string, defaultPluginsInfo: DefaultPluginsInfo, pluginSettings: PluginSettings,
) => {
const pluginPaths: string[] = [];
if (!await shim.fsDriver().exists(defaultPluginsDir)) {
logger.info(`Could not find default plugins' directory: ${defaultPluginsDir} - skipping installation.`);
return pluginSettings;
return { pluginPaths, pluginSettings };
}
const defaultPluginsPaths = await shim.fsDriver().readDirStats(defaultPluginsDir);
if (defaultPluginsPaths.length <= 0) {
logger.info(`Default plugins' directory is empty: ${defaultPluginsDir} - skipping installation.`);
return pluginSettings;
logger.info(`Default plugins' directory is empty: ${defaultPluginsDir} - no default plugins will be installed.`);
}
const installedPlugins = Setting.value('installedDefaultPlugins');
for (const pluginStat of defaultPluginsPaths) {
const pluginId = pluginStat.path;
// Each plugin should be within a folder with the same ID as the plugin
const pluginFolderName = pluginStat.path;
const pluginId = pluginFolderName;
// if pluginId is present in 'installedDefaultPlugins' array or it doesn't have default plugin ID, then we won't install it again as default plugin
if (installedPlugins.includes(pluginId) || !defaultPluginsId.includes(pluginId)) {
logger.debug(`Skipping default plugin ${pluginId}, ${!defaultPluginsId.includes(pluginId) ? '(Not a default)' : ''}`);
if (!defaultPluginsInfo.hasOwnProperty(pluginId)) {
logger.warn(`Default plugin ${pluginId} is missing in defaultPluginsInfo. Not loading.`);
continue;
}
const defaultPluginPath: string = path.join(defaultPluginsDir, pluginId, 'plugin.jpl');
await service.installPlugin(defaultPluginPath, false);
pluginPaths.push(join(defaultPluginsDir, pluginFolderName, 'plugin.jpl'));
pluginSettings = produce(pluginSettings, (draft: PluginSettings) => {
draft[pluginId] = defaultPluginSetting();
// 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();
}
});
}
return { pluginSettings, pluginPaths };
};
export const loadAndRunDefaultPlugins = async (
service: PluginService,
defaultPluginsDir: string,
defaultPluginsInfo: DefaultPluginsInfo,
originalPluginSettings: PluginSettings,
): Promise<PluginSettings> => {
const { pluginPaths, pluginSettings } = await getDefaultPluginPathsAndSettings(
defaultPluginsDir, defaultPluginsInfo, originalPluginSettings,
) ?? { pluginPaths: [], pluginSettings: originalPluginSettings };
await service.loadAndRunPlugins(pluginPaths, pluginSettings, { builtIn: true, devMode: false });
return pluginSettings;
}
};
export function setSettingsForDefaultPlugins(defaultPluginsInfo: DefaultPluginsInfo) {
const installedDefaultPlugins = Setting.value('installedDefaultPlugins');
// 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);
// only set initial settings if the plugin is not present in installedDefaultPlugins array
for (const pluginId of Object.keys(defaultPluginsInfo)) {
if (!defaultPluginsInfo[pluginId].settings) continue;
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]);
}
const isFirstLoadOfDefaultPlugin = (pluginId: string) => {
// Not installed?
if (!pluginSettings[pluginId]) {
return false;
}
}
}
export function getDefaultPluginsInstallState(service: PluginService, defaultPluginsId: string[]): PluginSettings {
const settings: PluginSettings = {};
for (const pluginId of defaultPluginsId) {
if (!service.pluginIds.includes(pluginId)) continue;
if (!Setting.setArrayValue('installedDefaultPlugins', pluginId)) {
settings[pluginId] = defaultPluginSetting();
// Not the first load
if (installedDefaultPlugins.includes(pluginId)) {
return false;
}
}
return settings;
}
export function updateDefaultPluginsInstallState(newPluginStates: PluginSettings, ConfigScreen: React.Component<any, any>) {
if (Object.keys(newPluginStates).length === 0) return;
const key = 'plugins.states';
const md = Setting.settingMetadata(key);
let newValue = Setting.value('plugins.states');
newValue = {
...newValue, ...newPluginStates,
// 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;
};
shared.updateSettingValue(ConfigScreen, key, newValue);
if (md.autoSave) {
shared.scheduleSaveSettings(ConfigScreen);
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);
}
}
}
};