From 22e60380564956d9e943b8fedab384a9f0c7c15c Mon Sep 17 00:00:00 2001 From: marcello Date: Sun, 21 Oct 2018 11:03:26 +0100 Subject: [PATCH 1/7] hotkey --- src/abapFsProvider.ts | 24 +++++++-------------- src/adt/AdtConnection.ts | 2 +- src/adt/AdtFile.ts | 19 ----------------- src/adt/AdtNode.ts | 40 +++++++++++++++++------------------ src/adt/AdtParser.ts | 4 ++-- src/adt/AdtPathManager.ts | 29 ++++++++++++++++++++----- src/adt/adtPathResolver.ts | 18 ++++++++++------ src/config.ts | 15 ++++++++++--- src/extension.ts | 43 +++++++++++++++++++++++--------------- 9 files changed, 103 insertions(+), 91 deletions(-) delete mode 100644 src/adt/AdtFile.ts diff --git a/src/abapFsProvider.ts b/src/abapFsProvider.ts index 26a48f0..2b76ca8 100644 --- a/src/abapFsProvider.ts +++ b/src/abapFsProvider.ts @@ -1,14 +1,11 @@ import * as vscode from "vscode" import { AdtPathManager } from "./adt/AdtPathManager" -import { AdtNode } from "./adt/AdtNode" export class AbapFsProvider implements vscode.FileSystemProvider { private _pathManager = new AdtPathManager() private _eventEmitter = new vscode.EventEmitter() readonly onDidChangeFile: vscode.Event = this ._eventEmitter.event - rooturl: string = "" - root: AdtNode = new AdtNode("") watch( uri: vscode.Uri, options: { recursive: boolean; excludes: string[] } @@ -16,25 +13,18 @@ export class AbapFsProvider implements vscode.FileSystemProvider { throw new Error("Method not implemented.") } stat(uri: vscode.Uri): vscode.FileStat | Thenable { - const uristring = uri.toString() - if (this.rooturl === "") this.rooturl = uristring - if (this.rooturl === uristring) { - const newroot = this._pathManager - .fetchDirectory(uristring) - .then(newroot => (this.root = newroot)) - return newroot - } - throw new Error("not found") + console.log(uri.toString()) + return this._pathManager.fetchFileOrDir(uri).then(n => { + console.log(n) + return n + }) } readDirectory( uri: vscode.Uri ): [string, vscode.FileType][] | Thenable<[string, vscode.FileType][]> { - if (uri.toString() !== this.rooturl || !this.root) { - throw new Error("Only root directory for now...") - } const result: [string, vscode.FileType][] = [] - Array.from(this.root.entries).forEach(([key, value]) => - result.push([key, value.type]) + Array.from(this._pathManager.getDirectory(uri).entries).forEach( + ([key, value]) => result.push([key, value.type]) ) return result } diff --git a/src/adt/AdtConnection.ts b/src/adt/AdtConnection.ts index ed93d24..dea73ee 100644 --- a/src/adt/AdtConnection.ts +++ b/src/adt/AdtConnection.ts @@ -69,7 +69,7 @@ export class AdtConnection { headers: { "x-csrf-token": this._csrftoken } - } + } as request.Options //workaround for compiler bug } private myrequest(options: request.Options): Promise { return new Promise((resolve, reject) => { diff --git a/src/adt/AdtFile.ts b/src/adt/AdtFile.ts deleted file mode 100644 index 14b7837..0000000 --- a/src/adt/AdtFile.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { FileStat, FileType } from "vscode" - -export class AdtFile implements FileStat { - type: FileType = FileType.File - name: string - ctime: number - mtime: number - size: number = 0 - data: any - constructor( - name: string, - ctime: number = Date.now(), - mtime: number = Date.now() - ) { - this.name = name - this.ctime = ctime - this.mtime = mtime - } -} diff --git a/src/adt/AdtNode.ts b/src/adt/AdtNode.ts index cdabffb..2406c44 100644 --- a/src/adt/AdtNode.ts +++ b/src/adt/AdtNode.ts @@ -1,38 +1,36 @@ -import { FileStat, FileType } from "vscode" -import { AdtFile } from "./AdtFile" +import { FileStat, FileType, Uri } from "vscode" import { ObjectNode } from "./AdtParser" - -export type AdtDirItem = AdtFile | AdtNode - +const lastPathEntry = (uri: Uri) => + uri.path.replace(/\/$/, "").replace(/.*\//, "") export class AdtNode implements FileStat { - static fromTreeContent(fromTreeContent: ObjectNode[]): AdtNode { - const node = new AdtNode("") - return node - } + // static fromTreeContent(fromTreeContent: ObjectNode[]): AdtNode { + // const node = new AdtNode("") + // return node + // } type: FileType = FileType.Directory name: string ctime: number mtime: number size: number = 0 - entries: Map - constructor( - name: string, - ctime: number = Date.now(), - mtime: number = Date.now() - ) { - this.name = name - this.ctime = ctime - this.mtime = mtime + entries: Map + path: Uri + + constructor(path: Uri, name?: string) { + this.name = name ? name : lastPathEntry(path) + this.ctime = Date.now() + this.mtime = Date.now() this.entries = new Map() + this.path = path } setChildrenFromTreeContent(children: ObjectNode[]): AdtNode { this.entries.clear() children.forEach(objnode => { this.entries.set( objnode.OBJECT_NAME, - objnode.EXPANDABLE - ? new AdtNode(objnode.OBJECT_NAME) - : new AdtFile(objnode.OBJECT_NAME) + new AdtNode( + this.path.with({ path: objnode.OBJECT_URI }), + objnode.OBJECT_NAME + ) ) }) return this diff --git a/src/adt/AdtParser.ts b/src/adt/AdtParser.ts index 5c06889..e20d5f0 100644 --- a/src/adt/AdtParser.ts +++ b/src/adt/AdtParser.ts @@ -52,12 +52,12 @@ export const getNode = (...args: any[]) => { ) return [functions, rest] } - const fn = (...fargs: any) => { + const fn = (...fargs: any[]) => { const [functions, rest] = split(...fargs) if (functions.length === 0) return rest[0] const piped = pipe(...functions) return rest.length === 0 - ? (...iargs: any) => fn(piped, ...iargs) + ? (...iargs: any[]) => fn(piped, ...iargs) : piped(...rest) } return fn(...args) diff --git a/src/adt/AdtPathManager.ts b/src/adt/AdtPathManager.ts index f3c4e9e..0628bf1 100644 --- a/src/adt/AdtPathManager.ts +++ b/src/adt/AdtPathManager.ts @@ -1,23 +1,42 @@ import { AdtNode } from "./AdtNode" -import { adtPathResolver, AdtPath } from "./adtPathResolver" +import { adtPathResolver } from "./adtPathResolver" import { AdtConnectionManager } from "./AdtConnectionManager" import { Response } from "request" import { getNodeStructureTreeContent, ObjectNode } from "./AdtParser" +import { Uri } from "vscode" export class AdtPathManager { + getDirectory(uri: Uri): AdtNode { + throw new Error("Method not implemented.") + } private _cache: Map = new Map() private _manager = AdtConnectionManager.getManager() - parse(path: AdtPath, response: Response): any { + + parse(uri: Uri, response: Response): any { return getNodeStructureTreeContent(response.body).then( (children: ObjectNode[]) => { - const node = new AdtNode(path.url) + const node = new AdtNode(uri) node.setChildrenFromTreeContent(children) return node } ) } - fetchDirectory(url: string): Promise { + fetchFileOrDir(url: Uri): Promise { let path = adtPathResolver(url) + if (path.isRoot) { + let root = this._cache.get(url.toString()) + if (!root) { + root = new AdtNode(url, path.connectionName) + this._cache.set(url.toString(), root) + const firstChild = new AdtNode( + url.with({ + path: url.path + "repository/nodestructure" + }) + ) + root.entries.set(firstChild.name, firstChild) + } + return new Promise(resolve => resolve(root)) + } return new Promise((resolve, reject) => { if (path) { let key = path!.connectionName + path!.path @@ -29,7 +48,7 @@ export class AdtPathManager { conn .request(path!.path, path!.method) .then(response => { - return this.parse(path!, response) + return this.parse(url, response) }) .then(file => { this._cache.set(key, file) diff --git a/src/adt/adtPathResolver.ts b/src/adt/adtPathResolver.ts index b8a4883..a1f33ae 100644 --- a/src/adt/adtPathResolver.ts +++ b/src/adt/adtPathResolver.ts @@ -1,23 +1,29 @@ +import { Uri } from "vscode" + export interface AdtPath { method: string path: string connectionName: string isFolder: boolean - url: string + url: Uri + isRoot: boolean } -export const adtPathResolver = (url: string): AdtPath | undefined => { - const match = url.match(/adt:\/\/([^\/]+)\/sap\/bc\/adt(.*)/i) +export const adtPathResolver = (url: Uri): AdtPath => { + const urlString = url.toString() + const match = urlString.match(/adt:\/\/([^\/]+)\/sap\/bc\/adt(.*)/i) if (match) { let method = "GET" let isFolder = true + let isRoot = false let [connectionName, path] = match.splice(1) - if (path.match(/^(\/?|(\/repository(\/?nodestructure)?))$/i)) { + if (path.match(/^\/?|$/i)) { //partial root path = "/sap/bc/adt/repository/nodestructure" method = "POST" + isRoot = true } else if (!path.match(/\/(.*)\//)) { throw new Error("Not found") } - return { method, path, connectionName, isFolder, url } - } + return { method, path, connectionName, isFolder, url, isRoot } + } else throw new Error("not found") } diff --git a/src/config.ts b/src/config.ts index 7b6c574..59f4f12 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,13 +1,22 @@ import * as vscode from "vscode" +export interface RemoteConfig { + name: string + url: string + username: string + password: string +} -const config = (name: string, remote: any) => { +const config = (name: string, remote: RemoteConfig) => { const conf = { url: "", ...remote, name, valid: true } conf.valid = !!(remote.url && remote.username && remote.password) return conf } -export function getRemoteList() { +export function getRemoteList(): RemoteConfig[] { const userConfig = vscode.workspace.getConfiguration("abapfs") const remote = userConfig.remote - return Object.keys(remote).map(name => config(name, remote[name])) + if (!remote) throw new Error("No destination configured") + return Object.keys(remote).map(name => + config(name, remote[name] as RemoteConfig) + ) } diff --git a/src/extension.ts b/src/extension.ts index cab7b7f..9b9969d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,11 +1,13 @@ "use strict" import * as vscode from "vscode" import { AbapFsProvider } from "./abapFsProvider" -import { getRemoteList } from "./config" +import { getRemoteList, RemoteConfig } from "./config" import { AdtConnectionManager } from "./adt/AdtConnectionManager" -function selectRemote() { +function selectRemote(connection: string): Thenable { const remotes = getRemoteList() + if (remotes[1] && remotes[1].name === connection) + return new Promise(resolve => resolve(remotes[1])) return vscode.window .showQuickPick( remotes.map(remote => ({ @@ -17,7 +19,10 @@ function selectRemote() { placeHolder: "Please choose a remote" } ) - .then(selection => selection && selection.remote) + .then(selection => { + if (selection) return selection.remote + throw new Error("No connection selected") + }) } export function activate(context: vscode.ExtensionContext) { @@ -28,20 +33,24 @@ export function activate(context: vscode.ExtensionContext) { }) ) - let disposable = vscode.commands.registerCommand("abapfs.connect", () => { - selectRemote().then(remote => { - return AdtConnectionManager.getManager() - .setConn(remote) - .then(() => { - if (remote) { - vscode.workspace.updateWorkspaceFolders(0, 0, { - uri: vscode.Uri.parse("adt://" + remote.name + "/sap/bc/adt/"), - name: "ABAP" - }) - } - }) - }) - }) + let disposable = vscode.commands.registerCommand( + "abapfs.connect", + (selector: any) => { + const connection = selector && selector.connection + selectRemote(connection).then(remote => { + return AdtConnectionManager.getManager() + .setConn(remote) + .then(() => { + if (remote) { + vscode.workspace.updateWorkspaceFolders(0, 0, { + uri: vscode.Uri.parse("adt://" + remote.name + "/sap/bc/adt/"), + name: remote.name + "(ABAP)" + }) + } + }) + }) + } + ) context.subscriptions.push(disposable) } From 7295b220c2dd8c208842d091984d1114e55b8726 Mon Sep 17 00:00:00 2001 From: marcello Date: Mon, 22 Oct 2018 07:59:48 +0100 Subject: [PATCH 2/7] half refactor --- src/abapFsProvider.ts | 10 +++-- src/adt/AdtConnection.ts | 19 +++++++++ src/adt/AdtNode.ts | 10 +++-- src/adt/AdtPathClassifier.ts | 80 +++++++++++++++++++++++++++++++++++ src/adt/AdtPathManager.ts | 81 ++++++++++++++++++------------------ src/extension.ts | 6 ++- 6 files changed, 156 insertions(+), 50 deletions(-) create mode 100644 src/adt/AdtPathClassifier.ts diff --git a/src/abapFsProvider.ts b/src/abapFsProvider.ts index 2b76ca8..7d2dd05 100644 --- a/src/abapFsProvider.ts +++ b/src/abapFsProvider.ts @@ -15,7 +15,7 @@ export class AbapFsProvider implements vscode.FileSystemProvider { stat(uri: vscode.Uri): vscode.FileStat | Thenable { console.log(uri.toString()) return this._pathManager.fetchFileOrDir(uri).then(n => { - console.log(n) + // console.log(n) return n }) } @@ -23,9 +23,11 @@ export class AbapFsProvider implements vscode.FileSystemProvider { uri: vscode.Uri ): [string, vscode.FileType][] | Thenable<[string, vscode.FileType][]> { const result: [string, vscode.FileType][] = [] - Array.from(this._pathManager.getDirectory(uri).entries).forEach( - ([key, value]) => result.push([key, value.type]) - ) + const dir = this._pathManager.getDirectory(uri) + if (dir) + Array.from(dir.entries).forEach(([key, value]) => + result.push([key.replace(/\//g, "_"), value.type]) + ) return result } createDirectory(uri: vscode.Uri): void | Thenable { diff --git a/src/adt/AdtConnection.ts b/src/adt/AdtConnection.ts index dea73ee..8063607 100644 --- a/src/adt/AdtConnection.ts +++ b/src/adt/AdtConnection.ts @@ -1,4 +1,7 @@ import * as request from "request" +import { AdtPathClassifier } from "./AdtPathClassifier" +import { Uri } from "vscode" + enum ConnStatus { new, active, @@ -12,13 +15,16 @@ export class AdtConnection { private _csrftoken: string = "fetch" private _status: ConnStatus = ConnStatus.new private _listeners: Array = [] + pathclassifier: AdtPathClassifier constructor(name: string, url: string, username: string, password: string) { this.name = name this.url = url this.username = username this.password = password + this.pathclassifier = new AdtPathClassifier() } + isActive(): boolean { return this._status === ConnStatus.active } @@ -42,6 +48,19 @@ export class AdtConnection { }) } + vsRequest(vsUri: Uri, config: request.Options) { + const uri = this.pathclassifier.originalFromVscode(vsUri) + const info = this.pathclassifier.adtUriInfo(uri!) //TODO: error handling + let options: any = {} //{...config} + if (info.uri.query !== "") { + ;(options.qs = info.uri.query), (options.useQuerystring = true) + } + options = { ...options, ...config } + return this.myrequest( + this.createrequest(info.uri.path, info.method, options) + ) + } + request( path: string, method: string = "GET", diff --git a/src/adt/AdtNode.ts b/src/adt/AdtNode.ts index 2406c44..65906d3 100644 --- a/src/adt/AdtNode.ts +++ b/src/adt/AdtNode.ts @@ -3,10 +3,6 @@ import { ObjectNode } from "./AdtParser" const lastPathEntry = (uri: Uri) => uri.path.replace(/\/$/, "").replace(/.*\//, "") export class AdtNode implements FileStat { - // static fromTreeContent(fromTreeContent: ObjectNode[]): AdtNode { - // const node = new AdtNode("") - // return node - // } type: FileType = FileType.Directory name: string ctime: number @@ -35,4 +31,10 @@ export class AdtNode implements FileStat { }) return this } + escapeName = (n: string) => n.replace(/\//g, "_") + + childPath(childname: string): string { + const sep = this.path.path.match(/\/$/) ? "" : "/" + return this.path.path + sep + this.escapeName(childname) + } } diff --git a/src/adt/AdtPathClassifier.ts b/src/adt/AdtPathClassifier.ts new file mode 100644 index 0000000..2b81e8b --- /dev/null +++ b/src/adt/AdtPathClassifier.ts @@ -0,0 +1,80 @@ +import { Uri, FileType, FileSystemError } from "vscode" +import { ObjectNode } from "./AdtParser" +// visual studio paths are hierarchic, adt ones aren't +// so we need a way to translate the hierarchic ones to the original ones +// this file is concerned with telling whether a path is a real ADT one or one from vscode +// /sap/bc/adt/repository/nodestructure (with ampty query) is the root of both +// also, several objects have namespaces. +// Class /foo/bar of package /foo/baz in code will have a path like +// /sap/bc/adt/repository/nodestructure/foo/baz/foo/bar +// the actual adt path would be something like: +// /sap/bc/adt/oo/classes/%2Ffoo%2Fbar +export enum AdtUrlType { + NODESTRUCTURE, + NAMESPACE, + CLASS, + SIMPLE +} + +export interface AdtPathInfo { + source: Uri + uri: Uri + type: AdtUrlType + method: string + isDirectory: FileType +} + +export class AdtPathClassifier { + private nameSpaces: Set = new Set() + private pathinfo: Map = new Map() + + private subpath(vsuri: Uri): string { + const matches = vsuri.path.match( + /^\/sap\/bc\/adt\/repository\/nodestructure\/?(.*)/i + ) + if (!matches) throw new Error("Path did not originate in vscode") + return matches[1] + } + + public originalFromVscode(vsuri: Uri): Uri | undefined { + const subpart = this.subpath(vsuri) + if (subpart === "" && vsuri.query === "") return vsuri + //root + else { + const info = this.pathinfo.get(subpart) + return info ? info.uri : undefined + } + } + public registerNamespace(connection: string, node: ObjectNode) { + const matches = node.OBJECT_NAME.match(/^(\/[^\/]+\/)/) + if (matches) this.nameSpaces.add(matches[1]) + } + public adtUriInfo(original: Uri): AdtPathInfo { + return this.uriInfo(original, original) + } + public registerCodeUri(codeUri: Uri, adtUri: Uri) {} + + private uriInfo(uri: Uri, source: Uri): AdtPathInfo { + if (uri.path.match(/\/sap\/bc\/adt\/.*\//i)) + throw FileSystemError.FileNotFound(uri) + let type, method, isDirectory + if (uri.path.match(/\/repository\/nodestructure\/?$/i)) { + type = AdtUrlType.NODESTRUCTURE + method = "POST" + isDirectory = FileType.Directory + } else if (uri.path.match(/\/oo\/classes\//i)) { + isDirectory = FileType.Directory + type = AdtUrlType.CLASS + method = "GET" + } else if (uri.path.match(/\/wb\/object_type\/devck\/object_name\/(.*)/i)) { + isDirectory = FileType.Directory + type = AdtUrlType.NODESTRUCTURE + method = "GET" + } else { + isDirectory = FileType.File + type = AdtUrlType.SIMPLE + method = "GET" + } + return { source, uri, type, method, isDirectory } + } +} diff --git a/src/adt/AdtPathManager.ts b/src/adt/AdtPathManager.ts index 0628bf1..dfca9cc 100644 --- a/src/adt/AdtPathManager.ts +++ b/src/adt/AdtPathManager.ts @@ -1,18 +1,30 @@ import { AdtNode } from "./AdtNode" -import { adtPathResolver } from "./adtPathResolver" import { AdtConnectionManager } from "./AdtConnectionManager" import { Response } from "request" import { getNodeStructureTreeContent, ObjectNode } from "./AdtParser" -import { Uri } from "vscode" +import { Uri, FileType } from "vscode" + +const asPromise = (x: AdtNode) => new Promise(resolve => resolve(x)) +const isValid = (uri: Uri) => uri.path.match(/\/sap\/bc\/adt\/.*\//i) + +const getMethod = (uri: Uri): string => { + return "POST" +} +const key = (uri: Uri) => uri.authority + uri.path export class AdtPathManager { - getDirectory(uri: Uri): AdtNode { - throw new Error("Method not implemented.") + getDirectory(uri: Uri): AdtNode | undefined { + return this.getDirCached(uri) } - private _cache: Map = new Map() + private _dircache: Map = new Map() private _manager = AdtConnectionManager.getManager() - parse(uri: Uri, response: Response): any { + private actualUri(original: Uri): Uri { + if (!isValid(original)) throw new Error("Not found") + return original + } + + parse(uri: Uri, response: Response): Promise { return getNodeStructureTreeContent(response.body).then( (children: ObjectNode[]) => { const node = new AdtNode(uri) @@ -21,44 +33,31 @@ export class AdtPathManager { } ) } + getDirCached(url: Uri) { + return this._dircache.get(key(url)) + } + setDirCached(url: Uri, directory: AdtNode): void { + this._dircache.set(key(url), directory) + } fetchFileOrDir(url: Uri): Promise { - let path = adtPathResolver(url) - if (path.isRoot) { - let root = this._cache.get(url.toString()) - if (!root) { - root = new AdtNode(url, path.connectionName) - this._cache.set(url.toString(), root) - const firstChild = new AdtNode( - url.with({ - path: url.path + "repository/nodestructure" - }) - ) - root.entries.set(firstChild.name, firstChild) - } - return new Promise(resolve => resolve(root)) - } + url = this.actualUri(url) + const cached = this.getDirCached(url) + if (cached) return asPromise(cached) + return new Promise((resolve, reject) => { - if (path) { - let key = path!.connectionName + path!.path - let response = this._cache.get(key) - if (response) { - resolve(response) - } else { - this._manager.findConn(path.connectionName).then(conn => { - conn - .request(path!.path, path!.method) - .then(response => { - return this.parse(url, response) - }) - .then(file => { - this._cache.set(key, file) - resolve(file) - }) + this._manager.findConn(url.authority).then(conn => { + conn + .request(url.path, getMethod(url)) + .then(response => { + return this.parse(url, response) }) - } - } else { - reject() - } + .then(file => { + if (file.type === FileType.Directory) { + this.setDirCached(url, file) + } + resolve(file) + }) + }) }) } } diff --git a/src/extension.ts b/src/extension.ts index 9b9969d..9d74894 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -43,7 +43,11 @@ export function activate(context: vscode.ExtensionContext) { .then(() => { if (remote) { vscode.workspace.updateWorkspaceFolders(0, 0, { - uri: vscode.Uri.parse("adt://" + remote.name + "/sap/bc/adt/"), + uri: vscode.Uri.parse( + "adt://" + + remote.name + + "/sap/bc/adt/repository/nodestructure" + ), name: remote.name + "(ABAP)" }) } From f04df5d3f23410f0135ef24aed32742a0fac486c Mon Sep 17 00:00:00 2001 From: marcello Date: Mon, 22 Oct 2018 08:52:12 +0100 Subject: [PATCH 3/7] refactor fixed root --- src/adt/AdtConnection.ts | 9 +++++---- src/adt/AdtPathClassifier.ts | 14 +++++++++++--- src/adt/AdtPathManager.ts | 20 +++++++++++--------- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/adt/AdtConnection.ts b/src/adt/AdtConnection.ts index 8063607..fdc6ae6 100644 --- a/src/adt/AdtConnection.ts +++ b/src/adt/AdtConnection.ts @@ -1,6 +1,6 @@ import * as request from "request" import { AdtPathClassifier } from "./AdtPathClassifier" -import { Uri } from "vscode" +import { Uri, FileSystemError } from "vscode" enum ConnStatus { new, @@ -48,10 +48,11 @@ export class AdtConnection { }) } - vsRequest(vsUri: Uri, config: request.Options) { + vsRequest(vsUri: Uri, config: request.CoreOptions = {}) { const uri = this.pathclassifier.originalFromVscode(vsUri) - const info = this.pathclassifier.adtUriInfo(uri!) //TODO: error handling - let options: any = {} //{...config} + if (!uri) throw FileSystemError.FileNotFound(vsUri) + const info = this.pathclassifier.adtUriInfo(uri) + let options: any = {} if (info.uri.query !== "") { ;(options.qs = info.uri.query), (options.useQuerystring = true) } diff --git a/src/adt/AdtPathClassifier.ts b/src/adt/AdtPathClassifier.ts index 2b81e8b..b153570 100644 --- a/src/adt/AdtPathClassifier.ts +++ b/src/adt/AdtPathClassifier.ts @@ -24,6 +24,13 @@ export interface AdtPathInfo { isDirectory: FileType } +export const isValid = (vsUri: Uri): boolean => { + const matches = vsUri.path.match( + /^\/sap\/bc\/adt\/repository\/nodestructure\/?(.*)/i + ) + return !!(matches && (matches[1] === "" || matches[1].match(/\//))) +} + export class AdtPathClassifier { private nameSpaces: Set = new Set() private pathinfo: Map = new Map() @@ -32,7 +39,8 @@ export class AdtPathClassifier { const matches = vsuri.path.match( /^\/sap\/bc\/adt\/repository\/nodestructure\/?(.*)/i ) - if (!matches) throw new Error("Path did not originate in vscode") + if (!matches) + throw FileSystemError.NoPermissions("Path did not originate in vscode") return matches[1] } @@ -45,7 +53,7 @@ export class AdtPathClassifier { return info ? info.uri : undefined } } - public registerNamespace(connection: string, node: ObjectNode) { + public registerNamespace(node: ObjectNode) { const matches = node.OBJECT_NAME.match(/^(\/[^\/]+\/)/) if (matches) this.nameSpaces.add(matches[1]) } @@ -55,7 +63,7 @@ export class AdtPathClassifier { public registerCodeUri(codeUri: Uri, adtUri: Uri) {} private uriInfo(uri: Uri, source: Uri): AdtPathInfo { - if (uri.path.match(/\/sap\/bc\/adt\/.*\//i)) + if (!uri.path.match(/\/sap\/bc\/adt\/.*\/?/i)) throw FileSystemError.FileNotFound(uri) let type, method, isDirectory if (uri.path.match(/\/repository\/nodestructure\/?$/i)) { diff --git a/src/adt/AdtPathManager.ts b/src/adt/AdtPathManager.ts index dfca9cc..a18d7e9 100644 --- a/src/adt/AdtPathManager.ts +++ b/src/adt/AdtPathManager.ts @@ -2,14 +2,11 @@ import { AdtNode } from "./AdtNode" import { AdtConnectionManager } from "./AdtConnectionManager" import { Response } from "request" import { getNodeStructureTreeContent, ObjectNode } from "./AdtParser" -import { Uri, FileType } from "vscode" +import { Uri, FileType, FileSystemError } from "vscode" +import { AdtPathClassifier, isValid } from "./AdtPathClassifier" const asPromise = (x: AdtNode) => new Promise(resolve => resolve(x)) -const isValid = (uri: Uri) => uri.path.match(/\/sap\/bc\/adt\/.*\//i) -const getMethod = (uri: Uri): string => { - return "POST" -} const key = (uri: Uri) => uri.authority + uri.path export class AdtPathManager { @@ -20,13 +17,18 @@ export class AdtPathManager { private _manager = AdtConnectionManager.getManager() private actualUri(original: Uri): Uri { - if (!isValid(original)) throw new Error("Not found") + if (!isValid(original)) throw FileSystemError.FileNotFound(original) return original } - parse(uri: Uri, response: Response): Promise { + parse( + uri: Uri, + response: Response, + classifier: AdtPathClassifier + ): Promise { return getNodeStructureTreeContent(response.body).then( (children: ObjectNode[]) => { + children.forEach(child => classifier.registerNamespace(child)) const node = new AdtNode(uri) node.setChildrenFromTreeContent(children) return node @@ -47,9 +49,9 @@ export class AdtPathManager { return new Promise((resolve, reject) => { this._manager.findConn(url.authority).then(conn => { conn - .request(url.path, getMethod(url)) + .vsRequest(url) .then(response => { - return this.parse(url, response) + return this.parse(url, response, conn.pathclassifier) }) .then(file => { if (file.type === FileType.Directory) { From c410de6cf7ff87abc942d122140d7dd3604b8fc4 Mon Sep 17 00:00:00 2001 From: marcello Date: Tue, 23 Oct 2018 10:39:09 +0100 Subject: [PATCH 4/7] more refactoring --- src/adt/AbapObject.ts | 31 +++++++++++++++++ src/adt/AbapObjectFactory.ts | 16 +++++++++ src/adt/AbapPackage.ts | 16 +++++++++ src/adt/AdtNode.ts | 33 +++++-------------- src/adt/AdtPathClassifier.ts | 12 ++++++- src/adt/AdtPathManager.ts | 55 ++++++++++--------------------- src/adt/AdtServer.ts | 64 ++++++++++++++++++++++++++++++++++++ src/adt/adtPathResolver.ts | 29 ---------------- 8 files changed, 164 insertions(+), 92 deletions(-) create mode 100644 src/adt/AbapObject.ts create mode 100644 src/adt/AbapObjectFactory.ts create mode 100644 src/adt/AbapPackage.ts create mode 100644 src/adt/AdtServer.ts delete mode 100644 src/adt/adtPathResolver.ts diff --git a/src/adt/AbapObject.ts b/src/adt/AbapObject.ts new file mode 100644 index 0000000..964dec7 --- /dev/null +++ b/src/adt/AbapObject.ts @@ -0,0 +1,31 @@ +import { Uri } from "vscode" + +export interface AbapObjectPart { + type: string + name: string + parent: AbapObject +} + +export class AbapObject { + type: string + name: string + path: string + + constructor(type: string, name: string, path: string) { + this.name = name + this.type = type + this.path = path + } + + getUri(base: Uri): Uri { + return base.with({ path: this.path }) + } + namespace(): string { + return this.name.match(/^\//) + ? this.name.replace(/^\/([^\/]*)\/.*/, "$1") + : "" + } + nameinns(): string { + return this.name.replace(/^\/[^\/]*\/(.*)/, "$1") + } +} diff --git a/src/adt/AbapObjectFactory.ts b/src/adt/AbapObjectFactory.ts new file mode 100644 index 0000000..6414dcc --- /dev/null +++ b/src/adt/AbapObjectFactory.ts @@ -0,0 +1,16 @@ +import { ObjectNode } from "./AdtParser" +import { AbapObject } from "./AbapObject" +import { AbapPackage } from "./AbapPackage" + +export function fromObjectNode(node: ObjectNode): AbapObject { + switch (node.OBJECT_TYPE) { + case "DEVC/K": + return new AbapPackage( + node.OBJECT_TYPE, + node.OBJECT_NAME, + node.OBJECT_URI + ) + default: + return new AbapObject(node.OBJECT_TYPE, node.OBJECT_NAME, node.OBJECT_URI) + } +} diff --git a/src/adt/AbapPackage.ts b/src/adt/AbapPackage.ts new file mode 100644 index 0000000..e906e7a --- /dev/null +++ b/src/adt/AbapPackage.ts @@ -0,0 +1,16 @@ +import { AbapObject } from "./AbapObject" +import { Uri } from "vscode" + +export class AbapPackage extends AbapObject { + getUri(base: Uri): Uri { + const ptype = encodeURIComponent(this.type) + const pname = encodeURIComponent(this.name) + const query = Uri.parse( + "adt://npl/sap/bc/adt/repository/nodestructure?parent_name=%24ABAPGIT_GIT&parent_tech_name=%24ABAPGIT_GIT&user_name=MURBANI&parent_type=DEVC%2FK&withShortDescriptions=true" + ) + if (query.path === "") console.log("dummy") + return base.with({ + query: `parent_name=${pname}&parent_tech_name=${pname}&parent_type=${ptype}&withShortDescriptions=true` + }) + } +} diff --git a/src/adt/AdtNode.ts b/src/adt/AdtNode.ts index 65906d3..e88a110 100644 --- a/src/adt/AdtNode.ts +++ b/src/adt/AdtNode.ts @@ -1,40 +1,23 @@ import { FileStat, FileType, Uri } from "vscode" -import { ObjectNode } from "./AdtParser" -const lastPathEntry = (uri: Uri) => - uri.path.replace(/\/$/, "").replace(/.*\//, "") export class AdtNode implements FileStat { type: FileType = FileType.Directory - name: string ctime: number mtime: number size: number = 0 entries: Map - path: Uri - - constructor(path: Uri, name?: string) { - this.name = name ? name : lastPathEntry(path) + uri: Uri + needRefresh(): any { + return this.type === FileType.Directory && this.entries.size === 0 + } + constructor(path: Uri) { this.ctime = Date.now() this.mtime = Date.now() this.entries = new Map() - this.path = path - } - setChildrenFromTreeContent(children: ObjectNode[]): AdtNode { - this.entries.clear() - children.forEach(objnode => { - this.entries.set( - objnode.OBJECT_NAME, - new AdtNode( - this.path.with({ path: objnode.OBJECT_URI }), - objnode.OBJECT_NAME - ) - ) - }) - return this + this.uri = path } - escapeName = (n: string) => n.replace(/\//g, "_") childPath(childname: string): string { - const sep = this.path.path.match(/\/$/) ? "" : "/" - return this.path.path + sep + this.escapeName(childname) + const sep = this.uri.path.match(/\/$/) || childname.match(/^\//) ? "" : "/" + return this.uri.path + sep + childname } } diff --git a/src/adt/AdtPathClassifier.ts b/src/adt/AdtPathClassifier.ts index b153570..66c9428 100644 --- a/src/adt/AdtPathClassifier.ts +++ b/src/adt/AdtPathClassifier.ts @@ -1,5 +1,6 @@ import { Uri, FileType, FileSystemError } from "vscode" import { ObjectNode } from "./AdtParser" +import { AdtNode } from "./AdtNode" // visual studio paths are hierarchic, adt ones aren't // so we need a way to translate the hierarchic ones to the original ones // this file is concerned with telling whether a path is a real ADT one or one from vscode @@ -28,7 +29,7 @@ export const isValid = (vsUri: Uri): boolean => { const matches = vsUri.path.match( /^\/sap\/bc\/adt\/repository\/nodestructure\/?(.*)/i ) - return !!(matches && (matches[1] === "" || matches[1].match(/\//))) + return !!(matches && !matches[1].match(/^\./)) } export class AdtPathClassifier { @@ -43,6 +44,15 @@ export class AdtPathClassifier { throw FileSystemError.NoPermissions("Path did not originate in vscode") return matches[1] } + public addVsPath(parent: AdtNode, name: string, path: string) { + const sep = parent.uri.path.match(/\/$/) || name.match(/^\//) ? "" : "/" + const vspath = parent.uri.path + sep + name + const vsuri = parent.uri.with({ path: vspath }) + const adtUri = Uri.parse(vsuri.scheme + "://" + vsuri.authority + path) + const info = this.uriInfo(adtUri, vsuri) //TODO error handling + const subpart = this.subpath(vsuri) + this.pathinfo.set(subpart, info) + } public originalFromVscode(vsuri: Uri): Uri | undefined { const subpart = this.subpath(vsuri) diff --git a/src/adt/AdtPathManager.ts b/src/adt/AdtPathManager.ts index a18d7e9..d9946d8 100644 --- a/src/adt/AdtPathManager.ts +++ b/src/adt/AdtPathManager.ts @@ -1,65 +1,46 @@ import { AdtNode } from "./AdtNode" -import { AdtConnectionManager } from "./AdtConnectionManager" import { Response } from "request" import { getNodeStructureTreeContent, ObjectNode } from "./AdtParser" -import { Uri, FileType, FileSystemError } from "vscode" -import { AdtPathClassifier, isValid } from "./AdtPathClassifier" +import { Uri, FileSystemError } from "vscode" +import { isValid } from "./AdtPathClassifier" +import { getServer, AdtServer } from "./AdtServer" +import { fromObjectNode } from "./AbapObjectFactory" const asPromise = (x: AdtNode) => new Promise(resolve => resolve(x)) -const key = (uri: Uri) => uri.authority + uri.path - export class AdtPathManager { getDirectory(uri: Uri): AdtNode | undefined { - return this.getDirCached(uri) + return getServer(uri.authority).getDirectory(uri.path) } - private _dircache: Map = new Map() - private _manager = AdtConnectionManager.getManager() private actualUri(original: Uri): Uri { if (!isValid(original)) throw FileSystemError.FileNotFound(original) return original } - parse( - uri: Uri, - response: Response, - classifier: AdtPathClassifier - ): Promise { + parse(uri: Uri, response: Response, server: AdtServer): Promise { return getNodeStructureTreeContent(response.body).then( (children: ObjectNode[]) => { - children.forEach(child => classifier.registerNamespace(child)) const node = new AdtNode(uri) - node.setChildrenFromTreeContent(children) + server.addNodes(node, children.map(fromObjectNode)) + return node } ) } - getDirCached(url: Uri) { - return this._dircache.get(key(url)) - } - setDirCached(url: Uri, directory: AdtNode): void { - this._dircache.set(key(url), directory) - } + fetchFileOrDir(url: Uri): Promise { + const server = getServer(url.authority) url = this.actualUri(url) - const cached = this.getDirCached(url) + + const cached = server.getDirectory(url.path) + if (cached && !cached.needRefresh()) { + return asPromise(cached) + } if (cached) return asPromise(cached) - return new Promise((resolve, reject) => { - this._manager.findConn(url.authority).then(conn => { - conn - .vsRequest(url) - .then(response => { - return this.parse(url, response, conn.pathclassifier) - }) - .then(file => { - if (file.type === FileType.Directory) { - this.setDirCached(url, file) - } - resolve(file) - }) - }) - }) + return server.connectionP + .then(conn => conn.vsRequest(url)) + .then(response => this.parse(url, response, server)) } } diff --git a/src/adt/AdtServer.ts b/src/adt/AdtServer.ts new file mode 100644 index 0000000..b45878f --- /dev/null +++ b/src/adt/AdtServer.ts @@ -0,0 +1,64 @@ +import { AdtConnectionManager } from "./AdtConnectionManager" +import { AdtConnection } from "./AdtConnection" +import { AdtNode } from "./AdtNode" +import { Uri } from "vscode" +import { AbapObject } from "./AbapObject" + +export class AdtServer { + readonly connectionId: string + readonly connectionP: Promise + private directories: Map = new Map() + private objectUris: Map = new Map() + + private addChildrenToNs(node: AdtNode, objects: AbapObject[]) { + objects.forEach(object => { + const childname = node.childPath(object.nameinns()) + const child = new AdtNode(node.uri.with({ path: childname })) + node.entries.set(object.nameinns(), child) + this.objectUris.set(childname, object.getUri(node.uri)) + }) + } + + addNodes(parent: AdtNode, objects: AbapObject[]) { + this.directories.set(parent.uri.path, parent) + const namespaces = objects.reduce((map, obj) => { + const nsname = obj.namespace() + let ns = map.get(nsname) + if (!ns) { + ns = [] + map.set(nsname, ns) + } + ns.push(obj) + return map + }, new Map()) + + namespaces.forEach((objects, name) => { + if (name === "") this.addChildrenToNs(parent, objects) + else { + const nodeName = parent.childPath(name) + const node = new AdtNode(parent.uri.with({ path: nodeName })) + parent.entries.set(name, node) + this.addChildrenToNs(node, objects) + this.directories.set(nodeName, node) + } + }) + } + + getDirectory(name: string): AdtNode | undefined { + return this.directories.get(name) + } + + constructor(connectionId: string) { + this.connectionId = connectionId + this.connectionP = AdtConnectionManager.getManager().findConn(connectionId) + } +} +const servers = new Map() +export const getServer = (connId: string): AdtServer => { + let server = servers.get(connId) + if (!server) { + server = new AdtServer(connId) + servers.set(connId, server) + } + return server +} diff --git a/src/adt/adtPathResolver.ts b/src/adt/adtPathResolver.ts deleted file mode 100644 index a1f33ae..0000000 --- a/src/adt/adtPathResolver.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Uri } from "vscode" - -export interface AdtPath { - method: string - path: string - connectionName: string - isFolder: boolean - url: Uri - isRoot: boolean -} -export const adtPathResolver = (url: Uri): AdtPath => { - const urlString = url.toString() - const match = urlString.match(/adt:\/\/([^\/]+)\/sap\/bc\/adt(.*)/i) - if (match) { - let method = "GET" - let isFolder = true - let isRoot = false - let [connectionName, path] = match.splice(1) - if (path.match(/^\/?|$/i)) { - //partial root - path = "/sap/bc/adt/repository/nodestructure" - method = "POST" - isRoot = true - } else if (!path.match(/\/(.*)\//)) { - throw new Error("Not found") - } - return { method, path, connectionName, isFolder, url, isRoot } - } else throw new Error("not found") -} From df0a6a0e0ec3ee35758e4635a5058d113ad6525d Mon Sep 17 00:00:00 2001 From: marcello Date: Tue, 23 Oct 2018 23:27:57 +0100 Subject: [PATCH 5/7] namespace support --- src/abapFsProvider.ts | 6 +-- src/adt/AbapPackage.ts | 4 -- src/adt/AdtConnection.ts | 31 ++---------- src/adt/AdtPathClassifier.ts | 98 ------------------------------------ src/adt/AdtPathManager.ts | 22 +++----- src/adt/AdtServer.ts | 24 ++++++++- 6 files changed, 36 insertions(+), 149 deletions(-) delete mode 100644 src/adt/AdtPathClassifier.ts diff --git a/src/abapFsProvider.ts b/src/abapFsProvider.ts index 7d2dd05..9ccdd23 100644 --- a/src/abapFsProvider.ts +++ b/src/abapFsProvider.ts @@ -13,11 +13,7 @@ export class AbapFsProvider implements vscode.FileSystemProvider { throw new Error("Method not implemented.") } stat(uri: vscode.Uri): vscode.FileStat | Thenable { - console.log(uri.toString()) - return this._pathManager.fetchFileOrDir(uri).then(n => { - // console.log(n) - return n - }) + return this._pathManager.fetchFileOrDir(uri) } readDirectory( uri: vscode.Uri diff --git a/src/adt/AbapPackage.ts b/src/adt/AbapPackage.ts index e906e7a..6571419 100644 --- a/src/adt/AbapPackage.ts +++ b/src/adt/AbapPackage.ts @@ -5,10 +5,6 @@ export class AbapPackage extends AbapObject { getUri(base: Uri): Uri { const ptype = encodeURIComponent(this.type) const pname = encodeURIComponent(this.name) - const query = Uri.parse( - "adt://npl/sap/bc/adt/repository/nodestructure?parent_name=%24ABAPGIT_GIT&parent_tech_name=%24ABAPGIT_GIT&user_name=MURBANI&parent_type=DEVC%2FK&withShortDescriptions=true" - ) - if (query.path === "") console.log("dummy") return base.with({ query: `parent_name=${pname}&parent_tech_name=${pname}&parent_type=${ptype}&withShortDescriptions=true` }) diff --git a/src/adt/AdtConnection.ts b/src/adt/AdtConnection.ts index fdc6ae6..a09d4b9 100644 --- a/src/adt/AdtConnection.ts +++ b/src/adt/AdtConnection.ts @@ -1,6 +1,6 @@ import * as request from "request" -import { AdtPathClassifier } from "./AdtPathClassifier" -import { Uri, FileSystemError } from "vscode" +// import { AdtPathClassifier } from "./AdtPathClassifier" +import { Uri } from "vscode" enum ConnStatus { new, @@ -15,14 +15,12 @@ export class AdtConnection { private _csrftoken: string = "fetch" private _status: ConnStatus = ConnStatus.new private _listeners: Array = [] - pathclassifier: AdtPathClassifier constructor(name: string, url: string, username: string, password: string) { this.name = name this.url = url this.username = username this.password = password - this.pathclassifier = new AdtPathClassifier() } isActive(): boolean { @@ -48,28 +46,9 @@ export class AdtConnection { }) } - vsRequest(vsUri: Uri, config: request.CoreOptions = {}) { - const uri = this.pathclassifier.originalFromVscode(vsUri) - if (!uri) throw FileSystemError.FileNotFound(vsUri) - const info = this.pathclassifier.adtUriInfo(uri) - let options: any = {} - if (info.uri.query !== "") { - ;(options.qs = info.uri.query), (options.useQuerystring = true) - } - options = { ...options, ...config } - return this.myrequest( - this.createrequest(info.uri.path, info.method, options) - ) - } - - request( - path: string, - method: string = "GET", - config: request.Options | Object = {} - ): Promise { - let relativePath = path.replace(/(?:adt:\/)?\/[^\/]*\/sap\/bc\/adt/i, "") - const request = this.createrequest(relativePath, method, config) - return this.myrequest(request) + request(uri: Uri, method: string): Promise { + const path = uri.query ? uri.path + "?" + uri.query : uri.path + return this.myrequest(this.createrequest(path, method)) } private createrequest( diff --git a/src/adt/AdtPathClassifier.ts b/src/adt/AdtPathClassifier.ts deleted file mode 100644 index 66c9428..0000000 --- a/src/adt/AdtPathClassifier.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { Uri, FileType, FileSystemError } from "vscode" -import { ObjectNode } from "./AdtParser" -import { AdtNode } from "./AdtNode" -// visual studio paths are hierarchic, adt ones aren't -// so we need a way to translate the hierarchic ones to the original ones -// this file is concerned with telling whether a path is a real ADT one or one from vscode -// /sap/bc/adt/repository/nodestructure (with ampty query) is the root of both -// also, several objects have namespaces. -// Class /foo/bar of package /foo/baz in code will have a path like -// /sap/bc/adt/repository/nodestructure/foo/baz/foo/bar -// the actual adt path would be something like: -// /sap/bc/adt/oo/classes/%2Ffoo%2Fbar -export enum AdtUrlType { - NODESTRUCTURE, - NAMESPACE, - CLASS, - SIMPLE -} - -export interface AdtPathInfo { - source: Uri - uri: Uri - type: AdtUrlType - method: string - isDirectory: FileType -} - -export const isValid = (vsUri: Uri): boolean => { - const matches = vsUri.path.match( - /^\/sap\/bc\/adt\/repository\/nodestructure\/?(.*)/i - ) - return !!(matches && !matches[1].match(/^\./)) -} - -export class AdtPathClassifier { - private nameSpaces: Set = new Set() - private pathinfo: Map = new Map() - - private subpath(vsuri: Uri): string { - const matches = vsuri.path.match( - /^\/sap\/bc\/adt\/repository\/nodestructure\/?(.*)/i - ) - if (!matches) - throw FileSystemError.NoPermissions("Path did not originate in vscode") - return matches[1] - } - public addVsPath(parent: AdtNode, name: string, path: string) { - const sep = parent.uri.path.match(/\/$/) || name.match(/^\//) ? "" : "/" - const vspath = parent.uri.path + sep + name - const vsuri = parent.uri.with({ path: vspath }) - const adtUri = Uri.parse(vsuri.scheme + "://" + vsuri.authority + path) - const info = this.uriInfo(adtUri, vsuri) //TODO error handling - const subpart = this.subpath(vsuri) - this.pathinfo.set(subpart, info) - } - - public originalFromVscode(vsuri: Uri): Uri | undefined { - const subpart = this.subpath(vsuri) - if (subpart === "" && vsuri.query === "") return vsuri - //root - else { - const info = this.pathinfo.get(subpart) - return info ? info.uri : undefined - } - } - public registerNamespace(node: ObjectNode) { - const matches = node.OBJECT_NAME.match(/^(\/[^\/]+\/)/) - if (matches) this.nameSpaces.add(matches[1]) - } - public adtUriInfo(original: Uri): AdtPathInfo { - return this.uriInfo(original, original) - } - public registerCodeUri(codeUri: Uri, adtUri: Uri) {} - - private uriInfo(uri: Uri, source: Uri): AdtPathInfo { - if (!uri.path.match(/\/sap\/bc\/adt\/.*\/?/i)) - throw FileSystemError.FileNotFound(uri) - let type, method, isDirectory - if (uri.path.match(/\/repository\/nodestructure\/?$/i)) { - type = AdtUrlType.NODESTRUCTURE - method = "POST" - isDirectory = FileType.Directory - } else if (uri.path.match(/\/oo\/classes\//i)) { - isDirectory = FileType.Directory - type = AdtUrlType.CLASS - method = "GET" - } else if (uri.path.match(/\/wb\/object_type\/devck\/object_name\/(.*)/i)) { - isDirectory = FileType.Directory - type = AdtUrlType.NODESTRUCTURE - method = "GET" - } else { - isDirectory = FileType.File - type = AdtUrlType.SIMPLE - method = "GET" - } - return { source, uri, type, method, isDirectory } - } -} diff --git a/src/adt/AdtPathManager.ts b/src/adt/AdtPathManager.ts index d9946d8..2e96ffa 100644 --- a/src/adt/AdtPathManager.ts +++ b/src/adt/AdtPathManager.ts @@ -1,10 +1,9 @@ import { AdtNode } from "./AdtNode" import { Response } from "request" import { getNodeStructureTreeContent, ObjectNode } from "./AdtParser" -import { Uri, FileSystemError } from "vscode" -import { isValid } from "./AdtPathClassifier" import { getServer, AdtServer } from "./AdtServer" import { fromObjectNode } from "./AbapObjectFactory" +import { Uri } from "vscode" const asPromise = (x: AdtNode) => new Promise(resolve => resolve(x)) @@ -13,11 +12,6 @@ export class AdtPathManager { return getServer(uri.authority).getDirectory(uri.path) } - private actualUri(original: Uri): Uri { - if (!isValid(original)) throw FileSystemError.FileNotFound(original) - return original - } - parse(uri: Uri, response: Response, server: AdtServer): Promise { return getNodeStructureTreeContent(response.body).then( (children: ObjectNode[]) => { @@ -29,18 +23,18 @@ export class AdtPathManager { ) } - fetchFileOrDir(url: Uri): Promise { - const server = getServer(url.authority) - url = this.actualUri(url) + fetchFileOrDir(vsUrl: Uri): Promise { + const server = getServer(vsUrl.authority) - const cached = server.getDirectory(url.path) + const cached = server.getDirectory(vsUrl.path) if (cached && !cached.needRefresh()) { return asPromise(cached) } - if (cached) return asPromise(cached) + + const url = server.actualUri(vsUrl) return server.connectionP - .then(conn => conn.vsRequest(url)) - .then(response => this.parse(url, response, server)) + .then(conn => conn.request(url, "POST")) + .then(response => this.parse(vsUrl, response, server)) } } diff --git a/src/adt/AdtServer.ts b/src/adt/AdtServer.ts index b45878f..b66f9c0 100644 --- a/src/adt/AdtServer.ts +++ b/src/adt/AdtServer.ts @@ -1,9 +1,24 @@ import { AdtConnectionManager } from "./AdtConnectionManager" import { AdtConnection } from "./AdtConnection" import { AdtNode } from "./AdtNode" -import { Uri } from "vscode" +import { Uri, FileSystemError } from "vscode" import { AbapObject } from "./AbapObject" - +// visual studio paths are hierarchic, adt ones aren't +// so we need a way to translate the hierarchic ones to the original ones +// this file is concerned with telling whether a path is a real ADT one or one from vscode +// /sap/bc/adt/repository/nodestructure (with ampty query) is the root of both +// also, several objects have namespaces. +// Class /foo/bar of package /foo/baz in code will have a path like +// /sap/bc/adt/repository/nodestructure/foo/baz/foo/bar +// the actual adt path would be something like: +// /sap/bc/adt/oo/classes/%2Ffoo%2Fbar +// so we need to do quite a bit of transcoding +const isValid = (vsUri: Uri): boolean => { + const matches = vsUri.path.match( + /^\/sap\/bc\/adt\/repository\/nodestructure\/?(.*)/i + ) + return !!(matches && !matches[1].match(/^\./)) +} export class AdtServer { readonly connectionId: string readonly connectionP: Promise @@ -19,6 +34,11 @@ export class AdtServer { }) } + actualUri(original: Uri): Uri { + if (!isValid(original)) throw FileSystemError.FileNotFound(original) + return this.objectUris.get(original.path) || original + } + addNodes(parent: AdtNode, objects: AbapObject[]) { this.directories.set(parent.uri.path, parent) const namespaces = objects.reduce((map, obj) => { From efa7675ea6bc969dccfb7f3d7ec58f7509982695 Mon Sep 17 00:00:00 2001 From: marcello Date: Wed, 24 Oct 2018 09:20:33 +0100 Subject: [PATCH 6/7] subpackages, broken file support --- src/{adt => abap}/AbapObject.ts | 4 ++ src/{adt => abap}/AbapObjectFactory.ts | 2 +- src/{adt => abap}/AbapPackage.ts | 3 ++ src/abapFsProvider.ts | 12 ++---- src/adt/AdtNode.ts | 15 +++++-- src/adt/AdtPathManager.ts | 57 ++++++++++++++++++-------- src/adt/AdtServer.ts | 28 ++++++++++--- 7 files changed, 86 insertions(+), 35 deletions(-) rename src/{adt => abap}/AbapObject.ts (94%) rename src/{adt => abap}/AbapObjectFactory.ts (89%) rename src/{adt => abap}/AbapPackage.ts (91%) diff --git a/src/adt/AbapObject.ts b/src/abap/AbapObject.ts similarity index 94% rename from src/adt/AbapObject.ts rename to src/abap/AbapObject.ts index 964dec7..ce41c51 100644 --- a/src/adt/AbapObject.ts +++ b/src/abap/AbapObject.ts @@ -17,6 +17,10 @@ export class AbapObject { this.path = path } + isLeaf() { + return true + } + getUri(base: Uri): Uri { return base.with({ path: this.path }) } diff --git a/src/adt/AbapObjectFactory.ts b/src/abap/AbapObjectFactory.ts similarity index 89% rename from src/adt/AbapObjectFactory.ts rename to src/abap/AbapObjectFactory.ts index 6414dcc..16efc58 100644 --- a/src/adt/AbapObjectFactory.ts +++ b/src/abap/AbapObjectFactory.ts @@ -1,4 +1,4 @@ -import { ObjectNode } from "./AdtParser" +import { ObjectNode } from "../adt/AdtParser" import { AbapObject } from "./AbapObject" import { AbapPackage } from "./AbapPackage" diff --git a/src/adt/AbapPackage.ts b/src/abap/AbapPackage.ts similarity index 91% rename from src/adt/AbapPackage.ts rename to src/abap/AbapPackage.ts index 6571419..a7deded 100644 --- a/src/adt/AbapPackage.ts +++ b/src/abap/AbapPackage.ts @@ -2,6 +2,9 @@ import { AbapObject } from "./AbapObject" import { Uri } from "vscode" export class AbapPackage extends AbapObject { + isLeaf() { + return false + } getUri(base: Uri): Uri { const ptype = encodeURIComponent(this.type) const pname = encodeURIComponent(this.name) diff --git a/src/abapFsProvider.ts b/src/abapFsProvider.ts index 9ccdd23..57252e2 100644 --- a/src/abapFsProvider.ts +++ b/src/abapFsProvider.ts @@ -30,15 +30,9 @@ export class AbapFsProvider implements vscode.FileSystemProvider { throw new Error("Method not implemented.") } readFile(uri: vscode.Uri): Uint8Array | Thenable { - // if (uri.path === "/dummy.abap" && this.root) { - // return this.root.then(x => { - // const child = x.entries.get("dummy.abap") - // if (child && child instanceof AdtFile && child.data) { - // return child.data - // } - // }) - // } - throw new Error("Method not implemented.") + const file = this._pathManager.find(uri) + if (file && file.body) return file.body + return new Uint8Array([]) } writeFile( uri: vscode.Uri, diff --git a/src/adt/AdtNode.ts b/src/adt/AdtNode.ts index e88a110..2d694b3 100644 --- a/src/adt/AdtNode.ts +++ b/src/adt/AdtNode.ts @@ -1,23 +1,32 @@ import { FileStat, FileType, Uri } from "vscode" export class AdtNode implements FileStat { - type: FileType = FileType.Directory + type: FileType ctime: number mtime: number size: number = 0 entries: Map uri: Uri + fetched: boolean + body: Buffer | undefined needRefresh(): any { - return this.type === FileType.Directory && this.entries.size === 0 + return !this.fetched } - constructor(path: Uri) { + constructor(path: Uri, isDirectory: boolean, fetched: boolean) { this.ctime = Date.now() this.mtime = Date.now() this.entries = new Map() this.uri = path + this.type = isDirectory ? FileType.Directory : FileType.File + this.fetched = fetched } childPath(childname: string): string { const sep = this.uri.path.match(/\/$/) || childname.match(/^\//) ? "" : "/" return this.uri.path + sep + childname } + setContents(body: Buffer): void { + this.body = body + this.size = body.length + this.fetched = true + } } diff --git a/src/adt/AdtPathManager.ts b/src/adt/AdtPathManager.ts index 2e96ffa..0b7b49e 100644 --- a/src/adt/AdtPathManager.ts +++ b/src/adt/AdtPathManager.ts @@ -2,39 +2,64 @@ import { AdtNode } from "./AdtNode" import { Response } from "request" import { getNodeStructureTreeContent, ObjectNode } from "./AdtParser" import { getServer, AdtServer } from "./AdtServer" -import { fromObjectNode } from "./AbapObjectFactory" -import { Uri } from "vscode" - -const asPromise = (x: AdtNode) => new Promise(resolve => resolve(x)) +import { fromObjectNode } from "../abap/AbapObjectFactory" +import { Uri, FileSystemError, FileType } from "vscode" export class AdtPathManager { getDirectory(uri: Uri): AdtNode | undefined { return getServer(uri.authority).getDirectory(uri.path) } + find(uri: Uri): AdtNode | undefined { + const server = getServer(uri.authority) + let node = server.getDirectory(uri.path) + if (node) return node + const matches = uri.path.match(/(.*)\/([^\/]+)$/) + if (matches) { + const [dir, name] = matches.slice(1) + let parent = server.getDirectory(dir) + let node = parent && parent.entries.get(name) + if (node) return node + } + } - parse(uri: Uri, response: Response, server: AdtServer): Promise { - return getNodeStructureTreeContent(response.body).then( - (children: ObjectNode[]) => { - const node = new AdtNode(uri) - server.addNodes(node, children.map(fromObjectNode)) - - return node - } + parse( + uri: Uri, + response: Response, + server: AdtServer, + node: AdtNode | undefined + ): Promise | AdtNode { + if ( + response.request.uri.path && + response.request.uri.path.match(/\/nodestructure/i) ) + return getNodeStructureTreeContent(response.body).then( + (children: ObjectNode[]) => { + if (node) node.entries.clear() + else node = new AdtNode(uri, true, true) + server.addNodes(node, children.map(fromObjectNode)) + node.fetched = true + return node + } + ) + else if (node && node.type === FileType.File) { + node.setContents(response.body) + return node + } + throw FileSystemError.FileNotFound(uri.path) } - fetchFileOrDir(vsUrl: Uri): Promise { + fetchFileOrDir(vsUrl: Uri): Promise | AdtNode { const server = getServer(vsUrl.authority) - const cached = server.getDirectory(vsUrl.path) + const cached = this.find(vsUrl) if (cached && !cached.needRefresh()) { - return asPromise(cached) + return cached } const url = server.actualUri(vsUrl) return server.connectionP .then(conn => conn.request(url, "POST")) - .then(response => this.parse(vsUrl, response, server)) + .then(response => this.parse(vsUrl, response, server, cached)) } } diff --git a/src/adt/AdtServer.ts b/src/adt/AdtServer.ts index b66f9c0..455cc32 100644 --- a/src/adt/AdtServer.ts +++ b/src/adt/AdtServer.ts @@ -1,8 +1,8 @@ import { AdtConnectionManager } from "./AdtConnectionManager" import { AdtConnection } from "./AdtConnection" import { AdtNode } from "./AdtNode" -import { Uri, FileSystemError } from "vscode" -import { AbapObject } from "./AbapObject" +import { Uri, FileSystemError, FileType } from "vscode" +import { AbapObject } from "../abap/AbapObject" // visual studio paths are hierarchic, adt ones aren't // so we need a way to translate the hierarchic ones to the original ones // this file is concerned with telling whether a path is a real ADT one or one from vscode @@ -28,9 +28,14 @@ export class AdtServer { private addChildrenToNs(node: AdtNode, objects: AbapObject[]) { objects.forEach(object => { const childname = node.childPath(object.nameinns()) - const child = new AdtNode(node.uri.with({ path: childname })) + const child = new AdtNode( + node.uri.with({ path: childname }), + !object.isLeaf(), + false + ) node.entries.set(object.nameinns(), child) this.objectUris.set(childname, object.getUri(node.uri)) + if(child.type=== FileType.Directory)this.directories.set(childname,child) }) } @@ -52,16 +57,27 @@ export class AdtServer { return map }, new Map()) + //for every namespace create a node, add the children to it + // so package /foo/bar will be rendered in + // a namespace folder foo + // with a package bar inside namespaces.forEach((objects, name) => { - if (name === "") this.addChildrenToNs(parent, objects) - else { + if (name !== "") { const nodeName = parent.childPath(name) - const node = new AdtNode(parent.uri.with({ path: nodeName })) + const node = new AdtNode( + parent.uri.with({ path: nodeName }), + true, + true + ) parent.entries.set(name, node) this.addChildrenToNs(node, objects) this.directories.set(nodeName, node) } }) + //add objects without a namespace + namespaces.forEach((objects, name) => { + if (name === "") this.addChildrenToNs(parent, objects) + }) } getDirectory(name: string): AdtNode | undefined { From 7a4799ba37a5a8d393e0b99b35bb0d79a4555eb8 Mon Sep 17 00:00:00 2001 From: marcello Date: Thu, 25 Oct 2018 00:09:00 +0100 Subject: [PATCH 7/7] viewing *some* files --- README.md | 6 +++++- src/abap/AbapFunctionGroup.ts | 21 +++++++++++++++++++++ src/abap/AbapFunctionModule.ts | 8 ++++++++ src/abap/AbapObject.ts | 2 +- src/abap/AbapObjectFactory.ts | 21 ++++++++++++++------- src/abap/AbapPackage.ts | 1 + src/abap/AbapSimpleObject.ts | 8 ++++++++ src/adt/AdtConnection.ts | 3 ++- src/adt/AdtNode.ts | 7 ++++--- src/adt/AdtPathManager.ts | 5 ++++- 10 files changed, 68 insertions(+), 14 deletions(-) create mode 100644 src/abap/AbapFunctionGroup.ts create mode 100644 src/abap/AbapFunctionModule.ts create mode 100644 src/abap/AbapSimpleObject.ts diff --git a/README.md b/README.md index 7f9fdb8..3138ce7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ # ABAP remote filesystem for visual studio code Ideally one day this will allow you to edit your ABAP code directly in Visual studio code -Very early stages, for now it only displays a list of packages +Very early stages, for now it only displays some packages and a handful of object types, no local objects,subroutines in object lists... + +Even things that do work need a big refactor +![image](https://user-images.githubusercontent.com/2453277/47466602-dd99dc00-d7e9-11e8-97ed-28e23dfd8f90.png) +syntax highlighting added manually with the [ABAP language extension](https://marketplace.visualstudio.com/items?itemName=larshp.vscode-abap),picture was too lame without it :) ## Features diff --git a/src/abap/AbapFunctionGroup.ts b/src/abap/AbapFunctionGroup.ts new file mode 100644 index 0000000..f38e684 --- /dev/null +++ b/src/abap/AbapFunctionGroup.ts @@ -0,0 +1,21 @@ +import { AbapObject } from "./AbapObject" +import { Uri } from "vscode" + +export class AbapFunctionGroup extends AbapObject { + isLeaf() { + return false + } + getUri(base: Uri): Uri { + const ptype = encodeURIComponent(this.type) + const pname = encodeURIComponent(this.name) + const techname = encodeURIComponent( + this.namespace() === "" + ? "SAPL" + this.name + : `/${this.namespace()}/SAPL${this.nameinns}` + ) + return base.with({ + path: "/sap/bc/adt/repository/nodestructure", + query: `parent_name=${pname}&parent_tech_name=${techname}&parent_type=${ptype}&withShortDescriptions=true` + }) + } +} diff --git a/src/abap/AbapFunctionModule.ts b/src/abap/AbapFunctionModule.ts new file mode 100644 index 0000000..4d2667d --- /dev/null +++ b/src/abap/AbapFunctionModule.ts @@ -0,0 +1,8 @@ +import { AbapObject } from "./AbapObject" +import { Uri } from "vscode" + +export class AbapFunctionModule extends AbapObject { + getUri(base: Uri): Uri { + return base.with({ path: this.path + "/source/main" }) + } +} diff --git a/src/abap/AbapObject.ts b/src/abap/AbapObject.ts index ce41c51..ba1fc6b 100644 --- a/src/abap/AbapObject.ts +++ b/src/abap/AbapObject.ts @@ -22,7 +22,7 @@ export class AbapObject { } getUri(base: Uri): Uri { - return base.with({ path: this.path }) + return base.with({ path: this.path + "/source/main" }) } namespace(): string { return this.name.match(/^\//) diff --git a/src/abap/AbapObjectFactory.ts b/src/abap/AbapObjectFactory.ts index 16efc58..dc33c07 100644 --- a/src/abap/AbapObjectFactory.ts +++ b/src/abap/AbapObjectFactory.ts @@ -1,16 +1,23 @@ import { ObjectNode } from "../adt/AdtParser" import { AbapObject } from "./AbapObject" import { AbapPackage } from "./AbapPackage" +import { AbapFunctionGroup } from "./AbapFunctionGroup" +import { AbapSimpleObject } from "./AbapSimpleObject" export function fromObjectNode(node: ObjectNode): AbapObject { + let objtype = AbapObject switch (node.OBJECT_TYPE) { case "DEVC/K": - return new AbapPackage( - node.OBJECT_TYPE, - node.OBJECT_NAME, - node.OBJECT_URI - ) - default: - return new AbapObject(node.OBJECT_TYPE, node.OBJECT_NAME, node.OBJECT_URI) + objtype = AbapPackage + break + case "FUGR/F": + objtype = AbapFunctionGroup + break + case "TABL/DT": + case "DOMA/DT": + case "DTEL/DE": + objtype = AbapSimpleObject + break } + return new objtype(node.OBJECT_TYPE, node.OBJECT_NAME, node.OBJECT_URI) } diff --git a/src/abap/AbapPackage.ts b/src/abap/AbapPackage.ts index a7deded..8f82f53 100644 --- a/src/abap/AbapPackage.ts +++ b/src/abap/AbapPackage.ts @@ -9,6 +9,7 @@ export class AbapPackage extends AbapObject { const ptype = encodeURIComponent(this.type) const pname = encodeURIComponent(this.name) return base.with({ + path: "/sap/bc/adt/repository/nodestructure", query: `parent_name=${pname}&parent_tech_name=${pname}&parent_type=${ptype}&withShortDescriptions=true` }) } diff --git a/src/abap/AbapSimpleObject.ts b/src/abap/AbapSimpleObject.ts new file mode 100644 index 0000000..e58acdf --- /dev/null +++ b/src/abap/AbapSimpleObject.ts @@ -0,0 +1,8 @@ +import { AbapObject } from "./AbapObject" +import { Uri } from "vscode" + +export class AbapSimpleObject extends AbapObject { + getUri(base: Uri): Uri { + return base.with({ path: this.path }) + } +} diff --git a/src/adt/AdtConnection.ts b/src/adt/AdtConnection.ts index a09d4b9..3651bf8 100644 --- a/src/adt/AdtConnection.ts +++ b/src/adt/AdtConnection.ts @@ -66,7 +66,8 @@ export class AdtConnection { }, method, headers: { - "x-csrf-token": this._csrftoken + "x-csrf-token": this._csrftoken, + Accept: "*/*" } } as request.Options //workaround for compiler bug } diff --git a/src/adt/AdtNode.ts b/src/adt/AdtNode.ts index 2d694b3..15a6919 100644 --- a/src/adt/AdtNode.ts +++ b/src/adt/AdtNode.ts @@ -1,4 +1,5 @@ import { FileStat, FileType, Uri } from "vscode" + export class AdtNode implements FileStat { type: FileType ctime: number @@ -24,9 +25,9 @@ export class AdtNode implements FileStat { const sep = this.uri.path.match(/\/$/) || childname.match(/^\//) ? "" : "/" return this.uri.path + sep + childname } - setContents(body: Buffer): void { - this.body = body - this.size = body.length + setContents(body: string): void { + this.body = Buffer.from(body) + this.size = this.body.length this.fetched = true } } diff --git a/src/adt/AdtPathManager.ts b/src/adt/AdtPathManager.ts index 0b7b49e..99c99c8 100644 --- a/src/adt/AdtPathManager.ts +++ b/src/adt/AdtPathManager.ts @@ -59,7 +59,10 @@ export class AdtPathManager { const url = server.actualUri(vsUrl) return server.connectionP - .then(conn => conn.request(url, "POST")) + .then(conn => conn.request(url, this.getMethod(url))) .then(response => this.parse(vsUrl, response, server, cached)) } + getMethod(uri: Uri): string { + return uri.path.match(/\/nodestructure/i) ? "POST" : "GET" + } }