Skip to content

Commit e715f39

Browse files
committed
💄
1 parent c13bf7a commit e715f39

10 files changed

Lines changed: 138 additions & 343 deletions

File tree

src/vs/platform/userDataSync/common/abstractSynchronizer.ts

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,87 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { Disposable } from 'vs/base/common/lifecycle';
7-
import { IFileService } from 'vs/platform/files/common/files';
7+
import { IFileService, IFileContent } from 'vs/platform/files/common/files';
88
import { VSBuffer } from 'vs/base/common/buffer';
99
import { URI } from 'vs/base/common/uri';
10-
import { SyncSource } from 'vs/platform/userDataSync/common/userDataSync';
10+
import { SyncSource, SyncStatus, IUserData, IUserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSync';
1111
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
12-
import { joinPath } from 'vs/base/common/resources';
12+
import { joinPath, dirname } from 'vs/base/common/resources';
1313
import { toLocalISOString } from 'vs/base/common/date';
1414
import { ThrottledDelayer } from 'vs/base/common/async';
15+
import { Emitter, Event } from 'vs/base/common/event';
1516

1617
export abstract class AbstractSynchroniser extends Disposable {
1718

1819
protected readonly syncFolder: URI;
1920
private cleanUpDelayer: ThrottledDelayer<void>;
2021

22+
private _status: SyncStatus = SyncStatus.Idle;
23+
get status(): SyncStatus { return this._status; }
24+
private _onDidChangStatus: Emitter<SyncStatus> = this._register(new Emitter<SyncStatus>());
25+
readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangStatus.event;
26+
27+
protected readonly _onDidChangeLocal: Emitter<void> = this._register(new Emitter<void>());
28+
readonly onDidChangeLocal: Event<void> = this._onDidChangeLocal.event;
29+
30+
protected readonly lastSyncResource: URI;
31+
2132
constructor(
2233
readonly source: SyncSource,
2334
@IFileService protected readonly fileService: IFileService,
24-
@IEnvironmentService environmentService: IEnvironmentService
35+
@IEnvironmentService environmentService: IEnvironmentService,
36+
@IUserDataSyncStoreService protected readonly userDataSyncStoreService: IUserDataSyncStoreService,
2537
) {
2638
super();
2739
this.syncFolder = joinPath(environmentService.userDataSyncHome, source);
40+
this.lastSyncResource = joinPath(this.syncFolder, `.lastSync${source}.json`);
2841
this.cleanUpDelayer = new ThrottledDelayer(50);
2942
}
3043

44+
protected setStatus(status: SyncStatus): void {
45+
if (this._status !== status) {
46+
this._status = status;
47+
this._onDidChangStatus.fire(status);
48+
}
49+
}
50+
51+
async hasPreviouslySynced(): Promise<boolean> {
52+
const lastSyncData = await this.getLastSyncUserData();
53+
return !!lastSyncData;
54+
}
55+
56+
async hasRemoteData(): Promise<boolean> {
57+
const remoteUserData = await this.getRemoteUserData();
58+
return remoteUserData.content !== null;
59+
}
60+
61+
async resetLocal(): Promise<void> {
62+
try {
63+
await this.fileService.del(this.lastSyncResource);
64+
} catch (e) { /* ignore */ }
65+
}
66+
67+
protected async getLastSyncUserData<T extends IUserData>(): Promise<T | null> {
68+
try {
69+
const content = await this.fileService.readFile(this.lastSyncResource);
70+
return JSON.parse(content.value.toString());
71+
} catch (error) {
72+
return null;
73+
}
74+
}
75+
76+
protected async updateLastSyncUserData<T extends IUserData>(lastSyncUserData: T): Promise<void> {
77+
await this.fileService.writeFile(this.lastSyncResource, VSBuffer.fromString(JSON.stringify(lastSyncUserData)));
78+
}
79+
80+
protected getRemoteUserData(lastSyncData?: IUserData | null): Promise<IUserData> {
81+
return this.userDataSyncStoreService.read(this.getRemoteDataResourceKey(), lastSyncData || null, this.source);
82+
}
83+
84+
protected async updateRemoteUserData(content: string, ref: string | null): Promise<string> {
85+
return this.userDataSyncStoreService.write(this.getRemoteDataResourceKey(), content, ref, this.source);
86+
}
87+
3188
protected async backupLocal(content: VSBuffer): Promise<void> {
3289
const resource = joinPath(this.syncFolder, toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, ''));
3390
await this.fileService.writeFile(resource, content);
@@ -43,4 +100,40 @@ export abstract class AbstractSynchroniser extends Disposable {
43100
}
44101
}
45102

103+
protected abstract getRemoteDataResourceKey(): string;
104+
}
105+
106+
export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
107+
108+
constructor(
109+
protected readonly file: URI,
110+
readonly source: SyncSource,
111+
@IFileService fileService: IFileService,
112+
@IEnvironmentService environmentService: IEnvironmentService,
113+
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
114+
) {
115+
super(source, fileService, environmentService, userDataSyncStoreService);
116+
this._register(this.fileService.watch(dirname(file)));
117+
this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(file))(() => this._onDidChangeLocal.fire()));
118+
}
119+
120+
protected async getLocalFileContent(): Promise<IFileContent | null> {
121+
try {
122+
return await this.fileService.readFile(this.file);
123+
} catch (error) {
124+
return null;
125+
}
126+
}
127+
128+
protected async updateLocalFileContent(newContent: string, oldContent: IFileContent | null): Promise<void> {
129+
if (oldContent) {
130+
// file exists already
131+
await this.backupLocal(oldContent.value);
132+
await this.fileService.writeFile(this.file, VSBuffer.fromString(newContent), oldContent);
133+
} else {
134+
// file does not exist
135+
await this.fileService.createFile(this.file, VSBuffer.fromString(newContent), { overwrite: false });
136+
}
137+
}
138+
46139
}

