Merge from vscode 5d18ad4c5902e3bddbc9f78da82dfc2ac349e908 (#9683)

This commit is contained in:
Anthony Dresser
2020-03-20 01:17:27 -07:00
committed by GitHub
parent 1520441b84
commit dd8fb9433b
89 changed files with 3095 additions and 445 deletions

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { IFileService, IFileContent, FileChangesEvent, FileSystemProviderError, FileSystemProviderErrorCode, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files';
import { IFileService, IFileContent, FileChangesEvent, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files';
import { VSBuffer } from 'vs/base/common/buffer';
import { URI } from 'vs/base/common/uri';
import { SyncResource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, Conflict } from 'vs/platform/userDataSync/common/userDataSync';
@@ -110,6 +110,9 @@ export abstract class AbstractSynchroniser extends Disposable {
async sync(ref?: string): Promise<void> {
if (!this.isEnabled()) {
if (this.status !== SyncStatus.Idle) {
await this.stop();
}
this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as it is disabled.`);
return;
}
@@ -264,6 +267,7 @@ export abstract class AbstractSynchroniser extends Disposable {
protected abstract readonly version: number;
protected abstract performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<SyncStatus>;
abstract stop(): Promise<void>;
}
export interface IFileSyncPreviewResult {
@@ -299,7 +303,7 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
async stop(): Promise<void> {
this.cancel();
this.logService.trace(`${this.syncResourceLogLabel}: Stopped synchronizing ${this.resource.toLowerCase()}.`);
this.logService.info(`${this.syncResourceLogLabel}: Stopped synchronizing ${this.resource.toLowerCase()}.`);
try {
await this.fileService.del(this.localPreviewResource);
} catch (e) { /* ignore */ }
@@ -339,7 +343,7 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
await this.fileService.createFile(this.file, VSBuffer.fromString(newContent), { overwrite: false });
}
} catch (e) {
if ((e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) ||
if ((e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_NOT_FOUND) ||
(e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE)) {
throw new UserDataSyncError(e.message, UserDataSyncErrorCode.LocalPreconditionFailed);
} else {

View File

@@ -0,0 +1,202 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { values } from 'vs/base/common/map';
import { IStringDictionary } from 'vs/base/common/collections';
import { deepClone } from 'vs/base/common/objects';
export interface IMergeResult {
added: IStringDictionary<string>;
updated: IStringDictionary<string>;
removed: string[];
conflicts: string[];
remote: IStringDictionary<string> | null;
}
export function merge(local: IStringDictionary<string>, remote: IStringDictionary<string> | null, base: IStringDictionary<string> | null, resolvedConflicts: IStringDictionary<string | null> = {}): IMergeResult {
const added: IStringDictionary<string> = {};
const updated: IStringDictionary<string> = {};
const removed: Set<string> = new Set<string>();
if (!remote) {
return {
added,
removed: values(removed),
updated,
conflicts: [],
remote: local
};
}
const localToRemote = compare(local, remote);
if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) {
// No changes found between local and remote.
return {
added,
removed: values(removed),
updated,
conflicts: [],
remote: null
};
}
const baseToLocal = compare(base, local);
const baseToRemote = compare(base, remote);
const remoteContent: IStringDictionary<string> = deepClone(remote);
const conflicts: Set<string> = new Set<string>();
const handledConflicts: Set<string> = new Set<string>();
const handleConflict = (key: string): void => {
if (handledConflicts.has(key)) {
return;
}
handledConflicts.add(key);
const conflictContent = resolvedConflicts[key];
// add to conflicts
if (conflictContent === undefined) {
conflicts.add(key);
}
// remove the snippet
else if (conflictContent === null) {
delete remote[key];
if (local[key]) {
removed.add(key);
}
}
// add/update the snippet
else {
if (local[key]) {
if (local[key] !== conflictContent) {
updated[key] = conflictContent;
}
} else {
added[key] = conflictContent;
}
remoteContent[key] = conflictContent;
}
};
// Removed snippets in Local
for (const key of values(baseToLocal.removed)) {
// Conflict - Got updated in remote.
if (baseToRemote.updated.has(key)) {
// Add to local
added[key] = remote[key];
}
// Remove it in remote
else {
delete remoteContent[key];
}
}
// Removed snippets in Remote
for (const key of values(baseToRemote.removed)) {
if (handledConflicts.has(key)) {
continue;
}
// Conflict - Got updated in local
if (baseToLocal.updated.has(key)) {
handleConflict(key);
}
// Also remove in Local
else {
removed.add(key);
}
}
// Updated snippets in Local
for (const key of values(baseToLocal.updated)) {
if (handledConflicts.has(key)) {
continue;
}
// Got updated in remote
if (baseToRemote.updated.has(key)) {
// Has different value
if (localToRemote.updated.has(key)) {
handleConflict(key);
}
} else {
remoteContent[key] = local[key];
}
}
// Updated snippets in Remote
for (const key of values(baseToRemote.updated)) {
if (handledConflicts.has(key)) {
continue;
}
// Got updated in local
if (baseToLocal.updated.has(key)) {
// Has different value
if (localToRemote.updated.has(key)) {
handleConflict(key);
}
} else if (local[key] !== undefined) {
updated[key] = remote[key];
}
}
// Added snippets in Local
for (const key of values(baseToLocal.added)) {
if (handledConflicts.has(key)) {
continue;
}
// Got added in remote
if (baseToRemote.added.has(key)) {
// Has different value
if (localToRemote.updated.has(key)) {
handleConflict(key);
}
} else {
remoteContent[key] = local[key];
}
}
// Added snippets in remote
for (const key of values(baseToRemote.added)) {
if (handledConflicts.has(key)) {
continue;
}
// Got added in local
if (baseToLocal.added.has(key)) {
// Has different value
if (localToRemote.updated.has(key)) {
handleConflict(key);
}
} else {
added[key] = remote[key];
}
}
return { added, removed: values(removed), updated, conflicts: values(conflicts), remote: areSame(remote, remoteContent) ? null : remoteContent };
}
function compare(from: IStringDictionary<string> | null, to: IStringDictionary<string> | null): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
const fromKeys = from ? Object.keys(from) : [];
const toKeys = to ? Object.keys(to) : [];
const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
const updated: Set<string> = new Set<string>();
for (const key of fromKeys) {
if (removed.has(key)) {
continue;
}
const fromSnippet = from![key]!;
const toSnippet = to![key]!;
if (fromSnippet !== toSnippet) {
updated.add(key);
}
}
return { added, removed, updated };
}
function areSame(a: IStringDictionary<string>, b: IStringDictionary<string>): boolean {
const { added, removed, updated } = compare(a, b);
return added.size === 0 && removed.size === 0 && updated.size === 0;
}

View File

@@ -0,0 +1,403 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, Conflict, USER_DATA_SYNC_SCHEME, PREVIEW_DIR_NAME, UserDataSyncError, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IFileService, FileChangesEvent, IFileStat, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { AbstractSynchroniser, IRemoteUserData, ISyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IStringDictionary } from 'vs/base/common/collections';
import { URI } from 'vs/base/common/uri';
import { joinPath, extname, relativePath, isEqualOrParent, isEqual, basename } from 'vs/base/common/resources';
import { VSBuffer } from 'vs/base/common/buffer';
import { merge } from 'vs/platform/userDataSync/common/snippetsMerge';
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
interface ISyncPreviewResult {
readonly local: IStringDictionary<IFileContent>;
readonly remoteUserData: IRemoteUserData;
readonly lastSyncUserData: IRemoteUserData | null;
readonly added: IStringDictionary<string>;
readonly updated: IStringDictionary<string>;
readonly removed: string[];
readonly conflicts: Conflict[];
readonly resolvedConflicts: IStringDictionary<string | null>;
readonly remote: IStringDictionary<string> | null;
}
export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
protected readonly version: number = 1;
private readonly snippetsFolder: URI;
private readonly snippetsPreviewFolder: URI;
private syncPreviewResultPromise: CancelablePromise<ISyncPreviewResult> | null = null;
constructor(
@IEnvironmentService environmentService: IEnvironmentService,
@IFileService fileService: IFileService,
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
@IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService,
@IUserDataSyncLogService logService: IUserDataSyncLogService,
@IConfigurationService configurationService: IConfigurationService,
@IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
@ITelemetryService telemetryService: ITelemetryService,
) {
super(SyncResource.Snippets, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService);
this.snippetsFolder = environmentService.snippetsHome;
this.snippetsPreviewFolder = joinPath(this.syncFolder, PREVIEW_DIR_NAME);
this._register(this.fileService.watch(environmentService.userRoamingDataHome));
this._register(this.fileService.watch(this.snippetsFolder));
this._register(this.fileService.onDidFilesChange(e => this.onFileChanges(e)));
}
private onFileChanges(e: FileChangesEvent): void {
if (!e.changes.some(change => isEqualOrParent(change.resource, this.snippetsFolder))) {
return;
}
if (!this.isEnabled()) {
return;
}
// Sync again if local file has changed and current status is in conflicts
if (this.status === SyncStatus.HasConflicts) {
this.syncPreviewResultPromise!.then(result => {
this.cancel();
this.doSync(result.remoteUserData, result.lastSyncUserData).then(status => this.setStatus(status));
});
}
// Otherwise fire change event
else {
this._onDidChangeLocal.fire();
}
}
async pull(): Promise<void> {
if (!this.isEnabled()) {
this.logService.info(`${this.syncResourceLogLabel}: Skipped pulling snippets as it is disabled.`);
return;
}
this.stop();
try {
this.logService.info(`${this.syncResourceLogLabel}: Started pulling snippets...`);
this.setStatus(SyncStatus.Syncing);
const lastSyncUserData = await this.getLastSyncUserData();
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
if (remoteUserData.syncData !== null) {
const local = await this.getSnippetsFileContents();
const localSnippets = this.toSnippetsContents(local);
const remoteSnippets = this.parseSnippets(remoteUserData.syncData);
const { added, updated, remote, removed } = merge(localSnippets, remoteSnippets, localSnippets);
this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve<ISyncPreviewResult>({
added, removed, updated, remote, remoteUserData, local, lastSyncUserData, conflicts: [], resolvedConflicts: {}
}));
await this.apply();
}
// No remote exists to pull
else {
this.logService.info(`${this.syncResourceLogLabel}: Remote snippets does not exist.`);
}
this.logService.info(`${this.syncResourceLogLabel}: Finished pulling snippets.`);
} finally {
this.setStatus(SyncStatus.Idle);
}
}
async push(): Promise<void> {
if (!this.isEnabled()) {
this.logService.info(`${this.syncResourceLogLabel}: Skipped pushing snippets as it is disabled.`);
return;
}
this.stop();
try {
this.logService.info(`${this.syncResourceLogLabel}: Started pushing snippets...`);
this.setStatus(SyncStatus.Syncing);
const local = await this.getSnippetsFileContents();
const localSnippets = this.toSnippetsContents(local);
const { added, removed, updated, remote } = merge(localSnippets, null, null);
const lastSyncUserData = await this.getLastSyncUserData();
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve<ISyncPreviewResult>({
added, removed, updated, remote, remoteUserData, local, lastSyncUserData, conflicts: [], resolvedConflicts: {}
}));
await this.apply(true);
this.logService.info(`${this.syncResourceLogLabel}: Finished pushing snippets.`);
} finally {
this.setStatus(SyncStatus.Idle);
}
}
async stop(): Promise<void> {
await this.clearConflicts();
this.cancel();
this.logService.info(`${this.syncResourceLogLabel}: Stopped synchronizing ${this.syncResourceLogLabel}.`);
this.setStatus(SyncStatus.Idle);
}
async getConflictContent(conflictResource: URI): Promise<string | null> {
if (isEqualOrParent(conflictResource.with({ scheme: this.syncFolder.scheme }), this.snippetsPreviewFolder) && this.syncPreviewResultPromise) {
const result = await this.syncPreviewResultPromise;
const key = relativePath(this.snippetsPreviewFolder, conflictResource.with({ scheme: this.snippetsPreviewFolder.scheme }))!;
if (conflictResource.scheme === this.snippetsPreviewFolder.scheme) {
return result.local[key] ? result.local[key].value.toString() : null;
} else if (result.remoteUserData && result.remoteUserData.syncData) {
const snippets = this.parseSnippets(result.remoteUserData.syncData);
return snippets[key] || null;
}
}
return null;
}
async getRemoteContent(ref?: string, fragment?: string): Promise<string | null> {
const content = await super.getRemoteContent(ref);
if (content !== null && fragment) {
return this.getFragment(content, fragment);
}
return content;
}
async getLocalBackupContent(ref?: string, fragment?: string): Promise<string | null> {
let content = await super.getLocalBackupContent(ref);
if (content !== null && fragment) {
return this.getFragment(content, fragment);
}
return content;
}
private getFragment(content: string, fragment: string): string | null {
const syncData = this.parseSyncData(content);
return syncData ? this.getFragmentFromSyncData(syncData, fragment) : null;
}
private getFragmentFromSyncData(syncData: ISyncData, fragment: string): string | null {
switch (fragment) {
case 'snippets':
return syncData.content;
default:
const remoteSnippets = this.parseSnippets(syncData);
return remoteSnippets[fragment] || null;
}
}
async acceptConflict(conflictResource: URI, content: string): Promise<void> {
const conflict = this.conflicts.filter(({ local, remote }) => isEqual(local, conflictResource) || isEqual(remote, conflictResource))[0];
if (this.status === SyncStatus.HasConflicts && conflict) {
const key = relativePath(this.snippetsPreviewFolder, conflict.local)!;
let previewResult = await this.syncPreviewResultPromise!;
this.cancel();
previewResult.resolvedConflicts[key] = content || null;
this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(previewResult.local, previewResult.remoteUserData, previewResult.lastSyncUserData, previewResult.resolvedConflicts, token));
previewResult = await this.syncPreviewResultPromise;
this.setConflicts(previewResult.conflicts);
if (!this.conflicts.length) {
await this.apply();
this.setStatus(SyncStatus.Idle);
}
}
}
async hasLocalData(): Promise<boolean> {
try {
const localSnippets = await this.getSnippetsFileContents();
if (Object.keys(localSnippets).length) {
return true;
}
} catch (error) {
/* ignore error */
}
return false;
}
protected async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<SyncStatus> {
try {
const previewResult = await this.getPreview(remoteUserData, lastSyncUserData);
this.setConflicts(previewResult.conflicts);
if (this.conflicts.length) {
return SyncStatus.HasConflicts;
}
await this.apply();
return SyncStatus.Idle;
} catch (e) {
this.syncPreviewResultPromise = null;
if (e instanceof UserDataSyncError) {
switch (e.code) {
case UserDataSyncErrorCode.LocalPreconditionFailed:
// Rejected as there is a new local version. Syncing again.
this.logService.info(`${this.syncResourceLogLabel}: Failed to synchronize snippets as there is a new local version available. Synchronizing again...`);
return this.performSync(remoteUserData, lastSyncUserData);
}
}
throw e;
}
}
private getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<ISyncPreviewResult> {
if (!this.syncPreviewResultPromise) {
this.syncPreviewResultPromise = createCancelablePromise(token => this.getSnippetsFileContents()
.then(local => this.generatePreview(local, remoteUserData, lastSyncUserData, {}, token)));
}
return this.syncPreviewResultPromise;
}
protected cancel(): void {
if (this.syncPreviewResultPromise) {
this.syncPreviewResultPromise.cancel();
this.syncPreviewResultPromise = null;
}
}
private async clearConflicts(): Promise<void> {
if (this.conflicts.length) {
await Promise.all(this.conflicts.map(({ local }) => this.fileService.del(local)));
this.setConflicts([]);
}
}
private async generatePreview(local: IStringDictionary<IFileContent>, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: IStringDictionary<string | null>, token: CancellationToken): Promise<ISyncPreviewResult> {
const localSnippets = this.toSnippetsContents(local);
const remoteSnippets: IStringDictionary<string> | null = remoteUserData.syncData ? this.parseSnippets(remoteUserData.syncData) : null;
const lastSyncSnippets: IStringDictionary<string> | null = lastSyncUserData ? this.parseSnippets(lastSyncUserData.syncData!) : null;
if (remoteSnippets) {
this.logService.trace(`${this.syncResourceLogLabel}: Merging remote snippets with local snippets...`);
} else {
this.logService.trace(`${this.syncResourceLogLabel}: Remote snippets does not exist. Synchronizing snippets for the first time.`);
}
const mergeResult = merge(localSnippets, remoteSnippets, lastSyncSnippets, resolvedConflicts);
const conflicts: Conflict[] = [];
for (const key of mergeResult.conflicts) {
const localPreview = joinPath(this.snippetsPreviewFolder, key);
conflicts.push({ local: localPreview, remote: localPreview.with({ scheme: USER_DATA_SYNC_SCHEME }) });
const content = local[key];
if (!token.isCancellationRequested) {
await this.fileService.writeFile(localPreview, content ? content.value : VSBuffer.fromString(''));
}
}
for (const conflict of this.conflicts) {
// clear obsolete conflicts
if (!conflicts.some(({ local }) => isEqual(local, conflict.local))) {
try {
await this.fileService.del(conflict.local);
} catch (error) {
// Ignore & log
this.logService.error(error);
}
}
}
return { remoteUserData, local, lastSyncUserData, added: mergeResult.added, removed: mergeResult.removed, updated: mergeResult.updated, conflicts, remote: mergeResult.remote, resolvedConflicts };
}
private async apply(forcePush?: boolean): Promise<void> {
if (!this.syncPreviewResultPromise) {
return;
}
let { added, removed, updated, local, remote, remoteUserData, lastSyncUserData } = await this.syncPreviewResultPromise;
const hasChanges = Object.keys(added).length || removed.length || Object.keys(updated).length || remote;
if (!hasChanges) {
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing snippets.`);
}
if (Object.keys(added).length || removed.length || Object.keys(updated).length) {
// back up all snippets
await this.backupLocal(JSON.stringify(this.toSnippetsContents(local)));
await this.updateLocalSnippets(added, removed, updated, local);
}
if (remote) {
// update remote
this.logService.trace(`${this.syncResourceLogLabel}: Updating remote snippets...`);
const content = JSON.stringify(remote);
remoteUserData = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref);
this.logService.info(`${this.syncResourceLogLabel}: Updated remote snippets`);
}
if (lastSyncUserData?.ref !== remoteUserData.ref) {
// update last sync
this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized snippets...`);
await this.updateLastSyncUserData(remoteUserData);
this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized snippets`);
}
this.syncPreviewResultPromise = null;
}
private async updateLocalSnippets(added: IStringDictionary<string>, removed: string[], updated: IStringDictionary<string>, local: IStringDictionary<IFileContent>): Promise<void> {
for (const key of removed) {
const resource = joinPath(this.snippetsFolder, key);
this.logService.trace(`${this.syncResourceLogLabel}: Deleting snippet...`, basename(resource));
await this.fileService.del(resource);
this.logService.info(`${this.syncResourceLogLabel}: Deleted snippet`, basename(resource));
}
for (const key of Object.keys(added)) {
const resource = joinPath(this.snippetsFolder, key);
this.logService.trace(`${this.syncResourceLogLabel}: Creating snippet...`, basename(resource));
await this.fileService.createFile(resource, VSBuffer.fromString(added[key]), { overwrite: false });
this.logService.info(`${this.syncResourceLogLabel}: Created snippet`, basename(resource));
}
for (const key of Object.keys(updated)) {
const resource = joinPath(this.snippetsFolder, key);
this.logService.trace(`${this.syncResourceLogLabel}: Updating snippet...`, basename(resource));
await this.fileService.writeFile(resource, VSBuffer.fromString(updated[key]), local[key]);
this.logService.info(`${this.syncResourceLogLabel}: Updated snippet`, basename(resource));
}
}
private parseSnippets(syncData: ISyncData): IStringDictionary<string> {
return JSON.parse(syncData.content);
}
private toSnippetsContents(snippetsFileContents: IStringDictionary<IFileContent>): IStringDictionary<string> {
const snippets: IStringDictionary<string> = {};
for (const key of Object.keys(snippetsFileContents)) {
snippets[key] = snippetsFileContents[key].value.toString();
}
return snippets;
}
private async getSnippetsFileContents(): Promise<IStringDictionary<IFileContent>> {
const snippets: IStringDictionary<IFileContent> = {};
let stat: IFileStat;
try {
stat = await this.fileService.resolve(this.snippetsFolder);
} catch (e) {
// No snippets
if (e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_NOT_FOUND) {
return snippets;
} else {
throw e;
}
}
for (const entry of stat.children || []) {
const resource = entry.resource;
if (extname(resource) === '.json') {
const key = relativePath(this.snippetsFolder, resource)!;
const content = await this.fileService.readFile(resource);
snippets[key] = content;
}
}
return snippets;
}
}

View File

@@ -138,10 +138,11 @@ export function getUserDataSyncStore(productService: IProductService, configurat
export const enum SyncResource {
Settings = 'settings',
Keybindings = 'keybindings',
Snippets = 'snippets',
Extensions = 'extensions',
GlobalState = 'globalState'
}
export const ALL_SYNC_RESOURCES: SyncResource[] = [SyncResource.Settings, SyncResource.Keybindings, SyncResource.Extensions, SyncResource.GlobalState];
export const ALL_SYNC_RESOURCES: SyncResource[] = [SyncResource.Settings, SyncResource.Keybindings, SyncResource.Snippets, SyncResource.Extensions, SyncResource.GlobalState];
export interface IUserDataManifest {
latest?: Record<SyncResource, string>
@@ -373,10 +374,3 @@ export function getSyncResourceFromLocalPreview(localPreview: URI, environmentSe
localPreview = localPreview.with({ scheme: environmentService.userDataSyncHome.scheme });
return ALL_SYNC_RESOURCES.filter(syncResource => isEqualOrParent(localPreview, joinPath(environmentService.userDataSyncHome, syncResource, PREVIEW_DIR_NAME)))[0];
}
export function getSyncResourceFromRemotePreview(remotePreview: URI, environmentService: IEnvironmentService): SyncResource | undefined {
if (remotePreview.scheme !== USER_DATA_SYNC_SCHEME) {
return undefined;
}
remotePreview = remotePreview.with({ scheme: environmentService.userDataSyncHome.scheme });
return ALL_SYNC_RESOURCES.filter(syncResource => isEqualOrParent(remotePreview, joinPath(environmentService.userDataSyncHome, syncResource, PREVIEW_DIR_NAME)))[0];
}

View File

@@ -18,6 +18,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag
import { URI } from 'vs/base/common/uri';
import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync';
import { isEqual } from 'vs/base/common/resources';
import { SnippetsSynchroniser } from 'vs/platform/userDataSync/common/snippetsSync';
type SyncErrorClassification = {
source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
@@ -55,6 +56,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
private readonly settingsSynchroniser: SettingsSynchroniser;
private readonly keybindingsSynchroniser: KeybindingsSynchroniser;
private readonly snippetsSynchroniser: SnippetsSynchroniser;
private readonly extensionsSynchroniser: ExtensionsSynchroniser;
private readonly globalStateSynchroniser: GlobalStateSynchroniser;
@@ -68,9 +70,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
super();
this.settingsSynchroniser = this._register(this.instantiationService.createInstance(SettingsSynchroniser));
this.keybindingsSynchroniser = this._register(this.instantiationService.createInstance(KeybindingsSynchroniser));
this.snippetsSynchroniser = this._register(this.instantiationService.createInstance(SnippetsSynchroniser));
this.globalStateSynchroniser = this._register(this.instantiationService.createInstance(GlobalStateSynchroniser));
this.extensionsSynchroniser = this._register(this.instantiationService.createInstance(ExtensionsSynchroniser));
this.synchronisers = [this.settingsSynchroniser, this.keybindingsSynchroniser, this.globalStateSynchroniser, this.extensionsSynchroniser];
this.synchronisers = [this.settingsSynchroniser, this.keybindingsSynchroniser, this.snippetsSynchroniser, this.globalStateSynchroniser, this.extensionsSynchroniser];
this.updateStatus();
if (this.userDataSyncStoreService.userDataSyncStore) {