3
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
4
*--------------------------------------------------------------------------------------------*/
5
5
6
- import { basename , isEqual , joinPath } from 'vs/base/common/resources' ;
6
+ import { joinPath } from 'vs/base/common/resources' ;
7
7
import { URI } from 'vs/base/common/uri' ;
8
8
import { coalesce } from 'vs/base/common/arrays' ;
9
9
import { equals , deepClone } from 'vs/base/common/objects' ;
@@ -111,14 +111,6 @@ export class WorkingCopyBackupsModel {
111
111
this . cache . delete ( resource ) ;
112
112
}
113
113
114
- move ( source : URI , target : URI ) : void {
115
- const entry = this . cache . get ( source ) ;
116
- if ( entry ) {
117
- this . cache . delete ( source ) ;
118
- this . cache . set ( target , entry ) ;
119
- }
120
- }
121
-
122
114
clear ( ) : void {
123
115
this . cache . clear ( ) ;
124
116
}
@@ -230,43 +222,15 @@ class WorkingCopyBackupServiceImpl extends Disposable implements IWorkingCopyBac
230
222
// Create backup model
231
223
this . model = await WorkingCopyBackupsModel . create ( this . backupWorkspaceHome , this . fileService ) ;
232
224
233
- // Migrate hashes as needed. We used to hash with a MD5
234
- // sum of the path but switched to our own simpler hash
235
- // to avoid a node.js dependency. We still want to
236
- // support the older hash to prevent dataloss, so we:
237
- // - iterate over all backups
238
- // - detect if the file name length is 32 (MD5 length)
239
- // - read the backup's target file path
240
- // - rename the backup to the new hash
241
- // - update the backup in our model
242
- for ( const backupResource of this . model . get ( ) ) {
243
- if ( basename ( backupResource ) . length !== 32 ) {
244
- continue ; // not a MD5 hash, already uses new hash function
245
- }
246
-
247
- try {
248
- const identifier = await this . resolveIdentifier ( backupResource , this . model ) ;
249
- if ( ! identifier ) {
250
- this . logService . warn ( `Backup: Unable to read target URI of backup ${ backupResource } for migration to new hash.` ) ;
251
- continue ;
252
- }
253
-
254
- const expectedBackupResource = this . toBackupResource ( identifier ) ;
255
- if ( ! isEqual ( expectedBackupResource , backupResource ) ) {
256
- await this . fileService . move ( backupResource , expectedBackupResource , true ) ;
257
- this . model . move ( backupResource , expectedBackupResource ) ;
258
- }
259
- } catch ( error ) {
260
- this . logService . error ( `Backup: Unable to migrate backup ${ backupResource } to new hash.` ) ;
261
- }
262
- }
263
-
264
225
return this . model ;
265
226
}
266
227
267
228
async hasBackups ( ) : Promise < boolean > {
268
229
const model = await this . ready ;
269
230
231
+ // Ensure to await any pending backup operations
232
+ await this . joinBackups ( ) ;
233
+
270
234
return model . count ( ) > 0 ;
271
235
}
272
236
@@ -409,48 +373,60 @@ class WorkingCopyBackupServiceImpl extends Disposable implements IWorkingCopyBac
409
373
async getBackups ( ) : Promise < IWorkingCopyIdentifier [ ] > {
410
374
const model = await this . ready ;
411
375
376
+ // Ensure to await any pending backup operations
377
+ await this . joinBackups ( ) ;
378
+
412
379
const backups = await Promise . all ( model . get ( ) . map ( backupResource => this . resolveIdentifier ( backupResource , model ) ) ) ;
413
380
414
381
return coalesce ( backups ) ;
415
382
}
416
383
417
384
private async resolveIdentifier ( backupResource : URI , model : WorkingCopyBackupsModel ) : Promise < IWorkingCopyIdentifier | undefined > {
385
+ let res : IWorkingCopyIdentifier | undefined = undefined ;
418
386
419
- // Read the entire backup preamble by reading up to
420
- // `PREAMBLE_MAX_LENGTH` in the backup file until
421
- // the `PREAMBLE_END_MARKER` is found
422
- const backupPreamble = await this . readToMatchingString ( backupResource , WorkingCopyBackupServiceImpl . PREAMBLE_END_MARKER , WorkingCopyBackupServiceImpl . PREAMBLE_MAX_LENGTH ) ;
423
- if ( ! backupPreamble ) {
424
- return undefined ;
425
- }
387
+ await this . ioOperationQueues . queueFor ( backupResource ) . queue ( async ( ) => {
388
+ if ( ! model . has ( backupResource ) ) {
389
+ return ; // require backup to be present
390
+ }
426
391
427
- // Figure out the offset in the preamble where meta
428
- // information possibly starts. This can be `-1` for
429
- // older backups without meta.
430
- const metaStartIndex = backupPreamble . indexOf ( WorkingCopyBackupServiceImpl . PREAMBLE_META_SEPARATOR ) ;
431
-
432
- // Extract the preamble content for resource and meta
433
- let resourcePreamble : string ;
434
- let metaPreamble : string | undefined ;
435
- if ( metaStartIndex > 0 ) {
436
- resourcePreamble = backupPreamble . substring ( 0 , metaStartIndex ) ;
437
- metaPreamble = backupPreamble . substr ( metaStartIndex + 1 ) ;
438
- } else {
439
- resourcePreamble = backupPreamble ;
440
- metaPreamble = undefined ;
441
- }
392
+ // Read the entire backup preamble by reading up to
393
+ // `PREAMBLE_MAX_LENGTH` in the backup file until
394
+ // the `PREAMBLE_END_MARKER` is found
395
+ const backupPreamble = await this . readToMatchingString ( backupResource , WorkingCopyBackupServiceImpl . PREAMBLE_END_MARKER , WorkingCopyBackupServiceImpl . PREAMBLE_MAX_LENGTH ) ;
396
+ if ( ! backupPreamble ) {
397
+ return ;
398
+ }
399
+
400
+ // Figure out the offset in the preamble where meta
401
+ // information possibly starts. This can be `-1` for
402
+ // older backups without meta.
403
+ const metaStartIndex = backupPreamble . indexOf ( WorkingCopyBackupServiceImpl . PREAMBLE_META_SEPARATOR ) ;
404
+
405
+ // Extract the preamble content for resource and meta
406
+ let resourcePreamble : string ;
407
+ let metaPreamble : string | undefined ;
408
+ if ( metaStartIndex > 0 ) {
409
+ resourcePreamble = backupPreamble . substring ( 0 , metaStartIndex ) ;
410
+ metaPreamble = backupPreamble . substr ( metaStartIndex + 1 ) ;
411
+ } else {
412
+ resourcePreamble = backupPreamble ;
413
+ metaPreamble = undefined ;
414
+ }
415
+
416
+ // Try to parse the meta preamble for figuring out
417
+ // `typeId` and `meta` if defined.
418
+ const { typeId, meta } = this . parsePreambleMeta ( metaPreamble ) ;
442
419
443
- // Try to parse the meta preamble for figuring out
444
- // `typeId` and `meta` if defined.
445
- const { typeId, meta } = this . parsePreambleMeta ( metaPreamble ) ;
420
+ // Update model entry with now resolved meta
421
+ model . update ( backupResource , meta ) ;
446
422
447
- // Update model entry with now resolved meta
448
- model . update ( backupResource , meta ) ;
423
+ res = {
424
+ typeId : typeId ?? NO_TYPE_ID ,
425
+ resource : URI . parse ( resourcePreamble )
426
+ } ;
427
+ } ) ;
449
428
450
- return {
451
- typeId : typeId ?? NO_TYPE_ID ,
452
- resource : URI . parse ( resourcePreamble )
453
- } ;
429
+ return res ;
454
430
}
455
431
456
432
private async readToMatchingString ( backupResource : URI , matchingString : string , maximumBytesToRead : number ) : Promise < string | undefined > {
@@ -469,50 +445,57 @@ class WorkingCopyBackupServiceImpl extends Disposable implements IWorkingCopyBac
469
445
const backupResource = this . toBackupResource ( identifier ) ;
470
446
471
447
const model = await this . ready ;
472
- if ( ! model . has ( backupResource ) ) {
473
- return undefined ; // require backup to be present
474
- }
475
448
476
- // Load the backup content and peek into the first chunk
477
- // to be able to resolve the meta data
478
- const backupStream = await this . fileService . readFileStream ( backupResource ) ;
479
- const peekedBackupStream = await peekStream ( backupStream . value , 1 ) ;
480
- const firstBackupChunk = VSBuffer . concat ( peekedBackupStream . buffer ) ;
481
-
482
- // We have seen reports (e.g. https://github.com/microsoft/vscode/issues/78500) where
483
- // if VSCode goes down while writing the backup file, the file can turn empty because
484
- // it always first gets truncated and then written to. In this case, we will not find
485
- // the meta-end marker ('\n') and as such the backup can only be invalid. We bail out
486
- // here if that is the case.
487
- const preambleEndIndex = firstBackupChunk . buffer . indexOf ( WorkingCopyBackupServiceImpl . PREAMBLE_END_MARKER_CHARCODE ) ;
488
- if ( preambleEndIndex === - 1 ) {
489
- this . logService . trace ( `Backup: Could not find meta end marker in ${ backupResource } . The file is probably corrupt (filesize: ${ backupStream . size } ).` ) ;
490
-
491
- return undefined ;
492
- }
449
+ let res : IResolvedWorkingCopyBackup < T > | undefined = undefined ;
493
450
494
- const preambelRaw = firstBackupChunk . slice ( 0 , preambleEndIndex ) . toString ( ) ;
451
+ await this . ioOperationQueues . queueFor ( backupResource ) . queue ( async ( ) => {
452
+ if ( ! model . has ( backupResource ) ) {
453
+ return ; // require backup to be present
454
+ }
495
455
496
- // Extract meta data (if any)
497
- let meta : T | undefined ;
498
- const metaStartIndex = preambelRaw . indexOf ( WorkingCopyBackupServiceImpl . PREAMBLE_META_SEPARATOR ) ;
499
- if ( metaStartIndex !== - 1 ) {
500
- meta = this . parsePreambleMeta ( preambelRaw . substr ( metaStartIndex + 1 ) ) . meta as T ;
501
- }
456
+ // Load the backup content and peek into the first chunk
457
+ // to be able to resolve the meta data
458
+ const backupStream = await this . fileService . readFileStream ( backupResource ) ;
459
+ const peekedBackupStream = await peekStream ( backupStream . value , 1 ) ;
460
+ const firstBackupChunk = VSBuffer . concat ( peekedBackupStream . buffer ) ;
461
+
462
+ // We have seen reports (e.g. https://github.com/microsoft/vscode/issues/78500) where
463
+ // if VSCode goes down while writing the backup file, the file can turn empty because
464
+ // it always first gets truncated and then written to. In this case, we will not find
465
+ // the meta-end marker ('\n') and as such the backup can only be invalid. We bail out
466
+ // here if that is the case.
467
+ const preambleEndIndex = firstBackupChunk . buffer . indexOf ( WorkingCopyBackupServiceImpl . PREAMBLE_END_MARKER_CHARCODE ) ;
468
+ if ( preambleEndIndex === - 1 ) {
469
+ this . logService . trace ( `Backup: Could not find meta end marker in ${ backupResource } . The file is probably corrupt (filesize: ${ backupStream . size } ).` ) ;
470
+
471
+ return undefined ;
472
+ }
502
473
503
- // Update model entry with now resolved meta
504
- model . update ( backupResource , meta ) ;
474
+ const preambelRaw = firstBackupChunk . slice ( 0 , preambleEndIndex ) . toString ( ) ;
505
475
506
- // Build a new stream without the preamble
507
- const firstBackupChunkWithoutPreamble = firstBackupChunk . slice ( preambleEndIndex + 1 ) ;
508
- let value : VSBufferReadableStream ;
509
- if ( peekedBackupStream . ended ) {
510
- value = bufferToStream ( firstBackupChunkWithoutPreamble ) ;
511
- } else {
512
- value = prefixedBufferStream ( firstBackupChunkWithoutPreamble , peekedBackupStream . stream ) ;
513
- }
476
+ // Extract meta data (if any)
477
+ let meta : T | undefined ;
478
+ const metaStartIndex = preambelRaw . indexOf ( WorkingCopyBackupServiceImpl . PREAMBLE_META_SEPARATOR ) ;
479
+ if ( metaStartIndex !== - 1 ) {
480
+ meta = this . parsePreambleMeta ( preambelRaw . substr ( metaStartIndex + 1 ) ) . meta as T ;
481
+ }
482
+
483
+ // Update model entry with now resolved meta
484
+ model . update ( backupResource , meta ) ;
485
+
486
+ // Build a new stream without the preamble
487
+ const firstBackupChunkWithoutPreamble = firstBackupChunk . slice ( preambleEndIndex + 1 ) ;
488
+ let value : VSBufferReadableStream ;
489
+ if ( peekedBackupStream . ended ) {
490
+ value = bufferToStream ( firstBackupChunkWithoutPreamble ) ;
491
+ } else {
492
+ value = prefixedBufferStream ( firstBackupChunkWithoutPreamble , peekedBackupStream . stream ) ;
493
+ }
494
+
495
+ res = { value, meta } ;
496
+ } ) ;
514
497
515
- return { value , meta } ;
498
+ return res ;
516
499
}
517
500
518
501
private parsePreambleMeta < T extends IWorkingCopyBackupMeta > ( preambleMetaRaw : string | undefined ) : { typeId : string | undefined ; meta : T | undefined } {
0 commit comments