119
};
120
}
122
>
/**
123
>
* Build a {@link AGENT_HOST_SCHEME} URI for a given connection authority
124
>
* and remote path. Assumes the remote path is a `file://` resource.
125
>
*/
126
>
export function agentHostUri(authority: string, path: string): URI {
127
return toAgentHostUri(URI.file(path), authority);
128
}
130
>
/**
131
>
* Extract the remote filesystem path from a {@link AGENT_HOST_SCHEME} URI.
132
>
*/
133
>
export function agentHostRemotePath(uri: URI): string {
134
return fromAgentHostUri(uri).path;
135
}
137
>
// ---- Abstract base ----------------------------------------------------------
138
>
139
>
interface IAuthorityEntry {
140
>
/**
141
>
* All currently-registered connections for this authority, oldest
142
>
* first. The active connection is the last entry (most recent
143
>
* registration wins). Older registrations are kept so that if a
144
>
* caller registers `A`, then `B`, then disposes `B`, we transparently
145
>
* fall back to `A` instead of entering a grace window.
146
>
*
147
>
* Empty while the entry is inside the grace window.
148
>
*/
149
>
connections: IRemoteFilesystemConnection[];
150
>
/**
151
>
* Pending eviction timer; armed while {@link connections} is empty,
152
>
* cleared on re-registration or eviction.
153
>
*/
154
>
readonly expiry: MutableDisposable<IDisposable>;
155
>
}
156
>
157
>
/**
158
>
* {@link IFileSystemProvider} that proxies filesystem operations
159
>
* through a {@link IRemoteFilesystemConnection}.
160
>
*
161
>
* URIs encode the original scheme and authority in the path so any remote
162
>
* resource can be represented. Subclasses provide the URI decode function
163
>
* and scheme-specific helpers.
164
>
*
165
>
* Individual connections are identified by the URI's authority component.
166
>
*/
167
>
export abstract class AHPFileSystemProvider extends Disposable implements IFileSystemProvider, IFileSystemProviderWithFileRealpathCapability {
168
>
169
>
readonly capabilities =
170
>
FileSystemProviderCapabilities.PathCaseSensitive |
171
>
FileSystemProviderCapabilities.FileReadWrite |
172
>
FileSystemProviderCapabilities.FileFolderCopy |
173
>
FileSystemProviderCapabilities.FileRealpath;
174
>
175
>
private readonly _onDidChangeCapabilities = this._register(new Emitter<void>());
176
>
readonly onDidChangeCapabilities = this._onDidChangeCapabilities.event;
177
>
178
>
private readonly _onDidChangeFile = this._register(new Emitter<readonly IFileChange[]>());
179
>
readonly onDidChangeFile = this._onDidChangeFile.event;
180
>
private readonly _onDidWatchError = this._register(new Emitter<string>());
181
>
readonly onDidWatchError = this._onDidWatchError.event;
182
>
183
>
/**
184
>
* Per-authority registration slot. We keep the slot alive for a brief
185
>
* grace period after the last registration is disposed, so an
186
>
* operation issued during a reconnection window can wait for the
187
>
* replacement registration instead of failing immediately.
188
>
*/
189
>
private readonly _authorities = new Map<string, IAuthorityEntry>();
190
>
191
>
/**
192
>
* Fires the authority whose active connection has changed: added,
193
>
* replaced, fallen back to an older registration, entered the grace
194
>
* window (no active connection), or evicted. Long-lived consumers
195
>
* (e.g. {@link watch}) subscribe here so they continue to receive
196
>
* notifications across full entry eviction + later re-creation —
197
>
* something a per-entry emitter cannot offer.
198
>
*/
199
>
private readonly _onDidChangeConnection = this._register(new Emitter<string>());
200
>
201
>
/**
202
>
* Grace period during which {@link _getConnection} will await a new
203
>
* registration after the previous one is disposed. Covers the window
204
>
* where a transport is briefly torn down and re-registered (e.g. an
205
>
* agent-host client reconnect that races a plugin sync). 5s matches
206
>
* the typical reconnect timeout. Consumers should still implement
207
>
* logical retries for longer reconnection latencies, but this is a
208
>
* low level, best-effort mechanism.
209
>
*
210
>
* Tests can override this via the constructor parameter.
211
>
*/
212
>
private static readonly _DEFAULT_CONNECTION_GRACE_MS = 5000;
213
>
214
>
constructor(
215
private readonly _connectionGraceMs: number = AHPFileSystemProvider._DEFAULT_CONNECTION_GRACE_MS,
216
) {
217
super();
218
}
220
>
/**
221
>
* Register a mapping from a URI authority to a connection.
222
>
* Returns a disposable that unregisters the mapping. Multiple
223
>
* concurrent registrations for the same authority are supported;
224
>
* the most recent registration wins, and disposing it falls back to
225
>
* the previous one (if any). After the *last* registration is
226
>
* disposed the entry is held open for {@link _connectionGraceMs} so
227
>
* that a reconnect can replace it without orphaning in-flight
228
>
* operations.
229
>
*/
230
>
registerAuthority(authority: string, connection: IRemoteFilesystemConnection): IDisposable {
231
let entry = this._authorities.get(authority);
232
if (!entry) {