Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/files/common/diskFileSystemProvider.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { insert } from '../../../base/common/arrays.js';
7
import { ThrottledDelayer } from '../../../base/common/async.js';
8
import { onUnexpectedError } from '../../../base/common/errors.js';
9
import { Emitter } from '../../../base/common/event.js';
10
import { removeTrailingPathSeparator } from '../../../base/common/extpath.js';
11
import { Disposable, IDisposable, toDisposable } from '../../../base/common/lifecycle.js';
12
import { normalize } from '../../../base/common/path.js';
13
import { URI } from '../../../base/common/uri.js';
14
import { IFileChange, IFileSystemProvider, IWatchOptions } from './files.js';
15
import { AbstractNonRecursiveWatcherClient, AbstractUniversalWatcherClient, ILogMessage, INonRecursiveWatchRequest, IRecursiveWatcherOptions, isRecursiveWatchRequest, IUniversalWatchRequest, reviveFileChanges } from './watcher.js';
16
import { ILogService, LogLevel } from '../../log/common/log.js';
17
18
export interface IDiskFileSystemProviderOptions {
19
watcher?: {
20
21
/**
22
* Extra options for the recursive file watching.
23
*/
24
recursive?: IRecursiveWatcherOptions;
25
26
/**
27
* Forces all file watch requests to run through a
28
* single universal file watcher, both recursive
29
* and non-recursively.
30
*
31
* Enabling this option might cause some overhead,
32
* specifically the universal file watcher will run
33
* in a separate process given its complexity. Only
34
* enable it when you understand the consequences.
35
*/
36
forceUniversal?: boolean;
37
};
38
}
39
40
export abstract class AbstractDiskFileSystemProvider extends Disposable implements
41
Pick<IFileSystemProvider, 'watch'>,
42
Pick<IFileSystemProvider, 'onDidChangeFile'>,
43
Pick<IFileSystemProvider, 'onDidWatchError'> {
44
45
constructor(
46
protected readonly logService: ILogService,
47
private readonly options?: IDiskFileSystemProviderOptions
48
) {
49
super();
50
}
51
52
protected readonly _onDidChangeFile = this._register(new Emitter<readonly IFileChange[]>());
53
readonly onDidChangeFile = this._onDidChangeFile.event;
54
55
protected readonly _onDidWatchError = this._register(new Emitter<string>());
56
readonly onDidWatchError = this._onDidWatchError.event;
57
58
watch(resource: URI, opts: IWatchOptions): IDisposable {
59
if (opts.recursive || this.options?.watcher?.forceUniversal) {
60
return this.watchUniversal(resource, opts);
61
}
62
63
return this.watchNonRecursive(resource, opts);
64
}
65
66
private getRefreshWatchersDelay(count: number): number {
67
if (count > 200) {
68
// If there are many requests to refresh, start to throttle
69
// the refresh to reduce pressure. We see potentially thousands
70
// of requests coming in on startup repeatedly so we take it easy.
71
return 500;
72
}
73
74
// By default, use a short delay to keep watchers updating fast but still
75
// with a delay so that we can efficiently deduplicate requests or reuse
76
// existing watchers.
77
return 0;
78
}
79
80
//#region File Watching (universal)
81
82
private universalWatcher: AbstractUniversalWatcherClient | undefined;
83
84
private readonly universalWatchRequests: IUniversalWatchRequest[] = [];
85
private readonly universalWatchRequestDelayer = this._register(new ThrottledDelayer<void>(this.getRefreshWatchersDelay(this.universalWatchRequests.length)));
86
87
private watchUniversal(resource: URI, opts: IWatchOptions): IDisposable {
88
const request = this.toWatchRequest(resource, opts);
89
const remove = insert(this.universalWatchRequests, request);
90
91
// Trigger update
92
this.refreshUniversalWatchers();
93
94
return toDisposable(() => {
95
96
// Remove from list of paths to watch universally
97
remove();
98
99
// Trigger update
100
this.refreshUniversalWatchers();
101
});
102
}
103
104
private toWatchRequest(resource: URI, opts: IWatchOptions): IUniversalWatchRequest {
105
const request: IUniversalWatchRequest = {
106
path: this.toWatchPath(resource),
107
excludes: opts.excludes,
108
includes: opts.includes,
109
recursive: opts.recursive,
110
filter: opts.filter,
111
correlationId: opts.correlationId
112
};
113
114
if (isRecursiveWatchRequest(request)) {
115
116
// Adjust for polling
117
const usePolling = this.options?.watcher?.recursive?.usePolling;
118
if (usePolling === true) {
119
request.pollingInterval = this.options?.watcher?.recursive?.pollingInterval ?? 5000;
120
} else if (Array.isArray(usePolling)) {
121
if (usePolling.includes(request.path)) {
122
request.pollingInterval = this.options?.watcher?.recursive?.pollingInterval ?? 5000;
123
}
124
}
125
}
126
127
return request;
128
}
129
130
private refreshUniversalWatchers(): void {
131
this.universalWatchRequestDelayer.trigger(() => {
132
return this.doRefreshUniversalWatchers();
133
}, this.getRefreshWatchersDelay(this.universalWatchRequests.length)).catch(error => onUnexpectedError(error));
134
}
135
136
private doRefreshUniversalWatchers(): Promise<void> {
137
138
// Create watcher if this is the first time
139
if (!this.universalWatcher) {
140
this.universalWatcher = this._register(this.createUniversalWatcher(
141
changes => this._onDidChangeFile.fire(reviveFileChanges(changes)),
142
msg => this.onWatcherLogMessage(msg),
143
this.logService.getLevel() === LogLevel.Trace
144
));
145
146
// Apply log levels dynamically
147
this._register(this.logService.onDidChangeLogLevel(() => {
148
this.universalWatcher?.setVerboseLogging(this.logService.getLevel() === LogLevel.Trace);
149
}));
150
}
151
152
// Ask to watch the provided paths
153
return this.universalWatcher.watch(this.universalWatchRequests);
154
}
155
156
protected abstract createUniversalWatcher(
157
onChange: (changes: IFileChange[]) => void,
158
onLogMessage: (msg: ILogMessage) => void,
159
verboseLogging: boolean
160
): AbstractUniversalWatcherClient;
161
162
//#endregion
163
164
//#region File Watching (non-recursive)
165
166
private nonRecursiveWatcher: AbstractNonRecursiveWatcherClient | undefined;
167
168
private readonly nonRecursiveWatchRequests: INonRecursiveWatchRequest[] = [];
169
private readonly nonRecursiveWatchRequestDelayer = this._register(new ThrottledDelayer<void>(this.getRefreshWatchersDelay(this.nonRecursiveWatchRequests.length)));
170
171
private watchNonRecursive(resource: URI, opts: IWatchOptions): IDisposable {
172
173
// Add to list of paths to watch non-recursively
174
const request: INonRecursiveWatchRequest = {
175
path: this.toWatchPath(resource),
176
excludes: opts.excludes,
177
includes: opts.includes,
178
recursive: false,
179
filter: opts.filter,
180
correlationId: opts.correlationId
181
};
182
const remove = insert(this.nonRecursiveWatchRequests, request);
183
184
// Trigger update
185
this.refreshNonRecursiveWatchers();
186
187
return toDisposable(() => {
188
189
// Remove from list of paths to watch non-recursively
190
remove();
191
192
// Trigger update
193
this.refreshNonRecursiveWatchers();
194
});
195
}
196
197
private refreshNonRecursiveWatchers(): void {
198
this.nonRecursiveWatchRequestDelayer.trigger(() => {
199
return this.doRefreshNonRecursiveWatchers();
200
}, this.getRefreshWatchersDelay(this.nonRecursiveWatchRequests.length)).catch(error => onUnexpectedError(error));
201
}
202
203
private doRefreshNonRecursiveWatchers(): Promise<void> {
204
205
// Create watcher if this is the first time
206
if (!this.nonRecursiveWatcher) {
207
this.nonRecursiveWatcher = this._register(this.createNonRecursiveWatcher(
208
changes => this._onDidChangeFile.fire(reviveFileChanges(changes)),
209
msg => this.onWatcherLogMessage(msg),
210
this.logService.getLevel() === LogLevel.Trace
211
));
212
213
// Apply log levels dynamically
214
this._register(this.logService.onDidChangeLogLevel(() => {
215
this.nonRecursiveWatcher?.setVerboseLogging(this.logService.getLevel() === LogLevel.Trace);
216
}));
217
}
218
219
// Ask to watch the provided paths
220
return this.nonRecursiveWatcher.watch(this.nonRecursiveWatchRequests);
221
}
222
223
protected abstract createNonRecursiveWatcher(
224
onChange: (changes: IFileChange[]) => void,
225
onLogMessage: (msg: ILogMessage) => void,
226
verboseLogging: boolean
227
): AbstractNonRecursiveWatcherClient;
228
229
//#endregion
230
231
private onWatcherLogMessage(msg: ILogMessage): void {
232
if (msg.type === 'error') {
233
this._onDidWatchError.fire(msg.message);
234
}
235
236
this.logWatcherMessage(msg);
237
}
238
239
protected logWatcherMessage(msg: ILogMessage): void {
240
this.logService[msg.type](msg.message);
241
}
242
243
protected toFilePath(resource: URI): string {
244
return normalize(resource.fsPath);
245
}
246
247
private toWatchPath(resource: URI): string {
248
const filePath = this.toFilePath(resource);
249
250
// Ensure to have any trailing path separators removed, otherwise
251
// we may believe the path is not "real" and will convert every
252
// event back to this form, which is not warranted.
253
// See also https://github.com/microsoft/vscode/issues/210517
254
return removeTrailingPathSeparator(filePath);
255
}
256
}
257
258