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 18 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
3 changes: 3 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -1576,6 +1576,9 @@ 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.d.ts
packages/lib/services/plugins/defaultPlugins.js
packages/lib/services/plugins/defaultPlugins.js.map
packages/lib/services/plugins/reducer.d.ts
packages/lib/services/plugins/reducer.js
packages/lib/services/plugins/reducer.js.map
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -1566,6 +1566,9 @@ 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.d.ts
packages/lib/services/plugins/defaultPlugins.js
packages/lib/services/plugins/defaultPlugins.js.map
packages/lib/services/plugins/reducer.d.ts
packages/lib/services/plugins/reducer.js
packages/lib/services/plugins/reducer.js.map
Expand Down
Binary file not shown.
Binary file not shown.
55 changes: 54 additions & 1 deletion packages/app-cli/tests/services/plugins/PluginService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import PluginRunner from '../../../app/services/plugins/PluginRunner';
import PluginService from '@joplin/lib/services/plugins/PluginService';
import PluginService, { InitialSettings } from '@joplin/lib/services/plugins/PluginService';
import { ContentScriptType } from '@joplin/lib/services/plugins/api/types';
import MdToHtml from '@joplin/renderer/MdToHtml';
import shim from '@joplin/lib/shim';
Expand All @@ -9,6 +9,7 @@ import Note from '@joplin/lib/models/Note';
import Folder from '@joplin/lib/models/Folder';
import { expectNotThrow, setupDatabaseAndSynchronizer, switchClient, expectThrow, createTempDir, supportDir } from '@joplin/lib/testing/test-utils';
import { newPluginScript } from '../../testUtils';
import path = require('path');

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

Expand Down Expand Up @@ -271,6 +272,17 @@ describe('services_PluginService', function() {
expect(await fs.pathExists(installedPluginPath)).toBe(true);
}));

it('should install default plugins', (async () => {
const service = newPluginService();
const pluginsPath = path.join(__dirname, '..', '/defaultPlugins/');
const pluginSettings = service.unserializePluginSettings(Setting.value('plugins.states'));
await service.installDefaultPlugins(pluginsPath, pluginSettings, service);
const installedPluginPath1 = `${Setting.value('pluginDir')}/io.github.jackgruber.backup.jpl`;
const installedPluginPath2 = `${Setting.value('pluginDir')}/plugin.calebjohn.rich-markdown.jpl`;
expect(await fs.pathExists(installedPluginPath1)).toBe(true);
expect(await fs.pathExists(installedPluginPath2)).toBe(true);
}));

it('should rename the plugin archive to the right name', (async () => {
const tempDir = await createTempDir();
const service = newPluginService();
Expand Down Expand Up @@ -305,4 +317,45 @@ describe('services_PluginService', function() {
expect(JSON.parse(folders[0].title)).toBe(expectedPath);
}));

test('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: "",
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': '/JoplinBackupTest',
},
};

service.setSettingsForDefaultPlugins(initialSettings);
expect(Setting.value('plugin-io.github.jackgruber.backup.path')).toBe('/JoplinBackupTest');
await service.destroy();
});

});
11 changes: 9 additions & 2 deletions packages/app-desktop/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import shim from '@joplin/lib/shim';
import AlarmService from '@joplin/lib/services/AlarmService';
import AlarmServiceDriverNode from '@joplin/lib/services/AlarmServiceDriverNode';
import Logger, { TargetType } from '@joplin/lib/Logger';
import Setting from '@joplin/lib/models/Setting';
import Setting, { Env } from '@joplin/lib/models/Setting';
import actionApi from '@joplin/lib/services/rest/actionApi.desktop';
import BaseApplication from '@joplin/lib/BaseApplication';
import DebugService from '@joplin/lib/debug/DebugService';
Expand Down Expand Up @@ -63,6 +63,7 @@ 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 { runIntegrationTests } from '@joplin/lib/services/e2ee/ppkTestUtils';

