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' ;
1011import { ResourceQueue } from 'vs/base/common/async' ;
1112import { IBackupFileService } from 'vs/workbench/services/backup/common/backup' ;
1213import { IFileService } from 'vs/platform/files/common/files' ;
1314import { readToMatchingString } from 'vs/base/node/stream' ;
1415import { ITextBufferFactory , ITextSnapshot } from 'vs/editor/common/model' ;
1516import { 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' ;
1718import { Schemas } from 'vs/base/common/network' ;
1819import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService' ;
1920import { registerSingleton } from 'vs/platform/instantiation/common/extensions' ;
@@ -23,47 +24,47 @@ import { TextSnapshotReadable } from 'vs/workbench/services/textfile/common/text
2324export 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
3435export 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
347354registerSingleton ( IBackupFileService , BackupFileService ) ;
0 commit comments