Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Desktop: Install default plugins on first-app-start #6585

Merged
merged 34 commits into from
Sep 1, 2022
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
bd677c4
initial commit
mak2002 Jun 16, 2022
eb67977
don't load default plugins immediately after installing
mak2002 Jun 16, 2022
302b380
Merge branch 'dev' into install-default-plugins
laurent22 Jun 18, 2022
d8d86f2
fix installed state of default plugins
mak2002 Jun 21, 2022
75d2601
Merge branch 'install-default-plugins' of https://github.com/mak2002/…
mak2002 Jun 21, 2022
4491cf0
organize the code for installing default plugins
mak2002 Jun 23, 2022
84c02c9
improve types of functions
mak2002 Jun 24, 2022
8d61611
add function for default Plugins settings
mak2002 Jun 25, 2022
3c14807
add _built_in field in plugins manifest
mak2002 Jun 25, 2022
191c8a5
added test
mak2002 Jun 28, 2022
32c003b
implement initial settings for plugins
mak2002 Jun 29, 2022
3536393
removed code for mkdir command
mak2002 Jun 29, 2022
4aa26bc
remove unnecessary files
mak2002 Jun 29, 2022
78c2ac7
added tests for initial settings for default plugins
mak2002 Jul 2, 2022
46007c5
remove async keyword from app.ts
mak2002 Jul 2, 2022
ebde62b
Merge branch 'laurent22:dev' into install-default-plugins
mak2002 Jul 6, 2022
4d05817
Merge branch 'laurent22:dev' into install-default-plugins
mak2002 Jul 10, 2022
360de71
Merge branch 'laurent22:dev' into install-default-plugins
mak2002 Jul 13, 2022
25820d3
Merge branch 'dev' into install-default-plugins
mak2002 Jul 22, 2022
b80c487
refactor code
mak2002 Jul 25, 2022
228aa96
small fix
mak2002 Jul 25, 2022
aa3a5fa
Merge branch 'install-default-plugins' of https://github.com/mak2002/…
mak2002 Jul 25, 2022
343c4de
remove old plugins
mak2002 Jul 25, 2022
b68fd07
refactor code
mak2002 Jul 28, 2022
fbc1793
Merge branch 'laurent22:dev' into install-default-plugins
mak2002 Jul 30, 2022
d1db3ec
move default plugins functions into separate folder | update tests
mak2002 Jul 31, 2022
53ddafb
remove test plugins
mak2002 Jul 31, 2022
4cc80d1
small fix
mak2002 Jul 31, 2022
90dfd0d
add comment for checkArrayAndUpdate method
mak2002 Jul 31, 2022
a4fcf8e
make function for initial settings to support profileDir
mak2002 Jul 31, 2022
6ef7c3d
refactor plugins info object
mak2002 Aug 10, 2022
5f6671d
fix import
mak2002 Aug 10, 2022
d90dee1
update import in ConfigScreen
mak2002 Aug 11, 2022
263d33b
Merge branch 'laurent22:dev' into install-default-plugins
mak2002 Aug 30, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ packages/app-cli/tests/services/plugins/api/JoplinViewMenuItem.js.map
packages/app-cli/tests/services/plugins/api/JoplinWorkspace.d.ts
packages/app-cli/tests/services/plugins/api/JoplinWorkspace.js
packages/app-cli/tests/services/plugins/api/JoplinWorkspace.js.map
packages/app-cli/tests/services/plugins/defaultPluginsUtils.d.ts
packages/app-cli/tests/services/plugins/defaultPluginsUtils.js
packages/app-cli/tests/services/plugins/defaultPluginsUtils.js.map
packages/app-cli/tests/services/plugins/sandboxProxy.d.ts
packages/app-cli/tests/services/plugins/sandboxProxy.js
packages/app-cli/tests/services/plugins/sandboxProxy.js.map
Expand Down Expand Up @@ -1603,6 +1606,12 @@ packages/lib/services/plugins/api/JoplinWorkspace.js.map
packages/lib/services/plugins/api/types.d.ts
packages/lib/services/plugins/api/types.js
packages/lib/services/plugins/api/types.js.map
packages/lib/services/plugins/defaultPlugins/defaultPluginsUtils.d.ts
packages/lib/services/plugins/defaultPlugins/defaultPluginsUtils.js
packages/lib/services/plugins/defaultPlugins/defaultPluginsUtils.js.map
packages/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo.d.ts
packages/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo.js
packages/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo.js.map
packages/lib/services/plugins/reducer.d.ts
packages/lib/services/plugins/reducer.js
packages/lib/services/plugins/reducer.js.map
Expand Down
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ packages/app-cli/tests/services/plugins/api/JoplinViewMenuItem.js.map
packages/app-cli/tests/services/plugins/api/JoplinWorkspace.d.ts
packages/app-cli/tests/services/plugins/api/JoplinWorkspace.js
packages/app-cli/tests/services/plugins/api/JoplinWorkspace.js.map
packages/app-cli/tests/services/plugins/defaultPluginsUtils.d.ts
packages/app-cli/tests/services/plugins/defaultPluginsUtils.js
packages/app-cli/tests/services/plugins/defaultPluginsUtils.js.map
packages/app-cli/tests/services/plugins/sandboxProxy.d.ts
packages/app-cli/tests/services/plugins/sandboxProxy.js
packages/app-cli/tests/services/plugins/sandboxProxy.js.map
Expand Down Expand Up @@ -1593,6 +1596,12 @@ packages/lib/services/plugins/api/JoplinWorkspace.js.map
packages/lib/services/plugins/api/types.d.ts
packages/lib/services/plugins/api/types.js
packages/lib/services/plugins/api/types.js.map
packages/lib/services/plugins/defaultPlugins/defaultPluginsUtils.d.ts
packages/lib/services/plugins/defaultPlugins/defaultPluginsUtils.js
packages/lib/services/plugins/defaultPlugins/defaultPluginsUtils.js.map
packages/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo.d.ts
packages/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo.js
packages/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo.js.map
packages/lib/services/plugins/reducer.d.ts
packages/lib/services/plugins/reducer.js
packages/lib/services/plugins/reducer.js.map
Expand Down
210 changes: 210 additions & 0 deletions packages/app-cli/tests/services/plugins/defaultPluginsUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import { installDefaultPlugins, getDefaultPluginsInstallState, setSettingsForDefaultPlugins, checkPreInstalledDefaultPlugins } from '@joplin/lib/services/plugins/defaultPlugins/defaultPluginsUtils';
import PluginRunner from '../../../app/services/plugins/PluginRunner';
import * as fs from 'fs-extra';
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't import*. Only import the functions that you need