const pluginClasses = [
Expand Down Expand Up @@ -261,7 +262,7 @@ class Application extends BaseApplication {
service.initialize(packageInfo.version, PlatformImplementation.instance(), pluginRunner, this.store());
service.isSafeMode = Setting.value('isSafeMode');

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
Expand All @@ -273,6 +274,11 @@ class Application extends BaseApplication {
}

try {
const devEnv = Setting.constants_.env === Env.Dev;
let pluginsPath = '';
devEnv ? pluginsPath = path.join(__dirname, '..', 'app-desktop/build/defaultPlugins/') : pluginsPath = path.join(process.resourcesPath, 'build/defaultPlugins/');
Copy link
Owner

Choose a reason for hiding this comment

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

use bridge().buildDir()


pluginSettings = await service.installDefaultPlugins(pluginsPath, pluginSettings, service);
if (await shim.fsDriver().exists(Setting.value('pluginDir'))) {
await service.loadAndRunPlugins(Setting.value('pluginDir'), pluginSettings);
}
Expand Down Expand Up @@ -320,6 +326,7 @@ class Application extends BaseApplication {
type: 'STARTUP_PLUGINS_LOADED',
value: true,
});
service.setSettingsForDefaultPlugins(service.initialPluginsSettings);
}
}, 500);
}
Expand Down
Binary file not shown.
Binary file not shown.
15 changes: 15 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,7 @@ 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 getDefaultPluginSettings from '@joplin/lib/services/plugins/defaultPlugins';
const { KeymapConfigScreen } = require('../KeymapConfig/KeymapConfigScreen');

const settingKeyToControl: any = {
Expand Down Expand Up @@ -60,12 +61,26 @@ class ConfigScreenComponent extends React.Component<any, any> {
this.setState({ settings: this.props.settings });
}

updatePluginsStates(value: any) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please use a descriptive parameter name value should be newPluginStates. And please add typing.

const key = 'plugins.states';
const md = Setting.settingMetadata(key);
shared.updateSettingValue(this, key, value);

if (md.autoSave) {
shared.scheduleSaveSettings(this);
}
}

CalebJohn marked this conversation as resolved.
Show resolved Hide resolved
componentDidMount() {
if (this.props.defaultSection) {
this.setState({ selectedSectionName: this.props.defaultSection }, () => {
this.switchSection(this.props.defaultSection);
});
}
if (!Setting.value('updatedDefaultPluginsInstallStates')) {
Copy link
Owner

Choose a reason for hiding this comment

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

Doesn't it mean the plugin settings will be set only once? How about when we add a new default plugin?

Copy link
Contributor Author

@mak2002 mak2002 Jul 28, 2022

Choose a reason for hiding this comment

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

Now checking in installedDefaultPlugins array to see if new default plugin is available.

this.updatePluginsStates(getDefaultPluginSettings());
Setting.setValue('updatedDefaultPluginsInstallStates', true);
}
}

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
1 change: 0 additions & 1 deletion packages/lib/BaseApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -838,7 +838,6 @@ export default class BaseApplication {
Setting.setValue('sync.interval', 3600);
}

Setting.setValue('firstStart', 0);
} else {
Setting.applyDefaultMigrations();
}
Expand Down
8 changes: 8 additions & 0 deletions packages/lib/models/Setting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1534,6 +1534,14 @@ class Setting extends BaseModel {
storage: SettingStorage.Database,
},

updatedDefaultPluginsInstallStates: {
value: false,
type: SettingItemType.Bool,
public: false,
appTypes: [AppType.Desktop],
storage: SettingStorage.File,
},
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this won't cause a dependency loop. Please correct me if I am wrong.


