1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-21 09:38:01 +02:00

Desktop: Regression: Fixed external edit file watching

This commit is contained in:
Laurent Cozic 2020-11-16 11:03:44 +00:00
parent 6103aad2a7
commit a808281dd2
9 changed files with 66 additions and 52 deletions

View File

@ -964,6 +964,9 @@ packages/lib/services/BaseService.js.map
packages/lib/services/CommandService.d.ts packages/lib/services/CommandService.d.ts
packages/lib/services/CommandService.js packages/lib/services/CommandService.js
packages/lib/services/CommandService.js.map packages/lib/services/CommandService.js.map
packages/lib/services/ExternalEditWatcher.d.ts
packages/lib/services/ExternalEditWatcher.js
packages/lib/services/ExternalEditWatcher.js.map
packages/lib/services/KeymapService.d.ts packages/lib/services/KeymapService.d.ts
packages/lib/services/KeymapService.js packages/lib/services/KeymapService.js
packages/lib/services/KeymapService.js.map packages/lib/services/KeymapService.js.map

3
.gitignore vendored
View File

@ -956,6 +956,9 @@ packages/lib/services/BaseService.js.map
packages/lib/services/CommandService.d.ts packages/lib/services/CommandService.d.ts
packages/lib/services/CommandService.js packages/lib/services/CommandService.js
packages/lib/services/CommandService.js.map packages/lib/services/CommandService.js.map
packages/lib/services/ExternalEditWatcher.d.ts
packages/lib/services/ExternalEditWatcher.js
packages/lib/services/ExternalEditWatcher.js.map
packages/lib/services/KeymapService.d.ts packages/lib/services/KeymapService.d.ts
packages/lib/services/KeymapService.js packages/lib/services/KeymapService.js
packages/lib/services/KeymapService.js.map packages/lib/services/KeymapService.js.map

View File

@ -21,6 +21,7 @@ import menuCommandNames from './gui/menuCommandNames';
import { LayoutItem } from './gui/ResizableLayout/utils/types'; import { LayoutItem } from './gui/ResizableLayout/utils/types';
import stateToWhenClauseContext from './services/commands/stateToWhenClauseContext'; import stateToWhenClauseContext from './services/commands/stateToWhenClauseContext';
import ResourceService from '@joplin/lib/services/ResourceService'; import ResourceService from '@joplin/lib/services/ResourceService';
import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher';
const { FoldersScreenUtils } = require('@joplin/lib/folders-screen-utils.js'); const { FoldersScreenUtils } = require('@joplin/lib/folders-screen-utils.js');
const MasterKey = require('@joplin/lib/models/MasterKey'); const MasterKey = require('@joplin/lib/models/MasterKey');
@ -31,7 +32,6 @@ const { reg } = require('@joplin/lib/registry.js');
const packageInfo = require('./packageInfo.js'); const packageInfo = require('./packageInfo.js');
const DecryptionWorker = require('@joplin/lib/services/DecryptionWorker'); const DecryptionWorker = require('@joplin/lib/services/DecryptionWorker');
const ClipperServer = require('@joplin/lib/ClipperServer'); const ClipperServer = require('@joplin/lib/ClipperServer');
const ExternalEditWatcher = require('@joplin/lib/services/ExternalEditWatcher');
const { webFrame } = require('electron'); const { webFrame } = require('electron');
const Menu = bridge().Menu; const Menu = bridge().Menu;
const PluginManager = require('@joplin/lib/services/PluginManager'); const PluginManager = require('@joplin/lib/services/PluginManager');
@ -691,7 +691,7 @@ class Application extends BaseApplication {
} }
ExternalEditWatcher.instance().setLogger(reg.logger()); ExternalEditWatcher.instance().setLogger(reg.logger());
ExternalEditWatcher.instance().dispatch = this.store().dispatch; ExternalEditWatcher.instance().initialize(bridge, this.store().dispatch);
ResourceEditWatcher.instance().initialize(reg.logger(), (action: any) => { this.store().dispatch(action); }); ResourceEditWatcher.instance().initialize(reg.logger(), (action: any) => { this.store().dispatch(action); });

View File

