Skip to content

Commit

Permalink
files - implement ctime properly as btime (fix #84525)
Browse files Browse the repository at this point in the history
  • Loading branch information
bpasero committed Nov 12, 2019
1 parent d5ff86c commit 5ae52b6
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 11 deletions.
Expand Up @@ -23,6 +23,8 @@ suite('workspace-fs', () => {
assert.equal(typeof stat.mtime, 'number');
assert.equal(typeof stat.ctime, 'number');

assert.ok(stat.mtime > 0);
assert.ok(stat.ctime > 0);

const entries = await vscode.workspace.fs.readDirectory(root);
assert.ok(entries.length > 0);
Expand Down
3 changes: 3 additions & 0 deletions src/vs/platform/files/common/fileService.ts
Expand Up @@ -209,6 +209,8 @@ export class FileService extends Disposable implements IFileService {
});
}

private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat | { type: FileType } & Partial<IStat>, siblings: number | undefined, resolveMetadata: boolean, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise<IFileStat>;
private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat, siblings: number | undefined, resolveMetadata: true, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise<IFileStatWithMetadata>;
private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat | { type: FileType } & Partial<IStat>, siblings: number | undefined, resolveMetadata: boolean, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise<IFileStat> {

// convert to file stat
Expand All @@ -219,6 +221,7 @@ export class FileService extends Disposable implements IFileService {
isSymbolicLink: (stat.type & FileType.SymbolicLink) !== 0,
isReadonly: !!(provider.capabilities & FileSystemProviderCapabilities.Readonly),
mtime: stat.mtime,
ctime: stat.ctime,
size: stat.size,
etag: etag({ mtime: stat.mtime, size: stat.size })
};
Expand Down
24 changes: 21 additions & 3 deletions src/vs/platform/files/common/files.ts
Expand Up @@ -207,8 +207,17 @@ export enum FileType {

export interface IStat {
type: FileType;

/**
* The last modification date represented as millis from unix epoch.
*/
mtime: number;

/**
* The creation date represented as millis from unix epoch.
*/
ctime: number;

size: number;
}

Expand Down Expand Up @@ -583,14 +592,21 @@ interface IBaseStat {
size?: number;

/**
* The last modification date represented
* as millis from unix epoch.
* The last modification date represented as millis from unix epoch.
*
* The value may or may not be resolved as
* it is optional.
*/
mtime?: number;

/**
* The creation date represented as millis from unix epoch.
*
* The value may or may not be resolved as
* it is optional.
*/
ctime?: number;

/**
* A unique identifier thet represents the
* current state of the file or directory.
Expand All @@ -608,6 +624,7 @@ interface IBaseStat {

export interface IBaseStatWithMetadata extends IBaseStat {
mtime: number;
ctime: number;
etag: string;
size: number;
}
Expand Down Expand Up @@ -635,6 +652,7 @@ export interface IFileStat extends IBaseStat {

export interface IFileStatWithMetadata extends IFileStat, IBaseStatWithMetadata {
mtime: number;
ctime: number;
etag: string;
size: number;
children?: IFileStatWithMetadata[];
Expand Down Expand Up @@ -703,7 +721,7 @@ export interface IResolveFileOptions {
readonly resolveSingleChildDescendants?: boolean;

/**
* Will resolve mtime, size and etag of files if enabled. This can have a negative impact
* Will resolve mtime, ctime, size and etag of files if enabled. This can have a negative impact
* on performance and thus should only be used when these values are required.
*/
readonly resolveMetadata?: boolean;
Expand Down
2 changes: 1 addition & 1 deletion src/vs/platform/files/node/diskFileSystemProvider.ts
Expand Up @@ -80,7 +80,7 @@ export class DiskFileSystemProvider extends Disposable implements

return {
type: this.toType(stat, isSymbolicLink),
ctime: stat.ctime.getTime(),
ctime: stat.birthtime.getTime(), // intentionally not using ctime here, we want the creation time
mtime: stat.mtime.getTime(),
size: stat.size
};
Expand Down
44 changes: 38 additions & 6 deletions src/vs/platform/files/test/node/diskFileService.test.ts
Expand Up @@ -213,23 +213,32 @@ suite('Disk File Service', function () {
assert.equal(exists, false);
});

test('resolve', async () => {
const resolved = await service.resolve(URI.file(testDir), { resolveTo: [URI.file(join(testDir, 'deep'))] });
assert.equal(resolved.children!.length, 8);
test('resolve - file', async () => {
const resource = URI.file(getPathFromAmdModule(require, './fixtures/resolver/index.html'));
const resolved = await service.resolve(resource);

const deep = (getByName(resolved, 'deep')!);
assert.equal(deep.children!.length, 4);
assert.equal(resolved.name, 'index.html');
assert.equal(resolved.resource.toString(), resource.toString());
assert.equal(resolved.children, undefined);
assert.ok(resolved.mtime! > 0);
assert.ok(resolved.ctime! > 0);
assert.ok(resolved.size! > 0);
});

test('resolve - directory', async () => {
const testsElements = ['examples', 'other', 'index.html', 'site.css'];

const result = await service.resolve(URI.file(getPathFromAmdModule(require, './fixtures/resolver')));
const resource = URI.file(getPathFromAmdModule(require, './fixtures/resolver'));
const result = await service.resolve(resource);

assert.ok(result);
assert.equal(result.resource.toString(), resource.toString());
assert.equal(result.name, 'resolver');
assert.ok(result.children);
assert.ok(result.children!.length > 0);
assert.ok(result!.isDirectory);
assert.ok(result.mtime! > 0);
assert.ok(result.ctime! > 0);
assert.equal(result.children!.length, testsElements.length);

assert.ok(result.children!.every(entry => {
Expand All @@ -242,12 +251,18 @@ suite('Disk File Service', function () {
assert.ok(basename(value.resource.fsPath));
if (['examples', 'other'].indexOf(basename(value.resource.fsPath)) >= 0) {
assert.ok(value.isDirectory);
assert.equal(value.mtime, undefined);
assert.equal(value.ctime, undefined);
} else if (basename(value.resource.fsPath) === 'index.html') {
assert.ok(!value.isDirectory);
assert.ok(!value.children);
assert.equal(value.mtime, undefined);
assert.equal(value.ctime, undefined);
} else if (basename(value.resource.fsPath) === 'site.css') {
assert.ok(!value.isDirectory);
assert.ok(!value.children);
assert.equal(value.mtime, undefined);
assert.equal(value.ctime, undefined);
} else {
assert.ok(!'Unexpected value ' + basename(value.resource.fsPath));
}
Expand All @@ -260,9 +275,12 @@ suite('Disk File Service', function () {
const result = await service.resolve(URI.file(getPathFromAmdModule(require, './fixtures/resolver')), { resolveMetadata: true });

assert.ok(result);
assert.equal(result.name, 'resolver');
assert.ok(result.children);
assert.ok(result.children!.length > 0);
assert.ok(result!.isDirectory);
assert.ok(result.mtime! > 0);
assert.ok(result.ctime! > 0);
assert.equal(result.children!.length, testsElements.length);

assert.ok(result.children!.every(entry => {
Expand All @@ -277,18 +295,32 @@ suite('Disk File Service', function () {
assert.ok(basename(value.resource.fsPath));
if (['examples', 'other'].indexOf(basename(value.resource.fsPath)) >= 0) {
assert.ok(value.isDirectory);
assert.ok(value.mtime! > 0);
assert.ok(value.ctime! > 0);
} else if (basename(value.resource.fsPath) === 'index.html') {
assert.ok(!value.isDirectory);
assert.ok(!value.children);
assert.ok(value.mtime! > 0);
assert.ok(value.ctime! > 0);
} else if (basename(value.resource.fsPath) === 'site.css') {
assert.ok(!value.isDirectory);
assert.ok(!value.children);
assert.ok(value.mtime! > 0);
assert.ok(value.ctime! > 0);
} else {
assert.ok(!'Unexpected value ' + basename(value.resource.fsPath));
}
});
});

test('resolve - directory with resolveTo', async () => {
const resolved = await service.resolve(URI.file(testDir), { resolveTo: [URI.file(join(testDir, 'deep'))] });
assert.equal(resolved.children!.length, 8);

const deep = (getByName(resolved, 'deep')!);
assert.equal(deep.children!.length, 4);
});

test('resolve - directory - resolveTo single directory', async () => {
const resolverFixturesPath = getPathFromAmdModule(require, './fixtures/resolver');
const result = await service.resolve(URI.file(resolverFixturesPath), { resolveTo: [URI.file(join(resolverFixturesPath, 'other/deep'))] });
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/browser/mainThreadFileSystem.ts
Expand Up @@ -52,7 +52,7 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape {
$stat(uri: UriComponents): Promise<IStat> {
return this._fileService.resolve(URI.revive(uri), { resolveMetadata: true }).then(stat => {
return {
ctime: 0,
ctime: stat.ctime,
mtime: stat.mtime,
size: stat.size,
type: MainThreadFileSystem._getFileType(stat)
Expand Down
Expand Up @@ -32,6 +32,7 @@ import { Schemas } from 'vs/base/common/network';

export interface IBackupMetaData {
mtime: number;
ctime: number;
size: number;
etag: string;
orphaned: boolean;
Expand Down Expand Up @@ -224,6 +225,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
if (isEqual(target, this.resource) && this.lastResolvedFileStat) {
meta = {
mtime: this.lastResolvedFileStat.mtime,
ctime: this.lastResolvedFileStat.ctime,
size: this.lastResolvedFileStat.size,
etag: this.lastResolvedFileStat.etag,
orphaned: this.inOrphanMode
Expand Down Expand Up @@ -313,6 +315,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
resource: this.resource,
name: basename(this.resource),
mtime: resolvedBackup.meta ? resolvedBackup.meta.mtime : Date.now(),
ctime: resolvedBackup.meta ? resolvedBackup.meta.ctime : Date.now(),
size: resolvedBackup.meta ? resolvedBackup.meta.size : 0,
etag: resolvedBackup.meta ? resolvedBackup.meta.etag : ETAG_DISABLED, // etag disabled if unknown!
value: resolvedBackup.value,
Expand Down Expand Up @@ -397,6 +400,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
resource: this.resource,
name: content.name,
mtime: content.mtime,
ctime: content.ctime,
size: content.size,
etag: content.etag,
isDirectory: false,
Expand Down
4 changes: 4 additions & 0 deletions src/vs/workbench/test/workbenchTestServices.ts
Expand Up @@ -262,6 +262,7 @@ export class TestTextFileService extends NativeTextFileService {
resource: content.resource,
name: content.name,
mtime: content.mtime,
ctime: content.ctime,
etag: content.etag,
encoding: 'utf8',
value: await createTextBufferFactoryFromStream(content.value),
Expand Down Expand Up @@ -996,6 +997,7 @@ export class TestFileService implements IFileService {
etag: 'index.txt',
encoding: 'utf8',
mtime: Date.now(),
ctime: Date.now(),
name: resources.basename(resource),
size: 1
});
Expand All @@ -1022,6 +1024,7 @@ export class TestFileService implements IFileService {
etag: 'index.txt',
encoding: 'utf8',
mtime: Date.now(),
ctime: Date.now(),
size: 1,
name: resources.basename(resource)
});
Expand All @@ -1033,6 +1036,7 @@ export class TestFileService implements IFileService {
etag: 'index.txt',
encoding: 'utf8',
mtime: Date.now(),
ctime: Date.now(),
size: 42,
isDirectory: false,
name: resources.basename(resource)
Expand Down

0 comments on commit 5ae52b6

Please sign in to comment.