From b26602f2237938106ebaaba52bf7dc54aecf00cc Mon Sep 17 00:00:00 2001 From: Christopher Date: Fri, 29 Mar 2019 12:55:46 +0000 Subject: [PATCH] perf: All fs is done async (#540) --- src/commands/command.ts | 26 +++++++++++++---------- src/commands/deleteUnversioned.ts | 22 ++++++++++++-------- src/commands/openFile.ts | 7 +++++-- src/commands/openHeadFile.ts | 2 +- src/fs/exists.ts | 2 +- src/fs/index.ts | 9 ++++++++ src/fs/lstat.ts | 4 ++++ src/fs/mkdir.ts | 4 ++++ src/fs/read_file.ts | 4 ++++ src/fs/readdir.ts | 13 ++---------- src/fs/rmdir.ts | 4 ++++ src/fs/stat.ts | 15 +++----------- src/fs/unlink.ts | 4 ++++ src/fs/write_file.ts | 4 ++++ src/historyView/common.ts | 11 ++++++---- src/historyView/repoLogProvider.ts | 12 +++++------ src/model.ts | 4 +--- src/svnRepository.ts | 6 +++--- src/tempFiles.ts | 10 +++------ src/test/fs/exists.test.ts | 33 ++++++++++++++++++++++++++++++ src/util.ts | 15 +++++++------- 21 files changed, 133 insertions(+), 78 deletions(-) create mode 100644 src/fs/index.ts create mode 100644 src/fs/lstat.ts create mode 100644 src/fs/mkdir.ts create mode 100644 src/fs/read_file.ts create mode 100644 src/fs/rmdir.ts create mode 100644 src/fs/unlink.ts create mode 100644 src/fs/write_file.ts create mode 100644 src/test/fs/exists.test.ts diff --git a/src/commands/command.ts b/src/commands/command.ts index 4c1d8766..cf78d852 100644 --- a/src/commands/command.ts +++ b/src/commands/command.ts @@ -1,4 +1,3 @@ -import * as fs from "original-fs"; import * as path from "path"; import { commands, @@ -16,6 +15,7 @@ import { WorkspaceEdit } from "vscode"; import { ICommandOptions, Status, SvnUriAction } from "../common/types"; +import { exists, readFile, stat, unlink } from "../fs"; import { inputIgnoreList } from "../ignoreitems"; import { applyLineChanges } from "../lineChanges"; import { Model } from "../model"; @@ -194,7 +194,7 @@ export abstract class Command implements Disposable { preserveFocus?: boolean, preserveSelection?: boolean ): Promise { - let left = this.getLeftResource(resource, against); + let left = await this.getLeftResource(resource, against); let right = this.getRightResource(resource, against); const title = this.getTitle(resource, against); @@ -209,8 +209,8 @@ export abstract class Command implements Disposable { } if ( - fs.existsSync(right.fsPath) && - fs.statSync(right.fsPath).isDirectory() + (await exists(right.fsPath)) && + (await stat(right.fsPath)).isDirectory() ) { return; } @@ -244,10 +244,10 @@ export abstract class Command implements Disposable { ); } - protected getLeftResource( + protected async getLeftResource( resource: Resource, against: string = "" - ): Uri | undefined { + ): Promise { if (resource.remote) { if (resource.type !== Status.DELETED) { return toSvnUri(resource.resourceUri, SvnUriAction.SHOW, { @@ -266,11 +266,11 @@ export abstract class Command implements Disposable { // Show file if has conflicts marks if ( resource.type === Status.CONFLICTED && - fs.existsSync(resource.resourceUri.fsPath) + (await exists(resource.resourceUri.fsPath)) ) { - const text = fs.readFileSync(resource.resourceUri.fsPath, { + const text = (await readFile(resource.resourceUri.fsPath, { encoding: "utf8" - }); + })) as string; // Check for lines begin with "<<<<<<", "=======", ">>>>>>>" if (/^<{7}[^]+^={7}[^]+^>{7}/m.test(text)) { @@ -394,8 +394,12 @@ export abstract class Command implements Disposable { try { const tempFile = path.join(repository.root, ".svn", "tmp", "svn.patch"); - if (fs.existsSync(tempFile)) { - fs.unlinkSync(tempFile); + if (await exists(tempFile)) { + try { + await unlink(tempFile); + } catch (err) { + // TODO(cjohnston)//log error + } } const uri = Uri.file(tempFile).with({ diff --git a/src/commands/deleteUnversioned.ts b/src/commands/deleteUnversioned.ts index 3384d6b0..24788908 100644 --- a/src/commands/deleteUnversioned.ts +++ b/src/commands/deleteUnversioned.ts @@ -1,5 +1,5 @@ -import * as fs from "original-fs"; import { SourceControlResourceState, window } from "vscode"; +import { exists, lstat, unlink } from "../fs"; import { deleteDirectory } from "../util"; import { Command } from "./command"; @@ -24,16 +24,20 @@ export class DeleteUnversioned extends Command { for (const uri of uris) { const fsPath = uri.fsPath; - if (!fs.existsSync(fsPath)) { - continue; - } + try { + if (!(await exists(fsPath))) { + continue; + } - const stat = fs.lstatSync(fsPath); + const stat = await lstat(fsPath); - if (stat.isDirectory()) { - deleteDirectory(fsPath); - } else { - fs.unlinkSync(fsPath); + if (stat.isDirectory()) { + deleteDirectory(fsPath); + } else { + await unlink(fsPath); + } + } catch (err) { + // TODO(cjohnston) Show meaningful error to user } } } diff --git a/src/commands/openFile.ts b/src/commands/openFile.ts index bc58d5c2..42cffe29 100644 --- a/src/commands/openFile.ts +++ b/src/commands/openFile.ts @@ -1,4 +1,3 @@ -import * as fs from "original-fs"; import { SourceControlResourceState, TextDocumentShowOptions, @@ -7,6 +6,7 @@ import { window, workspace } from "vscode"; +import { exists, stat } from "../fs"; import { Resource } from "../resource"; import IncomingChangeNode from "../treeView/nodes/incomingChangeNode"; import { fromSvnUri } from "../uri"; @@ -65,7 +65,10 @@ export class OpenFile extends Command { const preview = uris.length === 1 ? true : false; const activeTextEditor = window.activeTextEditor; for (const uri of uris) { - if (fs.existsSync(uri.fsPath) && fs.statSync(uri.fsPath).isDirectory()) { + if ( + (await exists(uri.fsPath)) && + (await stat(uri.fsPath)).isDirectory() + ) { continue; } diff --git a/src/commands/openHeadFile.ts b/src/commands/openHeadFile.ts index 5afb72d8..9f5d4b97 100644 --- a/src/commands/openHeadFile.ts +++ b/src/commands/openHeadFile.ts @@ -26,7 +26,7 @@ export class OpenHeadFile extends Command { return; } - const HEAD = this.getLeftResource(resource, "HEAD"); + const HEAD = await this.getLeftResource(resource, "HEAD"); const basename = path.basename(resource.resourceUri.fsPath); if (!HEAD) { diff --git a/src/fs/exists.ts b/src/fs/exists.ts index ab741115..7b5e60c5 100644 --- a/src/fs/exists.ts +++ b/src/fs/exists.ts @@ -2,6 +2,6 @@ import { access } from "original-fs"; export function exists(path: string): Promise { return new Promise((resolve, reject) => { - access(path, err => (err ? reject(err) : resolve(true))); + access(path, err => (err ? resolve(false) : resolve(true))); }); } diff --git a/src/fs/index.ts b/src/fs/index.ts new file mode 100644 index 00000000..67465859 --- /dev/null +++ b/src/fs/index.ts @@ -0,0 +1,9 @@ +export { exists } from "./exists"; +export { lstat } from "./lstat"; +export { mkdir } from "./mkdir"; +export { readFile } from "./read_file"; +export { readdir } from "./readdir"; +export { rmdir } from "./rmdir"; +export { stat } from "./stat"; +export { unlink } from "./unlink"; +export { writeFile } from "./write_file"; diff --git a/src/fs/lstat.ts b/src/fs/lstat.ts new file mode 100644 index 00000000..387bc9a4 --- /dev/null +++ b/src/fs/lstat.ts @@ -0,0 +1,4 @@ +import { lstat as fsLstat } from "original-fs"; +import { promisify } from "util"; + +export const lstat = promisify(fsLstat); diff --git a/src/fs/mkdir.ts b/src/fs/mkdir.ts new file mode 100644 index 00000000..01ec05c7 --- /dev/null +++ b/src/fs/mkdir.ts @@ -0,0 +1,4 @@ +import { mkdir as fsMkdir } from "original-fs"; +import { promisify } from "util"; + +export const mkdir = promisify(fsMkdir); diff --git a/src/fs/read_file.ts b/src/fs/read_file.ts new file mode 100644 index 00000000..1c484fc6 --- /dev/null +++ b/src/fs/read_file.ts @@ -0,0 +1,4 @@ +import { readFile as fsReadFile } from "original-fs"; +import { promisify } from "util"; + +export const readFile = promisify(fsReadFile); diff --git a/src/fs/readdir.ts b/src/fs/readdir.ts index 25ae43d3..974272de 100644 --- a/src/fs/readdir.ts +++ b/src/fs/readdir.ts @@ -1,13 +1,4 @@ import { readdir as fsReaddir } from "original-fs"; +import { promisify } from "util"; -export function readdir(path: string): Promise { - return new Promise((resolve, reject) => { - fsReaddir(path, (err, files) => { - if (err) { - reject(err); - } - - resolve(files); - }); - }); -} +export const readdir = promisify(fsReaddir); diff --git a/src/fs/rmdir.ts b/src/fs/rmdir.ts new file mode 100644 index 00000000..b9454f6e --- /dev/null +++ b/src/fs/rmdir.ts @@ -0,0 +1,4 @@ +import { rmdir as fsRmdir } from "original-fs"; +import { promisify } from "util"; + +export const rmdir = promisify(fsRmdir); diff --git a/src/fs/stat.ts b/src/fs/stat.ts index 05cdfa06..2dbb1488 100644 --- a/src/fs/stat.ts +++ b/src/fs/stat.ts @@ -1,13 +1,4 @@ -import { stat as fsStat, Stats } from "original-fs"; +import { stat as fsStat } from "original-fs"; +import { promisify } from "util"; -export function stat(filePath: string): Promise { - return new Promise((resolve, reject) => { - fsStat(filePath, (err, stats) => { - if (err) { - reject(err); - } - - resolve(stats); - }); - }); -} +export const stat = promisify(fsStat); diff --git a/src/fs/unlink.ts b/src/fs/unlink.ts new file mode 100644 index 00000000..36423a4b --- /dev/null +++ b/src/fs/unlink.ts @@ -0,0 +1,4 @@ +import { unlink as fsUnlink } from "original-fs"; +import { promisify } from "util"; + +export const unlink = promisify(fsUnlink); diff --git a/src/fs/write_file.ts b/src/fs/write_file.ts new file mode 100644 index 00000000..3d2d7b09 --- /dev/null +++ b/src/fs/write_file.ts @@ -0,0 +1,4 @@ +import { writeFile as fsWriteFile } from "original-fs"; +import { promisify } from "util"; + +export const writeFile = promisify(fsWriteFile); diff --git a/src/historyView/common.ts b/src/historyView/common.ts index b385b2d9..0257c9ff 100644 --- a/src/historyView/common.ts +++ b/src/historyView/common.ts @@ -1,5 +1,4 @@ import { createHash } from "crypto"; -import * as fs from "original-fs"; import * as path from "path"; import { commands, @@ -10,6 +9,7 @@ import { window } from "vscode"; import { ISvnLogEntry, ISvnLogEntryPath } from "../common/types"; +import { exists, lstat } from "../fs"; import { configuration } from "../helpers/configuration"; import { IRemoteRepository } from "../remoteRepository"; import { SvnRI } from "../svnRI"; @@ -128,7 +128,10 @@ export function insertBaseMarker( return undefined; } -export function checkIfFile(e: SvnRI, local: boolean): boolean | undefined { +export async function checkIfFile( + e: SvnRI, + local: boolean +): Promise { if (e.localFullPath === undefined) { if (local) { window.showErrorMessage("No working copy for this path"); @@ -137,7 +140,7 @@ export function checkIfFile(e: SvnRI, local: boolean): boolean | undefined { } let stat; try { - stat = fs.lstatSync(e.localFullPath.fsPath); + stat = await lstat(e.localFullPath.fsPath); } catch { window.showWarningMessage( "Not available from this working copy: " + e.localFullPath @@ -238,7 +241,7 @@ async function downloadFile( const nm = repo.getPathNormalizer(); const ri = nm.parse(arg.toString(true)); const localPath = ri.localFullPath; - if (localPath === undefined || !fs.existsSync(localPath.path)) { + if (localPath === undefined || !(await exists(localPath.path))) { const errorMsg = "BASE revision doesn't exist for " + (localPath ? localPath.path : "remote path"); diff --git a/src/historyView/repoLogProvider.ts b/src/historyView/repoLogProvider.ts index 91c9907f..00596f09 100644 --- a/src/historyView/repoLogProvider.ts +++ b/src/historyView/repoLogProvider.ts @@ -1,4 +1,3 @@ -import * as fs from "original-fs"; import * as path from "path"; import { commands, @@ -17,6 +16,7 @@ import { ISvnLogEntry, ISvnLogEntryPath } from "../common/types"; +import { exists } from "../fs"; import { Model } from "../model"; import { IRemoteRepository } from "../remoteRepository"; import { Repository } from "../repository"; @@ -203,7 +203,7 @@ export class RepoLogProvider public addRepolikeGui() { const box = window.createInputBox(); box.prompt = "Enter SVN URL or local path"; - box.onDidAccept(() => { + box.onDidAccept(async () => { let repoLike = box.value; if ( !path.isAbsolute(repoLike) && @@ -213,7 +213,7 @@ export class RepoLogProvider ) { for (const wsf of workspace.workspaceFolders) { const joined = path.join(wsf.uri.fsPath, repoLike); - if (fs.existsSync(joined)) { + if (await exists(joined)) { repoLike = joined; break; } @@ -232,11 +232,11 @@ export class RepoLogProvider box.show(); } - public openFileRemoteCmd(element: ILogTreeItem) { + public async openFileRemoteCmd(element: ILogTreeItem) { const commit = element.data as ISvnLogEntryPath; const item = this.getCached(element); const ri = item.repo.getPathNormalizer().parse(commit._); - if (checkIfFile(ri, false) === false) { + if ((await checkIfFile(ri, false)) === false) { return; } const parent = (element.parent as ILogTreeItem).data as ISvnLogEntry; @@ -257,7 +257,7 @@ export class RepoLogProvider const commit = element.data as ISvnLogEntryPath; const item = this.getCached(element); const ri = item.repo.getPathNormalizer().parse(commit._); - if (checkIfFile(ri, false) === false) { + if ((await checkIfFile(ri, false)) === false) { return; } const parent = (element.parent as ILogTreeItem).data as ISvnLogEntry; diff --git a/src/model.ts b/src/model.ts index 162c51a9..74b382df 100644 --- a/src/model.ts +++ b/src/model.ts @@ -18,9 +18,7 @@ import { Status } from "./common/types"; import { debounce } from "./decorators"; -import { exists } from "./fs/exists"; -import { readdir } from "./fs/readdir"; -import { stat } from "./fs/stat"; +import { exists, readdir, stat } from "./fs"; import { configuration } from "./helpers/configuration"; import { RemoteRepository } from "./remoteRepository"; import { Repository } from "./repository"; diff --git a/src/svnRepository.ts b/src/svnRepository.ts index b6b6d544..7dea8be4 100644 --- a/src/svnRepository.ts +++ b/src/svnRepository.ts @@ -1,4 +1,3 @@ -import * as fs from "original-fs"; import * as path from "path"; import * as tmp from "tmp"; import { Uri, workspace } from "vscode"; @@ -12,6 +11,7 @@ import { Status } from "./common/types"; import { sequentialize } from "./decorators"; +import { exists, writeFile } from "./fs"; import { getBranchName } from "./helpers/branch"; import { configuration } from "./helpers/configuration"; import { parseInfoXml } from "./infoParser"; @@ -209,7 +209,7 @@ export class Repository { const args = ["commit", ...files]; - if (fs.existsSync(path.join(this.workspaceRoot, message))) { + if (await exists(path.join(this.workspaceRoot, message))) { args.push("--force-log"); } @@ -228,7 +228,7 @@ export class Repository { prefix: "svn-commit-message-" }); - fs.writeFileSync(tmpFile.name, message, "UTF-8"); + await writeFile(tmpFile.name, message, "UTF-8"); args.push("-F", tmpFile.name); args.push("--encoding", "UTF-8"); diff --git a/src/tempFiles.ts b/src/tempFiles.ts index da0c1cc8..215cdd25 100644 --- a/src/tempFiles.ts +++ b/src/tempFiles.ts @@ -1,11 +1,7 @@ -// use import { promises as fs } from "original-fs"; when nodejs will be updated -import * as fs from "original-fs"; import * as os from "os"; import * as path from "path"; -import * as util from "util"; import { Uri } from "vscode"; - -const writeFile = util.promisify(fs.writeFile); +import { exists, mkdir, writeFile } from "./fs"; export const tempdir = path.join(os.tmpdir(), "vscode-svn"); @@ -14,8 +10,8 @@ export async function dumpSvnFile( revision: string, payload: string ): Promise { - if (!fs.existsSync(tempdir)) { - await fs.mkdirSync(tempdir); + if (!await exists(tempdir)) { + await mkdir(tempdir); } const fname = `r${revision}_${path.basename(snvUri.fsPath)}`; const fpath = path.join(tempdir, fname); diff --git a/src/test/fs/exists.test.ts b/src/test/fs/exists.test.ts new file mode 100644 index 00000000..30a85700 --- /dev/null +++ b/src/test/fs/exists.test.ts @@ -0,0 +1,33 @@ +import * as assert from "assert"; +import { exists } from "../../fs/exists"; +import { newTempDir, destroyAllTempPaths } from "../testUtil"; +import { writeFileSync } from "fs"; +import { join } from "path"; + +suite("Test async exists wrapper", () => { + suiteTeardown(() => { + destroyAllTempPaths(); + }); + + test("Dir does exist", async () => { + const fullpath = newTempDir("test-dir"); + + assert.ok((await exists(fullpath))); + }); + + test("Dir does not exist", async () => { + assert.strictEqual((await exists('/tmp/thisfiledoesnotexsist')), false); + }); + + test("File does exist", async () => { + const testDirPath = newTempDir("test-file-dir"); + const filePath = join(testDirPath, 'testfile.txt'); + writeFileSync(filePath, 'test'); + + assert.ok((await exists(filePath))); + }); + + test("File does not exist", async () => { + assert.strictEqual((await exists('/tmp/thisfiledoesnotexsist.txt')), false); + }); +}); diff --git a/src/util.ts b/src/util.ts index 89a66f8e..5a4a2006 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,8 +1,7 @@ -import * as fs from "original-fs"; import * as path from "path"; -import * as util from "util"; import { commands, Event, window } from "vscode"; import { Operation } from "./common/types"; +import { exists, lstat, readdir, rmdir, unlink } from "./fs"; export interface IDisposable { dispose(): void; @@ -170,17 +169,17 @@ export function isReadOnly(operation: Operation): boolean { * @param {string} dirPath * @see https://stackoverflow.com/a/42505874/3027390 */ -export function deleteDirectory(dirPath: string) { - if (fs.existsSync(dirPath) && fs.lstatSync(dirPath).isDirectory()) { - fs.readdirSync(dirPath).forEach((entry: string) => { +export async function deleteDirectory(dirPath: string): Promise { + if (await exists(dirPath) && (await lstat(dirPath)).isDirectory()) { + (await readdir(dirPath)).forEach(async (entry: string) => { const entryPath = path.join(dirPath, entry); - if (fs.lstatSync(entryPath).isDirectory()) { + if ((await lstat(entryPath)).isDirectory()) { deleteDirectory(entryPath); } else { - fs.unlinkSync(entryPath); + await unlink(entryPath); } }); - fs.rmdirSync(dirPath); + await rmdir(dirPath); } }