import { setupDatabaseAndSynchronizer, supportDir, switchClient } from '@joplin/lib/testing/test-utils';
import PluginService, { defaultPluginSetting, InitialSettings, PluginSettings } from '@joplin/lib/services/plugins/PluginService';
import Setting from '@joplin/lib/models/Setting';

const testPluginDir = `${supportDir}/plugins`;

function newPluginService(appVersion: string = '1.4') {
const runner = new PluginRunner();
const service = new PluginService();
service.initialize(
appVersion,
{
joplin: {},
},
runner,
{
dispatch: () => {},
getState: () => {},
}
);
return service;
}

describe('defaultPluginsUtils', function() {

const pluginsId = ['joplin.plugin.ambrt.backlinksToNote', 'org.joplinapp.plugins.ToggleSidebars'];

beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
done();
});

it('should install default plugins with no previous default plugins installed', (async () => {
const testPluginDir = `${supportDir}/pluginRepo/plugins`;
Setting.setValue('installedDefaultPlugins', []);

const service = newPluginService('2.1');

const pluginSettings = service.unserializePluginSettings(Setting.value('plugins.states'));

const newPluginsSettings = await installDefaultPlugins(service, testPluginDir, pluginsId, pluginSettings);

const installedPluginPath1 = `${Setting.value('pluginDir')}/${pluginsId[0]}.jpl`;
const installedPluginPath2 = `${Setting.value('pluginDir')}/${pluginsId[1]}.jpl`;

expect(await fs.pathExists(installedPluginPath1)).toBe(true);
expect(await fs.pathExists(installedPluginPath2)).toBe(true);

expect(newPluginsSettings[pluginsId[0]]).toMatchObject(defaultPluginSetting());
expect(newPluginsSettings[pluginsId[1]]).toMatchObject(defaultPluginSetting());

}));

