diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts index 06728e206d9..7d552df04a8 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts @@ -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); diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index b789748febe..c3fd729d148 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -209,6 +209,8 @@ export class FileService extends Disposable implements IFileService { }); } + private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat | { type: FileType } & Partial, siblings: number | undefined, resolveMetadata: boolean, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise; + private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat, siblings: number | undefined, resolveMetadata: true, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise; private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat | { type: FileType } & Partial, siblings: number | undefined, resolveMetadata: boolean, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise { // convert to file stat @@ -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 }) }; diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 0356e8dfdd3..85e140f5b10 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -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; } @@ -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. @@ -608,6 +624,7 @@ interface IBaseStat { export interface IBaseStatWithMetadata extends IBaseStat { mtime: number; + ctime: number; etag: string; size: number; } @@ -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[]; @@ -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; diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index 7c7e255d80e..0187b5d9bcc 100644 --- a/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -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 }; diff --git a/src/vs/platform/files/test/node/diskFileService.test.ts b/src/vs/platform/files/test/node/diskFileService.test.ts index 32cbb18e545..f1a81f3e20e 100644 --- a/src/vs/platform/files/test/node/diskFileService.test.ts +++ b/src/vs/platform/files/test/node/diskFileService.test.ts @@ -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 => { @@ -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)); } @@ -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 => { @@ -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'))] }); diff --git a/src/vs/workbench/api/browser/mainThreadFileSystem.ts b/src/vs/workbench/api/browser/mainThreadFileSystem.ts index 3407a420ca7..fa8c5bdaf7e 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystem.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystem.ts @@ -52,7 +52,7 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { $stat(uri: UriComponents): Promise { 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) diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 5cfaec6676b..5e6a21d6987 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -32,6 +32,7 @@ import { Schemas } from 'vs/base/common/network'; export interface IBackupMetaData { mtime: number; + ctime: number; size: number; etag: string; orphaned: boolean; @@ -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 @@ -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, @@ -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, diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 9b4fbf6b47c..e536d21ee31 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -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), @@ -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 }); @@ -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) }); @@ -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)