提交 000f8392 编写于 作者: D Daniel Imms

Only persist collections marked to do so

上级 f6545699
......@@ -77,7 +77,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
if (this._environmentVariableService.collections.size > 0) {
const collectionAsArray = [...this._environmentVariableService.collections.entries()];
const serializedCollections: [string, ISerializableEnvironmentVariableCollection][] = collectionAsArray.map(e => {
return [e[0], serializeEnvironmentVariableCollection(e[1])];
return [e[0], serializeEnvironmentVariableCollection(e[1].map)];
});
this._proxy.$initEnvironmentVariableCollections(serializedCollections);
}
......@@ -357,9 +357,12 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
return terminal;
}
$setEnvironmentVariableCollection(extensionIdentifier: string, collection: ISerializableEnvironmentVariableCollection | undefined): void {
$setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: ISerializableEnvironmentVariableCollection | undefined): void {
if (collection) {
const translatedCollection = deserializeEnvironmentVariableCollection(collection);
const translatedCollection = {
persistent,
map: deserializeEnvironmentVariableCollection(collection)
};
this._environmentVariableService.set(extensionIdentifier, translatedCollection);
} else {
this._environmentVariableService.delete(extensionIdentifier);
......
......@@ -437,7 +437,7 @@ export interface MainThreadTerminalServiceShape extends IDisposable {
$stopSendingDataEvents(): void;
$startHandlingLinks(): void;
$stopHandlingLinks(): void;
$setEnvironmentVariableCollection(extensionIdentifier: string, collection: ISerializableEnvironmentVariableCollection | undefined): void;
$setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: ISerializableEnvironmentVariableCollection | undefined): void;
// Process
$sendProcessTitle(terminalId: number, title: string): void;
......
......@@ -643,12 +643,15 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
}
export class EnvironmentVariableCollection implements vscode.EnvironmentVariableCollection {
public readonly map: Map<string, vscode.EnvironmentVariableMutator> = new Map();
readonly map: Map<string, vscode.EnvironmentVariableMutator> = new Map();
protected readonly _onDidChangeCollection: Emitter<void> = new Emitter<void>();
get onDidChangeCollection(): Event<void> { return this._onDidChangeCollection && this._onDidChangeCollection.event; }
constructor(serialized?: ISerializableEnvironmentVariableCollection) {
constructor(
readonly persistent: boolean,
serialized?: ISerializableEnvironmentVariableCollection
) {
this.map = new Map(serialized);
}
......
......@@ -233,17 +233,22 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
this._isWorkspaceShellAllowed = isAllowed;
}
public getEnvironmentVariableCollection(extension: IExtensionDescription, persistent?: boolean): vscode.EnvironmentVariableCollection {
public getEnvironmentVariableCollection(extension: IExtensionDescription, persistent: boolean = false): vscode.EnvironmentVariableCollection {
let collection: EnvironmentVariableCollection | undefined;
if (persistent) {
// If persistent is specified, return the current collection if it exists
collection = this._environmentVariableCollections.get(extension.identifier.value);
// If persistence changed then create a new collection
if (collection && !collection.persistent) {
collection = undefined;
}
}
if (!collection) {
// If not persistent, clear out the current collection and create a new one
dispose(this._environmentVariableCollections.get(extension.identifier.value));
collection = new EnvironmentVariableCollection();
collection = new EnvironmentVariableCollection(persistent);
this._setEnvironmentVariableCollection(extension.identifier.value, collection);
}
......@@ -252,13 +257,13 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
private _syncEnvironmentVariableCollection(extensionIdentifier: string, collection: EnvironmentVariableCollection): void {
const serialized = serializeEnvironmentVariableCollection(collection.map);
this._proxy.$setEnvironmentVariableCollection(extensionIdentifier, serialized.length === 0 ? undefined : serialized);
this._proxy.$setEnvironmentVariableCollection(extensionIdentifier, collection.persistent, serialized.length === 0 ? undefined : serialized);
}
public $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void {
collections.forEach(entry => {
const extensionIdentifier = entry[0];
const collection = new EnvironmentVariableCollection(entry[1]);
const collection = new EnvironmentVariableCollection(true, entry[1]);
this._setEnvironmentVariableCollection(extensionIdentifier, collection);
});
}
......
......@@ -24,7 +24,13 @@ export interface IExtensionOwnedEnvironmentVariableMutator extends IEnvironmentV
readonly extensionIdentifier: string;
}
export type IEnvironmentVariableCollection = ReadonlyMap<string, IEnvironmentVariableMutator>;
export interface IEnvironmentVariableCollection {
readonly map: ReadonlyMap<string, IEnvironmentVariableMutator>;
}
export interface IEnvironmentVariableCollectionWithPersistence extends IEnvironmentVariableCollection {
readonly persistent: boolean;
}
export interface IMergedEnvironmentVariableCollectionDiff {
added: ReadonlyMap<string, IExtensionOwnedEnvironmentVariableMutator[]>;
......
......@@ -11,7 +11,7 @@ export class MergedEnvironmentVariableCollection implements IMergedEnvironmentVa
constructor(collections: Map<string, IEnvironmentVariableCollection>) {
collections.forEach((collection, extensionIdentifier) => {
const it = collection.entries();
const it = collection.map.entries();
let next = it.next();
while (!next.done) {
const variable = next.value[0];
......
......@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEnvironmentVariableService, IEnvironmentVariableCollection, IMergedEnvironmentVariableCollection, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { IEnvironmentVariableService, IMergedEnvironmentVariableCollection, ISerializableEnvironmentVariableCollection, IEnvironmentVariableCollectionWithPersistence } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { Event, Emitter } from 'vs/base/common/event';
import { debounce, throttle } from 'vs/base/common/decorators';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
......@@ -24,7 +24,7 @@ interface ISerializableExtensionEnvironmentVariableCollection {
export class EnvironmentVariableService implements IEnvironmentVariableService {
_serviceBrand: undefined;
collections: Map<string, IEnvironmentVariableCollection> = new Map();
collections: Map<string, IEnvironmentVariableCollectionWithPersistence> = new Map();
mergedCollection: IMergedEnvironmentVariableCollection;
private readonly _onDidChangeCollections = new Emitter<IMergedEnvironmentVariableCollection>();
......@@ -37,7 +37,10 @@ export class EnvironmentVariableService implements IEnvironmentVariableService {
const serializedPersistedCollections = this._storageService.get(ENVIRONMENT_VARIABLE_COLLECTIONS_KEY, StorageScope.WORKSPACE);
if (serializedPersistedCollections) {
const collectionsJson: ISerializableExtensionEnvironmentVariableCollection[] = JSON.parse(serializedPersistedCollections);
collectionsJson.forEach(c => this.collections.set(c.extensionIdentifier, deserializeEnvironmentVariableCollection(c.collection)));
collectionsJson.forEach(c => this.collections.set(c.extensionIdentifier, {
persistent: true,
map: deserializeEnvironmentVariableCollection(c.collection)
}));
// Asynchronously invalidate collections where extensions have been uninstalled, this is
// async to avoid making all functions on the service synchronous and because extensions
......@@ -50,7 +53,7 @@ export class EnvironmentVariableService implements IEnvironmentVariableService {
this._extensionService.onDidChangeExtensions(() => this._invalidateExtensionCollections());
}
set(extensionIdentifier: string, collection: IEnvironmentVariableCollection): void {
set(extensionIdentifier: string, collection: IEnvironmentVariableCollectionWithPersistence): void {
this.collections.set(extensionIdentifier, collection);
this._updateCollections();
}
......@@ -72,12 +75,14 @@ export class EnvironmentVariableService implements IEnvironmentVariableService {
}
protected _persistCollections(): void {
const keys = [...this.collections.keys()];
const collectionsJson: ISerializableExtensionEnvironmentVariableCollection[] = keys.map(extensionIdentifier => {
return {
const collectionsJson: ISerializableExtensionEnvironmentVariableCollection[] = [];
this.collections.forEach((collection, extensionIdentifier) => {
if (collection.persistent) {
collectionsJson.push({
extensionIdentifier,
collection: serializeEnvironmentVariableCollection(this.collections.get(extensionIdentifier)!)
};
collection: serializeEnvironmentVariableCollection(this.collections.get(extensionIdentifier)!.map)
});
}
});
const stringifiedJson = JSON.stringify(collectionsJson);
this._storageService.store(ENVIRONMENT_VARIABLE_COLLECTIONS_KEY, stringifiedJson, StorageScope.WORKSPACE);
......
......@@ -3,16 +3,16 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEnvironmentVariableCollection, IEnvironmentVariableMutator, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { IEnvironmentVariableMutator, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
// This file is shared between the renderer and extension host
export function serializeEnvironmentVariableCollection(collection: IEnvironmentVariableCollection): ISerializableEnvironmentVariableCollection {
export function serializeEnvironmentVariableCollection(collection: ReadonlyMap<string, IEnvironmentVariableMutator>): ISerializableEnvironmentVariableCollection {
return [...collection.entries()];
}
export function deserializeEnvironmentVariableCollection(
serializedCollection: ISerializableEnvironmentVariableCollection
): IEnvironmentVariableCollection {
): Map<string, IEnvironmentVariableMutator> {
return new Map<string, IEnvironmentVariableMutator>(serializedCollection);
}
......@@ -9,22 +9,30 @@ import { IProcessEnvironment } from 'vs/base/common/platform';
import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection';
import { deserializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
suite.only('EnvironmentVariable - MergedEnvironmentVariableCollection', () => {
suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => {
suite('ctor', () => {
test('Should keep entries that come after a Prepend or Append type mutators', () => {
const merged = new MergedEnvironmentVariableCollection(new Map([
['ext1', deserializeEnvironmentVariableCollection([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a1', type: EnvironmentVariableMutatorType.Prepend }]
])],
['ext2', deserializeEnvironmentVariableCollection([
])
}],
['ext2', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a2', type: EnvironmentVariableMutatorType.Append }]
])],
['ext3', deserializeEnvironmentVariableCollection([
])
}],
['ext3', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a3', type: EnvironmentVariableMutatorType.Prepend }]
])],
['ext4', deserializeEnvironmentVariableCollection([
])
}],
['ext4', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a4', type: EnvironmentVariableMutatorType.Append }]
])]
])
}]
]));
deepStrictEqual([...merged.map.entries()], [
['A', [
......@@ -38,18 +46,26 @@ suite.only('EnvironmentVariable - MergedEnvironmentVariableCollection', () => {
test('Should remove entries that come after a Replace type mutator', () => {
const merged = new MergedEnvironmentVariableCollection(new Map([
['ext1', deserializeEnvironmentVariableCollection([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a1', type: EnvironmentVariableMutatorType.Prepend }]
])],
['ext2', deserializeEnvironmentVariableCollection([
])
}],
['ext2', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a2', type: EnvironmentVariableMutatorType.Append }]
])],
['ext3', deserializeEnvironmentVariableCollection([
])
}],
['ext3', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a3', type: EnvironmentVariableMutatorType.Replace }]
])],
['ext4', deserializeEnvironmentVariableCollection([
])
}],
['ext4', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a4', type: EnvironmentVariableMutatorType.Append }]
])]
])
}]
]));
deepStrictEqual([...merged.map.entries()], [
['A', [
......@@ -64,11 +80,13 @@ suite.only('EnvironmentVariable - MergedEnvironmentVariableCollection', () => {
suite('applyToProcessEnvironment', () => {
test('should apply the collection to an environment', () => {
const merged = new MergedEnvironmentVariableCollection(new Map([
['ext', deserializeEnvironmentVariableCollection([
['ext', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }],
['B', { value: 'b', type: EnvironmentVariableMutatorType.Append }],
['C', { value: 'c', type: EnvironmentVariableMutatorType.Prepend }]
])]
])
}]
]));
const env: IProcessEnvironment = {
A: 'foo',
......@@ -85,11 +103,13 @@ suite.only('EnvironmentVariable - MergedEnvironmentVariableCollection', () => {
test('should apply the collection to environment entries with no values', () => {
const merged = new MergedEnvironmentVariableCollection(new Map([
['ext', deserializeEnvironmentVariableCollection([
['ext', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }],
['B', { value: 'b', type: EnvironmentVariableMutatorType.Append }],
['C', { value: 'c', type: EnvironmentVariableMutatorType.Prepend }]
])]
])
}]
]));
const env: IProcessEnvironment = {};
merged.applyToProcessEnvironment(env);
......@@ -105,9 +125,11 @@ suite.only('EnvironmentVariable - MergedEnvironmentVariableCollection', () => {
test('should generate added diffs from when the first entry is added', () => {
const merged1 = new MergedEnvironmentVariableCollection(new Map([]));
const merged2 = new MergedEnvironmentVariableCollection(new Map([
['ext1', deserializeEnvironmentVariableCollection([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }]
])]
])
}]
]));
const diff = merged1.diff(merged2);
strictEqual(diff.changed.size, 0);
......@@ -120,15 +142,19 @@ suite.only('EnvironmentVariable - MergedEnvironmentVariableCollection', () => {
test('should generate added diffs from the same extension', () => {
const merged1 = new MergedEnvironmentVariableCollection(new Map([
['ext1', deserializeEnvironmentVariableCollection([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }]
])]
])
}]
]));
const merged2 = new MergedEnvironmentVariableCollection(new Map([
['ext1', deserializeEnvironmentVariableCollection([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }],
['B', { value: 'b', type: EnvironmentVariableMutatorType.Append }]
])]
])
}]
]));
const diff = merged1.diff(merged2);
strictEqual(diff.changed.size, 0);
......@@ -141,18 +167,24 @@ suite.only('EnvironmentVariable - MergedEnvironmentVariableCollection', () => {
test('should generate added diffs from a different extension', () => {
const merged1 = new MergedEnvironmentVariableCollection(new Map([
['ext1', deserializeEnvironmentVariableCollection([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a1', type: EnvironmentVariableMutatorType.Prepend }]
])]
])
}]
]));
const merged2 = new MergedEnvironmentVariableCollection(new Map([
['ext2', deserializeEnvironmentVariableCollection([
['ext2', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a2', type: EnvironmentVariableMutatorType.Append }]
])],
['ext1', deserializeEnvironmentVariableCollection([
])
}],
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a1', type: EnvironmentVariableMutatorType.Prepend }]
])]
])
}]
]));
const diff = merged1.diff(merged2);
strictEqual(diff.changed.size, 0);
......@@ -162,13 +194,17 @@ suite.only('EnvironmentVariable - MergedEnvironmentVariableCollection', () => {
]);
const merged3 = new MergedEnvironmentVariableCollection(new Map([
['ext1', deserializeEnvironmentVariableCollection([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a1', type: EnvironmentVariableMutatorType.Prepend }]
])],
])
}],
// This entry should get removed
['ext2', deserializeEnvironmentVariableCollection([
['ext2', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a2', type: EnvironmentVariableMutatorType.Append }]
])]
])
}]
]));
const diff2 = merged1.diff(merged3);
strictEqual(diff2.changed.size, 0);
......@@ -178,18 +214,24 @@ suite.only('EnvironmentVariable - MergedEnvironmentVariableCollection', () => {
test('should remove entries in the diff that come after a Replce', () => {
const merged1 = new MergedEnvironmentVariableCollection(new Map([
['ext1', deserializeEnvironmentVariableCollection([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a1', type: EnvironmentVariableMutatorType.Replace }]
])]
])
}]
]));
const merged4 = new MergedEnvironmentVariableCollection(new Map([
['ext1', deserializeEnvironmentVariableCollection([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a1', type: EnvironmentVariableMutatorType.Replace }]
])],
])
}],
// This entry should get removed as it comes after a replace
['ext2', deserializeEnvironmentVariableCollection([
['ext2', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a2', type: EnvironmentVariableMutatorType.Append }]
])]
])
}]
]));
const diff = merged1.diff(merged4);
strictEqual(diff.changed.size, 0);
......@@ -199,15 +241,19 @@ suite.only('EnvironmentVariable - MergedEnvironmentVariableCollection', () => {
test('should generate removed diffs', () => {
const merged1 = new MergedEnvironmentVariableCollection(new Map([
['ext1', deserializeEnvironmentVariableCollection([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }],
['B', { value: 'b', type: EnvironmentVariableMutatorType.Replace }]
])]
])
}]
]));
const merged2 = new MergedEnvironmentVariableCollection(new Map([
['ext1', deserializeEnvironmentVariableCollection([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }]
])]
])
}]
]));
const diff = merged1.diff(merged2);
strictEqual(diff.changed.size, 0);
......@@ -219,16 +265,20 @@ suite.only('EnvironmentVariable - MergedEnvironmentVariableCollection', () => {
test('should generate changed diffs', () => {
const merged1 = new MergedEnvironmentVariableCollection(new Map([
['ext1', deserializeEnvironmentVariableCollection([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a1', type: EnvironmentVariableMutatorType.Replace }],
['B', { value: 'b', type: EnvironmentVariableMutatorType.Replace }]
])]
])
}]
]));
const merged2 = new MergedEnvironmentVariableCollection(new Map([
['ext1', deserializeEnvironmentVariableCollection([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a2', type: EnvironmentVariableMutatorType.Replace }],
['B', { value: 'b', type: EnvironmentVariableMutatorType.Append }]
])]
])
}]
]));
const diff = merged1.diff(merged2);
strictEqual(diff.added.size, 0);
......@@ -241,16 +291,20 @@ suite.only('EnvironmentVariable - MergedEnvironmentVariableCollection', () => {
test('should generate diffs with added, changed and removed', () => {
const merged1 = new MergedEnvironmentVariableCollection(new Map([
['ext1', deserializeEnvironmentVariableCollection([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a1', type: EnvironmentVariableMutatorType.Replace }],
['B', { value: 'b', type: EnvironmentVariableMutatorType.Prepend }]
])]
])
}]
]));
const merged2 = new MergedEnvironmentVariableCollection(new Map([
['ext1', deserializeEnvironmentVariableCollection([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a2', type: EnvironmentVariableMutatorType.Replace }],
['C', { value: 'c', type: EnvironmentVariableMutatorType.Append }]
])]
])
}]
]));
const diff = merged1.diff(merged2);
deepStrictEqual([...diff.added.entries()], [
......
......@@ -47,7 +47,7 @@ suite('EnvironmentVariable - EnvironmentVariableService', () => {
collection.set('A', { value: 'a', type: EnvironmentVariableMutatorType.Replace });
collection.set('B', { value: 'b', type: EnvironmentVariableMutatorType.Append });
collection.set('C', { value: 'c', type: EnvironmentVariableMutatorType.Prepend });
environmentVariableService.set('ext1', collection);
environmentVariableService.set('ext1', { map: collection, persistent: true });
deepStrictEqual([...environmentVariableService.mergedCollection.map.entries()], [
['A', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Replace, value: 'a' }]],
['B', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Append, value: 'b' }]],
......@@ -75,9 +75,9 @@ suite('EnvironmentVariable - EnvironmentVariableService', () => {
collection2.set('B', { value: 'b2', type: EnvironmentVariableMutatorType.Append });
collection3.set('A', { value: 'a3', type: EnvironmentVariableMutatorType.Prepend });
collection3.set('B', { value: 'b3', type: EnvironmentVariableMutatorType.Replace });
environmentVariableService.set('ext1', collection1);
environmentVariableService.set('ext2', collection2);
environmentVariableService.set('ext3', collection3);
environmentVariableService.set('ext1', { map: collection1, persistent: true });
environmentVariableService.set('ext2', { map: collection2, persistent: true });
environmentVariableService.set('ext3', { map: collection3, persistent: true });
deepStrictEqual([...environmentVariableService.mergedCollection.map.entries()], [
['A', [
{ extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Replace, value: 'a2' },
......@@ -94,9 +94,9 @@ suite('EnvironmentVariable - EnvironmentVariableService', () => {
collection1.set('A', { value: ':a1', type: EnvironmentVariableMutatorType.Append });
collection2.set('A', { value: 'a2:', type: EnvironmentVariableMutatorType.Prepend });
collection3.set('A', { value: 'a3', type: EnvironmentVariableMutatorType.Replace });
environmentVariableService.set('ext1', collection1);
environmentVariableService.set('ext2', collection2);
environmentVariableService.set('ext3', collection3);
environmentVariableService.set('ext1', { map: collection1, persistent: true });
environmentVariableService.set('ext2', { map: collection2, persistent: true });
environmentVariableService.set('ext3', { map: collection3, persistent: true });
// The entries should be ordered in the order they are applied
deepStrictEqual([...environmentVariableService.mergedCollection.map.entries()], [
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册