33 * Licensed under the MIT License. See License.txt in the project root for license information.
44 *--------------------------------------------------------------------------------------------*/
55
6- import { IUserDataSyncService , SyncStatus , IUserDataSyncStoreService , SyncResource , IUserDataSyncLogService , IUserDataSynchroniser , UserDataSyncErrorCode , UserDataSyncError , SyncResourceConflicts , ISyncResourceHandle , IUserDataManifest , ISyncTask , Change , IResourcePreview } from 'vs/platform/userDataSync/common/userDataSync' ;
6+ import { IUserDataSyncService , SyncStatus , IUserDataSyncStoreService , SyncResource , IUserDataSyncLogService , IUserDataSynchroniser , UserDataSyncErrorCode , UserDataSyncError , SyncResourceConflicts , ISyncResourceHandle , IUserDataManifest , ISyncTask , Change , IResourcePreview , IManualSyncTask , ISyncResourcePreview } from 'vs/platform/userDataSync/common/userDataSync' ;
77import { Disposable } from 'vs/base/common/lifecycle' ;
88import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation' ;
99import { Emitter , Event } from 'vs/base/common/event' ;
@@ -22,6 +22,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
2222import { IHeaders } from 'vs/base/parts/request/common/request' ;
2323import { generateUuid } from 'vs/base/common/uuid' ;
2424import { createCancelablePromise , CancelablePromise } from 'vs/base/common/async' ;
25+ import { isPromiseCanceledError } from 'vs/base/common/errors' ;
2526
2627type SyncErrorClassification = {
2728 resource ?: { classification : 'SystemMetaData' , purpose : 'FeatureInsight' , isMeasurement : true } ;
@@ -131,7 +132,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
131132 }
132133
133134 async createSyncTask ( ) : Promise < ISyncTask > {
134- this . telemetryService . publicLog2 ( 'sync/getmanifest' ) ;
135+ await this . checkEnablement ( ) ;
136+
135137 const executionId = generateUuid ( ) ;
136138 let manifest : IUserDataManifest | null ;
137139 try {
@@ -164,10 +166,26 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
164166 } ;
165167 }
166168
167- private recoveredSettings : boolean = false ;
168- private async sync ( manifest : IUserDataManifest | null , executionId : string , token : CancellationToken ) : Promise < void > {
169+ async createManualSyncTask ( ) : Promise < IManualSyncTask > {
169170 await this . checkEnablement ( ) ;
170171
172+ const executionId = generateUuid ( ) ;
173+ const syncHeaders : IHeaders = { 'X-Execution-Id' : executionId } ;
174+ let manifest : IUserDataManifest | null ;
175+ try {
176+ manifest = await this . userDataSyncStoreService . manifest ( syncHeaders ) ;
177+ } catch ( error ) {
178+ if ( error instanceof UserDataSyncError ) {
179+ this . telemetryService . publicLog2 < { resource ?: string , executionId ?: string } , SyncErrorClassification > ( `sync/error/${ error . code } ` , { resource : error . resource , executionId } ) ;
180+ }
181+ throw error ;
182+ }
183+
184+ return new ManualSyncTask ( manifest , syncHeaders , this . synchronisers , this . logService ) ;
185+ }
186+
187+ private recoveredSettings : boolean = false ;
188+ private async sync ( manifest : IUserDataManifest | null , executionId : string , token : CancellationToken ) : Promise < void > {
171189 if ( ! this . recoveredSettings ) {
172190 await this . settingsSynchroniser . recoverSettings ( ) ;
173191 this . recoveredSettings = true ;
@@ -276,6 +294,17 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
276294 return this . getSynchroniser ( resource ) . getMachineId ( syncResourceHandle ) ;
277295 }
278296
297+ async hasLocalData ( ) : Promise < boolean > {
298+ // skip global state synchronizer
299+ const synchronizers = [ this . settingsSynchroniser , this . keybindingsSynchroniser , this . snippetsSynchroniser , this . extensionsSynchroniser ] ;
300+ for ( const synchroniser of synchronizers ) {
301+ if ( await synchroniser . hasLocalData ( ) ) {
302+ return true ;
303+ }
304+ }
305+ return false ;
306+ }
307+
279308 async isFirstTimeSyncingWithAnotherMachine ( ) : Promise < boolean > {
280309 await this . checkEnablement ( ) ;
281310
@@ -414,22 +443,11 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
414443
415444 private computeConflicts ( ) : SyncResourceConflicts [ ] {
416445 return this . synchronisers . filter ( s => s . status === SyncStatus . HasConflicts )
417- . map ( s => ( { syncResource : s . resource , conflicts : s . conflicts . map ( r => this . toStrictResourcePreview ( r ) ) } ) ) ;
418- }
419-
420- private toStrictResourcePreview ( resourcePreview : IResourcePreview ) : IResourcePreview {
421- return {
422- localResource : resourcePreview . localResource ,
423- previewResource : resourcePreview . previewResource ,
424- remoteResource : resourcePreview . remoteResource ,
425- localChange : resourcePreview . localChange ,
426- remoteChange : resourcePreview . remoteChange ,
427- hasConflicts : resourcePreview . hasConflicts ,
428- } ;
446+ . map ( s => ( { syncResource : s . resource , conflicts : s . conflicts . map ( toStrictResourcePreview ) } ) ) ;
429447 }
430448
431449 getSynchroniser ( source : SyncResource ) : IUserDataSynchroniser {
432- return this . synchronisers . filter ( s => s . resource === source ) [ 0 ] ;
450+ return this . synchronisers . find ( s => s . resource === source ) ! ;
433451 }
434452
435453 private async checkEnablement ( ) : Promise < void > {
@@ -439,3 +457,143 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
439457 }
440458
441459}
460+
461+ class ManualSyncTask implements IManualSyncTask {
462+
463+ private previewsPromise : CancelablePromise < [ SyncResource , ISyncResourcePreview ] [ ] > | undefined ;
464+ private previews : [ SyncResource , ISyncResourcePreview ] [ ] | undefined ;
465+
466+ constructor ( readonly manifest : IUserDataManifest | null ,
467+ private readonly syncHeaders : IHeaders ,
468+ private readonly synchronisers : IUserDataSynchroniser [ ] ,
469+ private readonly logService : IUserDataSyncLogService ,
470+ ) { }
471+
472+ async preview ( ) : Promise < [ SyncResource , ISyncResourcePreview ] [ ] > {
473+ if ( ! this . previewsPromise ) {
474+ this . previewsPromise = createCancelablePromise ( token => this . getPreviews ( token ) ) ;
475+ }
476+ this . previews = await this . previewsPromise ;
477+ return this . previews ;
478+ }
479+
480+ async accept ( resource : URI , content : string ) : Promise < [ SyncResource , ISyncResourcePreview ] [ ] > {
481+ if ( ! this . previews ) {
482+ throw new Error ( 'You need to create preview before applying' ) ;
483+ }
484+ const index = this . previews . findIndex ( ( [ , preview ] ) => preview . resourcePreviews . some ( ( { localResource, previewResource, remoteResource } ) =>
485+ isEqual ( resource , localResource ) || isEqual ( resource , previewResource ) || isEqual ( resource , remoteResource ) ) ) ;
486+ if ( index !== - 1 ) {
487+ const synchroniser = this . synchronisers . find ( s => s . resource === this . previews ! [ index ] [ 0 ] ) ! ;
488+ /* force only if the resource is local or remote resource */
489+ const force = this . previews ! [ index ] [ 1 ] . resourcePreviews . some ( ( { localResource, remoteResource } ) => isEqual ( resource , localResource ) || isEqual ( resource , remoteResource ) ) ;
490+ const preview = await synchroniser . acceptPreviewContent ( resource , content , force , this . syncHeaders ) ;
491+ preview ? this . previews . splice ( index , 1 , this . toSyncResourcePreview ( synchroniser . resource , preview ) ) : this . previews . splice ( index , 1 ) ;
492+ }
493+ return this . previews ;
494+ }
495+
496+ async merge ( ) : Promise < [ SyncResource , ISyncResourcePreview ] [ ] > {
497+ if ( ! this . previews ) {
498+ throw new Error ( 'You need to create preview before applying' ) ;
499+ }
500+ const previews : [ SyncResource , ISyncResourcePreview ] [ ] = [ ] ;
501+ for ( const [ syncResource , preview ] of this . previews ) {
502+ const synchroniser = this . synchronisers . find ( s => s . resource === syncResource ) ! ;
503+ let newPreview : ISyncResourcePreview | null = null ;
504+ for ( const resourcePreview of preview . resourcePreviews ) {
505+ /* merge only if there are no conflicts */
506+ if ( ! resourcePreview . hasConflicts ) {
507+ const content = await synchroniser . resolveContent ( resourcePreview . previewResource ) || '' ;
508+ newPreview = await synchroniser . acceptPreviewContent ( resourcePreview . previewResource , content , false , this . syncHeaders ) ;
509+ }
510+ }
511+ if ( newPreview ) {
512+ previews . push ( this . toSyncResourcePreview ( syncResource , newPreview ) ) ;
513+ }
514+ }
515+ this . previews = previews ;
516+ return this . previews ;
517+ }
518+
519+ async pull ( ) : Promise < void > {
520+ if ( ! this . previews ) {
521+ throw new Error ( 'You need to create preview before applying' ) ;
522+ }
523+ for ( const [ syncResource , preview ] of this . previews ) {
524+ const synchroniser = this . synchronisers . find ( s => s . resource === syncResource ) ! ;
525+ for ( const resourcePreview of preview . resourcePreviews ) {
526+ const content = await synchroniser . resolveContent ( resourcePreview . remoteResource ) || '' ;
527+ await synchroniser . acceptPreviewContent ( resourcePreview . remoteResource , content , true , this . syncHeaders ) ;
528+ }
529+ }
530+ this . previews = [ ] ;
531+ }
532+
533+ async push ( ) : Promise < void > {
534+ if ( ! this . previews ) {
535+ throw new Error ( 'You need to create preview before applying' ) ;
536+ }
537+ for ( const [ syncResource , preview ] of this . previews ) {
538+ const synchroniser = this . synchronisers . find ( s => s . resource === syncResource ) ! ;
539+ for ( const resourcePreview of preview . resourcePreviews ) {
540+ const content = await synchroniser . resolveContent ( resourcePreview . localResource ) || '' ;
541+ await synchroniser . acceptPreviewContent ( resourcePreview . localResource , content , true , this . syncHeaders ) ;
542+ }
543+ }
544+ this . previews = [ ] ;
545+ }
546+
547+ async stop ( ) : Promise < void > {
548+ if ( this . previewsPromise ) {
549+ this . previewsPromise . cancel ( ) ;
550+ this . previewsPromise = undefined ;
551+ }
552+ this . previews = undefined ;
553+ for ( const synchroniser of this . synchronisers ) {
554+ try {
555+ await synchroniser . stop ( ) ;
556+ } catch ( error ) {
557+ if ( ! isPromiseCanceledError ( error ) ) {
558+ this . logService . error ( error ) ;
559+ }
560+ }
561+ }
562+ }
563+
564+ private async getPreviews ( token : CancellationToken ) : Promise < [ SyncResource , ISyncResourcePreview ] [ ] > {
565+ const result : [ SyncResource , ISyncResourcePreview ] [ ] = [ ] ;
566+ for ( const synchroniser of this . synchronisers ) {
567+ if ( token . isCancellationRequested ) {
568+ return [ ] ;
569+ }
570+ const preview = await synchroniser . preview ( this . manifest , this . syncHeaders ) ;
571+ if ( preview ) {
572+ result . push ( this . toSyncResourcePreview ( synchroniser . resource , preview ) ) ;
573+ }
574+ }
575+ return result ;
576+ }
577+
578+ private toSyncResourcePreview ( syncResource : SyncResource , preview : ISyncResourcePreview ) : [ SyncResource , ISyncResourcePreview ] {
579+ return [
580+ syncResource ,
581+ {
582+ isLastSyncFromCurrentMachine : preview . isLastSyncFromCurrentMachine ,
583+ resourcePreviews : preview . resourcePreviews . map ( toStrictResourcePreview )
584+ }
585+ ] ;
586+ }
587+
588+ }
589+
590+ function toStrictResourcePreview ( resourcePreview : IResourcePreview ) : IResourcePreview {
591+ return {
592+ localResource : resourcePreview . localResource ,
593+ previewResource : resourcePreview . previewResource ,
594+ remoteResource : resourcePreview . remoteResource ,
595+ localChange : resourcePreview . localChange ,
596+ remoteChange : resourcePreview . remoteChange ,
597+ hasConflicts : resourcePreview . hasConflicts ,
598+ } ;
599+ }
0 commit comments