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
17 changes: 0 additions & 17 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1764,12 +1764,10 @@
"@vscode/extension-telemetry": "^1.5.1",
"@xmldom/xmldom": "^0.9.9",
"axios": "^1.15.0",
"core-js": "^3.41.0",
"iconv-lite": "^0.7.2",
"istextorbinary": "^7.0.0",
"minimatch": "^10.2.5",
"node-cmd": "^5.0.0",
"vscode-cache": "^0.3.0",
"ws": "^8.20.0"
},
"extensionDependencies": [
Expand Down
47 changes: 27 additions & 20 deletions src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import axios from "axios";
import * as httpsModule from "https";
import * as vscode from "vscode";
import * as Cache from "vscode-cache";
import * as semver from "semver";
import {
getResolvedConnectionSpec,
Expand All @@ -21,9 +20,12 @@ const DEFAULT_SERVER_VERSION = "2016.2.0";
import * as Atelier from "./atelier";
import { isfsConfig } from "../utils/FileProviderUtil";

// Map of the authRequest promises for each username@host:port target to avoid concurrency issues
// Map of the authRequest promises for each username@host:port/pathPrefix target to avoid concurrency issues
const authRequestMap = new Map<string, Promise<any>>();

/** Map of `username@host:port/pathPrefix` to cookies */
const cookiesMap = new Map<string, string[]>();

interface ConnectionSettings {
serverName: string;
active: boolean;
Expand Down Expand Up @@ -170,12 +172,11 @@ export class AtelierAPI {
}

public get cookies(): string[] {
const cookies = this.cache.get("cookies", []);
return cookies;
return cookiesMap.get(this.mapKey()) ?? [];
}

public async clearCookies(): Promise<void> {
await this.cache.put("cookies", []);
public clearCookies(): void {
cookiesMap.delete(this.mapKey());
}

public xdebugUrl(): string {
Expand All @@ -191,8 +192,9 @@ export class AtelierAPI {
: "";
}

public async updateCookies(newCookies: string[]): Promise<void> {
const cookies = this.cache.get("cookies", []);
public updateCookies(newCookies: string[]): void {
const mapKey = this.mapKey();
const cookies = cookiesMap.get(mapKey) ?? [];
newCookies.forEach((cookie) => {
const [cookieName] = cookie.split("=");
const index = cookies.findIndex((el) => el.startsWith(cookieName));
Expand All @@ -202,7 +204,17 @@ export class AtelierAPI {
cookies.push(cookie);
}
});
await this.cache.put("cookies", cookies);
cookiesMap.set(mapKey, cookies);
}

/** Return the key for getting values from connection-specific Maps for this connection */
private mapKey(): string {
const { host, port, username } = this.config;
let pathPrefix = this._config.pathPrefix || "";
if (pathPrefix.length && !pathPrefix.startsWith("/")) {
pathPrefix = "/" + pathPrefix;
}
return `${username}@${host}:${port}${pathPrefix}`;
}

private setConnection(workspaceFolderName: string, namespace?: string): void {
Expand Down Expand Up @@ -284,11 +296,6 @@ export class AtelierAPI {
}
}

private get cache(): Cache {
const { host, port } = this.config;
return new Cache(extensionContext, `API:${host}:${port}`);
}

public get connInfo(): string {
const { port, docker, dockerService } = this.config;
return (docker ? "docker" + (dockerService ? `:${dockerService}:${port}` : "") : this.serverId) + `[${this.ns}]`;
Expand Down Expand Up @@ -358,9 +365,9 @@ export class AtelierAPI {
path = encodeURI(`${pathPrefix}/api/atelier/${path || ""}`) + buildParams();

const cookies = this.cookies;
const target = `${username}@${host}:${port}`;
const mapKey = this.mapKey();
let auth: Promise<any>;
let authRequest = authRequestMap.get(target);
let authRequest = authRequestMap.get(mapKey);
if (cookies.length || (method === "HEAD" && !originalPath)) {
auth = Promise.resolve(cookies);

Expand All @@ -372,7 +379,7 @@ export class AtelierAPI {
if (!authRequest) {
// Recursion point
authRequest = this.request(0, "HEAD", undefined, undefined, undefined, undefined, options);
authRequestMap.set(target, authRequest);
authRequestMap.set(mapKey, authRequest);
}
auth = authRequest;
}
Expand Down Expand Up @@ -438,7 +445,7 @@ export class AtelierAPI {
};
}
if (response.status === 401) {
authRequestMap.delete(target);
authRequestMap.delete(mapKey);
if (this.wsOrFile && !checkingConnection) {
setTimeout(() => {
checkConnection(
Expand All @@ -453,7 +460,7 @@ export class AtelierAPI {
await this.updateCookies(response.headers["set-cookie"] || []);
if (method === "HEAD") {
if (!originalPath) {
authRequestMap.delete(target);
authRequestMap.delete(mapKey);
return this.cookies;
} else if (response.status >= 400) {
// The HEAD /doc request errored out
Expand Down Expand Up @@ -552,7 +559,7 @@ export class AtelierAPI {
outputChannel.appendLine(`+- END ----------------------------------------------`);
}
// always discard the cached authentication promise
authRequestMap.delete(target);
authRequestMap.delete(mapKey);

// In some cases schedule an automatic retry.
// ENOTFOUND occurs if, say, the VPN to the server's network goes down.
Expand Down
63 changes: 16 additions & 47 deletions src/utils/classDefinition.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import * as vscode from "vscode";
import * as Cache from "vscode-cache";
import { onlyUnique } from ".";
import { AtelierAPI } from "../api";
import { extensionContext } from "../extension";
import { DocumentContentProvider } from "../providers/DocumentContentProvider";

export class ClassDefinition {
Expand All @@ -15,7 +13,6 @@ export class ClassDefinition {
}
private _className: string;
private _classFileName: string;
private _cache;
private _workspaceFolder: string;
private _namespace: string;

Expand All @@ -27,26 +24,14 @@ export class ClassDefinition {
}
this._className = ClassDefinition.normalizeClassName(className, false);
this._classFileName = ClassDefinition.normalizeClassName(className, true);
this._cache = new Cache(extensionContext, this._classFileName);
}

public async getDocument(): Promise<vscode.TextDocument> {
return vscode.workspace.openTextDocument(this.uri);
}

public store(kind: string, data: any): any {
return this._cache.put(kind, data, 36000).then(() => data);
}

public load(kind: string): any {
return this._cache.get(kind);
}

public async methods(scope: "any" | "class" | "instance" = "any"): Promise<any[]> {
const methods = this.load("methods-" + scope) || [];
if (methods.length) {
return Promise.resolve(methods);
}
const methods = [];
const filterScope = (method) => scope === "any" || method.scope === scope;
const api = new AtelierAPI(this.uri);
const getMethods = (content) => {
Expand All @@ -58,16 +43,13 @@ export class ClassDefinition {
if (extend.length) {
return api.actionIndex(extend).then((data) => getMethods(data.result.content));
}
return this.store("methods-" + scope, methods.filter(filterScope).filter(onlyUnique).sort());
return methods.filter(filterScope).filter(onlyUnique).sort();
Comment thread
isc-klu marked this conversation as resolved.
};
return api.actionIndex([this._classFileName]).then((data) => getMethods(data.result.content));
}

public async properties(): Promise<any[]> {
const properties = this.load("properties") || [];
if (properties.length) {
return Promise.resolve(properties);
}
const properties = [];
const api = new AtelierAPI(this.uri);
const getProperties = (content) => {
const extend = [];
Expand All @@ -78,16 +60,13 @@ export class ClassDefinition {
if (extend.length) {
return api.actionIndex(extend).then((data) => getProperties(data.result.content));
}
return this.store("properties", properties.filter(onlyUnique).sort());
return properties.filter(onlyUnique).sort();
};
return api.actionIndex([this._classFileName]).then((data) => getProperties(data.result.content));
}

public async parameters(): Promise<any[]> {
const parameters = this.load("parameters") || [];
if (parameters.length) {
return Promise.resolve(parameters);
}
const parameters = [];
const api = new AtelierAPI(this.uri);
const getParameters = (content) => {
const extend = [];
Expand All @@ -98,40 +77,30 @@ export class ClassDefinition {
if (extend.length) {
return api.actionIndex(extend).then((data) => getParameters(data.result.content));
}
return this.store("parameters", parameters.filter(onlyUnique).sort());
return parameters.filter(onlyUnique).sort();
};
return api.actionIndex([this._classFileName]).then((data) => getParameters(data.result.content));
}

public async super(): Promise<string[]> {
const superList = this.load("super");
if (superList) {
return Promise.resolve(superList);
}
const api = new AtelierAPI(this.uri);
const sql = `SELECT PrimarySuper FROM %Dictionary.CompiledClass
WHERE Name %inlist (SELECT $LISTFROMSTRING(Super, ',') FROM %Dictionary.CompiledClass WHERE Name = ?)`;
return api
.actionQuery(sql, [this._className])
.then(
(data) =>
data.result.content
.reduce(
(list: string[], el: { PrimarySuper: string }) =>
list.concat(el.PrimarySuper.split("~").filter((item) => item.length)),
[]
)
.filter((name: string) => name !== this._className)
// .filter(name => !['%Library.Base', '%Library.SystemBase'].includes(name))
.then((data) =>
data.result.content
.reduce(
(list: string[], el: { PrimarySuper: string }) =>
list.concat(el.PrimarySuper.split("~").filter((item) => item.length)),
[]
)
.filter((name: string) => name !== this._className)
)
.then((data) => this.store("super", data));
.then((data) => data);
}

public async includeCode(): Promise<string[]> {
const includeCode = this.load("includeCode");
if (includeCode) {
return Promise.resolve(includeCode);
}
const api = new AtelierAPI(this.uri);
const sql = `SELECT LIST(IncludeCode) List FROM %Dictionary.CompiledClass WHERE Name %INLIST (
SELECT $LISTFROMSTRING(PrimarySuper, '~') FROM %Dictionary.CompiledClass WHERE Name = ?)`;
Expand All @@ -144,7 +113,7 @@ export class ClassDefinition {
defaultIncludes
)
)
.then((data) => this.store("includeCode", data));
.then((data) => data);
}

public async getMemberLocation(name: string): Promise<vscode.Location> {
Expand Down
2 changes: 1 addition & 1 deletion webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const path = require("path");
const config = {
target: "node", // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/

entry: ["core-js/features/array/flat-map", "./src/extension.ts"], // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
entry: "./src/extension.ts", // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
output: {
// the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
path: path.resolve(__dirname, "dist"),
Expand Down
Loading