storageIpc.ts 6.8 KB
Newer Older
B
wip  
Benjamin Pasero 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
J
Joao Moreno 已提交
7
import { Event, Emitter } from 'vs/base/common/event';
8
import { IStorageChangeEvent, IStorageMainService } from 'vs/platform/storage/node/storageMainService';
9
import { IUpdateRequest, IStorageDatabase, IStorageItemsChangeEvent } from 'vs/base/parts/storage/common/storage';
10
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
11 12
import { ILogService } from 'vs/platform/log/common/log';
import { generateUuid } from 'vs/base/common/uuid';
R
Robo 已提交
13
import { instanceStorageKey, firstSessionDateStorageKey, lastSessionDateStorageKey, currentSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry';
14

15 16 17 18 19 20 21 22 23 24
type Key = string;
type Value = string;
type Item = [Key, Value];

interface ISerializableUpdateRequest {
	insert?: Item[];
	delete?: Key[];
}

interface ISerializableItemsChangeEvent {
B
Benjamin Pasero 已提交
25 26
	readonly changed?: Item[];
	readonly deleted?: Key[];
27
}
B
wip  
Benjamin Pasero 已提交
28

29
export class GlobalStorageDatabaseChannel extends Disposable implements IServerChannel {
B
wip  
Benjamin Pasero 已提交
30

31
	private static readonly STORAGE_CHANGE_DEBOUNCE_TIME = 100;
32

33 34
	private readonly _onDidChangeItems = this._register(new Emitter<ISerializableItemsChangeEvent>());
	readonly onDidChangeItems = this._onDidChangeItems.event;
B
wip  
Benjamin Pasero 已提交
35

B
Benjamin Pasero 已提交
36
	private readonly whenReady = this.init();
37 38 39

	constructor(
		private logService: ILogService,
40
		private storageMainService: IStorageMainService
41
	) {
42
		super();
43 44
	}

45 46 47 48
	private async init(): Promise<void> {
		try {
			await this.storageMainService.initialize();
		} catch (error) {
49
			this.logService.error(`[storage] init(): Unable to init global storage due to ${error}`);
50
		}
51

52 53 54 55
		// Apply global telemetry values as part of the initialization
		// These are global across all windows and thereby should be
		// written from the main process once.
		this.initTelemetry();
56

57 58
		// Setup storage change listeners
		this.registerListeners();
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
	}

	private initTelemetry(): void {
		const instanceId = this.storageMainService.get(instanceStorageKey, undefined);
		if (instanceId === undefined) {
			this.storageMainService.store(instanceStorageKey, generateUuid());
		}

		const firstSessionDate = this.storageMainService.get(firstSessionDateStorageKey, undefined);
		if (firstSessionDate === undefined) {
			this.storageMainService.store(firstSessionDateStorageKey, new Date().toUTCString());
		}

		const lastSessionDate = this.storageMainService.get(currentSessionDateStorageKey, undefined); // previous session date was the "current" one at that time
		const currentSessionDate = new Date().toUTCString(); // current session date is "now"
		this.storageMainService.store(lastSessionDateStorageKey, typeof lastSessionDate === 'undefined' ? null : lastSessionDate);
		this.storageMainService.store(currentSessionDateStorageKey, currentSessionDate);
76 77 78 79 80 81
	}

	private registerListeners(): void {

		// Listen for changes in global storage to send to listeners
		// that are listening. Use a debouncer to reduce IPC traffic.
82
		this._register(Event.debounce(this.storageMainService.onDidChangeStorage, (prev: IStorageChangeEvent[] | undefined, cur: IStorageChangeEvent) => {
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
			if (!prev) {
				prev = [cur];
			} else {
				prev.push(cur);
			}

			return prev;
		}, GlobalStorageDatabaseChannel.STORAGE_CHANGE_DEBOUNCE_TIME)(events => {
			if (events.length) {
				this._onDidChangeItems.fire(this.serializeEvents(events));
			}
		}));
	}

	private serializeEvents(events: IStorageChangeEvent[]): ISerializableItemsChangeEvent {
98 99
		const changed = new Map<Key, Value>();
		const deleted = new Set<Key>();
100 101 102
		events.forEach(event => {
			const existing = this.storageMainService.get(event.key);
			if (typeof existing === 'string') {
103 104 105
				changed.set(event.key, existing);
			} else {
				deleted.add(event.key);
106 107
			}
		});
108

B
Benjamin Pasero 已提交
109 110 111 112
		return {
			changed: Array.from(changed.entries()),
			deleted: Array.from(deleted.values())
		};
B
wip  
Benjamin Pasero 已提交
113 114
	}

115
	listen(_: unknown, event: string): Event<any> {
116 117 118
		switch (event) {
			case 'onDidChangeItems': return this.onDidChangeItems;
		}
B
wip  
Benjamin Pasero 已提交
119 120 121 122

		throw new Error(`Event not found: ${event}`);
	}

123 124 125 126 127 128
	async call(_: unknown, command: string, arg?: any): Promise<any> {

		// ensure to always wait for ready
		await this.whenReady;

		// handle call
B
wip  
Benjamin Pasero 已提交
129 130
		switch (command) {
			case 'getItems': {
B
Benjamin Pasero 已提交
131
				return Array.from(this.storageMainService.items.entries());
B
wip  
Benjamin Pasero 已提交
132 133 134
			}

			case 'updateItems': {
135 136 137 138
				const items: ISerializableUpdateRequest = arg;
				if (items.insert) {
					for (const [key, value] of items.insert) {
						this.storageMainService.store(key, value);
139
					}
140
				}
B
wip  
Benjamin Pasero 已提交
141

142 143 144 145 146
				if (items.delete) {
					items.delete.forEach(key => this.storageMainService.remove(key));
				}

				break;
B
wip  
Benjamin Pasero 已提交
147 148
			}

149 150 151
			default:
				throw new Error(`Call not found: ${command}`);
		}
B
wip  
Benjamin Pasero 已提交
152 153 154
	}
}

155
export class GlobalStorageDatabaseChannelClient extends Disposable implements IStorageDatabase {
B
wip  
Benjamin Pasero 已提交
156

157
	declare readonly _serviceBrand: undefined;
B
wip  
Benjamin Pasero 已提交
158

B
Benjamin Pasero 已提交
159 160
	private readonly _onDidChangeItemsExternal = this._register(new Emitter<IStorageItemsChangeEvent>());
	readonly onDidChangeItemsExternal = this._onDidChangeItemsExternal.event;
161

B
Benjamin Pasero 已提交
162
	private onDidChangeItemsOnMainListener: IDisposable | undefined;
163

164 165
	constructor(private channel: IChannel) {
		super();
B
wip  
Benjamin Pasero 已提交
166

167 168 169 170
		this.registerListeners();
	}

	private registerListeners(): void {
171
		this.onDidChangeItemsOnMainListener = this.channel.listen<ISerializableItemsChangeEvent>('onDidChangeItems')((e: ISerializableItemsChangeEvent) => this.onDidChangeItemsOnMain(e));
172 173 174
	}

	private onDidChangeItemsOnMain(e: ISerializableItemsChangeEvent): void {
175 176
		if (Array.isArray(e.changed) || Array.isArray(e.deleted)) {
			this._onDidChangeItemsExternal.fire({
B
Benjamin Pasero 已提交
177
				changed: e.changed ? new Map(e.changed) : undefined,
178 179
				deleted: e.deleted ? new Set<string>(e.deleted) : undefined
			});
180 181
		}
	}
B
wip  
Benjamin Pasero 已提交
182

183 184 185
	async getItems(): Promise<Map<string, string>> {
		const items: Item[] = await this.channel.call('getItems');

B
Benjamin Pasero 已提交
186
		return new Map(items);
B
wip  
Benjamin Pasero 已提交
187 188
	}

J
Johannes Rieken 已提交
189
	updateItems(request: IUpdateRequest): Promise<void> {
190 191 192
		const serializableRequest: ISerializableUpdateRequest = Object.create(null);

		if (request.insert) {
B
Benjamin Pasero 已提交
193
			serializableRequest.insert = Array.from(request.insert.entries());
194 195 196
		}

		if (request.delete) {
B
Benjamin Pasero 已提交
197
			serializableRequest.delete = Array.from(request.delete.values());
198 199 200
		}

		return this.channel.call('updateItems', serializableRequest);
B
wip  
Benjamin Pasero 已提交
201 202
	}

J
Johannes Rieken 已提交
203
	close(): Promise<void> {
204 205

		// when we are about to close, we start to ignore main-side changes since we close anyway
B
Benjamin Pasero 已提交
206
		dispose(this.onDidChangeItemsOnMainListener);
207

B
wip  
Benjamin Pasero 已提交
208 209
		return Promise.resolve(); // global storage is closed on the main side
	}
210 211 212 213

	dispose(): void {
		super.dispose();

B
Benjamin Pasero 已提交
214
		dispose(this.onDidChangeItemsOnMainListener);
215
	}
216
}