Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,6 @@ src/client/common/platform/errors.ts
src/client/common/platform/fs-temp.ts
src/client/common/platform/fs-paths.ts
src/client/common/platform/platformService.ts
src/client/common/platform/types.ts
src/client/common/platform/registry.ts
src/client/common/platform/pathUtils.ts
src/client/common/persistentState.ts
Expand Down
27 changes: 2 additions & 25 deletions src/client/common/platform/fileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { promisify } from 'util';
import * as vscode from 'vscode';
import '../extensions';
import { traceError } from '../logger';
import { convertFileType } from '../utils/filesystem';
import { createDirNotEmptyError, isFileExistsError, isFileNotFoundError, isNoPermissionsError } from './errors';
import { FileSystemPaths, FileSystemPathUtils } from './fs-paths';
import { TemporaryFileSystem } from './fs-temp';
Expand All @@ -31,30 +32,6 @@ import {

const ENCODING = 'utf8';

interface IKnowsFileType {
isFile(): boolean;
isDirectory(): boolean;
isSymbolicLink(): boolean;
}

// This helper function determines the file type of the given stats
// object. The type follows the convention of node's fs module, where
// a file has exactly one type. Symlinks are not resolved.
export function convertFileType(info: IKnowsFileType): FileType {
if (info.isFile()) {
return FileType.File;
}
if (info.isDirectory()) {
return FileType.Directory;
}
if (info.isSymbolicLink()) {
// The caller is responsible for combining this ("logical or")
// with File or Directory as necessary.
return FileType.SymbolicLink;
}
return FileType.Unknown;
}

export function convertStat(old: fs.Stats, filetype: FileType): FileStat {
return {
type: filetype,
Expand Down Expand Up @@ -550,7 +527,7 @@ export class FileSystem implements IFileSystem {
}

// eslint-disable-next-line @typescript-eslint/ban-types
public async writeFile(filename: string, data: {}): Promise<void> {
public async writeFile(filename: string, data: string | Buffer): Promise<void> {
return this.utils.raw.writeText(filename, data);
}

Expand Down
30 changes: 13 additions & 17 deletions src/client/common/platform/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { SemVer } from 'semver';
import * as vscode from 'vscode';
import { Architecture, OSType } from '../utils/platform';

//===========================
//= ==========================
// registry

export enum RegistryHive {
Expand All @@ -21,7 +21,7 @@ export interface IRegistry {
getValue(key: string, hive: RegistryHive, arch?: Architecture, name?: string): Promise<string | undefined | null>;
}

//===========================
//= ==========================
// platform

export const IsWindows = Symbol('IS_WINDOWS');
Expand All @@ -41,7 +41,7 @@ export interface IPlatformService {
getVersion(): Promise<SemVer>;
}

//===========================
//= ==========================
// temp FS

export type TemporaryFile = { filePath: string } & vscode.Disposable;
Expand All @@ -51,7 +51,7 @@ export interface ITempFileSystem {
createFile(suffix: string, mode?: number): Promise<TemporaryFile>;
}

//===========================
//= ==========================
// FS paths

// The low-level file path operations used by the extension.
Expand Down Expand Up @@ -87,19 +87,15 @@ export interface IFileSystemPathUtils {
getDisplayName(pathValue: string, cwd?: string): string;
}

//===========================
//= ==========================
// filesystem operations

// We could use FileType from utils/filesystem.ts, but it's simpler this way.
export import FileType = vscode.FileType;
export import FileStat = vscode.FileStat;
export type ReadStream = fs.ReadStream;
export type WriteStream = fs.WriteStream;

export type DirEntry = {
filename: string;
filetype: FileType;
};

// The low-level filesystem operations on which the extension depends.
export interface IRawFileSystem {
// Get information about a file (resolve symlinks).
Expand All @@ -111,23 +107,23 @@ export interface IRawFileSystem {
// Move the file to a different location (and/or rename it).
move(src: string, tgt: string): Promise<void>;

//***********************
//* **********************
// files

// Return the raw bytes of the given file.
readData(filename: string): Promise<Buffer>;
// Return the text of the given file (decoded from UTF-8).
readText(filename: string): Promise<string>;
// Write the given text to the file (UTF-8 encoded).
writeText(filename: string, data: {}): Promise<void>;
writeText(filename: string, data: string | Buffer): Promise<void>;
// Write the given text to the end of the file (UTF-8 encoded).
appendText(filename: string, text: string): Promise<void>;
// Copy a file.
copyFile(src: string, dest: string): Promise<void>;
// Delete a file.
rmfile(filename: string): Promise<void>;

//***********************
//* **********************
// directories

// Create the directory and any missing parent directories.
Expand All @@ -139,7 +135,7 @@ export interface IRawFileSystem {
// Return the contents of the directory.
listdir(dirname: string): Promise<[string, FileType][]>;

//***********************
//* **********************
// not async

// Get information about a file (resolve symlinks).
Expand All @@ -159,14 +155,14 @@ export interface IFileSystemUtils {
readonly pathUtils: IFileSystemPathUtils;
readonly tmp: ITempFileSystem;

//***********************
//* **********************
// aliases

createDirectory(dirname: string): Promise<void>;
deleteDirectory(dirname: string): Promise<void>;
deleteFile(filename: string): Promise<void>;

//***********************
//* **********************
// helpers

// Determine if the file exists, optionally requiring the type.
Expand All @@ -188,7 +184,7 @@ export interface IFileSystemUtils {
// Get the paths of all files matching the pattern.
search(globPattern: string): Promise<string[]>;

//***********************
//* **********************
// helpers (non-async)

fileExistsSync(path: string): boolean;
Expand Down
139 changes: 139 additions & 0 deletions src/client/common/utils/filesystem.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
import { logError } from '../../logging';
import { getOSType, OSType } from './platform';

/**
Expand All @@ -28,3 +31,139 @@ export function areSameFilename(filename1: string, filename2: string): boolean {
const norm2 = normalizeFilename(filename2);
return norm1 === norm2;
}

export import FileType = vscode.FileType;

export type DirEntry = {
filename: string;
filetype: FileType;
};

interface IKnowsFileType {
isFile(): boolean;
isDirectory(): boolean;
isSymbolicLink(): boolean;
}

// This helper function determines the file type of the given stats
// object. The type follows the convention of node's fs module, where
// a file has exactly one type. Symlinks are not resolved.
export function convertFileType(info: IKnowsFileType): FileType {
if (info.isFile()) {
return FileType.File;
}
if (info.isDirectory()) {
return FileType.Directory;
}
if (info.isSymbolicLink()) {
// The caller is responsible for combining this ("logical or")
// with File or Directory as necessary.
return FileType.SymbolicLink;
}
return FileType.Unknown;
}

/**
* Identify the file type for the given file.
*/
export async function getFileType(
filename: string,
opts: {
ignoreErrors: boolean;
} = { ignoreErrors: true },
): Promise<FileType | undefined> {
let stat: fs.Stats;
try {
stat = await fs.promises.lstat(filename);
} catch (err) {
if (err.code === 'ENOENT') {
return undefined;
}
if (opts.ignoreErrors) {
logError(`lstat() failed for "${filename}" (${err})`);
return FileType.Unknown;
}
throw err; // re-throw
}
return convertFileType(stat);
}

function normalizeFileTypes(filetypes: FileType | FileType[] | undefined): FileType[] | undefined {
if (filetypes === undefined) {
return undefined;
}
if (Array.isArray(filetypes)) {
if (filetypes.length === 0) {
return undefined;
}
return filetypes;
}
return [filetypes];
}

async function resolveFile(
file: string | DirEntry,
opts: {
ensure?: boolean;
onMissing?: FileType;
} = {},
): Promise<DirEntry | undefined> {
let filename: string;
if (typeof file !== 'string') {
if (!opts.ensure) {
if (opts.onMissing === undefined) {
return file;
}
// At least make sure it exists.
if ((await getFileType(file.filename)) !== undefined) {
return file;
}
}
filename = file.filename;
} else {
filename = file;
}

const filetype = (await getFileType(filename)) || opts.onMissing;
if (filetype === undefined) {
return undefined;
}
return { filename, filetype };
}

type FileFilterFunc = (file: string | DirEntry) => Promise<boolean>;

export function getFileFilter(
opts: {
ignoreMissing?: boolean;
ignoreFileType?: FileType | FileType[];
ensureEntry?: boolean;
} = {
ignoreMissing: true,
},
): FileFilterFunc | undefined {
const ignoreFileType = normalizeFileTypes(opts.ignoreFileType);

if (!opts.ignoreMissing && !ignoreFileType) {
// Do not filter.
return undefined;
}

async function filterFile(file: string | DirEntry): Promise<boolean> {
let entry = await resolveFile(file, { ensure: opts.ensureEntry });
if (!entry) {
if (opts.ignoreMissing) {
return false;
}
const filename = typeof file === 'string' ? file : file.filename;
entry = { filename, filetype: FileType.Unknown };
}
if (ignoreFileType) {
if (ignoreFileType.includes(entry!.filetype)) {
return false;
}
}
return true;
}
return filterFile;
}
25 changes: 1 addition & 24 deletions src/client/pythonEnvironments/common/commonUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@

import * as fs from 'fs';
import * as path from 'path';
import { convertFileType } from '../../common/platform/fileSystem';
import { DirEntry, FileType } from '../../common/platform/types';
import { convertFileType, DirEntry, FileType, getFileType } from '../../common/utils/filesystem';
import { getOSType, OSType } from '../../common/utils/platform';
import { logError } from '../../logging';
import { PythonVersion, UNKNOWN_PYTHON_VERSION } from '../base/info';
Expand Down Expand Up @@ -172,28 +171,6 @@ async function readDirEntries(
return entries;
}

async function getFileType(
filename: string,
opts: {
ignoreErrors: boolean;
} = { ignoreErrors: true },
): Promise<FileType | undefined> {
let stat: fs.Stats;
try {
stat = await fs.promises.lstat(filename);
} catch (err) {
if (err.code === 'ENOENT') {
return undefined;
}
if (opts.ignoreErrors) {
logError(`lstat() failed for "${filename}" (${err})`);
return FileType.Unknown;
}
throw err; // re-throw
}
return convertFileType(stat);
}

function matchFile(
filename: string,
filterFile: FileFilterFunc | undefined,
Expand Down