it('should install default plugins with previous default plugins installed', (async () => {

const testPluginDir = `${supportDir}/pluginRepo/plugins`;
Setting.setValue('installedDefaultPlugins', ['org.joplinapp.plugins.ToggleSidebars']);

const service = newPluginService('2.1');

const pluginSettings = service.unserializePluginSettings(Setting.value('plugins.states'));

const newPluginsSettings = await installDefaultPlugins(service, testPluginDir, pluginsId, pluginSettings);

const installedPluginPath1 = `${Setting.value('pluginDir')}/${pluginsId[0]}.jpl`;
const installedPluginPath2 = `${Setting.value('pluginDir')}/${pluginsId[1]}.jpl`;

expect(await fs.pathExists(installedPluginPath1)).toBe(true);
expect(await fs.pathExists(installedPluginPath2)).toBe(false);

expect(newPluginsSettings[pluginsId[0]]).toMatchObject(defaultPluginSetting());
expect(newPluginsSettings[pluginsId[1]]).toBeUndefined();
}));

it('should get default plugins install state', (async () => {
const testCases = [
{
'installedDefaultPlugins': [''],
'loadingPlugins': [`${testPluginDir}/simple`, `${testPluginDir}/jpl_test/org.joplinapp.FirstJplPlugin.jpl`],
'plugin1DefaultState': defaultPluginSetting(),
'plugin2DefaultState': defaultPluginSetting(),
'installedDefaultPlugins1': true,
'installedDefaultPlugins2': true,
},
{
'installedDefaultPlugins': [''],
'loadingPlugins': [`${testPluginDir}/simple`],
'plugin1DefaultState': defaultPluginSetting(),
'plugin2DefaultState': undefined,
'installedDefaultPlugins1': true,
'installedDefaultPlugins2': false,
},
{
'installedDefaultPlugins': ['org.joplinapp.plugins.Simple'],
'loadingPlugins': [`${testPluginDir}/simple`, `${testPluginDir}/jpl_test/org.joplinapp.FirstJplPlugin.jpl`],
'plugin1DefaultState': undefined,
'plugin2DefaultState': defaultPluginSetting(),
'installedDefaultPlugins1': true,
'installedDefaultPlugins2': true,
},
{
'installedDefaultPlugins': ['org.joplinapp.plugins.Simple'],
'loadingPlugins': [`${testPluginDir}/simple`],
'plugin1DefaultState': undefined,
'plugin2DefaultState': undefined,
'installedDefaultPlugins1': true,
'installedDefaultPlugins2': false,
},
];

for (const testCase of testCases) {
const service = newPluginService();
const pluginsId = ['org.joplinapp.plugins.Simple', 'org.joplinapp.FirstJplPlugin'];

Setting.setValue('installedDefaultPlugins', testCase.installedDefaultPlugins);
await service.loadAndRunPlugins(testCase.loadingPlugins, {});

// setting installedDefaultPlugins state
const defaultInstallStates: PluginSettings = getDefaultPluginsInstallState(service, pluginsId);

expect(defaultInstallStates[pluginsId[0]]).toStrictEqual(testCase.plugin1DefaultState);
expect(defaultInstallStates[pluginsId[1]]).toStrictEqual(testCase.plugin2DefaultState);


const installedDefaultPlugins = Setting.value('installedDefaultPlugins');
expect(installedDefaultPlugins.includes(pluginsId[0])).toBe(testCase.installedDefaultPlugins1);
expect(installedDefaultPlugins.includes(pluginsId[1])).toBe(testCase.installedDefaultPlugins2);

}

}));

