@@ -23,7 +23,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
2323import { FileChangesEvent , FileOperationError , FileOperationResult , IFileContent , IFileService } from 'vs/platform/files/common/files' ;
2424import { ILogService } from 'vs/platform/log/common/log' ;
2525import { getServiceMachineId } from 'vs/platform/externalServices/common/serviceMachineId' ;
26- import { IStorageService } from 'vs/platform/storage/common/storage' ;
26+ import { IStorageService , StorageScope , StorageTarget } from 'vs/platform/storage/common/storage' ;
2727import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry' ;
2828import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' ;
2929import { Change , getLastSyncResourceUri , IRemoteUserData , IResourcePreview as IBaseResourcePreview , ISyncData , IUserDataSyncResourcePreview as IBaseSyncResourcePreview , IUserData , IUserDataInitializer , IUserDataSyncBackupStoreService , IUserDataSyncConfiguration , IUserDataSynchroniser , IUserDataSyncLogService , IUserDataSyncEnablementService , IUserDataSyncStoreService , IUserDataSyncUtilService , MergeState , PREVIEW_DIR_NAME , SyncResource , SyncStatus , UserDataSyncError , UserDataSyncErrorCode , USER_DATA_SYNC_CONFIGURATION_SCOPE , USER_DATA_SYNC_SCHEME , IUserDataResourceManifest , getPathSegments , IUserDataSyncResourceConflicts , IUserDataSyncResource } from 'vs/platform/userDataSync/common/userDataSync' ;
@@ -98,14 +98,20 @@ interface ISyncResourcePreview extends IBaseSyncResourcePreview {
9898 readonly resourcePreviews : IEditableResourcePreview [ ] ;
9999}
100100
101+ interface ILastSyncUserDataState {
102+ readonly ref : string ;
103+ readonly version : string | undefined ;
104+ [ key : string ] : any ;
105+ }
106+
101107export abstract class AbstractSynchroniser extends Disposable implements IUserDataSynchroniser {
102108
103109 private syncPreviewPromise : CancelablePromise < ISyncResourcePreview > | null = null ;
104110
105111 protected readonly syncFolder : URI ;
106112 protected readonly syncPreviewFolder : URI ;
107113 protected readonly extUri : IExtUri ;
108- private readonly currentMachineIdPromise : Promise < string > ;
114+ protected readonly currentMachineIdPromise : Promise < string > ;
109115
110116 private _status : SyncStatus = SyncStatus . Idle ;
111117 get status ( ) : SyncStatus { return this . _status ; }
@@ -122,6 +128,7 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa
122128 readonly onDidChangeLocal : Event < void > = this . _onDidChangeLocal . event ;
123129
124130 protected readonly lastSyncResource : URI ;
131+ private readonly lastSyncUserDataStateKey = `${ this . collection ? `${ this . collection } .` : '' } ${ this . syncResource . syncResource } .lastSyncUserData` ;
125132 private hasSyncResourceStateVersionChanged : boolean = false ;
126133 protected readonly syncResourceLogLabel : string ;
127134
@@ -134,7 +141,7 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa
134141 readonly collection : string | undefined ,
135142 @IFileService protected readonly fileService : IFileService ,
136143 @IEnvironmentService protected readonly environmentService : IEnvironmentService ,
137- @IStorageService storageService : IStorageService ,
144+ @IStorageService protected readonly storageService : IStorageService ,
138145 @IUserDataSyncStoreService protected readonly userDataSyncStoreService : IUserDataSyncStoreService ,
139146 @IUserDataSyncBackupStoreService protected readonly userDataSyncBackupStoreService : IUserDataSyncBackupStoreService ,
140147 @IUserDataSyncEnablementService protected readonly userDataSyncEnablementService : IUserDataSyncEnablementService ,
@@ -327,7 +334,7 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa
327334 // Avoid cache and get latest remote user data - https://github.com/microsoft/vscode/issues/90624
328335 remoteUserData = await this . getRemoteUserData ( null ) ;
329336
330- // Get the latest last sync user data. Because multiples parallel syncs (in Web) could share same last sync data
337+ // Get the latest last sync user data. Because multiple parallel syncs (in Web) could share same last sync data
331338 // and one of them successfully updated remote and last sync state.
332339 lastSyncUserData = await this . getLastSyncUserData ( ) ;
333340
@@ -507,9 +514,12 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa
507514 }
508515
509516 async resetLocal ( ) : Promise < void > {
517+ this . storageService . remove ( this . lastSyncUserDataStateKey , StorageScope . APPLICATION ) ;
510518 try {
511519 await this . fileService . del ( this . lastSyncResource ) ;
512- } catch ( e ) { /* ignore */ }
520+ } catch ( e ) {
521+ this . logService . error ( e ) ;
522+ }
513523 }
514524
515525 private async doGenerateSyncResourcePreview ( remoteUserData : IRemoteUserData , lastSyncUserData : IRemoteUserData | null , apply : boolean , userDataSyncConfiguration : IUserDataSyncConfiguration , token : CancellationToken ) : Promise < ISyncResourcePreview > {
@@ -558,48 +568,116 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa
558568 return { syncResource : this . resource , profile : this . syncResource . profile , remoteUserData, lastSyncUserData, resourcePreviews, isLastSyncFromCurrentMachine : isRemoteDataFromCurrentMachine } ;
559569 }
560570
561- async getLastSyncUserData < T extends IRemoteUserData > ( ) : Promise < T | null > {
562- try {
563- const content = await this . fileService . readFile ( this . lastSyncResource ) ;
564- const parsed = JSON . parse ( content . value . toString ( ) ) ;
565- const resourceSyncStateVersion = this . userDataSyncEnablementService . getResourceSyncStateVersion ( this . resource ) ;
566- this . hasSyncResourceStateVersionChanged = parsed . version && resourceSyncStateVersion && parsed . version !== resourceSyncStateVersion ;
567- if ( this . hasSyncResourceStateVersionChanged ) {
568- this . logService . info ( `${ this . syncResourceLogLabel } : Reset last sync state because last sync state version ${ parsed . version } is not compatible with current sync state version ${ resourceSyncStateVersion } .` ) ;
569- await this . resetLocal ( ) ;
570- return null ;
571- }
571+ async getLastSyncUserData < T = IRemoteUserData & { [ key : string ] : any } > ( ) : Promise < T | null > {
572+ let storedLastSyncUserDataStateContent = this . storageService . get ( this . lastSyncUserDataStateKey , StorageScope . APPLICATION ) ;
573+ if ( ! storedLastSyncUserDataStateContent ) {
574+ storedLastSyncUserDataStateContent = await this . migrateLastSyncUserData ( ) ;
575+ }
572576
573- const userData : IUserData = parsed as IUserData ;
574- if ( userData . content === null ) {
575- return { ref : parsed . ref , syncData : null } as T ;
576- }
577- const syncData : ISyncData = JSON . parse ( userData . content ) ;
577+ // Last Sync Data state does not exist
578+ if ( ! storedLastSyncUserDataStateContent ) {
579+ this . logService . info ( ` ${ this . syncResourceLogLabel } : Last sync data state does not exist.` ) ;
580+ return null ;
581+ }
578582
579- /* Check if syncData is of expected type. Return only if matches */
580- if ( isSyncData ( syncData ) ) {
581- return { ...parsed , ...{ syncData, content : undefined } } ;
583+ const lastSyncUserDataState : ILastSyncUserDataState = JSON . parse ( storedLastSyncUserDataStateContent ) ;
584+ const resourceSyncStateVersion = this . userDataSyncEnablementService . getResourceSyncStateVersion ( this . resource ) ;
585+ this . hasSyncResourceStateVersionChanged = ! ! lastSyncUserDataState . version && ! ! resourceSyncStateVersion && lastSyncUserDataState . version !== resourceSyncStateVersion ;
586+ if ( this . hasSyncResourceStateVersionChanged ) {
587+ this . logService . info ( `${ this . syncResourceLogLabel } : Reset last sync state because last sync state version ${ lastSyncUserDataState . version } is not compatible with current sync state version ${ resourceSyncStateVersion } .` ) ;
588+ await this . resetLocal ( ) ;
589+ return null ;
590+ }
591+
592+ let syncData : ISyncData | null | undefined = undefined ;
593+
594+ // Get Last Sync Data from Local
595+ let retrial = 1 ;
596+ while ( syncData === undefined && retrial ++ < 6 /* Retry 5 times */ ) {
597+ try {
598+ const content = ( await this . fileService . readFile ( this . lastSyncResource ) ) . value . toString ( ) ;
599+ try { syncData = content ? JSON . parse ( content ) : null ; } catch ( e ) { /* Ignore */ }
600+ if ( syncData && ! isSyncData ( syncData ) ) {
601+ this . logService . info ( `${ this . syncResourceLogLabel } : Last sync data stored locally is invalid.` ) ;
602+ syncData = undefined ;
603+ }
604+ break ;
605+ } catch ( error ) {
606+ if ( error instanceof FileOperationError && error . fileOperationResult === FileOperationResult . FILE_NOT_FOUND ) {
607+ this . logService . info ( `${ this . syncResourceLogLabel } : Last sync resource does not exist locally.` ) ;
608+ break ;
609+ } else if ( error instanceof UserDataSyncError ) {
610+ throw error ;
611+ } else {
612+ // log and retry
613+ this . logService . error ( error , retrial ) ;
614+ }
582615 }
616+ }
583617
584- } catch ( error ) {
585- if ( error instanceof FileOperationError && error . fileOperationResult === FileOperationResult . FILE_NOT_FOUND ) {
586- this . logService . info ( `${ this . syncResourceLogLabel } : Not synced yet. Last sync resource does not exist.` ) ;
587- } else {
588- // log error always except when file does not exist
589- this . logService . error ( error ) ;
618+ // Get Last Sync Data from Remote
619+ if ( syncData === undefined ) {
620+ try {
621+ const content = await this . userDataSyncStoreService . resolveResourceContent ( this . resource , lastSyncUserDataState . ref , this . collection , this . syncHeaders ) ;
622+ syncData = content === null ? null : this . parseSyncData ( content ) ;
623+ await this . fileService . writeFile ( this . lastSyncResource , VSBuffer . fromString ( syncData ? JSON . stringify ( syncData ) : '' ) ) ;
624+ } catch ( error ) {
625+ if ( error instanceof UserDataSyncError && error . code === UserDataSyncErrorCode . NotFound ) {
626+ this . logService . info ( `${ this . syncResourceLogLabel } : Last sync resource does not exist on the server.` ) ;
627+ } else {
628+ throw error ;
629+ }
590630 }
591631 }
592- return null ;
632+
633+ // Last Sync Data Not Found
634+ if ( syncData === undefined ) {
635+ return null ;
636+ }
637+
638+ return {
639+ ...lastSyncUserDataState ,
640+ syncData,
641+ } as T ;
593642 }
594643
595644 protected async updateLastSyncUserData ( lastSyncRemoteUserData : IRemoteUserData , additionalProps : IStringDictionary < any > = { } ) : Promise < void > {
596- if ( additionalProps [ 'ref' ] || additionalProps [ 'content' ] || additionalProps [ ' version'] ) {
645+ if ( additionalProps [ 'ref' ] || additionalProps [ 'version' ] ) {
597646 throw new Error ( 'Cannot have core properties as additional' ) ;
598647 }
599648
600649 const version = this . userDataSyncEnablementService . getResourceSyncStateVersion ( this . resource ) ;
601- const lastSyncUserData = { ref : lastSyncRemoteUserData . ref , content : lastSyncRemoteUserData . syncData ? JSON . stringify ( lastSyncRemoteUserData . syncData ) : null , version, ...additionalProps } ;
602- await this . fileService . writeFile ( this . lastSyncResource , VSBuffer . fromString ( JSON . stringify ( lastSyncUserData ) ) ) ;
650+ const lastSyncUserDataState : ILastSyncUserDataState = {
651+ ref : lastSyncRemoteUserData . ref ,
652+ version,
653+ ...additionalProps
654+ } ;
655+
656+ this . storageService . store ( this . lastSyncUserDataStateKey , JSON . stringify ( lastSyncUserDataState ) , StorageScope . APPLICATION , StorageTarget . MACHINE ) ;
657+ await this . fileService . writeFile ( this . lastSyncResource , VSBuffer . fromString ( lastSyncRemoteUserData . syncData ? JSON . stringify ( lastSyncRemoteUserData . syncData ) : '' ) ) ;
658+ }
659+
660+ private async migrateLastSyncUserData ( ) : Promise < string | undefined > {
661+ try {
662+ const content = await this . fileService . readFile ( this . lastSyncResource ) ;
663+ const userData = JSON . parse ( content . value . toString ( ) ) ;
664+ await this . fileService . del ( this . lastSyncResource ) ;
665+ if ( userData . ref ) {
666+ this . storageService . store ( this . lastSyncUserDataStateKey , JSON . stringify ( {
667+ ...userData ,
668+ content : undefined ,
669+ } ) , StorageScope . APPLICATION , StorageTarget . MACHINE ) ;
670+ await this . fileService . writeFile ( this . lastSyncResource , VSBuffer . fromString ( userData . content || '' ) ) ;
671+ this . logService . info ( `${ this . syncResourceLogLabel } : Migrated data from last sync resource to last sync state.` ) ;
672+ }
673+ } catch ( error ) {
674+ if ( error instanceof FileOperationError && error . fileOperationResult === FileOperationResult . FILE_NOT_FOUND ) {
675+ this . logService . debug ( `${ this . syncResourceLogLabel } : Migrating last sync user data. Resource does not exist.` ) ;
676+ } else {
677+ this . logService . error ( error ) ;
678+ }
679+ }
680+ return this . storageService . get ( this . lastSyncUserDataStateKey , StorageScope . APPLICATION ) ;
603681 }
604682
605683 async getRemoteUserData ( lastSyncData : IRemoteUserData | null ) : Promise < IRemoteUserData > {
0 commit comments