Skip to content

Commit 2472f40

Browse files
Lock on change, fixes, refactor
1 parent b518a04 commit 2472f40

30 files changed

Lines changed: 429 additions & 173 deletions

.vscode/settings.json

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
// Place your settings in this file to overwrite default and user settings.
22
{
3-
"files.exclude": {
4-
"out": false // set this to true to hide the "out" folder with the compiled JS files
5-
},
6-
"search.exclude": {
7-
"out": true // set this to false to include "out" folder in search results
8-
},
9-
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
10-
"typescript.tsc.autoDetect": "off",
11-
"javascript.format.enable": false,
12-
"typescript.locale": "en",
13-
"gitlens.codeLens.enabled": false
14-
}
3+
"files.exclude": {
4+
"out": false // set this to true to hide the "out" folder with the compiled JS files
5+
},
6+
"search.exclude": {
7+
"out": true // set this to false to include "out" folder in search results
8+
},
9+
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
10+
"typescript.tsc.autoDetect": "off",
11+
"javascript.format.enable": false,
12+
"typescript.locale": "en",
13+
"gitlens.codeLens.enabled": false,
14+
"editor.fontLigatures": true
15+
}

README.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
# ABAP remote filesystem for visual studio code
22

3-
This extension allows editing and activation of ABAP code on your server directly in Visual studio code.
4-
Doesn't allow to create objects yet, but does read/save/activate several object types
3+
This extension allows editing and activation of ABAP code on your server directly in Visual studio code, including transport assignment and creation (if your system supports it).
54