it('should check pre-installed default plugins', (async () => {
// with previous pre-installed default plugins
Setting.setValue('installedDefaultPlugins', ['']);
let pluginSettings, installedDefaultPlugins;

pluginSettings = { [pluginsId[0]]: defaultPluginSetting() };
checkPreInstalledDefaultPlugins(pluginsId, pluginSettings);

installedDefaultPlugins = Setting.value('installedDefaultPlugins');
expect(installedDefaultPlugins.includes(pluginsId[0])).toBe(true);
expect(installedDefaultPlugins.includes(pluginsId[1])).toBe(false);


// with no previous pre-installed default plugins
Setting.setValue('installedDefaultPlugins', ['not-a-default-plugin']);
pluginSettings = {};
checkPreInstalledDefaultPlugins(pluginsId, pluginSettings);

installedDefaultPlugins = Setting.value('installedDefaultPlugins');
expect(installedDefaultPlugins.includes(pluginsId[0])).toBe(false);
expect(installedDefaultPlugins.includes(pluginsId[1])).toBe(false);

}));

it('should set initial settings for default plugins', async () => {
const service = newPluginService();

const pluginScript = `
/* joplin-manifest:
{
"id": "io.github.jackgruber.backup",
"manifest_version": 1,
"app_min_version": "1.4",
"name": "JS Bundle test",
"version": "1.0.0"
}
*/
joplin.plugins.register({
onStart: async function() {
await joplin.settings.registerSettings({
path: {
value: "initial-path",
type: 2,
section: "backupSection",
public: true,
label: "Backup path",
},
})
},
});`;

const plugin = await service.loadPluginFromJsBundle('', pluginScript);
await service.runPlugin(plugin);

const initialSettings: InitialSettings = {
'io.github.jackgruber.backup': {
'path': `${Setting.value('profileDir')}/testBackup`,
},
};

// with pre-installed default plugin
Setting.setValue('installedDefaultPlugins', ['io.github.jackgruber.backup']);
setSettingsForDefaultPlugins(initialSettings);
expect(Setting.value('plugin-io.github.jackgruber.backup.path')).toBe('initial-path');
await service.destroy();

// with no pre-installed default plugin
Setting.setValue('installedDefaultPlugins', ['']);
setSettingsForDefaultPlugins(initialSettings);
expect(Setting.value('plugin-io.github.jackgruber.backup.path')).toBe(`${Setting.value('profileDir')}/testBackup`);
await service.destroy();
});

});
12 changes: 10 additions & 2 deletions packages/app-desktop/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import sidebarCommands from './gui/Sidebar/commands/index';
import appCommands from './commands/index';
import libCommands from '@joplin/lib/commands/index';
import { homedir } from 'os';
import { defaultPlugins, initialSettings } from '@joplin/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo';
const electronContextMenu = require('./services/electron-context-menu');
// import populateDatabase from '@joplin/lib/services/debug/populateDatabase';

Expand All @@ -63,6 +64,8 @@ import checkForUpdates from './checkForUpdates';
import { AppState } from './app.reducer';
import syncDebugLog from '@joplin/lib/services/synchronizer/syncDebugLog';
import eventManager from '@joplin/lib/eventManager';
import path = require('path');
import { checkPreInstalledDefaultPlugins, installDefaultPlugins, setSettingsForDefaultPlugins } from '@joplin/lib/services/plugins/defaultPlugins/defaultPluginsUtils';
// import { runIntegrationTests } from '@joplin/lib/services/e2ee/ppkTestUtils';

