diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 94633b4613b9f..88e5a11f829bf 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -733,6 +733,16 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { } } + protected async deleteLocalFile(): Promise { + try { + await this.fileService.del(this.file); + } catch (e) { + if (!(e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_NOT_FOUND)) { + throw e; + } + } + } + private onFileChanges(e: FileChangesEvent): void { if (!e.contains(this.file)) { return; diff --git a/src/vs/platform/userDataSync/common/tasksSync.ts b/src/vs/platform/userDataSync/common/tasksSync.ts index 544a2ea5726b5..8873af72210dc 100644 --- a/src/vs/platform/userDataSync/common/tasksSync.ts +++ b/src/vs/platform/userDataSync/common/tasksSync.ts @@ -18,7 +18,7 @@ import { AbstractFileSynchroniser, AbstractInitializer, IAcceptResult, IFileReso import { Change, IRemoteUserData, IUserDataSyncBackupStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; interface ITasksSyncContent { - tasks: string; + tasks?: string; } interface ITasksResourcePreview extends IFileResourcePreview { @@ -28,7 +28,7 @@ interface ITasksResourcePreview extends IFileResourcePreview { export function getTasksContentFromSyncContent(syncContent: string, logService: ILogService): string | null { try { const parsed = JSON.parse(syncContent); - return parsed.tasks; + return parsed.tasks ?? null; } catch (e) { logService.error(e); return null; @@ -76,7 +76,7 @@ export class TasksSynchroniser extends AbstractFileSynchroniser implements IUser let hasRemoteChanged: boolean = false; let hasConflicts: boolean = false; - if (remoteContent) { + if (remoteUserData.syncData) { const localContent = fileContent ? fileContent.value.toString() : null; if (!lastSyncContent // First time sync || lastSyncContent !== localContent // Local has forwarded @@ -196,13 +196,17 @@ export class TasksSynchroniser extends AbstractFileSynchroniser implements IUser if (fileContent) { await this.backupLocal(JSON.stringify(this.toTasksSyncContent(fileContent.value.toString()))); } - await this.updateLocalFileContent(content || '{}', fileContent, force); + if (content) { + await this.updateLocalFileContent(content, fileContent, force); + } else { + await this.deleteLocalFile(); + } this.logService.info(`${this.syncResourceLogLabel}: Updated local tasks`); } if (remoteChange !== Change.None) { this.logService.trace(`${this.syncResourceLogLabel}: Updating remote tasks...`); - const remoteContents = JSON.stringify(this.toTasksSyncContent(content || '{}')); + const remoteContents = JSON.stringify(this.toTasksSyncContent(content)); remoteUserData = await this.updateRemoteUserData(remoteContents, force ? null : remoteUserData.ref); this.logService.info(`${this.syncResourceLogLabel}: Updated remote tasks`); } @@ -235,8 +239,8 @@ export class TasksSynchroniser extends AbstractFileSynchroniser implements IUser return null; } - private toTasksSyncContent(tasks: string): ITasksSyncContent { - return { tasks }; + private toTasksSyncContent(tasks: string | null): ITasksSyncContent { + return tasks ? { tasks } : {}; } } diff --git a/src/vs/platform/userDataSync/test/common/tasksSync.test.ts b/src/vs/platform/userDataSync/test/common/tasksSync.test.ts index e779a9b7df85a..cc9760bcf4c05 100644 --- a/src/vs/platform/userDataSync/test/common/tasksSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/tasksSync.test.ts @@ -444,6 +444,34 @@ suite('TasksSync', () => { assert.strictEqual((await fileService.readFile(tasksResource)).value.toString(), content); }); + test('when tasks file was removed in one client', async () => { + const fileService = client.instantiationService.get(IFileService); + const tasksResource = client.instantiationService.get(IUserDataProfilesService).defaultProfile.tasksResource; + await fileService.writeFile(tasksResource, VSBuffer.fromString(JSON.stringify({ + 'version': '2.0.0', + 'tasks': [] + }))); + await testObject.sync(await client.getResourceManifest()); + + const client2 = disposableStore.add(new UserDataSyncClient(server)); + await client2.setUp(true); + await client2.sync(); + + const tasksResource2 = client2.instantiationService.get(IUserDataProfilesService).defaultProfile.tasksResource; + const fileService2 = client2.instantiationService.get(IFileService); + fileService2.del(tasksResource2); + await client2.sync(); + + await testObject.sync(await client.getResourceManifest()); + + assert.deepStrictEqual(testObject.status, SyncStatus.Idle); + const lastSyncUserData = await testObject.getLastSyncUserData(); + const remoteUserData = await testObject.getRemoteUserData(null); + assert.strictEqual(getTasksContentFromSyncContent(lastSyncUserData!.syncData!.content!, client.instantiationService.get(ILogService)), null); + assert.strictEqual(getTasksContentFromSyncContent(remoteUserData!.syncData!.content!, client.instantiationService.get(ILogService)), null); + assert.strictEqual(await fileService.exists(tasksResource), false); + }); + test('when tasks file is created after first sync', async () => { const fileService = client.instantiationService.get(IFileService); const tasksResource = client.instantiationService.get(IUserDataProfilesService).defaultProfile.tasksResource; @@ -491,7 +519,7 @@ suite('TasksSync', () => { assert.deepStrictEqual(server.requests, []); }); - test('sync profile snippets', async () => { + test('sync profile tasks', async () => { const client2 = disposableStore.add(new UserDataSyncClient(server)); await client2.setUp(true); const profile = await client2.instantiationService.get(IUserDataProfilesService).createNamedProfile('profile1');