Skip to content

Commit

Permalink
#125970 move user configuration writes into separate service
Browse files Browse the repository at this point in the history
  • Loading branch information
sandy081 authored and jrieken committed Jun 15, 2021
1 parent 70ff746 commit a16da6a
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 80 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Queue } from 'vs/base/common/async';
import { VSBuffer } from 'vs/base/common/buffer';
import { JSONPath, parse, ParseError } from 'vs/base/common/json';
import { setProperty } from 'vs/base/common/jsonEdit';
import { Edit, FormattingOptions } from 'vs/base/common/jsonFormatter';
import { URI } from 'vs/base/common/uri';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';

export const enum UserConfigurationErrorCode {
ERROR_INVALID_FILE = 'ERROR_INVALID_FILE',
ERROR_FILE_MODIFIED_SINCE = 'ERROR_FILE_MODIFIED_SINCE'
}

export interface IJSONValue {
path: JSONPath;
value: any;
}

export const UserConfigurationFileServiceId = 'IUserConfigurationFileService';
export const IUserConfigurationFileService = createDecorator<IUserConfigurationFileService>(UserConfigurationFileServiceId);

export interface IUserConfigurationFileService {
readonly _serviceBrand: undefined;

updateSettings(value: IJSONValue, formattingOptions: FormattingOptions): Promise<void>;
}

export class UserConfigurationFileService implements IUserConfigurationFileService {

readonly _serviceBrand: undefined;

private readonly queue: Queue<void>;

constructor(
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IFileService private readonly fileService: IFileService,
@ILogService private readonly logService: ILogService,
) {
this.queue = new Queue<void>();
}

async updateSettings(value: IJSONValue, formattingOptions: FormattingOptions): Promise<void> {
return this.queue.queue(() => this.doWrite(this.environmentService.settingsResource, value, formattingOptions)); // queue up writes to prevent race conditions
}

private async doWrite(resource: URI, jsonValue: IJSONValue, formattingOptions: FormattingOptions): Promise<void> {
this.logService.trace(`${UserConfigurationFileServiceId}#write`, resource.toString(), jsonValue);
const { value, mtime, etag } = await this.fileService.readFile(resource, { atomic: true });
let content = value.toString();

const parseErrors: ParseError[] = [];
parse(content, parseErrors, { allowTrailingComma: true, allowEmptyContent: true });
if (parseErrors.length) {
throw new Error(UserConfigurationErrorCode.ERROR_INVALID_FILE);
}

const edit = this.getEdits(jsonValue, content, formattingOptions)[0];
if (edit) {
content = content.substring(0, edit.offset) + edit.content + content.substring(edit.offset + edit.length);
try {
await this.fileService.writeFile(resource, VSBuffer.fromString(content), { etag, mtime });
} catch (error) {
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE) {
throw new Error(UserConfigurationErrorCode.ERROR_FILE_MODIFIED_SINCE);
}
}
}
}

private getEdits({ value, path }: IJSONValue, modelContent: string, formattingOptions: FormattingOptions): Edit[] {
if (path.length) {
return setProperty(modelContent, path, value, formattingOptions);
}

// Without jsonPath, the entire configuration file is being replaced, so we just use JSON.stringify
const content = JSON.stringify(value, null, formattingOptions.insertSpaces && formattingOptions.tabSize ? ' '.repeat(formattingOptions.tabSize) : '\t');
return [{
content,
length: modelContent.length,
offset: 0
}];
}
}

Loading

0 comments on commit a16da6a

Please sign in to comment.