const pluginClasses = [
Expand Down Expand Up @@ -260,9 +263,9 @@ class Application extends BaseApplication {
const pluginRunner = new PluginRunner();
service.initialize(packageInfo.version, PlatformImplementation.instance(), pluginRunner, this.store());
service.isSafeMode = Setting.value('isSafeMode');
const defaultPluginsId = Object.keys(defaultPlugins);

const pluginSettings = service.unserializePluginSettings(Setting.value('plugins.states'));

let pluginSettings = service.unserializePluginSettings(Setting.value('plugins.states'));
{
// Users can add and remove plugins from the config screen at any
// time, however we only effectively uninstall the plugin the next
Expand All @@ -272,7 +275,11 @@ class Application extends BaseApplication {
Setting.setValue('plugins.states', newSettings);
}

checkPreInstalledDefaultPlugins(defaultPluginsId, pluginSettings);

try {
const pluginsDir = path.join(bridge().buildDir(), 'defaultPlugins');
pluginSettings = await installDefaultPlugins(service, pluginsDir, defaultPluginsId, pluginSettings);
if (await shim.fsDriver().exists(Setting.value('pluginDir'))) {
await service.loadAndRunPlugins(Setting.value('pluginDir'), pluginSettings);
}
Expand Down Expand Up @@ -320,6 +327,7 @@ class Application extends BaseApplication {
type: 'STARTUP_PLUGINS_LOADED',
value: true,
});
setSettingsForDefaultPlugins(initialSettings);
}
}, 500);
}
Expand Down
Binary file not shown.
Binary file not shown.
4 changes: 4 additions & 0 deletions packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
const shared = require('@joplin/lib/components/shared/config-shared.js');
import ClipperConfigScreen from '../ClipperConfigScreen';
import restart from '../../services/restart';
import PluginService from '@joplin/lib/services/plugins/PluginService';
import { getDefaultPluginsInstallState, updateDefaultPluginsInstallState } from '@joplin/lib/services/plugins/defaultPlugins/defaultPluginsUtils';
import { defaultPlugins } from '@joplin/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo';
const { KeymapConfigScreen } = require('../KeymapConfig/KeymapConfigScreen');

const settingKeyToControl: any = {
Expand Down Expand Up @@ -66,6 +69,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
this.switchSection(this.props.defaultSection);
});
}
updateDefaultPluginsInstallState(getDefaultPluginsInstallState(PluginService.instance(), Object.keys(defaultPlugins)), this);
}

private async handleSettingButton(key: string) {
Expand Down
3 changes: 2 additions & 1 deletion packages/app-desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
"afterSign": "./tools/notarizeMacApp.js",
"extraResources": [
"build/icons/**",
"build/images/**"
"build/images/**",
"build/defaultPlugins/**"
],
"afterAllArtifactBuild": "./generateSha512.js",
"asar": true,
Expand Down
18 changes: 18 additions & 0 deletions packages/lib/models/Setting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1546,6 +1546,13 @@ class Setting extends BaseModel {
public: false,
},

installedDefaultPlugins: {
value: [],
type: SettingItemType.Array,
public: false,
storage: SettingStorage.File,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be database here

},

// 'featureFlag.syncAccurateTimestamps': {
// value: false,
// type: SettingItemType.Bool,
Expand Down Expand Up @@ -1914,6 +1921,17 @@ class Setting extends BaseModel {
return this.setValue(key, !this.value(key));
}

// this method checks if the 'value' passed is present in the Setting "Array"
// If yes, then it just returns 'true'. If its not present then, it will
// update it and return 'false'
static checkArrayAndUpdate(settingName: string, value: string): boolean {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe call it setArrayValue. Make the method public

const settingValue: Array<any> = this.value(settingName);
if (settingValue.includes(value)) return true;
settingValue.push(value);
this.setValue(settingName, settingValue);
return false;
}

static objectValue(settingKey: string, objectKey: string, defaultValue: any = null) {
const o = this.value(settingKey);
if (!o || !(objectKey in o)) return defaultValue;
Expand Down
Loading