65
**Unless your system is very modern, write support will require you to install [this extension](https://github.com/marcellourbani/abapfs_extensions)** in your dev server to enable locking files
76

8-
**WRITE SUPPORT IS EXPERIMANTAL USE AT YOUR OWN RISK**
7+
**THIS SOFTWARE IS IN BETA TEST, USE AT YOUR OWN RISK**
98

109
![anim](https://user-images.githubusercontent.com/2453277/47482169-ae0cc300-d82d-11e8-8d19-f55dd877c166.gif)
1110
![image](https://user-images.githubusercontent.com/2453277/47466602-dd99dc00-d7e9-11e8-97ed-28e23dfd8f90.png)
@@ -21,16 +20,16 @@ The complete list of editable objects depends on your installation, on my local
2120
- programs/includes
2221
- function groups
2322
- classes
24-
- transformations
23+
- transformations (except creation)
2524

2625
![anim](https://user-images.githubusercontent.com/2453277/48232926-30a78d80-e3ab-11e8-8a12-00844431f9af.gif)
2726

2827
## setup
2928

30-
Too early to publish as an extension, there's a compiled extension you can run from source or install from the command line with
29+
Will soon be published in the marketplace, in the meanwhile there's a compiled extension you can run from source or install from the command line with
3130

3231
```shell
33-
code --install-extension vscode-abap-remote-fs-0.1.0.vsix
32+
code --install-extension vscode-abap-remote-fs-0.3.0.vsix
3433
```
3534

3635
The compiled file can be either downloaded from for the

images/abapfs_icon.png

20.4 KB
Loading

package-lock.json

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
"name": "vscode-abap-remote-fs",
33
"displayName": "vscode_abap_remote_fs",
44
"description": "Work on your ABAP code straight from the server",
5-
"version": "0.2.0",
5+
"version": "0.3.0",
66
"publisher": "murbani",
77
"license": "MIT",
8+
"icon": "images/abapfs_icon.png",
89
"author": {
910
"email": "marcello.urbani@gmail.com",
1011
"name": "Marcello Urbani"
@@ -43,12 +44,12 @@
4344
"tsconfig-paths": "^3.7.0",
4445
"tslint": "^5.8.0",
4546
"typescript": "^2.6.1",
46-
"vsce": "^1.52.0",
47-
"vscode": "^1.1.21"
47+
"vsce": "^1.53.2",
48+
"vscode": "^1.1.26"
4849
},
4950
"dependencies": {
50-
"request": "^2.88.0",
5151
"event-stream": "3.3.4",
52+
"request": "^2.88.0",
5253
"xml2js": "^0.4.19"
5354
},
5455
"activationEvents": [

src/adt/AdtConnection.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,24 @@ enum ConnStatus {
1111
active,
1212
failed
1313
}
14+
export interface StateRequestor {
15+
needStateFul: boolean
16+
}
1417
export class AdtConnection {
1518
readonly name: string
1619
readonly url: string
1720
readonly username: string
1821
readonly password: string
19-
//TODO: proper session support
20-
stateful = true
22+
23+
get stateful() {
24+
for (const r of this._stateRequestors) if (r.needStateFul) return true
25+
return false
26+
}
2127
private _csrftoken: string = FETCH_CSRF_TOKEN
2228
private _status: ConnStatus = ConnStatus.new
2329
private _listeners: Array<Function> = []
2430
private _clone?: AdtConnection
31+
private _stateRequestors: Set<StateRequestor> = new Set()
2532

2633
constructor(name: string, url: string, username: string, password: string) {
2734
this.name = name
@@ -44,12 +51,15 @@ export class AdtConnection {
4451
this.username,
4552
this.password
4653
)
47-
this._clone.stateful = false
4854
}
4955
await this._clone.connect()
5056
return this._clone
5157
}
5258

59+
addStateRequestor(r: StateRequestor) {
60+
this._stateRequestors.add(r)
61+
}
62+
5363
isActive(): boolean {
5464
return this._status === ConnStatus.active
5565
}
@@ -138,6 +148,21 @@ export class AdtConnection {
138148
query
139149
})
140150
}
151+
152+
dropSession() {
153+
return this.myrequest(
154+
"/sap/bc/adt/repository/informationsystem/objecttypes",
155+
"GET",
156+
{
157+
headers: {
158+
"x-csrf-token": this._csrftoken,
159+
"X-sap-adt-sessiontype": "",
160+
Accept: "*/*"
161+
}
162+
}
163+
)
164+
}
165+
141166
connect(): Promise<request.Response> {
142167
return this.myrequest(
143168
"/sap/bc/adt/repository/informationsystem/objecttypes?maxItemCount=999&name=*&data=usedByProvider"

src/adt/AdtExceptions.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { parsetoPromise, getFieldAttribute, recxml2js } from "./AdtParserBase"
1+
import {
2+
parseToPromise,
3+
getFieldAttribute,
4+
recxml2js
5+
} from "./parsers/AdtParserBase"
26
import { Response } from "request"
37
const TYPEID = Symbol()
48

@@ -23,7 +27,7 @@ export class AdtException extends Error {
2327
}
2428

2529
static async fromXml(xml: string): Promise<AdtException> {
26-
const raw: any = await parsetoPromise()(xml)
30+
const raw: any = await parseToPromise()(xml)
2731
const root: any = raw["exc:exception"]
2832
const namespace = getFieldAttribute("namespace", "id", root)
2933
const type = getFieldAttribute("type", "id", root)

src/adt/AdtServer.ts

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ import { AdtConnection } from "./AdtConnection"
22
import { Uri, FileSystemError, FileType, commands } from "vscode"
33
import { MetaFolder } from "../fs/MetaFolder"
44
import { AbapObjectNode, AbapNode, isAbapNode } from "../fs/AbapNode"
5-
import { AbapObject, TransportStatus, isAbapObject } from "../abap/AbapObject"
5+
import { AbapObject, TransportStatus, isAbapObject } from "./abap/AbapObject"
66
import { getRemoteList } from "../config"
77
import { selectTransport } from "./AdtTransports"
8-
import { AdtObjectActivator } from "./AdtObjectActivator"
8+
import { AdtObjectActivator } from "./operations/AdtObjectActivator"
99
import { pick } from "../functions"
10-
import { AdtObjectFinder } from "./AdtObjectFinder"
11-
import { AdtObjectCreator } from "./create/AdtObjectCreator"
12-
import { PACKAGE } from "./create/AdtObjectTypes"
10+
import { AdtObjectFinder } from "./operations/AdtObjectFinder"
11+
import { AdtObjectCreator } from "./operations/AdtObjectCreator"
12+
import { PACKAGE } from "./operations/AdtObjectTypes"
13+
import { LockManager } from "./operations/LockManager"
14+
import { AdtException } from "./AdtExceptions"
1315
export const ADTBASEURL = "/sap/bc/adt/repository/nodestructure"
1416

1517
/**
@@ -20,7 +22,7 @@ export const ADTBASEURL = "/sap/bc/adt/repository/nodestructure"
2022
const uriParts = (uri: Uri): string[] =>
2123
uri.path
2224
.split("/")
23-
.filter((v, idx, arr) => (idx > 0 && idx < arr.length - 1) || v) //ignore empty at begginning or end
25+
.filter((v, idx, arr) => (idx > 0 && idx < arr.length - 1) || v) //ignore empty at beginning or end
2426
/**
2527
* centralizes most API accesses
2628
* some will be delegated/provided from members or ABAP object nodes
@@ -30,7 +32,8 @@ export class AdtServer {
3032
private readonly activator: AdtObjectActivator
3133
readonly root: MetaFolder
3234
readonly objectFinder: AdtObjectFinder
33-
creator: AdtObjectCreator
35+
readonly creator: AdtObjectCreator
36+
readonly lockManager: LockManager
3437
private lastRefreshed?: string
3538

3639
/**
@@ -50,6 +53,7 @@ export class AdtServer {
5053
this.creator = new AdtObjectCreator(this)
5154
this.activator = new AdtObjectActivator(this.connection)
5255
this.objectFinder = new AdtObjectFinder(this.connection)
56+
this.lockManager = new LockManager(this.connection)
5357
this.connection
5458
.connect()
5559
.then(pick("body"))
@@ -109,32 +113,40 @@ export class AdtServer {
109113
if (!isAbapNode(file))
110114
throw FileSystemError.NoPermissions("Can only save source code")
111115

112-
await file.abapObject.lock(this.connection)
113-
if (file.abapObject.transport === TransportStatus.REQUIRED) {
116+
const obj = file.abapObject
117+
//check file is locked
118+
if (!this.lockManager.isLocked(obj))
119+
throw new AdtException(
120+
"lockNotFound",
121+
`Object not locked ${obj.type} ${obj.name}`
122+
)
123+
124+
if (obj.transport === TransportStatus.REQUIRED) {
114125
const transport = await selectTransport(
115-
file.abapObject.getContentsUri(this.connection),
126+
obj.getContentsUri(this.connection),
116127
"",
117128
this.connection
118129
)
119130
if (transport) file.abapObject.transport = transport
120131
}
121132

122-
await file.abapObject.setContents(this.connection, content)
133+
const lockId = this.lockManager.getLockId(obj)
134+
await obj.setContents(this.connection, content, lockId)
123135

124-
await file.abapObject.unlock(this.connection)
125136
await file.stat(this.connection)
137+
await this.lockManager.unlock(obj)
126138
//might have a race condition with user changing editor...
127139
commands.executeCommand("setContext", "abapfs:objectInactive", true)
128140
}
129141

130142
/**
131143
* converts vscode URI to ADT URI
132-
* @see findNodeHierarcy for more details
144+
* @see findNodeHierarchy for more details
133145
*
134146
* @param uri vscode URI
135147
*/
136148
findNode(uri: Uri): AbapNode {
137-
return this.findNodeHierarcy(uri)[0]
149+
return this.findNodeHierarchy(uri)[0]
138150
}
139151

140152
/**
@@ -145,7 +157,7 @@ export class AdtServer {
145157
* @abstract visual studio paths are hierarchic, adt ones aren't
146158
* so we need a way to translate the hierarchic ones to the original ones
147159
* this file is concerned with telling whether a path is a real ADT one or one from vscode
148-
* /sap/bc/adt/repository/nodestructure (with ampty query) is the root of both
160+
* /sap/bc/adt/repository/nodestructure (with empty query) is the root of both
149161
* also, several objects have namespaces.
150162
* Class /foo/bar of package /foo/baz in code will have a path like
151163
* /sap/bc/adt/repository/nodestructure/foo/baz/foo/bar
@@ -155,7 +167,7 @@ export class AdtServer {
155167
*
156168
* @param uri VSCode URI
157169
*/
158-
findNodeHierarcy(uri: Uri): AbapNode[] {
170+
findNodeHierarchy(uri: Uri): AbapNode[] {
159171
const parts = uriParts(uri)
160172
return parts.reduce(
161173
(current: AbapNode[], name) => {
@@ -185,7 +197,7 @@ export class AdtServer {
185197
for (const part of parts) {
186198
let next: AbapNode | undefined = node.getChild(part)
187199
if (!next && refreshable) {
188-
//refreshable will tipically be the current node or its first abap parent (usually a package)
200+
//refreshable will typically be the current node or its first abap parent (usually a package)
189201
await refreshable.refresh(this.connection)
190202
next = node.getChild(part)
191203
}
@@ -259,3 +271,10 @@ export const fromUri = (uri: Uri) => {
259271
if (uri.scheme === "adt") return getServer(uri.authority)
260272
throw FileSystemError.FileNotFound(uri)
261273
}
274+
export async function disconnect() {
275+
const promises: Promise<any>[] = []
276+
for (const server of servers) {
277+
promises.push(server[1].connection.dropSession())
278+
}
279+
await Promise.all(promises)
280+
}

src/adt/AdtTransports.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { JSON2AbapXML } from "../abap/JSONToAbapXml"
2-
import { parsetoPromise, getNode, recxml2js } from "./AdtParserBase"
1+
import { JSON2AbapXML } from "./abap/JSONToAbapXml"
2+
import { parseToPromise, getNode, recxml2js } from "./parsers/AdtParserBase"
33
import { mapWith, flat } from "../functions"
44
import { AdtConnection } from "./AdtConnection"
55
import { window, Uri } from "vscode"
@@ -84,7 +84,7 @@ export async function getTransportCandidates(
8484
})
8585
}
8686
)
87-
const rawdata = await parsetoPromise()(response.body)
87+
const rawdata = await parseToPromise()(response.body)
8888
const header = getNode(
8989
"asx:abap/asx:values/DATA",
9090
mapWith(recxml2js),

0 commit comments

Comments
 (0)