Skip to content

Commit 658e359

Browse files
committed
many improvements
1 parent 13c736c commit 658e359

17 files changed

+573
-609
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ node_modules/
44
.vscode/**
55
.vscode-test
66
out
7+
*.vsix

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Change Log
22

3+
## [0.7.12]
4+
5+
- Files in Server Explorer now can be edited.
6+
- Added more details about connection errors.
7+
- Improvements in Server Explorer build tree.
8+
- Fixed memory leak when exporting large amount of files.
9+
- Some other fixes
10+
311
## [0.7.11]
412

513
- added export setting "objectscript.export.addCategory" if enabled uses previous behavior, adds category folder to export folder, disabled by default

api/index.ts

Lines changed: 79 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,46 @@
1+
import * as vscode from 'vscode';
12
import * as httpModule from 'http';
23
import * as httpsModule from 'https';
3-
import { outputConsole, currentWorkspaceFolder } from '../utils';
4+
import { outputConsole, currentWorkspaceFolder, outputChannel } from '../utils';
45
const Cache = require('vscode-cache');
5-
import { config, extensionContext, workspaceState } from '../extension';
6+
import { config, extensionContext, workspaceState, FILESYSTEM_SCHEMA } from '../extension';
7+
import * as url from 'url';
8+
import * as request from 'request';
69

7-
const DEFAULT_API_VERSION: number = 3;
10+
const DEFAULT_API_VERSION: number = 1;
811

912
export class AtelierAPI {
1013
private _config: any;
1114
private _namespace: string;
1215
private _cache;
16+
private _workspaceFolder;
1317

1418
public get ns(): string {
1519
return this._namespace || this._config.ns;
1620
}
1721

1822
private get apiVersion(): number {
19-
return workspaceState.get(currentWorkspaceFolder() + ':apiVersion', DEFAULT_API_VERSION);
23+
return workspaceState.get(this._workspaceFolder + ':apiVersion', DEFAULT_API_VERSION);
2024
}
2125

22-
constructor(workspaceFolderName?: string) {
26+
constructor(wsOrFile?: string | vscode.Uri) {
27+
let workspaceFolderName: string = '';
28+
if (wsOrFile) {
29+
if (wsOrFile instanceof vscode.Uri) {
30+
if (wsOrFile.scheme === FILESYSTEM_SCHEMA) {
31+
workspaceFolderName = wsOrFile.authority
32+
let query = url.parse(decodeURIComponent(wsOrFile.toString()), true).query;
33+
if (query) {
34+
if (query.ns && query.ns !== '') {
35+
let namespace = query.ns.toString();
36+
this.setNamespace(namespace);
37+
}
38+
}
39+
}
40+
} else {
41+
workspaceFolderName = wsOrFile
42+
}
43+
}
2344
this.setConnection(workspaceFolderName || currentWorkspaceFolder());
2445
const { name, host, port } = this._config;
2546
this._cache = new Cache(extensionContext, `API:${name}:${host}:${port}`);
@@ -48,6 +69,7 @@ export class AtelierAPI {
4869
}
4970

5071
setConnection(workspaceFolderName: string) {
72+
this._workspaceFolder = workspaceFolderName;
5173
let conn = config('conn', workspaceFolderName);
5274
this._config = conn;
5375
}
@@ -97,6 +119,7 @@ export class AtelierAPI {
97119
headers['Cache-Control'] = 'no-cache';
98120

99121
const { host, port, username, password, https } = this._config;
122+
const proto = this._config.https ? 'https' : 'http';
100123
const http: any = this._config.https ? httpsModule : httpModule;
101124
const agent = new http.Agent({
102125
keepAlive: true,
@@ -109,66 +132,60 @@ export class AtelierAPI {
109132
body = JSON.stringify(body);
110133
}
111134

135+
// outputChannel.appendLine(`API: ${method} ${host}:${port}${path}`)
112136
return new Promise((resolve, reject) => {
113-
const req: httpModule.ClientRequest = http
114-
.request(
115-
{
116-
method,
117-
host,
118-
port,
119-
path,
120-
agent,
121-
auth: `${username}:${password}`,
122-
headers,
123-
body
124-
},
125-
(response: httpModule.IncomingMessage) => {
126-
if (response.statusCode < 200 || response.statusCode > 299) {
127-
reject(new Error('Failed to load page "' + path + '", status code: ' + response.statusCode));
128-
}
129-
this.updateCookies(response.headers['set-cookie']);
130-
// temporary data holder
131-
let body: string = '';
132-
response.on('data', chunk => {
133-
body += chunk;
134-
});
135-
response.on('end', () => {
136-
if (response.headers['content-type'].includes('json')) {
137-
const json = JSON.parse(body);
138-
if (json.console) {
139-
outputConsole(json.console);
140-
}
141-
if (json.result.status) {
142-
reject(new Error(json.result.status));
143-
return;
144-
}
145-
resolve(json);
146-
} else {
147-
resolve(body);
148-
}
149-
});
137+
request({
138+
url: `${proto}://${host}:${port}${path}`,
139+
method,
140+
host,
141+
port,
142+
agent,
143+
auth: { username, password },
144+
headers,
145+
body: (['PUT', 'POST'].includes(method) ? body : null)
146+
}, (error, response, responseBody) => {
147+
if (error) {
148+
reject(error)
149+
}
150+
if (response.statusCode === 401) {
151+
reject({
152+
code: 'Unauthorized',
153+
message: 'Not Authorized'
154+
})
155+
}
156+
if (response.headers['content-type'].includes('json')) {
157+
const json = JSON.parse(responseBody);
158+
responseBody = '';
159+
if (json.console) {
160+
outputConsole(json.console);
150161
}
151-
)
152-
.on('error', error => {
153-
reject(error);
154-
});
155-
if (['PUT', 'POST'].includes(method)) {
156-
req.write(body);
157-
}
158-
req.end();
159-
}).catch(error => {
160-
console.error(error);
161-
throw error;
162-
});
162+
if (json.result.status) {
163+
reject(new Error(json.result.status));
164+
return;
165+
}
166+
resolve(json);
167+
} else {
168+
resolve(responseBody);
169+
}
170+
})
171+
})
163172
}
164173

165174
serverInfo(): Promise<any> {
166-
return this.request(0, 'GET').then(info => {
167-
if (info && info.result && info.result.content && info.result.content.api > 0) {
168-
let apiVersion = info.result.content.api;
169-
return workspaceState.update(currentWorkspaceFolder() + ':apiVersion', apiVersion).then(() => info);
170-
}
171-
});
175+
return this.request(0, 'GET')
176+
.then(info => {
177+
if (info && info.result && info.result.content && info.result.content.api > 0) {
178+
let data = info.result.content;
179+
let apiVersion = data.api;
180+
if (!data.namespaces.includes(this.ns)) {
181+
throw {
182+
code: 'WrongNamespace',
183+
message: 'This server does not have specified namespace.'
184+
}
185+
}
186+
return workspaceState.update(currentWorkspaceFolder() + ':apiVersion', apiVersion).then(() => info);
187+
}
188+
});
172189
}
173190
// api v1+
174191
getDocNames({
@@ -216,6 +233,8 @@ export class AtelierAPI {
216233
}
217234
// v1+
218235
actionQuery(query: string, parameters: string[]): Promise<any> {
236+
// outputChannel.appendLine('SQL: ' + query);
237+
// outputChannel.appendLine('SQLPARAMS: ' + JSON.stringify(parameters));
219238
return this.request(1, 'POST', `${this.ns}/action/query`, {
220239
query,
221240
parameters

commands/compile.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import path = require('path');
44
import glob = require('glob');
55
import { AtelierAPI } from '../api';
66
import { currentFile, CurrentFile, outputChannel } from '../utils';
7-
import { documentContentProvider, config } from '../extension';
7+
import { documentContentProvider, config, fileSystemProvider, FILESYSTEM_SCHEMA } from '../extension';
88
import { DocumentContentProvider } from '../providers/DocumentContentProvider';
99

1010
async function compileFlags(): Promise<string> {
@@ -16,7 +16,7 @@ async function compileFlags(): Promise<string> {
1616
}
1717

1818
async function importFile(file: CurrentFile): Promise<any> {
19-
const api = new AtelierAPI();
19+
const api = new AtelierAPI(file.uri);
2020
return api
2121
.putDoc(
2222
file.name,
@@ -36,21 +36,27 @@ function updateOthers(others: string[]) {
3636
}
3737

3838
async function loadChanges(files: CurrentFile[]): Promise<any> {
39-
const api = new AtelierAPI();
39+
const api = new AtelierAPI(files[0].uri);
4040
return Promise.all(files.map(file =>
41-
api.getDoc(file.name).then(data => {
42-
fs.writeFileSync(file.fileName, (data.result.content || []).join('\n'));
43-
return api
44-
.actionIndex([file.name])
45-
.then(data => data.result.content[0].others)
46-
.then(updateOthers);
47-
})
41+
api.getDoc(file.name)
42+
.then(data => {
43+
const content = (data.result.content || []).join('\n')
44+
if (file.uri.scheme === 'file') {
45+
fs.writeFileSync(file.uri.fsPath, content);
46+
} else if (file.uri.scheme === FILESYSTEM_SCHEMA) {
47+
fileSystemProvider.writeFile(file.uri, Buffer.from(content), { overwrite: true, create: false })
48+
}
49+
return api
50+
.actionIndex([file.name])
51+
.then(data => data.result.content[0].others)
52+
.then(updateOthers);
53+
})
4854
));
4955
}
5056

5157
async function compile(docs: CurrentFile[], flags?: string): Promise<any> {
52-
flags = flags || config().compileFlags;
53-
const api = new AtelierAPI();
58+
flags = flags || config('compileFlags');
59+
const api = new AtelierAPI(docs[0].uri);
5460
return api
5561
.actionCompile(docs.map(el => el.name), flags)
5662
.then(data => {

commands/export.ts

Lines changed: 23 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { PackageNode } from '../explorer/models/packageNode';
77
import { ClassNode } from '../explorer/models/classesNode';
88
import { RoutineNode } from '../explorer/models/routineNode';
99
import { config } from '../extension';
10-
import Bottleneck from 'bottleneck';
1110
import { RootNode } from '../explorer/models/rootNode';
1211

1312
const filesFilter = (file: any) => {
@@ -17,16 +16,17 @@ const filesFilter = (file: any) => {
1716
return true;
1817
};
1918

20-
const getFileName = (folder: string, name: string, split: boolean, addCategory: boolean): string => {
19+
export const getFileName = (folder: string, name: string, split: boolean, addCategory: boolean): string => {
2120
let fileNameArray: string[] = name.split('.');
2221
let fileExt = fileNameArray.pop().toLowerCase();
23-
const cat = addCategory
24-
? fileExt === 'cls'
25-
? 'CLS'
26-
: ['int', 'mac', 'inc'].includes(fileExt)
27-
? 'RTN'
28-
: 'OTH'
29-
: null;
22+
const cat = (typeof addCategory == 'object' && addCategory[fileExt]) ?
23+
addCategory[fileExt] : (addCategory)
24+
? fileExt === 'cls'
25+
? 'CLS'
26+
: ['int', 'mac', 'inc'].includes(fileExt)
27+
? 'RTN'
28+
: 'OTH'
29+
: null;
3030
if (split) {
3131
let fileName = [folder, cat, ...fileNameArray].filter(notNull).join(path.sep);
3232
return [fileName, fileExt].join('.');
@@ -38,8 +38,7 @@ export async function exportFile(workspaceFolder: string, name: string, fileName
3838
if (!config('conn', workspaceFolder).active) {
3939
return Promise.reject('Connection not active');
4040
}
41-
const api = new AtelierAPI();
42-
api.setConnection(workspaceFolder);
41+
const api = new AtelierAPI(workspaceFolder);
4342
const log = status => outputChannel.appendLine(`export "${name}" as "${fileName}" - ${status}`);
4443
const folders = path.dirname(fileName);
4544
return mkdirSyncRecursive(folders)
@@ -116,43 +115,23 @@ export async function exportList(files: string[], workspaceFolder: string): Prom
116115
if (!files || !files.length) {
117116
vscode.window.showWarningMessage('Nothing to export');
118117
}
119-
const { atelier, folder, maxConcurrentConnections, addCategory } = config('export', workspaceFolder);
118+
const { atelier, folder, addCategory } = config('export', workspaceFolder);
120119

121120
const root = [workspaceFolderUri(workspaceFolder).fsPath, folder].join(path.sep);
122-
if (maxConcurrentConnections > 0) {
123-
const limiter = new Bottleneck({
124-
maxConcurrent: maxConcurrentConnections
125-
});
126-
const results = [];
127-
for (let i = 0; i < files.length; i++) {
128-
const result = await limiter.schedule(() =>
129-
exportFile(workspaceFolder, files[i], getFileName(root, files[i], atelier, addCategory))
130-
);
131-
results.push(result);
121+
const run = async (files) => {
122+
let errors = []
123+
for (const file of files) {
124+
await exportFile(workspaceFolder, file, getFileName(root, file, atelier, addCategory))
125+
.catch(error => { errors.push(`${file} - ${error}`) })
132126
}
133-
return results;
134-
}
135-
return Promise.all(
136-
files.map(file =>
137-
exportFile(workspaceFolder, file, getFileName(root, file, atelier, addCategory))
138-
.then(() => ({
139-
file,
140-
result: true,
141-
error: ''
142-
}))
143-
.catch(error => ({
144-
file,
145-
result: false,
146-
error
147-
}))
148-
)
149-
).then(files => {
150-
outputChannel.appendLine(`Exported items: ${files.filter(el => el.result).length}`);
151-
const failed = files.filter(el => !el.result).map(el => `${el.file} - ${el.error}`);
152-
if (files.find(el => !el.result)) {
153-
outputChannel.appendLine(`Items failed to export: \n${failed.join('\n')}`);
127+
outputChannel.appendLine(`Exported items: ${files.length - errors.length}`);
128+
if (errors.length) {
129+
outputChannel.appendLine(`Items failed to export: \n${errors.join('\n')}`);
154130
}
155-
});
131+
}
132+
return run(files).then(() => {
133+
outputChannel.appendLine('finish1');
134+
})
156135
}
157136

158137
export async function exportAll(workspaceFolder?: string): Promise<any> {

commands/serverActions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { config } from '../extension';
33

44
export async function serverActions(): Promise<void> {
55
const conn = config('conn');
6-
const connInfo = `${conn.host}:${conn.port}/${conn.ns}/`;
6+
const connInfo = `${conn.host}:${conn.port}[${conn.ns}]`;
77
const serverUrl = `${conn.https ? 'https' : 'http'}://${conn.host}:${conn.port}`;
88
const portalUrl = `${serverUrl}/csp/sys/UtilHome.csp?$NAMESPACE=${conn.ns}`;
99
const classRef = `${serverUrl}/csp/documatic/%25CSP.Documatic.cls?LIBRARY=${conn.ns}`;

explorer/models/classesNode.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class ClassNode extends NodeBase {
2626
contextValue: 'dataNode:classNode',
2727
command: {
2828
command: 'vscode-objectscript.explorer.openClass',
29-
arguments: [DocumentContentProvider.getUri(this.fullName, this._workspaceFolder, this._namespace)],
29+
arguments: [DocumentContentProvider.getUri(this.fullName, this._workspaceFolder, this._namespace, true)],
3030
title: 'Open class'
3131
}
3232
// iconPath: {

explorer/models/rootNode.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export class RootNode extends NodeBase {
6565
tree = tree.concat(parents);
6666
});
6767
tree = tree.filter((value, index, self) => self.findIndex(({ fullName }) => fullName === value.fullName) === index);
68-
tree = tree.sort((el1, el2) => (el1.fullName < el2.fullName ? -1 : el1.fullName > el2.fullName ? 1 : 0));
68+
tree = tree.sort((el1, el2) => el1.fullName.localeCompare(el2.fullName));
6969
tree.forEach(el => {
7070
el.nodes = tree.filter(ch => el.fullName === ch.parent);
7171
});

0 commit comments

Comments
 (0)