-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement file system watcher plugin API
Signed-off-by: Mykola Morhun <mmorhun@redhat.com>
- Loading branch information
Showing
7 changed files
with
331 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
packages/plugin-ext/src/main/browser/in-plugin-filesystem-watcher-manager.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
/******************************************************************************** | ||
* Copyright (C) 2018 Red Hat, Inc. and others. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License v. 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0. | ||
* | ||
* This Source Code may also be made available under the following Secondary | ||
* Licenses when the conditions for such availability set forth in the Eclipse | ||
* Public License v. 2.0 are satisfied: GNU General Public License, version 2 | ||
* with the GNU Classpath Exception which is available at | ||
* https://www.gnu.org/software/classpath/license.html. | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 | ||
********************************************************************************/ | ||
|
||
import { interfaces } from 'inversify'; | ||
import { FileSystemWatcher, FileChangeEvent, FileChangeType, FileChange } from '@theia/filesystem/lib/browser/filesystem-watcher'; | ||
import { WorkspaceExt } from '../../api/plugin-api'; | ||
import { FileWatcherSubscriberOptions } from '../../api/model'; | ||
import { parse, ParsedPattern, IRelativePattern } from '../../common/glob'; | ||
import { RelativePattern } from '../../plugin/types-impl'; | ||
import { theiaUritoUriComponents } from '../../common/uri-components'; | ||
|
||
/** | ||
* Actual implementation of file watching system of the plugin API. | ||
* Holds subscriptions (with its settings) from within plugins | ||
* and process all file system events in all workspace roots whether they matches to any subscription. | ||
* Only if event matches it will be sent into plugin side to specific subscriber. | ||
*/ | ||
export class InPluginFileSystemWatcherManager { | ||
|
||
private proxy: WorkspaceExt; | ||
private subscribers: Map<string, FileWatcherSubscriber>; | ||
private nextSubscriberId: number; | ||
|
||
constructor(proxy: WorkspaceExt, container: interfaces.Container) { | ||
this.proxy = proxy; | ||
this.subscribers = new Map<string, FileWatcherSubscriber>(); | ||
this.nextSubscriberId = 0; | ||
|
||
const fileSystemWatcher = container.get(FileSystemWatcher); | ||
fileSystemWatcher.onFilesChanged(event => this.onFilesChangedEventHandler(event)); | ||
} | ||
|
||
// Filter file system changes according to subscribers settings here to avoid unneeded traffic. | ||
onFilesChangedEventHandler(changes: FileChangeEvent): void { | ||
for (const change of changes) { | ||
switch (change.type) { | ||
case FileChangeType.UPDATED: | ||
for (const [id, subscriber] of this.subscribers) { | ||
if (!subscriber.ignoreChangeEvents && this.uriMatches(subscriber, change)) { | ||
this.proxy.$fileChanged({ subscriberId: id, uri: theiaUritoUriComponents(change.uri), type: 'updated' }); | ||
} | ||
} | ||
break; | ||
case FileChangeType.ADDED: | ||
for (const [id, subscriber] of this.subscribers) { | ||
if (!subscriber.ignoreCreateEvents && this.uriMatches(subscriber, change)) { | ||
this.proxy.$fileChanged({ subscriberId: id, uri: theiaUritoUriComponents(change.uri), type: 'created' }); | ||
} | ||
} | ||
break; | ||
case FileChangeType.DELETED: | ||
for (const [id, subscriber] of this.subscribers) { | ||
if (!subscriber.ignoreDeleteEvents && this.uriMatches(subscriber, change)) { | ||
this.proxy.$fileChanged({ subscriberId: id, uri: theiaUritoUriComponents(change.uri), type: 'deleted' }); | ||
} | ||
} | ||
break; | ||
} | ||
} | ||
} | ||
|
||
private uriMatches(subscriber: FileWatcherSubscriber, fileChange: FileChange): boolean { | ||
return subscriber.mather(fileChange.uri.path.toString()); | ||
} | ||
|
||
/** | ||
* Registers new file system events subscriber. | ||
* | ||
* @param options subscription options | ||
* @returns generated subscriber id | ||
*/ | ||
registerFileWatchSubscription(options: FileWatcherSubscriberOptions): string { | ||
const subscriberId = this.getNextId(); | ||
|
||
let globPatternMatcher: ParsedPattern; | ||
if (typeof options.globPattern === 'string') { | ||
globPatternMatcher = parse(options.globPattern); | ||
} else { | ||
const relativePattern: IRelativePattern = new RelativePattern(options.globPattern.base, options.globPattern.pattern); | ||
globPatternMatcher = parse(relativePattern); | ||
} | ||
|
||
const subscriber: FileWatcherSubscriber = { | ||
id: subscriberId, | ||
mather: globPatternMatcher, | ||
ignoreCreateEvents: options.ignoreCreateEvents === true, | ||
ignoreChangeEvents: options.ignoreChangeEvents === true, | ||
ignoreDeleteEvents: options.ignoreDeleteEvents === true | ||
}; | ||
this.subscribers.set(subscriberId, subscriber); | ||
|
||
return subscriberId; | ||
} | ||
|
||
unregisterFileWatchSubscription(subscriptionId: string): void { | ||
this.subscribers.delete(subscriptionId); | ||
} | ||
|
||
private getNextId(): string { | ||
return 'ipfsw' + this.nextSubscriberId++; | ||
} | ||
|
||
} | ||
|
||
interface FileWatcherSubscriber { | ||
id: string; | ||
mather: ParsedPattern; | ||
ignoreCreateEvents: boolean; | ||
ignoreChangeEvents: boolean; | ||
ignoreDeleteEvents: boolean; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
150 changes: 150 additions & 0 deletions
150
packages/plugin-ext/src/plugin/in-plugin-filesystem-watcher-proxy.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
/******************************************************************************** | ||
* Copyright (C) 2018 Red Hat, Inc. and others. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License v. 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0. | ||
* | ||
* This Source Code may also be made available under the following Secondary | ||
* Licenses when the conditions for such availability set forth in the Eclipse | ||
* Public License v. 2.0 are satisfied: GNU General Public License, version 2 | ||
* with the GNU Classpath Exception which is available at | ||
* https://www.gnu.org/software/classpath/license.html. | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 | ||
********************************************************************************/ | ||
|
||
import * as theia from '@theia/plugin'; | ||
import { Emitter, Event } from '@theia/core/lib/common/event'; | ||
import { WorkspaceMain } from '../api/plugin-api'; | ||
import { FileWatcherSubscriberOptions, FileChangeEventType } from '../api/model'; | ||
import URI from 'vscode-uri'; | ||
|
||
/** | ||
* This class is responsible for file watchers subscription registering and file system events proxying. | ||
* It contains no logic, only communicates with main side to add / remove subscription and | ||
* delivers file system events to corresponding subscribers. | ||
*/ | ||
export class InPluginFileSystemWatcherProxy { | ||
|
||
private proxy: WorkspaceMain; | ||
private subscribers: Map<string, Emitter<FileSystemEvent>>; | ||
|
||
constructor(proxy: WorkspaceMain) { | ||
this.proxy = proxy; | ||
this.subscribers = new Map<string, Emitter<FileSystemEvent>>(); | ||
} | ||
|
||
createFileSystemWatcher( | ||
globPattern: theia.GlobPattern, | ||
ignoreCreateEvents?: boolean, | ||
ignoreChangeEvents?: boolean, | ||
ignoreDeleteEvents?: boolean): theia.FileSystemWatcher { | ||
|
||
const perSubscriberEventEmitter = new Emitter<FileSystemEvent>(); | ||
const subscriberPrivateData: SubscriberData = { | ||
event: perSubscriberEventEmitter.event | ||
}; | ||
const fileWatcherSubscriberOptions: FileWatcherSubscriberOptions = { globPattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents }; | ||
// ids are generated by server side to be able handle several subscribers. | ||
this.proxy.$registerFileSystemWatcher(fileWatcherSubscriberOptions).then((id: string) => { | ||
// this is safe, because actual subscription happens on server side and response is | ||
// sent right after actual subscription, so no events are possible in between. | ||
this.subscribers.set(id, perSubscriberEventEmitter); | ||
subscriberPrivateData.unsubscribe = () => this.proxy.$unregisterFileSystemWatcher(id); | ||
}); | ||
return new FileSystemWatcher(subscriberPrivateData, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents); | ||
} | ||
|
||
onFileSystemEvent(id: string, uri: URI, type: FileChangeEventType) { | ||
const perSubscriberEventEmitter: Emitter<FileSystemEvent> | undefined = this.subscribers.get(id); | ||
if (perSubscriberEventEmitter) { | ||
perSubscriberEventEmitter.fire({ uri, type }); | ||
} else { | ||
// shouldn't happen | ||
// if it happens then a message was lost, unsubscribe to make state consistent | ||
this.proxy.$unregisterFileSystemWatcher(id); | ||
} | ||
} | ||
} | ||
|
||
class FileSystemWatcher implements theia.FileSystemWatcher { | ||
private subscriberData: SubscriberData; | ||
|
||
private onDidCreateEmitter: Emitter<theia.Uri>; | ||
private onDidChangeEmitter: Emitter<theia.Uri>; | ||
private onDidDeleteEmitter: Emitter<theia.Uri>; | ||
|
||
constructor( | ||
subscriberData: SubscriberData, | ||
private isIgnoreCreateEvents: boolean = false, | ||
private isIgnoreChangeEvents: boolean = false, | ||
private isIgnoreDeleteEvents: boolean = false | ||
) { | ||
this.onDidCreateEmitter = new Emitter<theia.Uri>(); | ||
this.onDidChangeEmitter = new Emitter<theia.Uri>(); | ||
this.onDidDeleteEmitter = new Emitter<theia.Uri>(); | ||
|
||
this.subscriberData = subscriberData; | ||
subscriberData.event((event: FileSystemEvent) => { | ||
// Here ignore event flags are not analyzed because all the logic is | ||
// moved to server side to avoid unneded data transfer via network. | ||
// The flags are present just to be read only accesible for user. | ||
switch (event.type) { | ||
case 'updated': | ||
this.onDidChangeEmitter.fire(event.uri); | ||
break; | ||
case 'created': | ||
this.onDidCreateEmitter.fire(event.uri); | ||
break; | ||
case 'deleted': | ||
this.onDidDeleteEmitter.fire(event.uri); | ||
break; | ||
} | ||
}); | ||
} | ||
|
||
get ignoreCreateEvents(): boolean { | ||
return this.isIgnoreCreateEvents; | ||
} | ||
|
||
get ignoreChangeEvents(): boolean { | ||
return this.isIgnoreChangeEvents; | ||
} | ||
|
||
get ignoreDeleteEvents(): boolean { | ||
return this.isIgnoreDeleteEvents; | ||
} | ||
|
||
get onDidCreate(): Event<theia.Uri> { | ||
return this.onDidCreateEmitter.event; | ||
} | ||
|
||
get onDidChange(): Event<theia.Uri> { | ||
return this.onDidChangeEmitter.event; | ||
} | ||
|
||
get onDidDelete(): Event<theia.Uri> { | ||
return this.onDidDeleteEmitter.event; | ||
} | ||
|
||
dispose(): void { | ||
this.onDidCreateEmitter.dispose(); | ||
this.onDidChangeEmitter.dispose(); | ||
this.onDidDeleteEmitter.dispose(); | ||
if (this.subscriberData.unsubscribe) { | ||
this.subscriberData.unsubscribe(); | ||
} | ||
} | ||
|
||
} | ||
|
||
interface FileSystemEvent { | ||
uri: URI, | ||
type: FileChangeEventType | ||
} | ||
|
||
interface SubscriberData { | ||
event: Event<FileSystemEvent> | ||
unsubscribe?: () => void; | ||
} |
Oops, something went wrong.