提交 62fefa24 编写于 作者: S Sandeep Somavarapu

User data changes

- Introduce containers
- Write tests for file user data provider
上级 2c3e8a8e
......@@ -45,16 +45,8 @@ export class FileUserDataProvider extends Disposable implements IUserDataProvide
async readFile(path: string): Promise<Uint8Array> {
const resource = this.toResource(path);
try {
const content = await this.fileService.readFile(resource);
return content.value.buffer;
} catch (e) {
const exists = await this.fileService.exists(resource);
if (exists) {
throw e;
}
}
return VSBuffer.fromString('').buffer;
const content = await this.fileService.readFile(resource);
return content.value.buffer;
}
writeFile(path: string, value: Uint8Array): Promise<void> {
......
......@@ -4,6 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { TernarySearchTree } from 'vs/base/common/map';
import { Registry } from 'vs/platform/registry/common/platform';
/**
* The userDataProvider is used to handle user specific application
......@@ -61,4 +63,38 @@ export interface IUserDataProvider {
* Throw an error if the path does not exist or points to a file.
*/
listFiles(path: string): Promise<string[]>;
}
\ No newline at end of file
}
export interface IUserDataContainerRegistry {
/**
* Register the given path as an user data container if user data files are stored under this path.
*
* It is required to register the container to access the user data files under the container.
*/
registerContainer(path: string): void;
/**
* Returns true if the given path is an user data container.
*/
isContainer(path: string): boolean;
}
class UserDataContainerRegistry implements IUserDataContainerRegistry {
private containers: TernarySearchTree<string> = TernarySearchTree.forStrings();
public registerContainer(path: string): void {
this.containers.set(path, path);
}
isContainer(path: string): boolean {
return !!this.containers.get(path) || !!this.containers.findSuperstr(path);
}
}
export const Extensions = {
UserDataContainers: 'workbench.contributions.userDataContainers'
};
Registry.add(Extensions.UserDataContainers, new UserDataContainerRegistry());
\ No newline at end of file
......@@ -5,11 +5,12 @@
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { FileSystemProviderCapabilities, FileWriteOptions, IStat, FileType, FileDeleteOptions, IWatchOptions, FileOverwriteOptions, IFileSystemProviderWithFileReadWriteCapability, IFileChange, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files';
import { IUserDataProvider } from 'vs/workbench/services/userData/common/userData';
import { IUserDataProvider, IUserDataContainerRegistry, Extensions } from 'vs/workbench/services/userData/common/userData';
import { URI } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { TernarySearchTree } from 'vs/base/common/map';
import { startsWith } from 'vs/base/common/strings';
import { Registry } from 'vs/platform/registry/common/platform';
export class UserDataFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability {
......@@ -32,11 +33,11 @@ export class UserDataFileSystemProvider extends Disposable implements IFileSyste
watch(resource: URI, opts: IWatchOptions): IDisposable {
const path = this.toPath(resource);
if (!path) {
throw new Error(`Invalud user data resource ${resource}`);
throw new Error(`Invalid user data resource ${resource}`);
}
return this.userDataProvider.onDidChangeFile(e => {
if (new UserDataChangesEvent(e).contains(path)) {
this.versions.set(path, (this.versions.get(path) || 1) + 1);
this.versions.set(path, this.getOrSetVersion(path) + 1);
this._onDidChangeFile.fire(new FileChangesEvent([{ resource, type: FileChangeType.UPDATED }]).changes);
}
});
......@@ -44,25 +45,36 @@ export class UserDataFileSystemProvider extends Disposable implements IFileSyste
async stat(resource: URI): Promise<IStat> {
const path = this.toPath(resource);
if (!path) {
throw new Error(`Invalud user data resource ${resource}`);
if (path === undefined) {
throw new Error(`Invalid user data resource ${resource}`);
}
if (this.isContainer(path)) {
return {
type: FileType.Directory,
ctime: 0,
mtime: this.getOrSetVersion(path),
size: 0
};
}
const result = await this.readFile(resource);
return {
type: FileType.File,
ctime: 0,
mtime: this.versions.get(path) || 0,
size: 0
mtime: this.getOrSetVersion(path),
size: result.byteLength
};
}
mkdir(resource: URI): Promise<void> { throw new Error('not supported'); }
delete(resource: URI, opts: FileDeleteOptions): Promise<void> { throw new Error('not supported'); }
rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> { throw new Error('not supported'); }
async readFile(resource: URI): Promise<Uint8Array> {
const path = this.toPath(resource);
if (!path) {
throw new Error(`Invalud user data resource ${resource}`);
throw new Error(`Invalid user data resource ${resource}`);
}
if (this.isContainer(path)) {
throw new Error(`Invalid user data file ${resource}`);
}
return this.userDataProvider.readFile(path);
}
......@@ -70,7 +82,10 @@ export class UserDataFileSystemProvider extends Disposable implements IFileSyste
async readdir(resource: URI): Promise<[string, FileType][]> {
const path = this.toPath(resource);
if (!path) {
throw new Error(`Invalud user data resource ${resource}`);
throw new Error(`Invalid user data resource ${resource}`);
}
if (!this.isContainer(path)) {
throw new Error(`Invalid user data container ${resource}`);
}
const children = await this.userDataProvider.listFiles(path);
return children.map(c => [c, FileType.Unknown]);
......@@ -79,11 +94,39 @@ export class UserDataFileSystemProvider extends Disposable implements IFileSyste
writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> {
const path = this.toPath(resource);
if (!path) {
throw new Error(`Invalud user data resource ${resource}`);
throw new Error(`Invalid user data resource ${resource}`);
}
if (this.isContainer(path)) {
throw new Error(`Invalid user data file ${resource}`);
}
return this.userDataProvider.writeFile(path, content);
}
delete(resource: URI, opts: FileDeleteOptions): Promise<void> {
const path = this.toPath(resource);
if (!path) {
throw new Error(`Invalid user data resource ${resource}`);
}
if (this.isContainer(path)) {
throw new Error(`Invalid user data file ${resource}`);
}
return this.userDataProvider.deleteFile(path);
}
private getOrSetVersion(path: string): number {
if (!this.versions.has(path)) {
this.versions.set(path, 1);
}
return this.versions.get(path)!;
}
private isContainer(path: string): boolean {
if (path === '') {
return true; // Root
}
return Registry.as<IUserDataContainerRegistry>(Extensions.UserDataContainers).isContainer(path);
}
private toPath(resource: URI): string | undefined {
const resourcePath = resource.toString();
const userDataHomePath = this.userDataHome.toString();
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as os from 'os';
import * as path from 'vs/base/common/path';
import * as uuid from 'vs/base/common/uuid';
import * as pfs from 'vs/base/node/pfs';
import { IFileService } from 'vs/platform/files/common/files';
import { FileService } from 'vs/workbench/services/files/common/fileService';
import { NullLogService } from 'vs/platform/log/common/log';
import { Schemas } from 'vs/base/common/network';
import { UserDataFileSystemProvider } from 'vs/workbench/services/userData/common/userDataFileSystemProvider';
import { URI } from 'vs/base/common/uri';
import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider';
import { joinPath } from 'vs/base/common/resources';
import { VSBuffer } from 'vs/base/common/buffer';
import { DiskFileSystemProvider } from 'vs/workbench/services/files/electron-browser/diskFileSystemProvider';
import { Registry } from 'vs/platform/registry/common/platform';
import { IUserDataContainerRegistry, Extensions } from 'vs/workbench/services/userData/common/userData';
suite('UserDataFileSystemProvider', () => {
let testObject: IFileService;
let userDataPath: string;
let userDataResource: URI;
const userDataContainersRegistry = Registry.as<IUserDataContainerRegistry>(Extensions.UserDataContainers);
setup(() => {
const logService = new NullLogService();
testObject = new FileService(logService);
testObject.registerProvider(Schemas.file, new DiskFileSystemProvider(logService));
userDataPath = path.join(os.tmpdir(), 'vsctests', uuid.generateUuid());
userDataResource = URI.from({ scheme: Schemas.userData, path: '/user' });
testObject.registerProvider(Schemas.userData, new UserDataFileSystemProvider(userDataResource, new FileUserDataProvider(URI.file(userDataPath), testObject)));
userDataContainersRegistry.registerContainer('testContainer');
return pfs.mkdirp(userDataPath);
});
teardown(() => {
return pfs.rimraf(userDataPath, pfs.RimRafMode.MOVE);
});
test('exists return false when file does not exist', async () => {
const exists = await testObject.exists(joinPath(userDataResource, 'settings.json'));
assert.equal(exists, false);
});
test('read file throws error if not exist', async () => {
try {
await testObject.readFile(joinPath(userDataResource, 'settings.json'));
assert.fail('Should fail since file does not exist');
} catch (e) { }
});
test('read existing file', async () => {
await pfs.writeFile(path.join(userDataPath, 'settings.json'), '{}');
const result = await testObject.readFile(joinPath(userDataResource, 'settings.json'));
assert.equal(result.value, '{}');
});
test('create file', async () => {
await testObject.createFile(joinPath(userDataResource, 'settings.json'), VSBuffer.fromString('{}'));
const result = await pfs.readFile(path.join(userDataPath, 'settings.json'));
assert.equal(result, '{}');
});
test('write file creates the file if not exist', async () => {
await testObject.writeFile(joinPath(userDataResource, 'settings.json'), VSBuffer.fromString('{}'));
const result = await pfs.readFile(path.join(userDataPath, 'settings.json'));
assert.equal(result, '{}');
});
test('write to existing file', async () => {
await pfs.writeFile(path.join(userDataPath, 'settings.json'), '{}');
await testObject.writeFile(joinPath(userDataResource, 'settings.json'), VSBuffer.fromString('{a:1}'));
const result = await pfs.readFile(path.join(userDataPath, 'settings.json'));
assert.equal(result, '{a:1}');
});
test('watch file - event is triggerred when file is created', async (done) => {
const resource = joinPath(userDataResource, 'settings.json');
const disposable = testObject.watch(resource);
testObject.onFileChanges(e => {
if (e.contains(resource)) {
disposable.dispose();
done();
}
});
await testObject.writeFile(resource, VSBuffer.fromString('{a:1}'));
});
test('watch file - event is triggerred when file is created externally', async (done) => {
const resource = joinPath(userDataResource, 'settings.json');
const disposable = testObject.watch(resource);
testObject.onFileChanges(e => {
if (e.contains(resource)) {
disposable.dispose();
done();
}
});
await pfs.writeFile(path.join(userDataPath, 'settings.json'), '{}');
});
test('watch file - event is triggerred when file is updated', async (done) => {
const resource = joinPath(userDataResource, 'settings.json');
await testObject.writeFile(resource, VSBuffer.fromString('{}'));
const disposable = testObject.watch(resource);
testObject.onFileChanges(e => {
if (e.contains(resource)) {
disposable.dispose();
done();
}
});
await testObject.writeFile(resource, VSBuffer.fromString('{a:1}'));
});
test('watch file - event is triggerred when file is update externally', async (done) => {
const resource = joinPath(userDataResource, 'settings.json');
await testObject.writeFile(resource, VSBuffer.fromString('{}'));
const disposable = testObject.watch(resource);
testObject.onFileChanges(e => {
if (e.contains(resource)) {
disposable.dispose();
done();
}
});
await pfs.writeFile(path.join(userDataPath, 'settings.json'), '{a:1}');
});
test('watch file - event is triggerred when file is deleted', async (done) => {
const resource = joinPath(userDataResource, 'settings.json');
await testObject.writeFile(resource, VSBuffer.fromString('{}'));
const disposable = testObject.watch(resource);
testObject.onFileChanges(e => {
if (e.contains(resource)) {
disposable.dispose();
done();
}
});
await testObject.del(resource);
});
test('watch file - event is triggerred when file is deleted externally', async (done) => {
const resource = joinPath(userDataResource, 'settings.json');
await testObject.writeFile(resource, VSBuffer.fromString('{}'));
const disposable = testObject.watch(resource);
testObject.onFileChanges(e => {
if (e.contains(resource)) {
disposable.dispose();
done();
}
});
await pfs.unlink(path.join(userDataPath, 'settings.json'));
});
test('delete file', async () => {
await pfs.writeFile(path.join(userDataPath, 'settings.json'), '');
await testObject.del(joinPath(userDataResource, 'settings.json'));
const result = await pfs.exists(path.join(userDataPath, 'settings.json'));
assert.equal(false, result);
});
test('exists return true for container', async () => {
const exists = await testObject.exists(joinPath(userDataResource, 'testContainer'));
assert.equal(exists, true);
});
test('exists return false for non registered container', async () => {
await pfs.mkdirp(path.join(userDataPath, 'container'));
const exists = await testObject.exists(joinPath(userDataResource, 'container'));
assert.equal(exists, false);
});
test('read file throws error for container', async () => {
try {
await testObject.readFile(joinPath(userDataResource, 'testContainer'));
assert.fail('Should fail since read file is not supported for container');
} catch (e) { }
});
test('read file under container', async () => {
await pfs.mkdirp(path.join(userDataPath, 'testContainer'));
await pfs.writeFile(path.join(userDataPath, 'testContainer', 'settings.json'), '{}');
const actual = await testObject.readFile(joinPath(userDataResource, 'testContainer/settings.json'));
assert.equal(actual.value, '{}');
});
test('create file throws error for container', async () => {
try {
await testObject.createFile(joinPath(userDataResource, 'testContainer'), VSBuffer.fromString('{}'));
assert.fail('Should fail since create file is not supported for container');
} catch (e) { }
});
test('create file under container that exists', async () => {
await pfs.mkdirp(path.join(userDataPath, 'testContainer'));
await testObject.createFile(joinPath(userDataResource, 'testContainer/settings.json'), VSBuffer.fromString('{}'));
const actual = await pfs.readFile(path.join(userDataPath, 'testContainer', 'settings.json'));
assert.equal(actual, '{}');
});
test('create file under container that does not exist', async () => {
await testObject.createFile(joinPath(userDataResource, 'testContainer/settings.json'), VSBuffer.fromString('{}'));
const actual = await pfs.readFile(path.join(userDataPath, 'testContainer', 'settings.json'));
assert.equal(actual, '{}');
});
test('write file throws error for container', async () => {
try {
await testObject.writeFile(joinPath(userDataResource, 'testContainer'), VSBuffer.fromString('{}'));
assert.fail('Should fail since write file is not supported for container');
} catch (e) { }
});
test('write to not existing file under container that exists', async () => {
await pfs.mkdirp(path.join(userDataPath, 'testContainer'));
await testObject.writeFile(joinPath(userDataResource, 'testContainer/settings.json'), VSBuffer.fromString('{}'));
const actual = await pfs.readFile(path.join(userDataPath, 'testContainer', 'settings.json'));
assert.equal(actual, '{}');
});
test('write to not existing file under container that does not exists', async () => {
await testObject.writeFile(joinPath(userDataResource, 'testContainer/settings.json'), VSBuffer.fromString('{}'));
const actual = await pfs.readFile(path.join(userDataPath, 'testContainer', 'settings.json'));
assert.equal(actual, '{}');
});
test('write to existing file under container', async () => {
await pfs.mkdirp(path.join(userDataPath, 'testContainer'));
await pfs.writeFile(path.join(userDataPath, 'testContainer', 'settings.json'), '{}');
await testObject.writeFile(joinPath(userDataResource, 'testContainer/settings.json'), VSBuffer.fromString('{a:1}'));
const actual = await pfs.readFile(path.join(userDataPath, 'testContainer', 'settings.json'));
assert.equal(actual.toString(), '{a:1}');
});
test('delete file throws error for container that does not exist', async () => {
try {
await testObject.del(joinPath(userDataResource, 'testContainer'));
assert.fail('Should fail since delete file is not supported for container');
} catch (e) { }
});
test('delete file throws error for container that exist', async () => {
await pfs.mkdirp(path.join(userDataPath, 'testContainer'));
try {
await testObject.del(joinPath(userDataResource, 'testContainer'));
assert.fail('Should fail since delete file is not supported for container');
} catch (e) { }
});
test('delete not existing file under container that exists', async () => {
await pfs.mkdirp(path.join(userDataPath, 'testContainer'));
try {
await testObject.del(joinPath(userDataResource, 'testContainer'));
assert.fail('Should fail since file does not exist');
} catch (e) { }
});
test('delete not existing file under container that does not exists', async () => {
try {
await testObject.del(joinPath(userDataResource, 'testContainer/settings.json'));
assert.fail('Should fail since file does not exist');
} catch (e) { }
});
test('delete existing file under container', async () => {
await pfs.mkdirp(path.join(userDataPath, 'testContainer'));
pfs.writeFile(path.join(userDataPath, 'testContainer', 'settings.json'), '{}');
await testObject.del(joinPath(userDataResource, 'testContainer/settings.json'));
const exists = await pfs.exists(path.join(userDataPath, 'testContainer', 'settings.json'));
assert.equal(exists, false);
});
test('watch file under container - event is triggerred when file is created', async (done) => {
const resource = joinPath(userDataResource, 'testContainer/settings.json');
const disposable = testObject.watch(resource);
testObject.onFileChanges(e => {
if (e.contains(resource)) {
disposable.dispose();
done();
}
});
await testObject.writeFile(joinPath(userDataResource, 'testContainer/settings.json'), VSBuffer.fromString('{a:1}'));
});
test('watch file under container - event is triggerred when file is created externally', async (done) => {
const resource = joinPath(userDataResource, 'testContainer/settings.json');
const disposable = testObject.watch(resource);
testObject.onFileChanges(e => {
if (e.contains(resource)) {
disposable.dispose();
done();
}
});
await pfs.mkdirp(path.join(userDataPath, 'testContainer'));
await pfs.writeFile(path.join(userDataPath, 'testContainer', 'settings.json'), '{}');
});
test('watch file under container - event is triggerred when file is updated', async (done) => {
const resource = joinPath(userDataResource, 'testContainer/settings.json');
await pfs.mkdirp(path.join(userDataPath, 'testContainer'));
await pfs.writeFile(path.join(userDataPath, 'testContainer', 'settings.json'), '{}');
const disposable = testObject.watch(resource);
testObject.onFileChanges(e => {
if (e.contains(resource)) {
disposable.dispose();
done();
}
});
await testObject.writeFile(resource, VSBuffer.fromString('{a:1}'));
});
test('watch file under container - event is triggerred when file is updated externally', async (done) => {
const resource = joinPath(userDataResource, 'testContainer/settings.json');
await pfs.mkdirp(path.join(userDataPath, 'testContainer'));
await pfs.writeFile(path.join(userDataPath, 'testContainer', 'settings.json'), '{}');
const disposable = testObject.watch(resource);
testObject.onFileChanges(e => {
if (e.contains(resource)) {
disposable.dispose();
done();
}
});
await pfs.writeFile(path.join(userDataPath, 'testContainer', 'settings.json'), '{a:1}');
});
test('watch file under container - event is triggerred when file is deleted', async (done) => {
const resource = joinPath(userDataResource, 'testContainer/settings.json');
await testObject.writeFile(resource, VSBuffer.fromString('{}'));
const disposable = testObject.watch(resource);
testObject.onFileChanges(e => {
if (e.contains(resource)) {
disposable.dispose();
done();
}
});
await testObject.del(resource);
});
test('watch file under container - event is triggerred when file is deleted externally', async (done) => {
const resource = joinPath(userDataResource, 'testContainer/settings.json');
await testObject.writeFile(resource, VSBuffer.fromString('{}'));
const disposable = testObject.watch(resource);
testObject.onFileChanges(e => {
if (e.contains(resource)) {
disposable.dispose();
done();
}
});
await pfs.unlink(path.join(userDataPath, 'testContainer', 'settings.json'));
});
test('watch container - event is triggerred when file under container is created', async (done) => {
const container = joinPath(userDataResource, 'testContainer');
const disposable = testObject.watch(container);
testObject.onFileChanges(e => {
if (e.contains(container)) {
disposable.dispose();
done();
}
});
await testObject.writeFile(joinPath(userDataResource, 'testContainer/settings.json'), VSBuffer.fromString('{a:1}'));
});
test('watch container - event is triggerred when file under container is created externally', async (done) => {
await pfs.mkdirp(path.join(userDataPath, 'testContainer'));
const container = joinPath(userDataResource, 'testContainer');
const disposable = testObject.watch(container);
testObject.onFileChanges(e => {
if (e.contains(container)) {
disposable.dispose();
done();
}
});
await pfs.writeFile(path.join(userDataPath, 'testContainer', 'settings.json'), '{}');
});
test('watch container - event is triggerred when file under container is deleted', async (done) => {
const container = joinPath(userDataResource, 'testContainer');
const resource = joinPath(userDataResource, 'testContainer/settings.json');
await testObject.writeFile(resource, VSBuffer.fromString('{}'));
const disposable = testObject.watch(container);
testObject.onFileChanges(e => {
if (e.contains(container)) {
disposable.dispose();
done();
}
});
await testObject.del(resource);
});
test('watch container - event is triggerred when file under container is deleted externally ', async (done) => {
const container = joinPath(userDataResource, 'testContainer');
const resource = joinPath(userDataResource, 'testContainer/settings.json');
await testObject.writeFile(resource, VSBuffer.fromString('{}'));
const disposable = testObject.watch(container);
testObject.onFileChanges(e => {
if (e.contains(container)) {
disposable.dispose();
done();
}
});
await pfs.unlink(path.join(userDataPath, 'testContainer', 'settings.json'));
});
});
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册