lastSettingDefaultMigration: {
value: -1,
type: SettingItemType.Int,
Expand Down
53 changes: 49 additions & 4 deletions packages/lib/services/plugins/PluginService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Setting from '../../models/Setting';
import Logger from '../../Logger';
import RepositoryApi from './RepositoryApi';
import produce from 'immer';
import path = require('path');
const compareVersions = require('compare-versions');
const uslug = require('@joplin/fork-uslug');

Expand All @@ -28,6 +29,14 @@ export interface Plugins {
[key: string]: Plugin;
}

export interface SettingAndValue {
[settingName: string]: string;
}

export interface InitialSettings {
[pluginId: string]: SettingAndValue;
}

export interface PluginSetting {
enabled: boolean;
deleted: boolean;
Expand Down Expand Up @@ -68,6 +77,12 @@ export default class PluginService extends BaseService {
return this.instance_;
}

public initialSettings: InitialSettings = {
'io.github.jackgruber.backup': {
'path': '/JoplinBackup',
},
};

private appVersion_: string;
private store_: any = null;
private platformImplementation_: any = null;
Expand Down Expand Up @@ -106,6 +121,10 @@ export default class PluginService extends BaseService {
};
}

public get initialPluginsSettings(): InitialSettings {
return this.initialSettings;
}

private deletePluginAt(pluginId: string) {
if (!this.plugins_[pluginId]) return;

Expand Down Expand Up @@ -416,7 +435,7 @@ export default class PluginService extends BaseService {
return this.installPluginFromRepo(repoApi, pluginId);
}

public async installPlugin(jplPath: string): Promise<Plugin> {
public async installPlugin(jplPath: string, loadPlugin: boolean = true): Promise<Plugin | null> {
logger.info(`Installing plugin: "${jplPath}"`);

// Before moving the plugin to the profile directory, we load it
Expand All @@ -429,9 +448,35 @@ export default class PluginService extends BaseService {
await shim.fsDriver().copy(jplPath, destPath);

// Now load it from the profile directory
const plugin = await this.loadPluginFromPath(destPath);
if (!this.plugins_[plugin.id]) this.setPluginAt(plugin.id, plugin);
return plugin;
if (loadPlugin) {
const plugin = await this.loadPluginFromPath(destPath);
if (!this.plugins_[plugin.id]) this.setPluginAt(plugin.id, plugin);
return plugin;
} else { return null; }
}

public async installDefaultPlugins(pathToPlugins: string, pluginSettings: PluginSettings, service: PluginService): Promise<PluginSettings> {
Copy link
Owner

Choose a reason for hiding this comment

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

why you pass the service to itself...

Copy link
Contributor Author

@mak2002 mak2002 Jul 22, 2022

Choose a reason for hiding this comment

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

Yes. It's not necessary. I will remove it.

if (!Setting.value('firstStart')) return pluginSettings;
const defaultPlugins = await shim.fsDriver().readDirStats(pathToPlugins);

for (const plugin of defaultPlugins) {
const defaultPluginPath: string = path.join(`${pathToPlugins}/${plugin.path}`);
await service.installPlugin(defaultPluginPath, false);

pluginSettings = produce(pluginSettings, (draft: PluginSettings) => {
draft[filename(plugin.path)] = defaultPluginSetting();
});
}
Setting.setValue('firstStart', 0);
Copy link
Owner

Choose a reason for hiding this comment

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

Why you are setting firstStart here??

Copy link
Owner

Choose a reason for hiding this comment

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

In fact this all firstStart is probably incorrect. It means that default plugins won't be enabled for existing installations. You need to have your own check to find out if a particular default plugin is already installed or not before installing it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have now removed the first-app-start logic and implemented my own check.

return pluginSettings;
}

public setSettingsForDefaultPlugins(initialSettings: InitialSettings) {
Object.keys(initialSettings).forEach(pluginId => {
Copy link
Owner

Choose a reason for hiding this comment

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

I don't see checks in here whether the settings are already set or not. It sounds like you'd overwrite user settings on each start.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now, I have added checks here.

Object.keys(initialSettings[pluginId]).forEach((setting) => {
Setting.setValue(`plugin-${pluginId}.${setting}`, initialSettings[pluginId][setting]);
});
});
}

private async pluginPath(pluginId: string) {
Expand Down
11 changes: 11 additions & 0 deletions packages/lib/services/plugins/defaultPlugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defaultPluginSetting, PluginSettings } from './PluginService';

const defaultPluginsId = ['io.github.jackgruber.backup', 'plugin.calebjohn.rich-markdown'];

export default function getDefaultPluginSettings(): PluginSettings {
const settings: PluginSettings = {};
defaultPluginsId.map((pluginId: string) => {
settings[pluginId] = defaultPluginSetting();
});
return settings;
}
Copy link
Owner

Choose a reason for hiding this comment

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

I feel this function doesn't do anything useful. Also you are duplicating the list of default plugin IDs here and in the place where settings are defined. That list should only appear once in the codebase.

1 change: 1 addition & 0 deletions packages/lib/services/plugins/utils/manifestFromObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export default function manifestFromObject(o: any): PluginManifest {
permissions: permissions,

_recommended: getBoolean('_recommended', false, false),
_built_in: getBoolean('_built_in', false, false),
Copy link
Owner

Choose a reason for hiding this comment

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

Is that used somewhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not currently. But I thought maybe we can use it in the future. For example, if we decided to have a special badge for default plugins. But if you want, I will remove it.

};

validatePluginId(manifest.id);
Expand Down
1 change: 1 addition & 0 deletions packages/lib/services/plugins/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ export interface PluginManifest {
_npm_package_name?: string;
_obsolete?: boolean;
_recommended?: boolean;
_built_in?: boolean;
}