Skip to content

Commit 94bb539

Browse files
author
Benjamin Pasero
committed
hot exit - convert to async/await
1 parent 1b12c1e commit 94bb539

2 files changed

Lines changed: 232 additions & 258 deletions

File tree

src/vs/workbench/services/backup/node/backupFileService.ts

Lines changed: 121 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,18 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import * as path from 'vs/base/common/path';
7-
import * as crypto from 'crypto';
8-
import * as pfs from 'vs/base/node/pfs';
9-
import { URI as Uri } from 'vs/base/common/uri';
6+
import { join } from 'vs/base/common/path';
7+
import { createHash } from 'crypto';
8+
import { readdir, readDirsInDir, rimraf, RimRafMode } from 'vs/base/node/pfs';
9+
import { URI } from 'vs/base/common/uri';
10+
import { coalesce } from 'vs/base/common/arrays';
1011
import { ResourceQueue } from 'vs/base/common/async';
1112
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
1213
import { IFileService } from 'vs/platform/files/common/files';
1314
import { readToMatchingString } from 'vs/base/node/stream';
1415
import { ITextBufferFactory, ITextSnapshot } from 'vs/editor/common/model';
1516
import { createTextBufferFactoryFromStream, createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel';
16-
import { keys } from 'vs/base/common/map';
17+
import { keys, ResourceMap } from 'vs/base/common/map';
1718
import { Schemas } from 'vs/base/common/network';
1819
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
1920
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
@@ -23,47 +24,47 @@ import { TextSnapshotReadable } from 'vs/workbench/services/textfile/common/text
2324
export interface IBackupFilesModel {
2425
resolve(backupRoot: string): Promise<IBackupFilesModel>;
2526

26-
add(resource: Uri, versionId?: number): void;
27-
has(resource: Uri, versionId?: number): boolean;
28-
get(): Uri[];
29-
remove(resource: Uri): void;
27+
add(resource: URI, versionId?: number): void;
28+
has(resource: URI, versionId?: number): boolean;
29+
get(): URI[];
30+
remove(resource: URI): void;
3031
count(): number;
3132
clear(): void;
3233
}
3334

3435
export class BackupFilesModel implements IBackupFilesModel {
35-
private cache: { [resource: string]: number /* version ID */ } = Object.create(null);
36+
private cache: ResourceMap<number /* version id */> = new ResourceMap();
3637

37-
resolve(backupRoot: string): Promise<IBackupFilesModel> {
38-
return pfs.readDirsInDir(backupRoot).then(backupSchemas => {
38+
async resolve(backupRoot: string): Promise<IBackupFilesModel> {
39+
try {
40+
const backupSchemas = await readDirsInDir(backupRoot);
3941

40-
// For all supported schemas
41-
return Promise.all(backupSchemas.map(backupSchema => {
42+
await Promise.all(backupSchemas.map(async backupSchema => {
4243

4344
// Read backup directory for backups
44-
const backupSchemaPath = path.join(backupRoot, backupSchema);
45-
return pfs.readdir(backupSchemaPath).then(backupHashes => {
46-
47-
// Remember known backups in our caches
48-
backupHashes.forEach(backupHash => {
49-
const backupResource = Uri.file(path.join(backupSchemaPath, backupHash));
50-
this.add(backupResource);
51-
});
52-
});
45+
const backupSchemaPath = join(backupRoot, backupSchema);
46+
const backupHashes = await readdir(backupSchemaPath);
47+
48+
// Remember known backups in our caches
49+
backupHashes.forEach(backupHash => this.add(URI.file(join(backupSchemaPath, backupHash))));
5350
}));
54-
}).then(() => this, error => this);
51+
} catch (error) {
52+
// ignore any errors
53+
}
54+
55+
return this;
5556
}
5657

57-
add(resource: Uri, versionId = 0): void {
58-
this.cache[resource.toString()] = versionId;
58+
add(resource: URI, versionId = 0): void {
59+
this.cache.set(resource, versionId);
5960
}
6061

6162
count(): number {
62-
return Object.keys(this.cache).length;
63+
return this.cache.size;
6364
}
6465

65-
has(resource: Uri, versionId?: number): boolean {
66-
const cachedVersionId = this.cache[resource.toString()];
66+
has(resource: URI, versionId?: number): boolean {
67+
const cachedVersionId = this.cache.get(resource);
6768
if (typeof cachedVersionId !== 'number') {
6869
return false; // unknown resource
6970
}
@@ -75,16 +76,16 @@ export class BackupFilesModel implements IBackupFilesModel {
7576
return true;
7677
}
7778

78-
get(): Uri[] {
79-
return Object.keys(this.cache).map(k => Uri.parse(k));
79+
get(): URI[] {
80+
return this.cache.keys();
8081
}
8182

82-
remove(resource: Uri): void {
83-
delete this.cache[resource.toString()];
83+
remove(resource: URI): void {
84+
this.cache.delete(resource);
8485
}
8586

8687
clear(): void {
87-
this.cache = Object.create(null);
88+
this.cache.clear();
8889
}
8990
}
9091

@@ -116,31 +117,31 @@ export class BackupFileService implements IBackupFileService {
116117
return this.impl.hasBackups();
117118
}
118119

119-
loadBackupResource(resource: Uri): Promise<Uri | undefined> {
120+
loadBackupResource(resource: URI): Promise<URI | undefined> {
120121
return this.impl.loadBackupResource(resource);
121122
}
122123

123-
backupResource(resource: Uri, content: ITextSnapshot, versionId?: number): Promise<void> {
124+
backupResource(resource: URI, content: ITextSnapshot, versionId?: number): Promise<void> {
124125
return this.impl.backupResource(resource, content, versionId);
125126
}
126127

127-
discardResourceBackup(resource: Uri): Promise<void> {
128+
discardResourceBackup(resource: URI): Promise<void> {
128129
return this.impl.discardResourceBackup(resource);
129130
}
130131

131132
discardAllWorkspaceBackups(): Promise<void> {
132133
return this.impl.discardAllWorkspaceBackups();
133134
}
134135

135-
getWorkspaceFileBackups(): Promise<Uri[]> {
136+
getWorkspaceFileBackups(): Promise<URI[]> {
136137
return this.impl.getWorkspaceFileBackups();
137138
}
138139

139-
resolveBackupContent(backup: Uri): Promise<ITextBufferFactory> {
140+
resolveBackupContent(backup: URI): Promise<ITextBufferFactory> {
140141
return this.impl.resolveBackupContent(backup);
141142
}
142143

143-
toBackupResource(resource: Uri): Uri {
144+
toBackupResource(resource: URI): URI {
144145
return this.impl.toBackupResource(resource);
145146
}
146147
}
@@ -179,104 +180,110 @@ class BackupFileServiceImpl implements IBackupFileService {
179180
return model.resolve(this.backupWorkspacePath);
180181
}
181182

182-
hasBackups(): Promise<boolean> {
183-
return this.ready.then(model => {
184-
return model.count() > 0;
185-
});
183+
async hasBackups(): Promise<boolean> {
184+
const model = await this.ready;
185+
186+
return model.count() > 0;
186187
}
187188

188-
loadBackupResource(resource: Uri): Promise<Uri | undefined> {
189-
return this.ready.then(model => {
189+
async loadBackupResource(resource: URI): Promise<URI | undefined> {
190+
const model = await this.ready;
190191

191-
// Return directly if we have a known backup with that resource
192-
const backupResource = this.toBackupResource(resource);
193-
if (model.has(backupResource)) {
194-
return backupResource;
195-
}
192+
// Return directly if we have a known backup with that resource
193+
const backupResource = this.toBackupResource(resource);
194+
if (model.has(backupResource)) {
195+
return backupResource;
196+
}
196197

197-
return undefined;
198-
});
198+
return undefined;
199199
}
200200

201-
backupResource(resource: Uri, content: ITextSnapshot, versionId?: number): Promise<void> {
201+
async backupResource(resource: URI, content: ITextSnapshot, versionId?: number): Promise<void> {
202202
if (this.isShuttingDown) {
203203
return Promise.resolve();
204204
}
205205

206-
return this.ready.then(model => {
207-
const backupResource = this.toBackupResource(resource);
208-
if (model.has(backupResource, versionId)) {
209-
return undefined; // return early if backup version id matches requested one
210-
}
206+
const model = await this.ready;
207+
208+
const backupResource = this.toBackupResource(resource);
209+
if (model.has(backupResource, versionId)) {
210+
return undefined; // return early if backup version id matches requested one
211+
}
212+
213+
return this.ioOperationQueues.queueFor(backupResource).queue(async () => {
214+
const preamble = `${resource.toString()}${BackupFileServiceImpl.META_MARKER}`;
211215

212-
return this.ioOperationQueues.queueFor(backupResource).queue(() => {
213-
const preamble = `${resource.toString()}${BackupFileServiceImpl.META_MARKER}`;
216+
// Update content with value
217+
await this.fileService.writeFile(backupResource, new TextSnapshotReadable(content, preamble));
214218

215-
// Update content with value
216-
return this.fileService.writeFile(backupResource, new TextSnapshotReadable(content, preamble)).then(() => model.add(backupResource, versionId));
217-
});
219+
// Update model
220+
model.add(backupResource, versionId);
218221
});
219222
}
220223

221-
discardResourceBackup(resource: Uri): Promise<void> {
222-
return this.ready.then(model => {
223-
const backupResource = this.toBackupResource(resource);
224+
async discardResourceBackup(resource: URI): Promise<void> {
225+
const model = await this.ready;
226+
const backupResource = this.toBackupResource(resource);
227+
228+
return this.ioOperationQueues.queueFor(backupResource).queue(async () => {
229+
await rimraf(backupResource.fsPath, RimRafMode.MOVE);
224230

225-
return this.ioOperationQueues.queueFor(backupResource).queue(() => {
226-
return pfs.rimraf(backupResource.fsPath, pfs.RimRafMode.MOVE).then(() => model.remove(backupResource));
227-
});
231+
model.remove(backupResource);
228232
});
229233
}
230234

231-
discardAllWorkspaceBackups(): Promise<void> {
235+
async discardAllWorkspaceBackups(): Promise<void> {
232236
this.isShuttingDown = true;
233237

234-
return this.ready.then(model => {
235-
return pfs.rimraf(this.backupWorkspacePath, pfs.RimRafMode.MOVE).then(() => model.clear());
236-
});
238+
const model = await this.ready;
239+
240+
await rimraf(this.backupWorkspacePath, RimRafMode.MOVE);
241+
242+
model.clear();
237243
}
238244

239-
getWorkspaceFileBackups(): Promise<Uri[]> {
240-
return this.ready.then(model => {
241-
const readPromises: Promise<Uri>[] = [];
245+
async getWorkspaceFileBackups(): Promise<URI[]> {
246+
const model = await this.ready;
247+
248+
const backups = await Promise.all(model.get().map(async fileBackup => {
249+
const backup = await readToMatchingString(fileBackup.fsPath, BackupFileServiceImpl.META_MARKER, 2000, 10000);
250+
if (!backup) {
251+
return undefined;
252+
}
242253

243-
model.get().forEach(fileBackup => {
244-
readPromises.push(
245-
readToMatchingString(fileBackup.fsPath, BackupFileServiceImpl.META_MARKER, 2000, 10000).then(Uri.parse)
246-
);
247-
});
254+
return URI.parse(backup);
255+
}));
248256

249-
return Promise.all(readPromises);
250-
});
257+
return coalesce(backups);
251258
}
252259

253-
resolveBackupContent(backup: Uri): Promise<ITextBufferFactory> {
254-
return this.fileService.readFileStream(backup).then(content => {
260+
async resolveBackupContent(backup: URI): Promise<ITextBufferFactory> {
261+
const content = await this.fileService.readFileStream(backup);
255262

256-
// Add a filter method to filter out everything until the meta marker
257-
let metaFound = false;
258-
const metaPreambleFilter = (chunk: VSBuffer) => {
259-
const chunkString = chunk.toString();
263+
// Add a filter method to filter out everything until the meta marker
264+
let metaFound = false;
265+
const metaPreambleFilter = (chunk: VSBuffer) => {
266+
const chunkString = chunk.toString();
260267

261-
if (!metaFound && chunk) {
262-
const metaIndex = chunkString.indexOf(BackupFileServiceImpl.META_MARKER);
263-
if (metaIndex === -1) {
264-
return VSBuffer.fromString(''); // meta not yet found, return empty string
265-
}
266-
267-
metaFound = true;
268-
return VSBuffer.fromString(chunkString.substr(metaIndex + 1)); // meta found, return everything after
268+
if (!metaFound && chunk) {
269+
const metaIndex = chunkString.indexOf(BackupFileServiceImpl.META_MARKER);
270+
if (metaIndex === -1) {
271+
return VSBuffer.fromString(''); // meta not yet found, return empty string
269272
}
270273

271-
return chunk;
272-
};
274+
metaFound = true;
273275

274-
return createTextBufferFactoryFromStream(content.value, metaPreambleFilter);
275-
});
276+
return VSBuffer.fromString(chunkString.substr(metaIndex + 1)); // meta found, return everything after
277+
}
278+
279+
return chunk;
280+
};
281+
282+
return createTextBufferFactoryFromStream(content.value, metaPreambleFilter);
276283
}
277284

278-
toBackupResource(resource: Uri): Uri {
279-
return Uri.file(path.join(this.backupWorkspacePath, resource.scheme, hashPath(resource)));
285+
toBackupResource(resource: URI): URI {
286+
return URI.file(join(this.backupWorkspacePath, resource.scheme, hashPath(resource)));
280287
}
281288
}
282289

@@ -290,7 +297,7 @@ export class InMemoryBackupFileService implements IBackupFileService {
290297
return Promise.resolve(this.backups.size > 0);
291298
}
292299

293-
loadBackupResource(resource: Uri): Promise<Uri | undefined> {
300+
loadBackupResource(resource: URI): Promise<URI | undefined> {
294301
const backupResource = this.toBackupResource(resource);
295302
if (this.backups.has(backupResource.toString())) {
296303
return Promise.resolve(backupResource);
@@ -299,14 +306,14 @@ export class InMemoryBackupFileService implements IBackupFileService {
299306
return Promise.resolve(undefined);
300307
}
301308

302-
backupResource(resource: Uri, content: ITextSnapshot, versionId?: number): Promise<void> {
309+
backupResource(resource: URI, content: ITextSnapshot, versionId?: number): Promise<void> {
303310
const backupResource = this.toBackupResource(resource);
304311
this.backups.set(backupResource.toString(), content);
305312

306313
return Promise.resolve();
307314
}
308315

309-
resolveBackupContent(backupResource: Uri): Promise<ITextBufferFactory> {
316+
resolveBackupContent(backupResource: URI): Promise<ITextBufferFactory> {
310317
const snapshot = this.backups.get(backupResource.toString());
311318
if (snapshot) {
312319
return Promise.resolve(createTextBufferFactoryFromSnapshot(snapshot));
@@ -315,11 +322,11 @@ export class InMemoryBackupFileService implements IBackupFileService {
315322
return Promise.reject('Unexpected backup resource to resolve');
316323
}
317324

318-
getWorkspaceFileBackups(): Promise<Uri[]> {
319-
return Promise.resolve(keys(this.backups).map(key => Uri.parse(key)));
325+
getWorkspaceFileBackups(): Promise<URI[]> {
326+
return Promise.resolve(keys(this.backups).map(key => URI.parse(key)));
320327
}
321328

322-
discardResourceBackup(resource: Uri): Promise<void> {
329+
discardResourceBackup(resource: URI): Promise<void> {
323330
this.backups.delete(this.toBackupResource(resource).toString());
324331

325332
return Promise.resolve();
@@ -331,17 +338,17 @@ export class InMemoryBackupFileService implements IBackupFileService {
331338
return Promise.resolve();
332339
}
333340

334-
toBackupResource(resource: Uri): Uri {
335-
return Uri.file(path.join(resource.scheme, hashPath(resource)));
341+
toBackupResource(resource: URI): URI {
342+
return URI.file(join(resource.scheme, hashPath(resource)));
336343
}
337344
}
338345

339346
/*
340347
* Exported only for testing
341348
*/
342-
export function hashPath(resource: Uri): string {
349+
export function hashPath(resource: URI): string {
343350
const str = resource.scheme === Schemas.file || resource.scheme === Schemas.untitled ? resource.fsPath : resource.toString();
344-
return crypto.createHash('md5').update(str).digest('hex');
351+
return createHash('md5').update(str).digest('hex');
345352
}
346353

347354
registerSingleton(IBackupFileService, BackupFileService);

0 commit comments

Comments
 (0)