src/vs/platform/userDataSync/common/extensionsSync.ts

Lines changed: 9 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,12 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource } from 'vs/platform/userDataSync/common/userDataSync';
7-
import { VSBuffer } from 'vs/base/common/buffer';
8-
import { Emitter, Event } from 'vs/base/common/event';
7+
import { Event } from 'vs/base/common/event';
98
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
10-
import { URI } from 'vs/base/common/uri';
11-
import { joinPath } from 'vs/base/common/resources';
129
import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
1310
import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
1411
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
1512
import { IFileService } from 'vs/platform/files/common/files';
16-
import { Queue } from 'vs/base/common/async';
1713
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
1814
import { localize } from 'vs/nls';
1915
import { merge } from 'vs/platform/userDataSync/common/extensionsMerge';
@@ -35,32 +31,17 @@ interface ILastSyncUserData extends IUserData {
3531

3632
export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
3733

38-
private static EXTERNAL_USER_DATA_EXTENSIONS_KEY: string = 'extensions';
39-
40-
private _status: SyncStatus = SyncStatus.Idle;
41-
get status(): SyncStatus { return this._status; }
42-
private _onDidChangStatus: Emitter<SyncStatus> = this._register(new Emitter<SyncStatus>());
43-
readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangStatus.event;
44-
45-
private _onDidChangeLocal: Emitter<void> = this._register(new Emitter<void>());
46-
readonly onDidChangeLocal: Event<void> = this._onDidChangeLocal.event;
47-
48-
private readonly lastSyncExtensionsResource: URI;
49-
private readonly replaceQueue: Queue<void>;
50-
5134
constructor(
5235
@IEnvironmentService environmentService: IEnvironmentService,
5336
@IFileService fileService: IFileService,
54-
@IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService,
37+
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
5538
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
5639
@IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService,
5740
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
5841
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
5942
@IConfigurationService private readonly configurationService: IConfigurationService,
6043
) {
61-
super(SyncSource.Extensions, fileService, environmentService);
62-
this.replaceQueue = this._register(new Queue());
63-
this.lastSyncExtensionsResource = joinPath(this.syncFolder, '.lastSyncExtensions');
44+
super(SyncSource.Extensions, fileService, environmentService, userDataSyncStoreService);
6445
this._register(
6546
Event.debounce(
6647
Event.any(
@@ -69,12 +50,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
6950
() => undefined, 500)(() => this._onDidChangeLocal.fire()));
7051
}
7152

72-
private setStatus(status: SyncStatus): void {
73-
if (this._status !== status) {
74-
this._status = status;
75-
this._onDidChangStatus.fire(status);
76-
}
77-
}
53+
protected getRemoteDataResourceKey(): string { return 'extensions'; }
7854

7955
async pull(): Promise<void> {
8056
if (!this.configurationService.getValue<boolean>('sync.enableExtensions')) {
@@ -176,16 +152,6 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
176152
throw new Error('Extensions: Conflicts should not occur');
177153
}
178154

179-
async hasPreviouslySynced(): Promise<boolean> {
180-
const lastSyncData = await this.getLastSyncUserData();
181-
return !!lastSyncData;
182-
}
183-
184-
async hasRemoteData(): Promise<boolean> {
185-
const remoteUserData = await this.getRemoteUserData();
186-
return remoteUserData.content !== null;
187-
}
188-
189155
async hasLocalData(): Promise<boolean> {
190156
try {
191157
const localExtensions = await this.getLocalExtensions();
@@ -202,30 +168,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
202168
return null;
203169
}
204170

205-
removeExtension(identifier: IExtensionIdentifier): Promise<void> {
206-
return this.replaceQueue.queue(async () => {
207-
const remoteData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, null);
208-
const remoteExtensions: ISyncExtension[] = remoteData.content ? JSON.parse(remoteData.content) : [];
209-
const ignoredExtensions = this.configurationService.getValue<string[]>('sync.ignoredExtensions') || [];
210-
const removedExtensions = remoteExtensions.filter(e => !ignoredExtensions.some(id => areSameExtensions({ id }, e.identifier)) && areSameExtensions(e.identifier, identifier));
211-
if (removedExtensions.length) {
212-
for (const removedExtension of removedExtensions) {
213-
remoteExtensions.splice(remoteExtensions.indexOf(removedExtension), 1);
214-
}
215-
this.logService.info(`Extensions: Removing extension '${identifier.id}' from remote.`);
216-
await this.writeToRemote(remoteExtensions, remoteData.ref);
217-
}
218-
});
219-
}
220-
221-
async resetLocal(): Promise<void> {
222-
try {
223-
await this.fileService.del(this.lastSyncExtensionsResource);
224-
} catch (e) { /* ignore */ }
225-
}
226-
227171
private async getPreview(): Promise<ISyncPreviewResult> {
228-
const lastSyncData = await this.getLastSyncUserData();
172+
const lastSyncData = await this.getLastSyncUserData<ILastSyncUserData>();
229173
const lastSyncExtensions: ISyncExtension[] | null = lastSyncData ? JSON.parse(lastSyncData.content!) : null;
230174
const skippedExtensions: ISyncExtension[] = lastSyncData ? lastSyncData.skippedExtensions || [] : [];
231175

@@ -262,13 +206,15 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
262206
if (remote) {
263207
// update remote
264208
this.logService.info('Extensions: Updating remote extensions...');
265-
remoteUserData = await this.writeToRemote(remote, forcePush ? null : remoteUserData.ref);
209+
const content = JSON.stringify(remote);
210+
const ref = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref);
211+
remoteUserData = { ref, content };
266212
}
267213

268214
if (remoteUserData.content) {
269215
// update last sync
270216
this.logService.info('Extensions: Updating last synchronised extensions...');
271-
await this.updateLastSyncValue({ ...remoteUserData, skippedExtensions });
217+
await this.updateLastSyncUserData<ILastSyncUserData>({ ...remoteUserData, skippedExtensions });
272218
}
273219
}
274220

@@ -331,27 +277,4 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
331277
.map(({ identifier }) => ({ identifier, enabled: !disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier)) }));
332278
}
333279

334-
private async getLastSyncUserData(): Promise<ILastSyncUserData | null> {
335-
try {
336-
const content = await this.fileService.readFile(this.lastSyncExtensionsResource);
337-
return JSON.parse(content.value.toString());
338-
} catch (error) {
339-
return null;
340-
}
341-
}
342-
343-
private async updateLastSyncValue(lastSyncUserData: ILastSyncUserData): Promise<void> {
344-
await this.fileService.writeFile(this.lastSyncExtensionsResource, VSBuffer.fromString(JSON.stringify(lastSyncUserData)));
345-
}
346-
347-
private getRemoteUserData(lastSyncData?: IUserData | null): Promise<IUserData> {
348-
return this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, lastSyncData || null, this.source);
349-
}
350-
351-
private async writeToRemote(extensions: ISyncExtension[], ref: string | null): Promise<IUserData> {
352-
const content = JSON.stringify(extensions);
353-
ref = await this.userDataSyncStoreService.write(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, content, ref, this.source);
354-
return { content, ref };
355-
}
356-
357280
}

0 commit comments

Comments
 (0)