From 78c1822d306504a97e0dc6c68b899ee66bfc94d1 Mon Sep 17 00:00:00 2001 From: Brett Saviano Date: Thu, 10 Apr 2025 16:40:59 -0400 Subject: [PATCH 1/4] Reduce REST traffic for server-side folders --- src/commands/project.ts | 10 ----- src/extension.ts | 26 +++++++++++++ .../FileSystemProvider/FileSystemProvider.ts | 37 +++++++++++-------- 3 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/commands/project.ts b/src/commands/project.ts index baaa856c..bc4d5374 100644 --- a/src/commands/project.ts +++ b/src/commands/project.ts @@ -680,11 +680,6 @@ export async function modifyProject( if (!args) return; const { node, api, project } = args; - // Technically a project is a "document", so tell the server that we're opening it - await new StudioActions().fireProjectUserAction(api, project, OtherStudioAction.OpenedDocument).catch(() => { - // Swallow error because showing it is more disruptive than using a potentially outdated project definition - }); - let items: ProjectItem[] = await api .actionQuery("SELECT Name, Type FROM %Studio.Project_ProjectItemsList(?,?) WHERE Type != 'GBL'", [project, "1"]) .then((data) => data.result.content); @@ -1149,11 +1144,6 @@ export async function modifyProjectMetadata(nodeOrUri: NodeBase | vscode.Uri | u if (!args) return; const { api, project } = args; - // Technically a project is a "document", so tell the server that we're opening it - await new StudioActions().fireProjectUserAction(api, project, OtherStudioAction.OpenedDocument).catch(() => { - // Swallow error because showing it is more disruptive than using a potentially outdated project definition - }); - try { const oldDesc: string = await api .actionQuery("SELECT Description FROM %Studio.Project WHERE Name = ?", [project]) diff --git a/src/extension.ts b/src/extension.ts index 83bd0fe9..254759fe 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -58,6 +58,7 @@ import { OtherStudioAction, contextSourceControlMenu, mainSourceControlMenu, + StudioActions, } from "./commands/studio"; import { addServerNamespaceToWorkspace, pickServerAndNamespace } from "./commands/addServerNamespaceToWorkspace"; import { jumpToTagAndOffset, openErrorLocation } from "./commands/jumpToTagAndOffset"; @@ -153,6 +154,7 @@ import { } from "./utils/documentIndex"; import { WorkspaceNode, NodeBase } from "./explorer/nodes"; import { showPlanWebview } from "./commands/showPlanPanel"; +import { isfsConfig } from "./utils/FileProviderUtil"; const packageJson = vscode.extensions.getExtension(extensionId).packageJSON; const extensionVersion = packageJson.version; @@ -708,6 +710,25 @@ async function systemModeWarning(wsFolders: readonly vscode.WorkspaceFolder[]): } } +/** + * Fire the `OpenedDocument` UserAction for any workspace folders + * that are showing the contents of a server-side project. + * This must be done because technically a project is a "document". + */ +async function fireOpenProjectUserAction(wsFolders: readonly vscode.WorkspaceFolder[]): Promise { + if (!wsFolders || wsFolders.length == 0) return; + for (const wsFolder of wsFolders) { + if (notIsfs(wsFolder.uri)) return; + const { project } = isfsConfig(wsFolder.uri); + if (!project) return; + const api = new AtelierAPI(wsFolder.uri); + if (!api.active) return; + new StudioActions().fireProjectUserAction(api, project, OtherStudioAction.OpenedDocument).catch(() => { + // Swallow error because showing it is more disruptive than using a potentially outdated project definition + }); + } +} + /** * Set when clause context keys so the ObjectScript Explorer and * Projects Explorer views are correctly shown or hidden depending @@ -941,6 +962,9 @@ export async function activate(context: vscode.ExtensionContext): Promise { // Warn about SystemMode systemModeWarning(vscode.workspace.workspaceFolders); + // Fire OpenedDocument UserAction for folders showing the contents of a server-side project + fireOpenProjectUserAction(vscode.workspace.workspaceFolders); + iscIcon = vscode.Uri.joinPath(context.extensionUri, "images", "fileIcon.svg"); // Index documents in all local workspace folders @@ -1531,6 +1555,8 @@ export async function activate(context: vscode.ExtensionContext): Promise { for (const r of e.removed) removeIndexOfWorkspaceFolder(r); // Show or hide explorer views as needed setExplorerContextKeys(); + // Fire OpenedDocument UserAction for added folders showing the contents of a server-side project + fireOpenProjectUserAction(e.added); }), vscode.commands.registerCommand("vscode-objectscript.importXMLFiles", importXMLFiles), vscode.commands.registerCommand("vscode-objectscript.exportToXMLFile", exportDocumentsToXMLFile), diff --git a/src/providers/FileSystemProvider/FileSystemProvider.ts b/src/providers/FileSystemProvider/FileSystemProvider.ts index f4ef26c6..871f61b7 100644 --- a/src/providers/FileSystemProvider/FileSystemProvider.ts +++ b/src/providers/FileSystemProvider/FileSystemProvider.ts @@ -2,7 +2,7 @@ import * as path from "path"; import * as vscode from "vscode"; import { isText } from "istextorbinary"; import { AtelierAPI } from "../../api"; -import { fireOtherStudioAction, OtherStudioAction, StudioActions } from "../../commands/studio"; +import { fireOtherStudioAction, OtherStudioAction } from "../../commands/studio"; import { isfsConfig, projectContentsFromUri, studioOpenDialogFromURI } from "../../utils/FileProviderUtil"; import { classNameRegex, @@ -234,6 +234,9 @@ export class FileSystemProvider implements vscode.FileSystemProvider { } public async stat(uri: vscode.Uri): Promise { + if (uri.path.includes(".vscode/") && !uri.path.endsWith("/settings.json")) { + throw vscode.FileSystemError.NoPermissions("Only settings.json is allowed within the /.vscode directory"); + } let entryPromise: Promise; let result: Entry; const redirectedUri = redirectDotvscodeRoot(uri); @@ -284,19 +287,14 @@ export class FileSystemProvider implements vscode.FileSystemProvider { } public async readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> { - uri = redirectDotvscodeRoot(uri); + if (uri.path.includes(".vscode/")) { + throw vscode.FileSystemError.NoPermissions("Cannot read the /.vscode directory"); + } const parent = await this._lookupAsDirectory(uri); const api = new AtelierAPI(uri); if (!api.active) throw vscode.FileSystemError.Unavailable(uri); const { csp, project } = isfsConfig(uri); if (project) { - if (["", "/"].includes(uri.path)) { - // Technically a project is a "document", so tell the server that we're opening it - await new StudioActions().fireProjectUserAction(api, project, OtherStudioAction.OpenedDocument).catch(() => { - // Swallow error because showing it is more disruptive than using a potentially outdated project definition - }); - } - // Get all items in the project return projectContentsFromUri(uri).then((entries) => entries.map((entry) => { @@ -405,7 +403,9 @@ export class FileSystemProvider implements vscode.FileSystemProvider { } public createDirectory(uri: vscode.Uri): void | Thenable { - uri = redirectDotvscodeRoot(uri); + if (uri.path.includes(".vscode/")) { + throw vscode.FileSystemError.NoPermissions("Cannot create a subdirectory of the /.vscode directory"); + } const basename = path.posix.basename(uri.path); const dirname = uri.with({ path: path.posix.dirname(uri.path) }); return this._lookupAsDirectory(dirname).then((parent) => { @@ -421,6 +421,9 @@ export class FileSystemProvider implements vscode.FileSystemProvider { } public async readFile(uri: vscode.Uri): Promise { + if (uri.path.includes(".vscode/") && !uri.path.endsWith("/settings.json")) { + throw vscode.FileSystemError.NoPermissions("Only settings.json is allowed within the /.vscode directory"); + } // Use _lookup() instead of _lookupAsFile() so we send // our cached mtime with the GET /doc request if we have it return this._lookup(uri, true).then((file: File) => { @@ -439,6 +442,9 @@ export class FileSystemProvider implements vscode.FileSystemProvider { overwrite: boolean; } ): void | Thenable { + if (uri.path.includes(".vscode/") && !uri.path.endsWith("/settings.json")) { + throw vscode.FileSystemError.NoPermissions("Only settings.json is allowed within the /.vscode directory"); + } uri = redirectDotvscodeRoot(uri); if (uri.path.startsWith("/.")) { throw vscode.FileSystemError.NoPermissions("dot-folders not supported by server"); @@ -606,6 +612,9 @@ export class FileSystemProvider implements vscode.FileSystemProvider { } public async delete(uri: vscode.Uri, options: { recursive: boolean }): Promise { + if (uri.path.includes(".vscode/") && !uri.path.endsWith("/settings.json")) { + throw vscode.FileSystemError.NoPermissions("Only settings.json is allowed within the /.vscode directory"); + } uri = redirectDotvscodeRoot(uri); const { project } = isfsConfig(uri); const csp = isCSP(uri); @@ -699,6 +708,9 @@ export class FileSystemProvider implements vscode.FileSystemProvider { if (vscode.workspace.getWorkspaceFolder(oldUri) != vscode.workspace.getWorkspaceFolder(newUri)) { throw vscode.FileSystemError.NoPermissions("Cannot rename a file across workspace folders"); } + if (oldUri.path.includes(".vscode/") || newUri.path.includes(".vscode/")) { + throw vscode.FileSystemError.NoPermissions("Cannot rename a file in the /.vscode directory"); + } // Check if the destination exists let newFileStat: vscode.FileStat; try { @@ -864,11 +876,6 @@ export class FileSystemProvider implements vscode.FileSystemProvider { // Fetch entry (a file or directory) from cache, else from server private async _lookup(uri: vscode.Uri, fillInPath?: boolean): Promise { const api = new AtelierAPI(uri); - if (uri.path === "/") { - await api.serverInfo().catch((error) => { - throw vscode.FileSystemError.Unavailable(stringifyError(error) || uri); - }); - } const config = api.config; const rootName = `${config.username}@${config.host}:${config.port}${config.pathPrefix}/${config.ns.toUpperCase()}`; let entry: Entry = this.superRoot.entries.get(rootName); From f6370dc69e3deb4146f9f6805d40c8325bd96806 Mon Sep 17 00:00:00 2001 From: Brett Saviano Date: Mon, 28 Apr 2025 08:10:03 -0400 Subject: [PATCH 2/4] Update FileSystemProvider.ts --- .../FileSystemProvider/FileSystemProvider.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/providers/FileSystemProvider/FileSystemProvider.ts b/src/providers/FileSystemProvider/FileSystemProvider.ts index 871f61b7..e2d1a3c6 100644 --- a/src/providers/FileSystemProvider/FileSystemProvider.ts +++ b/src/providers/FileSystemProvider/FileSystemProvider.ts @@ -234,9 +234,6 @@ export class FileSystemProvider implements vscode.FileSystemProvider { } public async stat(uri: vscode.Uri): Promise { - if (uri.path.includes(".vscode/") && !uri.path.endsWith("/settings.json")) { - throw vscode.FileSystemError.NoPermissions("Only settings.json is allowed within the /.vscode directory"); - } let entryPromise: Promise; let result: Entry; const redirectedUri = redirectDotvscodeRoot(uri); @@ -421,9 +418,6 @@ export class FileSystemProvider implements vscode.FileSystemProvider { } public async readFile(uri: vscode.Uri): Promise { - if (uri.path.includes(".vscode/") && !uri.path.endsWith("/settings.json")) { - throw vscode.FileSystemError.NoPermissions("Only settings.json is allowed within the /.vscode directory"); - } // Use _lookup() instead of _lookupAsFile() so we send // our cached mtime with the GET /doc request if we have it return this._lookup(uri, true).then((file: File) => { @@ -442,9 +436,6 @@ export class FileSystemProvider implements vscode.FileSystemProvider { overwrite: boolean; } ): void | Thenable { - if (uri.path.includes(".vscode/") && !uri.path.endsWith("/settings.json")) { - throw vscode.FileSystemError.NoPermissions("Only settings.json is allowed within the /.vscode directory"); - } uri = redirectDotvscodeRoot(uri); if (uri.path.startsWith("/.")) { throw vscode.FileSystemError.NoPermissions("dot-folders not supported by server"); @@ -612,9 +603,6 @@ export class FileSystemProvider implements vscode.FileSystemProvider { } public async delete(uri: vscode.Uri, options: { recursive: boolean }): Promise { - if (uri.path.includes(".vscode/") && !uri.path.endsWith("/settings.json")) { - throw vscode.FileSystemError.NoPermissions("Only settings.json is allowed within the /.vscode directory"); - } uri = redirectDotvscodeRoot(uri); const { project } = isfsConfig(uri); const csp = isCSP(uri); From 8583d21bd29627757610196cdbebed65feb98073 Mon Sep 17 00:00:00 2001 From: Brett Saviano Date: Wed, 30 Apr 2025 10:03:10 -0400 Subject: [PATCH 3/4] Update FileSystemProvider.ts --- src/providers/FileSystemProvider/FileSystemProvider.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/providers/FileSystemProvider/FileSystemProvider.ts b/src/providers/FileSystemProvider/FileSystemProvider.ts index 16417b07..20a9c3cf 100644 --- a/src/providers/FileSystemProvider/FileSystemProvider.ts +++ b/src/providers/FileSystemProvider/FileSystemProvider.ts @@ -400,9 +400,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { } public createDirectory(uri: vscode.Uri): void | Thenable { - if (uri.path.includes(".vscode/")) { - throw vscode.FileSystemError.NoPermissions("Cannot create a subdirectory of the /.vscode directory"); - } + uri = redirectDotvscodeRoot(uri); const basename = path.posix.basename(uri.path); const dirname = uri.with({ path: path.posix.dirname(uri.path) }); return this._lookupAsDirectory(dirname).then((parent) => { @@ -696,9 +694,6 @@ export class FileSystemProvider implements vscode.FileSystemProvider { if (vscode.workspace.getWorkspaceFolder(oldUri) != vscode.workspace.getWorkspaceFolder(newUri)) { throw vscode.FileSystemError.NoPermissions("Cannot rename a file across workspace folders"); } - if (oldUri.path.includes(".vscode/") || newUri.path.includes(".vscode/")) { - throw vscode.FileSystemError.NoPermissions("Cannot rename a file in the /.vscode directory"); - } // Check if the destination exists let newFileStat: vscode.FileStat; try { From 2f0ac240b78d86bfe427fdaa9e73499e10c4a7f0 Mon Sep 17 00:00:00 2001 From: Brett Saviano Date: Wed, 30 Apr 2025 11:23:28 -0400 Subject: [PATCH 4/4] Update FileSystemProvider.ts --- src/providers/FileSystemProvider/FileSystemProvider.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/providers/FileSystemProvider/FileSystemProvider.ts b/src/providers/FileSystemProvider/FileSystemProvider.ts index 20a9c3cf..715ed6c3 100644 --- a/src/providers/FileSystemProvider/FileSystemProvider.ts +++ b/src/providers/FileSystemProvider/FileSystemProvider.ts @@ -234,6 +234,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { } public async stat(uri: vscode.Uri): Promise { + if (!new AtelierAPI(uri).active) throw vscode.FileSystemError.Unavailable("Server connection is inactive"); let entryPromise: Promise; let result: Entry; const redirectedUri = redirectDotvscodeRoot(uri);