debt: remove user settings!
This commit is contained in:
parent
0c6503e12b
commit
a3b83e9d2d
|
@ -1,234 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as json from 'vs/base/common/json';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import {TPromise} from 'vs/base/common/winjs.base';
|
||||
import Event, {Emitter} from 'vs/base/common/event';
|
||||
|
||||
export interface ISettings {
|
||||
settings: any;
|
||||
settingsParseErrors?: string[];
|
||||
keybindings: any;
|
||||
}
|
||||
|
||||
export class UserSettings {
|
||||
|
||||
private static CHANGE_BUFFER_DELAY = 300;
|
||||
|
||||
globalSettings: ISettings;
|
||||
|
||||
private timeoutHandle: number;
|
||||
private watcher: fs.FSWatcher;
|
||||
private appSettingsPath: string;
|
||||
private appKeybindingsPath: string;
|
||||
|
||||
private _onChange: Emitter<ISettings>;
|
||||
|
||||
constructor(appSettingsPath: string, appKeybindingsPath: string) {
|
||||
this.appSettingsPath = appSettingsPath;
|
||||
this.appKeybindingsPath = appKeybindingsPath;
|
||||
this._onChange = new Emitter<ISettings>();
|
||||
|
||||
this.registerWatchers();
|
||||
}
|
||||
|
||||
static getValue(userDataPath: string, key: string, fallback?: any): TPromise<any> {
|
||||
// TODO@joao cleanup!
|
||||
const appSettingsPath = path.join(userDataPath, 'User', 'settings.json');
|
||||
|
||||
return new TPromise((c, e) => {
|
||||
fs.readFile(appSettingsPath, (error /* ignore */, fileContents) => {
|
||||
let root = Object.create(null);
|
||||
let content = fileContents ? fileContents.toString() : '{}';
|
||||
|
||||
let contents = Object.create(null);
|
||||
try {
|
||||
contents = json.parse(content);
|
||||
} catch (error) {
|
||||
// ignore parse problem
|
||||
}
|
||||
|
||||
for (let key in contents) {
|
||||
UserSettings.setNode(root, key, contents[key]);
|
||||
}
|
||||
|
||||
return c(UserSettings.doGetValue(root, key, fallback));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
get onChange(): Event<ISettings> {
|
||||
return this._onChange.event;
|
||||
}
|
||||
|
||||
getValue(key: string, fallback?: any): any {
|
||||
return UserSettings.doGetValue(this.globalSettings.settings, key, fallback);
|
||||
}
|
||||
|
||||
private static doGetValue(globalSettings: any, key: string, fallback?: any): any {
|
||||
if (!key) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
let value = globalSettings;
|
||||
|
||||
let parts = key.split('\.');
|
||||
while (parts.length && value) {
|
||||
let part = parts.shift();
|
||||
value = value[part];
|
||||
}
|
||||
|
||||
return typeof value !== 'undefined' ? value : fallback;
|
||||
}
|
||||
|
||||
private registerWatchers(): void {
|
||||
let self = this;
|
||||
function attachSettingsChangeWatcher(watchPath: string): void {
|
||||
self.watcher = fs.watch(watchPath);
|
||||
self.watcher.on('change', () => self.onSettingsFileChange());
|
||||
}
|
||||
|
||||
// Attach a watcher to the settings directory
|
||||
attachSettingsChangeWatcher(path.dirname(this.appSettingsPath));
|
||||
|
||||
// Follow symlinks for settings and keybindings and attach watchers if they resolve
|
||||
let followSymlinkPaths = [
|
||||
this.appSettingsPath,
|
||||
this.appKeybindingsPath
|
||||
];
|
||||
followSymlinkPaths.forEach((path) => {
|
||||
fs.lstat(path, (err, stats) => {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
if (stats.isSymbolicLink() && !stats.isDirectory()) {
|
||||
fs.readlink(path, (err, realPath) => {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
attachSettingsChangeWatcher(realPath);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private onSettingsFileChange(): void {
|
||||
|
||||
// we can get multiple change events for one change, so we buffer through a timeout
|
||||
if (this.timeoutHandle) {
|
||||
global.clearTimeout(this.timeoutHandle);
|
||||
this.timeoutHandle = null;
|
||||
}
|
||||
|
||||
this.timeoutHandle = global.setTimeout(() => {
|
||||
|
||||
// Reload
|
||||
let didChange = this.loadSync();
|
||||
|
||||
// Emit event
|
||||
if (didChange) {
|
||||
this._onChange.fire(this.globalSettings);
|
||||
}
|
||||
|
||||
}, UserSettings.CHANGE_BUFFER_DELAY);
|
||||
}
|
||||
|
||||
loadSync(): boolean {
|
||||
let loadedSettings = this.doLoadSync();
|
||||
if (!objects.equals(loadedSettings, this.globalSettings)) {
|
||||
|
||||
// Keep in class
|
||||
this.globalSettings = loadedSettings;
|
||||
|
||||
return true; // changed value
|
||||
}
|
||||
|
||||
return false; // no changed value
|
||||
}
|
||||
|
||||
private doLoadSync(): ISettings {
|
||||
let settings = this.doLoadSettingsSync();
|
||||
|
||||
return {
|
||||
settings: settings.contents,
|
||||
settingsParseErrors: settings.parseErrors,
|
||||
keybindings: this.doLoadKeybindingsSync()
|
||||
};
|
||||
}
|
||||
|
||||
private doLoadSettingsSync(): { contents: any; parseErrors?: string[]; } {
|
||||
let root = Object.create(null);
|
||||
let content = '{}';
|
||||
try {
|
||||
content = fs.readFileSync(this.appSettingsPath).toString();
|
||||
} catch (error) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
let contents = Object.create(null);
|
||||
try {
|
||||
contents = json.parse(content);
|
||||
} catch (error) {
|
||||
// parse problem
|
||||
return {
|
||||
contents: Object.create(null),
|
||||
parseErrors: [this.appSettingsPath]
|
||||
};
|
||||
}
|
||||
|
||||
for (let key in contents) {
|
||||
UserSettings.setNode(root, key, contents[key]);
|
||||
}
|
||||
|
||||
return {
|
||||
contents: root
|
||||
};
|
||||
}
|
||||
|
||||
private static setNode(root: any, key: string, value: any): any {
|
||||
let segments = key.split('.');
|
||||
let last = segments.pop();
|
||||
|
||||
let curr = root;
|
||||
segments.forEach((s) => {
|
||||
let obj = curr[s];
|
||||
switch (typeof obj) {
|
||||
case 'undefined':
|
||||
obj = curr[s] = {};
|
||||
break;
|
||||
case 'object':
|
||||
break;
|
||||
default:
|
||||
console.log('Conflicting user settings: ' + key + ' at ' + s + ' with ' + JSON.stringify(obj));
|
||||
}
|
||||
curr = obj;
|
||||
});
|
||||
curr[last] = value;
|
||||
}
|
||||
|
||||
private doLoadKeybindingsSync(): any {
|
||||
try {
|
||||
return json.parse(fs.readFileSync(this.appKeybindingsPath).toString());
|
||||
} catch (error) {
|
||||
// Ignore loading and parsing errors
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (this.watcher) {
|
||||
this.watcher.close();
|
||||
this.watcher = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,14 +6,22 @@
|
|||
'use strict';
|
||||
|
||||
import { app } from 'electron';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as json from 'vs/base/common/json';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { UserSettings, ISettings } from 'vs/base/node/userSettings';
|
||||
import { IEnvService } from 'vs/code/electron-main/env';
|
||||
import Event from 'vs/base/common/event';
|
||||
import Event, {Emitter} from 'vs/base/common/event';
|
||||
|
||||
export const ISettingsService = createDecorator<ISettingsService>('settingsService');
|
||||
|
||||
// TODO@Joao TODO@Ben - this needs to die
|
||||
export interface ISettings {
|
||||
settings: any;
|
||||
settingsParseErrors?: string[];
|
||||
keybindings: any;
|
||||
}
|
||||
|
||||
export interface ISettingsService {
|
||||
_serviceBrand: any;
|
||||
globalSettings: ISettings;
|
||||
|
@ -22,20 +30,47 @@ export interface ISettingsService {
|
|||
onChange: Event<ISettings>;
|
||||
}
|
||||
|
||||
export class SettingsManager extends UserSettings implements ISettingsService {
|
||||
/**
|
||||
* TODO@Joao TODO@Ben - this needs to die
|
||||
*
|
||||
* We need to decouple the settings with the keybindings.
|
||||
* We need to have each participant (renderer, main, cli, shared) be able
|
||||
* to listen on changes to each of these files, independently.
|
||||
*/
|
||||
|
||||
export class SettingsManager implements ISettingsService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
constructor(@IEnvService envService: IEnvService) {
|
||||
super(envService.appSettingsPath, envService.appKeybindingsPath);
|
||||
private static CHANGE_BUFFER_DELAY = 300;
|
||||
|
||||
app.on('will-quit', () => {
|
||||
this.dispose();
|
||||
});
|
||||
globalSettings: ISettings;
|
||||
|
||||
private timeoutHandle: number;
|
||||
private watcher: fs.FSWatcher;
|
||||
private appSettingsPath: string;
|
||||
private appKeybindingsPath: string;
|
||||
|
||||
private _onChange: Emitter<ISettings>;
|
||||
|
||||
constructor(@IEnvService envService: IEnvService) {
|
||||
this.appSettingsPath = envService.appSettingsPath;
|
||||
this.appKeybindingsPath = envService.appKeybindingsPath;
|
||||
this._onChange = new Emitter<ISettings>();
|
||||
|
||||
this.registerWatchers();
|
||||
app.on('will-quit', () => this.dispose());
|
||||
}
|
||||
|
||||
loadSync(): boolean {
|
||||
const settingsChanged = super.loadSync();
|
||||
let settingsChanged = false;
|
||||
let loadedSettings = this.doLoadSync();
|
||||
if (!objects.equals(loadedSettings, this.globalSettings)) {
|
||||
|
||||
// Keep in class
|
||||
this.globalSettings = loadedSettings;
|
||||
settingsChanged = true; // changed value
|
||||
}
|
||||
|
||||
// Store into global so that any renderer can access the value with remote.getGlobal()
|
||||
if (settingsChanged) {
|
||||
|
@ -44,4 +79,158 @@ export class SettingsManager extends UserSettings implements ISettingsService {
|
|||
|
||||
return settingsChanged;
|
||||
}
|
||||
|
||||
get onChange(): Event<ISettings> {
|
||||
return this._onChange.event;
|
||||
}
|
||||
|
||||
getValue(key: string, fallback?: any): any {
|
||||
return SettingsManager.doGetValue(this.globalSettings.settings, key, fallback);
|
||||
}
|
||||
|
||||
private static doGetValue(globalSettings: any, key: string, fallback?: any): any {
|
||||
if (!key) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
let value = globalSettings;
|
||||
|
||||
let parts = key.split('\.');
|
||||
while (parts.length && value) {
|
||||
let part = parts.shift();
|
||||
value = value[part];
|
||||
}
|
||||
|
||||
return typeof value !== 'undefined' ? value : fallback;
|
||||
}
|
||||
|
||||
private registerWatchers(): void {
|
||||
let self = this;
|
||||
function attachSettingsChangeWatcher(watchPath: string): void {
|
||||
self.watcher = fs.watch(watchPath);
|
||||
self.watcher.on('change', () => self.onSettingsFileChange());
|
||||
}
|
||||
|
||||
// Attach a watcher to the settings directory
|
||||
attachSettingsChangeWatcher(path.dirname(this.appSettingsPath));
|
||||
|
||||
// Follow symlinks for settings and keybindings and attach watchers if they resolve
|
||||
let followSymlinkPaths = [
|
||||
this.appSettingsPath,
|
||||
this.appKeybindingsPath
|
||||
];
|
||||
followSymlinkPaths.forEach((path) => {
|
||||
fs.lstat(path, (err, stats) => {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
if (stats.isSymbolicLink() && !stats.isDirectory()) {
|
||||
fs.readlink(path, (err, realPath) => {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
attachSettingsChangeWatcher(realPath);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private onSettingsFileChange(): void {
|
||||
|
||||
// we can get multiple change events for one change, so we buffer through a timeout
|
||||
if (this.timeoutHandle) {
|
||||
global.clearTimeout(this.timeoutHandle);
|
||||
this.timeoutHandle = null;
|
||||
}
|
||||
|
||||
this.timeoutHandle = global.setTimeout(() => {
|
||||
|
||||
// Reload
|
||||
let didChange = this.loadSync();
|
||||
|
||||
// Emit event
|
||||
if (didChange) {
|
||||
this._onChange.fire(this.globalSettings);
|
||||
}
|
||||
|
||||
}, SettingsManager.CHANGE_BUFFER_DELAY);
|
||||
}
|
||||
|
||||
private doLoadSync(): ISettings {
|
||||
let settings = this.doLoadSettingsSync();
|
||||
|
||||
return {
|
||||
settings: settings.contents,
|
||||
settingsParseErrors: settings.parseErrors,
|
||||
keybindings: this.doLoadKeybindingsSync()
|
||||
};
|
||||
}
|
||||
|
||||
private doLoadSettingsSync(): { contents: any; parseErrors?: string[]; } {
|
||||
let root = Object.create(null);
|
||||
let content = '{}';
|
||||
try {
|
||||
content = fs.readFileSync(this.appSettingsPath).toString();
|
||||
} catch (error) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
let contents = Object.create(null);
|
||||
try {
|
||||
contents = json.parse(content);
|
||||
} catch (error) {
|
||||
// parse problem
|
||||
return {
|
||||
contents: Object.create(null),
|
||||
parseErrors: [this.appSettingsPath]
|
||||
};
|
||||
}
|
||||
|
||||
for (let key in contents) {
|
||||
SettingsManager.setNode(root, key, contents[key]);
|
||||
}
|
||||
|
||||
return {
|
||||
contents: root
|
||||
};
|
||||
}
|
||||
|
||||
private static setNode(root: any, key: string, value: any): any {
|
||||
let segments = key.split('.');
|
||||
let last = segments.pop();
|
||||
|
||||
let curr = root;
|
||||
segments.forEach((s) => {
|
||||
let obj = curr[s];
|
||||
switch (typeof obj) {
|
||||
case 'undefined':
|
||||
obj = curr[s] = {};
|
||||
break;
|
||||
case 'object':
|
||||
break;
|
||||
default:
|
||||
console.log('Conflicting user settings: ' + key + ' at ' + s + ' with ' + JSON.stringify(obj));
|
||||
}
|
||||
curr = obj;
|
||||
});
|
||||
curr[last] = value;
|
||||
}
|
||||
|
||||
private doLoadKeybindingsSync(): any {
|
||||
try {
|
||||
return json.parse(fs.readFileSync(this.appKeybindingsPath).toString());
|
||||
} catch (error) {
|
||||
// Ignore loading and parsing errors
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (this.watcher) {
|
||||
this.watcher.close();
|
||||
this.watcher = null;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue