Skip to content

Commit 46ab00b

Browse files
Chore: Sync fuzzer: Re-use the same CLI process for commands run on the same client (#12913)
1 parent 07465dd commit 46ab00b

File tree

13 files changed

+517
-145
lines changed

13 files changed

+517
-145
lines changed

.eslintignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ packages/app-cli/app/app.js
9898
packages/app-cli/app/base-command.js
9999
packages/app-cli/app/command-apidoc.js
100100
packages/app-cli/app/command-attach.js
101+
packages/app-cli/app/command-batch.js
101102
packages/app-cli/app/command-cat.js
102103
packages/app-cli/app/command-config.js
103104
packages/app-cli/app/command-cp.js
@@ -135,6 +136,7 @@ packages/app-cli/app/gui/StatusBarWidget.js
135136
packages/app-cli/app/services/plugins/PluginRunner.js
136137
packages/app-cli/app/setupCommand.js
137138
packages/app-cli/app/utils/initializeCommandService.js
139+
packages/app-cli/app/utils/iterateStdin.js
138140
packages/app-cli/app/utils/shimInitCli.js
139141
packages/app-cli/app/utils/testUtils.js
140142
packages/app-cli/tests/HtmlToMd.js
@@ -1747,6 +1749,7 @@ packages/tools/fuzzer/utils/SeededRandom.js
17471749
packages/tools/fuzzer/utils/getNumberProperty.js
17481750
packages/tools/fuzzer/utils/getProperty.js
17491751
packages/tools/fuzzer/utils/getStringProperty.js
1752+
packages/tools/fuzzer/utils/openDebugSession.js
17501753
packages/tools/fuzzer/utils/retryWithCount.js
17511754
packages/tools/generate-database-types.js
17521755
packages/tools/generate-images.js

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ packages/app-cli/app/app.js
7171
packages/app-cli/app/base-command.js
7272
packages/app-cli/app/command-apidoc.js
7373
packages/app-cli/app/command-attach.js
74+
packages/app-cli/app/command-batch.js
7475
packages/app-cli/app/command-cat.js
7576
packages/app-cli/app/command-config.js
7677
packages/app-cli/app/command-cp.js
@@ -108,6 +109,7 @@ packages/app-cli/app/gui/StatusBarWidget.js
108109
packages/app-cli/app/services/plugins/PluginRunner.js
109110
packages/app-cli/app/setupCommand.js
110111
packages/app-cli/app/utils/initializeCommandService.js
112+
packages/app-cli/app/utils/iterateStdin.js
111113
packages/app-cli/app/utils/shimInitCli.js
112114
packages/app-cli/app/utils/testUtils.js
113115
packages/app-cli/tests/HtmlToMd.js
@@ -1720,6 +1722,7 @@ packages/tools/fuzzer/utils/SeededRandom.js
17201722
packages/tools/fuzzer/utils/getNumberProperty.js
17211723
packages/tools/fuzzer/utils/getProperty.js
17221724
packages/tools/fuzzer/utils/getStringProperty.js
1725+
packages/tools/fuzzer/utils/openDebugSession.js
17231726
packages/tools/fuzzer/utils/retryWithCount.js
17241727
packages/tools/generate-database-types.js
17251728
packages/tools/generate-images.js

packages/app-cli/app/app.ts

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import Tag from '@joplin/lib/models/Tag';
99
import Setting, { Env } from '@joplin/lib/models/Setting';
1010
import { reg } from '@joplin/lib/registry.js';
1111
import { dirname, fileExtension } from '@joplin/lib/path-utils';
12-
import { splitCommandString } from '@joplin/utils';
1312
import { _ } from '@joplin/lib/locale';
1413
import { pathExists, readFile, readdirSync } from 'fs-extra';
1514
import RevisionService from '@joplin/lib/services/RevisionService';
@@ -19,7 +18,6 @@ import { FolderEntity, NoteEntity } from '@joplin/lib/services/database/types';
1918
import initializeCommandService from './utils/initializeCommandService';
2019
const { cliUtils } = require('./cli-utils.js');
2120
const Cache = require('@joplin/lib/Cache');
22-
const { splitCommandBatch } = require('@joplin/lib/string-utils');
2321

2422
class Application extends BaseApplication {
2523

@@ -381,22 +379,6 @@ class Application extends BaseApplication {
381379
return output;
382380
}
383381

384-
public async commandList(argv: string[]) {
385-
if (argv.length && argv[0] === 'batch') {
386-
const commands = [];
387-
const commandLines = splitCommandBatch(await readFile(argv[1], 'utf-8'));
388-
389-
for (const commandLine of commandLines) {
390-
if (!commandLine.trim()) continue;
391-
const splitted = splitCommandString(commandLine.trim());
392-
commands.push(splitted);
393-
}
394-
return commands;
395-
} else {
396-
return [argv];
397-
}
398-
}
399-
400382
// We need this special case here because by the time the `version` command
401383
// runs, the keychain has already been setup.
402384
public checkIfKeychainEnabled(argv: string[]) {
@@ -433,15 +415,10 @@ class Application extends BaseApplication {
433415
if (argv.length) {
434416
this.gui_ = this.dummyGui();
435417

436-
this.currentFolder_ = await Folder.load(Setting.value('activeFolderId'));
437-
438418
await this.applySettingsSideEffects();
439-
419+
await this.refreshCurrentFolder();
440420
try {
441-
const commands = await this.commandList(argv);
442-
for (const command of commands) {
443-
await this.execCommand(command);
444-
}
421+
await this.execCommand(argv);
445422
} catch (error) {
446423
if (this.showStackTraces_) {
447424
console.error(error);

packages/app-cli/app/command-batch.js

Lines changed: 0 additions & 19 deletions
This file was deleted.
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { splitCommandBatch } from '@joplin/lib/string-utils';
2+
import BaseCommand from './base-command';
3+
import { _ } from '@joplin/lib/locale';
4+
import { splitCommandString } from '@joplin/utils';
5+
import iterateStdin from './utils/iterateStdin';
6+
import { readFile } from 'fs-extra';
7+
import app from './app';
8+
9+
interface Options {
10+
'file-path': string;
11+
options: {
12+
'continue-on-failure': boolean;
13+
};
14+
}
15+
16+
class Command extends BaseCommand {
17+
public usage() {
18+
return 'batch <file-path>';
19+
}
20+
21+
public options() {
22+
return [
23+
// These are present mostly for testing purposes
24+
['--continue-on-failure', 'Continue running commands when one command in the batch fails.'],
25+
];
26+
}
27+
28+
public description() {
29+
return _('Runs the commands contained in the text file. There should be one command per line.');
30+
}
31+
32+
private streamCommands_ = async function*(filePath: string) {
33+
const processLines = function*(lines: string) {
34+
const commandLines = splitCommandBatch(lines);
35+
36+
for (const command of commandLines) {
37+
if (!command.trim()) continue;
38+
yield splitCommandString(command.trim());
39+
}
40+
};
41+
42+
if (filePath === '-') { // stdin
43+
// Iterating over standard input conflicts with the CLI app's GUI.
44+
if (app().hasGui()) {
45+
throw new Error(_('Reading commands from standard input is only available in CLI mode.'));
46+
}
47+
48+
for await (const lines of iterateStdin('command> ')) {
49+
yield* processLines(lines);
50+
}
51+
} else {
52+
const data = await readFile(filePath, 'utf-8');
53+
yield* processLines(data);
54+
}
55+
};
56+
57+
public async action(options: Options) {
58+
let lastError;
59+
for await (const command of this.streamCommands_(options['file-path'])) {
60+
try {
61+
await app().refreshCurrentFolder();
62+
await app().execCommand(command);
63+
} catch (error) {
64+
if (options.options['continue-on-failure']) {
65+
app().stdout(error.message);
66+
lastError = error;
67+
} else {
68+
throw error;
69+
}
70+
}
71+
}
72+
73+
if (lastError) {
74+
throw lastError;
75+
}
76+
}
77+
}
78+
79+
module.exports = Command;

packages/app-cli/app/command-server.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,25 @@ class Command extends BaseCommand {
1414
return `${_('Start, stop or check the API server. To specify on which port it should run, set the api.port config variable. Commands are (%s).', ['start', 'stop', 'status'].join('|'))} This is an experimental feature - use at your own risks! It is recommended that the server runs off its own separate profile so that no two CLI instances access that profile at the same time. Use --profile to specify the profile path.`;
1515
}
1616

17+
options() {
18+
return [
19+
['--exit-early', 'Allow the command to exit while the server is still running. The server will still stop when the app exits. Valid only for the `start` subcommand.'],
20+
['--quiet', 'Log less information to the console. More verbose logs will still be available through log-clipper.txt.'],
21+
];
22+
}
23+
1724
async action(args) {
1825
const command = args.command;
1926

2027
const ClipperServer = require('@joplin/lib/ClipperServer').default;
2128
ClipperServer.instance().initialize();
2229
const stdoutFn = (...s) => this.stdout(s.join(' '));
30+
const ignoreOutputFn = ()=>{};
2331
const clipperLogger = new Logger();
2432
clipperLogger.addTarget('file', { path: `${Setting.value('profileDir')}/log-clipper.txt` });
2533
clipperLogger.addTarget('console', { console: {
26-
info: stdoutFn,
27-
warn: stdoutFn,
34+
info: args.options.quiet ? ignoreOutputFn : stdoutFn,
35+
warn: args.options.quiet ? ignoreOutputFn : stdoutFn,
2836
error: stdoutFn,
2937
} });
3038
ClipperServer.instance().setDispatch(() => {});
@@ -38,7 +46,11 @@ class Command extends BaseCommand {
3846
this.stdout(_('Server is already running on port %d', runningOnPort));
3947
} else {
4048
await shim.fsDriver().writeFile(pidPath, process.pid.toString(), 'utf-8');
41-
await ClipperServer.instance().start(); // Never exit
49+
const promise = ClipperServer.instance().start();
50+
51+
if (!args.options['exit-early']) {
52+
await promise; // Never exit
53+
}
4254
}
4355
} else if (command === 'status') {
4456
this.stdout(runningOnPort ? _('Server is running on port %d', runningOnPort) : _('Server is not running.'));
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { createInterface } from 'readline/promises';
2+
3+
const iterateStdin = async function*(prompt: string) {
4+
let nextLineListeners: (()=> void)[] = [];
5+
const dispatchAllListeners = () => {
6+
const listeners = nextLineListeners;
7+
nextLineListeners = [];
8+
for (const listener of listeners) {
9+
listener();
10+
}
11+
};
12+
13+
const rl = createInterface({
14+
input: process.stdin,
15+
output: process.stdout,
16+
});
17+
rl.setPrompt(prompt);
18+
19+
let buffer: string[] = [];
20+
rl.on('line', (line) => {
21+
buffer.push(line);
22+
dispatchAllListeners();
23+
});
24+
25+
let done = false;
26+
rl.on('close', () => {
27+
done = true;
28+
dispatchAllListeners();
29+
});
30+
31+
const readNextLines = () => {
32+
return new Promise<string|null>(resolve => {
33+
if (done) {
34+
resolve(null);
35+
} else if (buffer.length > 0) {
36+
resolve(buffer.join('\n'));
37+
buffer = [];
38+
} else {
39+
nextLineListeners.push(() => {
40+
resolve(buffer.join('\n'));
41+
buffer = [];
42+
});
43+
}
44+
});
45+
};
46+
47+
while (!done) {
48+
rl.prompt();
49+
const lines = await readNextLines();
50+
yield lines;
51+
}
52+
};
53+
54+
export default iterateStdin;

packages/lib/BaseApplication.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -395,17 +395,18 @@ export default class BaseApplication {
395395
// - All the calls below are cheap or do nothing if there's nothing
396396
// to do.
397397
'syncInfoCache': async () => {
398-
if (this.hasGui()) {
399-
appLogger.info('"syncInfoCache" was changed - setting up encryption related code');
398+
appLogger.info('"syncInfoCache" was changed - setting up encryption related code');
400399

401-
await loadMasterKeysFromSettings(EncryptionService.instance());
402-
void DecryptionWorker.instance().scheduleStart();
403-
const loadedMasterKeyIds = EncryptionService.instance().loadedMasterKeyIds();
400+
await loadMasterKeysFromSettings(EncryptionService.instance());
401+
const loadedMasterKeyIds = EncryptionService.instance().loadedMasterKeyIds();
402+
403+
this.dispatch({
404+
type: 'MASTERKEY_REMOVE_NOT_LOADED',
405+
ids: loadedMasterKeyIds,
406+
});
404407

405-
this.dispatch({
406-
type: 'MASTERKEY_REMOVE_NOT_LOADED',
407-
ids: loadedMasterKeyIds,
408-
});
408+
if (this.hasGui()) {
409+
void DecryptionWorker.instance().scheduleStart();
409410

410411
// Schedule a sync operation so that items that need to be encrypted
411412
// are sent to sync target.

0 commit comments

Comments
 (0)