@ -1,8 +1,8 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService'; import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale'; import { _ } from '@joplin/lib/locale';
import { stateUtils } from '@joplin/lib/reducer'; import { stateUtils } from '@joplin/lib/reducer';
import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher';
const Note = require('@joplin/lib/models/Note'); const Note = require('@joplin/lib/models/Note');
const ExternalEditWatcher = require('@joplin/lib/services/ExternalEditWatcher');
const bridge = require('electron').remote.require('./bridge').default; const bridge = require('electron').remote.require('./bridge').default;
export const declaration: CommandDeclaration = { export const declaration: CommandDeclaration = {

View File

@ -1,7 +1,7 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService'; import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale'; import { _ } from '@joplin/lib/locale';
import { stateUtils } from '@joplin/lib/reducer'; import { stateUtils } from '@joplin/lib/reducer';
const ExternalEditWatcher = require('@joplin/lib/services/ExternalEditWatcher'); import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher';
export const declaration: CommandDeclaration = { export const declaration: CommandDeclaration = {
name: 'stopExternalEditing', name: 'stopExternalEditing',

View File

@ -29,6 +29,7 @@ import markupLanguageUtils from '@joplin/lib/markupLanguageUtils';
import usePrevious from '../hooks/usePrevious'; import usePrevious from '../hooks/usePrevious';
import Setting from '@joplin/lib/models/Setting'; import Setting from '@joplin/lib/models/Setting';
import stateToWhenClauseContext from '../../services/commands/stateToWhenClauseContext'; import stateToWhenClauseContext from '../../services/commands/stateToWhenClauseContext';
import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher';
const { themeStyle } = require('@joplin/lib/theme'); const { themeStyle } = require('@joplin/lib/theme');
const { substrWithEllipsis } = require('@joplin/lib/string-utils'); const { substrWithEllipsis } = require('@joplin/lib/string-utils');
@ -36,7 +37,6 @@ const NoteSearchBar = require('../NoteSearchBar.min.js');
const { reg } = require('@joplin/lib/registry.js'); const { reg } = require('@joplin/lib/registry.js');
const Note = require('@joplin/lib/models/Note.js'); const Note = require('@joplin/lib/models/Note.js');
const bridge = require('electron').remote.require('./bridge').default; const bridge = require('electron').remote.require('./bridge').default;
const ExternalEditWatcher = require('@joplin/lib/services/ExternalEditWatcher');
const NoteRevisionViewer = require('../NoteRevisionViewer.min'); const NoteRevisionViewer = require('../NoteRevisionViewer.min');
const commands = [ const commands = [

View File

@ -1,49 +1,57 @@
const Logger = require('../Logger').default; import Logger from '../Logger';
import Setting from '../models/Setting';
import shim from '../shim';
import { fileExtension, basename, toSystemSlashes } from '../path-utils';
import time from '../time';
import { NoteEntity } from './database/types';
const Note = require('../models/Note'); const Note = require('../models/Note');
const Setting = require('../models/Setting').default;
const shim = require('../shim').default;
const EventEmitter = require('events'); const EventEmitter = require('events');
const { splitCommandString } = require('../string-utils'); const { splitCommandString } = require('../string-utils');
const { fileExtension, basename } = require('../path-utils');
const spawn = require('child_process').spawn; const spawn = require('child_process').spawn;
const chokidar = require('chokidar'); const chokidar = require('chokidar');
const bridge = require('electron').remote.require('./bridge').default;
const time = require('../time').default;
const { ErrorNotFound } = require('./rest/utils/errors'); const { ErrorNotFound } = require('./rest/utils/errors');
class ExternalEditWatcher { export default class ExternalEditWatcher {
constructor() {
this.logger_ = new Logger();
this.dispatch = () => {};
this.watcher_ = null;
this.eventEmitter_ = new EventEmitter();
this.skipNextChangeEvent_ = {};
this.chokidar_ = chokidar;
}
static instance() { private dispatch: Function;
private bridge_: Function;
private logger_: Logger = new Logger();
private watcher_: any = null;
private eventEmitter_: any = new EventEmitter();
private skipNextChangeEvent_: any = {};
private chokidar_: any = chokidar;
private static instance_: ExternalEditWatcher;
public static instance() {
if (this.instance_) return this.instance_; if (this.instance_) return this.instance_;
this.instance_ = new ExternalEditWatcher(); this.instance_ = new ExternalEditWatcher();
return this.instance_; return this.instance_;
} }
externalApi() { public initialize(bridge: Function, dispatch: Function) {
const loadNote = async (noteId) => { this.bridge_ = bridge;
this.dispatch = dispatch;
}
public externalApi() {
const loadNote = async (noteId: string) => {
const note = await Note.load(noteId); const note = await Note.load(noteId);
if (!note) throw new ErrorNotFound(`No such note: ${noteId}`); if (!note) throw new ErrorNotFound(`No such note: ${noteId}`);
return note; return note;
}; };
return { return {
openAndWatch: async ({ noteId }) => { openAndWatch: async (args: any) => {
const note = await loadNote(noteId); const note = await loadNote(args.noteId);
return this.openAndWatch(note); return this.openAndWatch(note);
}, },
stopWatching: async ({ noteId }) => { stopWatching: async (args: any) => {
return this.stopWatching(noteId); return this.stopWatching(args.noteId);
}, },
noteIsWatched: async ({ noteId }) => { noteIsWatched: async (args: any) => {
const note = await loadNote(noteId); const note = await loadNote(args.noteId);
return this.noteIsWatched(note); return this.noteIsWatched(note);
}, },
}; };
@ -53,15 +61,15 @@ class ExternalEditWatcher {
return Setting.value('profileDir'); return Setting.value('profileDir');
} }
on(eventName, callback) { on(eventName: string, callback: Function) {
return this.eventEmitter_.on(eventName, callback); return this.eventEmitter_.on(eventName, callback);
} }
off(eventName, callback) { off(eventName: string, callback: Function) {
return this.eventEmitter_.removeListener(eventName, callback); return this.eventEmitter_.removeListener(eventName, callback);
} }
setLogger(l) { setLogger(l: Logger) {
this.logger_ = l; this.logger_ = l;
} }
@ -69,12 +77,12 @@ class ExternalEditWatcher {
return this.logger_; return this.logger_;
} }
watch(fileToWatch) { watch(fileToWatch: string) {
if (!this.chokidar_) return; if (!this.chokidar_) return;
if (!this.watcher_) { if (!this.watcher_) {
this.watcher_ = this.chokidar_.watch(fileToWatch); this.watcher_ = this.chokidar_.watch(fileToWatch);
this.watcher_.on('all', async (event, path) => { this.watcher_.on('all', async (event: string, path: string) => {
// For now, to investigate the lost content issue when using an external editor, // For now, to investigate the lost content issue when using an external editor,
// make all the debug statement to info() so that it goes to the log file. // make all the debug statement to info() so that it goes to the log file.
// Those that were previous debug() statements are marked as "was_debug" // Those that were previous debug() statements are marked as "was_debug"
@ -140,7 +148,8 @@ class ExternalEditWatcher {
}); });
// Hack to support external watcher on some linux applications (gedit, gvim, etc) // Hack to support external watcher on some linux applications (gedit, gvim, etc)
// taken from https://github.com/paulmillr/chokidar/issues/591 // taken from https://github.com/paulmillr/chokidar/issues/591
this.watcher_.on('raw', async (event, path, { watchedPath }) => { this.watcher_.on('raw', async (event: string, _path: string, options: any) => {
const watchedPath: string = options.watchedPath;
/* was_debug */ this.logger().info(`ExternalEditWatcher: Raw event: ${event}: ${watchedPath}`); /* was_debug */ this.logger().info(`ExternalEditWatcher: Raw event: ${event}: ${watchedPath}`);
if (event === 'rename') { if (event === 'rename') {
this.watcher_.unwatch(watchedPath); this.watcher_.unwatch(watchedPath);
@ -154,12 +163,12 @@ class ExternalEditWatcher {
return this.watcher_; return this.watcher_;
} }
noteIdToFilePath_(noteId) { noteIdToFilePath_(noteId: string) {
return `${this.tempDir()}/edit-${noteId}.md`; return `${this.tempDir()}/edit-${noteId}.md`;
} }
noteFilePathToId_(path) { noteFilePathToId_(path: string) {
let id = path.split('/'); let id: any = toSystemSlashes(path, 'linux').split('/');
if (!id.length) throw new Error(`Invalid path: ${path}`); if (!id.length) throw new Error(`Invalid path: ${path}`);
id = id[id.length - 1]; id = id[id.length - 1];
id = id.split('.'); id = id.split('.');
@ -186,7 +195,7 @@ class ExternalEditWatcher {
return output; return output;
} }
noteIsWatched(note) { noteIsWatched(note: NoteEntity) {
if (!this.watcher_) return false; if (!this.watcher_) return false;
const noteFilename = basename(this.noteIdToFilePath_(note.id)); const noteFilename = basename(this.noteIdToFilePath_(note.id));
@ -219,7 +228,7 @@ class ExternalEditWatcher {
}; };
} }
async spawnCommand(path, args, options) { async spawnCommand(path: string, args: string[], options: any) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// App bundles need to be opened using the `open` command. // App bundles need to be opened using the `open` command.
// Additional args can be specified after --args, and the // Additional args can be specified after --args, and the
@ -238,7 +247,7 @@ class ExternalEditWatcher {
path = 'open'; path = 'open';
} }
const wrapError = error => { const wrapError = (error: any) => {
if (!error) return error; if (!error) return error;
const msg = error.message ? [error.message] : []; const msg = error.message ? [error.message] : [];
msg.push(`Command was: "${path}" ${args.join(' ')}`); msg.push(`Command was: "${path}" ${args.join(' ')}`);
@ -257,7 +266,7 @@ class ExternalEditWatcher {
} }
}, 100); }, 100);
subProcess.on('error', error => { subProcess.on('error', (error: any) => {
shim.clearInterval(iid); shim.clearInterval(iid);
reject(wrapError(error)); reject(wrapError(error));
}); });
@ -267,18 +276,19 @@ class ExternalEditWatcher {
}); });
} }
async openAndWatch(note) { async openAndWatch(note: NoteEntity) {
if (!note || !note.id) { if (!note || !note.id) {
this.logger().warn('ExternalEditWatcher: Cannot open note: ', note); this.logger().warn('ExternalEditWatcher: Cannot open note: ', note);
return; return;
} }
const filePath = await this.writeNoteToFile_(note); const filePath = await this.writeNoteToFile_(note);
if (!filePath) return;
this.watch(filePath); this.watch(filePath);
const cmd = this.textEditorCommand(); const cmd = this.textEditorCommand();
if (!cmd) { if (!cmd) {
bridge().openExternal(`file://${filePath}`); this.bridge_().openExternal(`file://${filePath}`);
} else { } else {
cmd.args.push(filePath); cmd.args.push(filePath);
await this.spawnCommand(cmd.path, cmd.args, { detached: true }); await this.spawnCommand(cmd.path, cmd.args, { detached: true });
@ -292,7 +302,7 @@ class ExternalEditWatcher {
this.logger().info(`ExternalEditWatcher: Started watching ${filePath}`); this.logger().info(`ExternalEditWatcher: Started watching ${filePath}`);
} }
async stopWatching(noteId) { async stopWatching(noteId: string) {
if (!noteId) return; if (!noteId) return;
const filePath = this.noteIdToFilePath_(noteId); const filePath = this.noteIdToFilePath_(noteId);
@ -319,7 +329,7 @@ class ExternalEditWatcher {
}); });
} }
async updateNoteFile(note) { async updateNoteFile(note: NoteEntity) {
if (!this.noteIsWatched(note)) return; if (!this.noteIsWatched(note)) return;
if (!note || !note.id) { if (!note || !note.id) {
@ -336,10 +346,10 @@ class ExternalEditWatcher {
this.writeNoteToFile_(note); this.writeNoteToFile_(note);
} }
async writeNoteToFile_(note) { async writeNoteToFile_(note: NoteEntity) {
if (!note || !note.id) { if (!note || !note.id) {
this.logger().warn('ExternalEditWatcher: Cannot update note file: ', note); this.logger().warn('ExternalEditWatcher: Cannot update note file: ', note);
return; return null;
} }
const filePath = this.noteIdToFilePath_(note.id); const filePath = this.noteIdToFilePath_(note.id);
@ -348,5 +358,3 @@ class ExternalEditWatcher {
return filePath; return filePath;
} }
} }
module.exports = ExternalEditWatcher;

View File

@ -1,5 +1,5 @@
import ResourceEditWatcher from '../ResourceEditWatcher/index'; import ResourceEditWatcher from '../ResourceEditWatcher/index';
const ExternalEditWatcher = require('../ExternalEditWatcher'); import ExternalEditWatcher from '../ExternalEditWatcher';
export default { export default {

View File

@ -1,4 +1,4 @@
const MdToHtml = require('./MdToHtml').default; import MdToHtml from './MdToHtml';
const HtmlToHtml = require('./HtmlToHtml'); const HtmlToHtml = require('./HtmlToHtml');
const htmlUtils = require('./htmlUtils'); const htmlUtils = require('./htmlUtils');
const MarkdownIt = require('markdown-it'); const MarkdownIt = require('markdown-it');