11import { FileType , Uri } from "vscode" ;
22
3+ import { AxiosResponse } from "axios" ;
4+
35import { getSession } from ".." ;
46import {
57 FOLDER_TYPES ,
@@ -15,30 +17,34 @@ import {
1517} from "../../components/ContentNavigator/types" ;
1618import {
1719 createStaticFolder ,
18- isItemInRecycleBin ,
1920 isReference ,
2021} from "../../components/ContentNavigator/utils" ;
2122import { appendSessionLogFn } from "../../components/logViewer" ;
2223import { FileProperties , FileSystemApi } from "./api/compute" ;
2324import { getApiConfig } from "./common" ;
2425import {
2526 getLink ,
26- getPermission ,
2727 getResourceId ,
2828 getResourceIdFromItem ,
2929 getSasServerUri ,
3030 getTypeName ,
3131 resourceType ,
3232} from "./util" ;
3333
34+ const SAS_SERVER_HOME_DIRECTORY = "SAS_SERVER_HOME_DIRECTORY" ;
35+
3436class RestSASServerAdapter implements ContentAdapter {
3537 protected baseUrl : string ;
3638 protected fileSystemApi : ReturnType < typeof FileSystemApi > ;
3739 protected sessionId : string ;
3840 private rootFolders : RootFolderMap ;
41+ private fileMetadataMap : {
42+ [ id : string ] : { etag : string ; lastModified ?: string ; contentType ?: string } ;
43+ } ;
3944
4045 public constructor ( ) {
4146 this . rootFolders = { } ;
47+ this . fileMetadataMap = { } ;
4248 }
4349
4450 public async connect ( ) : Promise < void > {
@@ -82,16 +88,11 @@ class RestSASServerAdapter implements ContentAdapter {
8288 ) : Promise < ContentItem | undefined > {
8389 const response = await this . fileSystemApi . createFileOrDirectory ( {
8490 sessionId : this . sessionId ,
85- fileOrDirectoryPath : parentItem . uri . replace (
86- `/compute/sessions/${ this . sessionId } /files/` ,
87- "" ,
88- ) ,
91+ fileOrDirectoryPath : this . trimComputePrefix ( parentItem . uri ) ,
8992 fileProperties : { name : folderName , isDirectory : true } ,
9093 } ) ;
9194
92- return this . enrichWithDataProviderProperties (
93- this . filePropertiesToContentItem ( response . data ) ,
94- ) ;
95+ return this . filePropertiesToContentItem ( response . data ) ;
9596 }
9697
9798 public async createNewItem (
@@ -101,18 +102,15 @@ class RestSASServerAdapter implements ContentAdapter {
101102 ) : Promise < ContentItem | undefined > {
102103 const response = await this . fileSystemApi . createFileOrDirectory ( {
103104 sessionId : this . sessionId ,
104- fileOrDirectoryPath : parentItem . uri . replace (
105- `/compute/sessions/${ this . sessionId } /files/` ,
106- "" ,
107- ) ,
105+ fileOrDirectoryPath : this . trimComputePrefix ( parentItem . uri ) ,
108106 fileProperties : { name : fileName , isDirectory : false } ,
109107 } ) ;
110108
111109 if ( buffer ) {
112110 const etag = response . headers . etag ;
113- const filePath = getLink ( response . data . links , "GET" , "self" ) . uri . replace (
114- `/compute/sessions/ ${ this . sessionId } /files/` ,
115- "" ,
111+ // TODO (sas-server) This could be combined with update content most likely.
112+ const filePath = this . trimComputePrefix (
113+ getLink ( response . data . links , "GET" , "self" ) . uri ,
116114 ) ;
117115 await this . fileSystemApi . updateFileContentOnSystem ( {
118116 sessionId : this . sessionId ,
@@ -122,9 +120,7 @@ class RestSASServerAdapter implements ContentAdapter {
122120 } ) ;
123121 }
124122
125- return this . enrichWithDataProviderProperties (
126- this . filePropertiesToContentItem ( response . data ) ,
127- ) ;
123+ return this . filePropertiesToContentItem ( response . data ) ;
128124 }
129125
130126 public async deleteItem ( item : ContentItem ) : Promise < boolean > {
@@ -134,60 +130,42 @@ class RestSASServerAdapter implements ContentAdapter {
134130 public async getChildItems ( parentItem : ContentItem ) : Promise < ContentItem [ ] > {
135131 // If the user is fetching child items of the root folder, give them the
136132 // "home" directory
137- const id = "SAS_SERVER_HOME_DIRECTORY" ;
138133 if ( parentItem . id === SERVER_FOLDER_ID ) {
139134 return [
140- this . enrichWithDataProviderProperties ( {
141- ... createStaticFolder (
142- id ,
135+ this . filePropertiesToContentItem (
136+ createStaticFolder (
137+ SAS_SERVER_HOME_DIRECTORY ,
143138 "Home" ,
144139 "userRoot" ,
145140 `/compute/sessions/${ this . sessionId } /files/~fs~/members` ,
146141 "getDirectoryMembers" ,
147142 ) ,
148- creationTimeStamp : 0 ,
149- modifiedTimeStamp : 0 ,
150- permission : undefined ,
151- } ) ,
143+ ) ,
152144 ] ;
153145 }
154146
155147 const { data } = await this . fileSystemApi . getDirectoryMembers ( {
156148 sessionId : this . sessionId ,
157- directoryPath : parseMemberUri (
149+ directoryPath : this . trimComputePrefix (
158150 getLink ( parentItem . links , "GET" , "getDirectoryMembers" ) . uri ,
159- this . sessionId ,
160- ) ,
151+ ) . replace ( "/members" , "" ) ,
161152 } ) ;
162153
163154 // TODO (sas-server) We need to paginate and sort results
164155 return data . items . map ( ( childItem : FileProperties , index ) => ( {
165156 ...this . filePropertiesToContentItem ( childItem ) ,
166157 uid : `${ parentItem . uid } /${ index } ` ,
167- ...this . enrichWithDataProviderProperties (
168- this . filePropertiesToContentItem ( childItem ) ,
169- ) ,
170158 } ) ) ;
171-
172- function parseMemberUri ( uri : string , sessionId : string ) : string {
173- return uri
174- . replace ( `/compute/sessions/${ sessionId } /files/` , "" )
175- . replace ( "/members" , "" ) ;
176- }
177159 }
178160
179161 public async getContentOfItem ( item : ContentItem ) : Promise < string > {
180162 throw new Error ( "getContentOfItem" ) ;
181163 }
182164
183165 public async getContentOfUri ( uri : Uri ) : Promise < string > {
184- // TODO (sas-server) We're using this a bunch. Make things more better-er
185- const path = getResourceId ( uri ) . replace (
186- `/compute/sessions/${ this . sessionId } /files/` ,
187- "" ,
188- ) ;
166+ const path = this . trimComputePrefix ( getResourceId ( uri ) ) ;
189167
190- const { data } = await this . fileSystemApi . getFileContentFromSystem (
168+ const response = await this . fileSystemApi . getFileContentFromSystem (
191169 {
192170 sessionId : this . sessionId ,
193171 filePath : path ,
@@ -197,11 +175,13 @@ class RestSASServerAdapter implements ContentAdapter {
197175 } ,
198176 ) ;
199177
178+ this . updateFileMetadata ( path , response ) ;
179+
200180 // Disabling typescript checks on this line as this function is typed
201181 // to return AxiosResponse<void,any>. However, it appears to return
202182 // AxiosResponse<string,>.
203183 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
204- return data as unknown as string ;
184+ return response . data as unknown as string ;
205185 }
206186
207187 public async getFolderPathForItem ( item : ContentItem ) : Promise < string > {
@@ -214,18 +194,13 @@ class RestSASServerAdapter implements ContentAdapter {
214194
215195 public async getItemOfUri ( uri : Uri ) : Promise < ContentItem > {
216196 const resourceId = getResourceId ( uri ) ;
197+
217198 const { data } = await this . fileSystemApi . getFileorDirectoryProperties ( {
218199 sessionId : this . sessionId ,
219- // TODO (sas-server) cleanup/reuse this
220- fileOrDirectoryPath : resourceId . replace (
221- `/compute/sessions/${ this . sessionId } /files/` ,
222- "" ,
223- ) ,
200+ fileOrDirectoryPath : this . trimComputePrefix ( resourceId ) ,
224201 } ) ;
225202
226- return this . enrichWithDataProviderProperties (
227- this . filePropertiesToContentItem ( data ) ,
228- ) ;
203+ return this . filePropertiesToContentItem ( data ) ;
229204 }
230205
231206 public async getParentOfItem (
@@ -251,7 +226,7 @@ class RestSASServerAdapter implements ContentAdapter {
251226 this . rootFolders [ delegateFolderName ] = {
252227 ...result . data ,
253228 uid : `${ index } ` ,
254- ...this . enrichWithDataProviderProperties ( result . data ) ,
229+ ...this . filePropertiesToContentItem ( result . data ) ,
255230 } ;
256231 }
257232
@@ -293,56 +268,50 @@ class RestSASServerAdapter implements ContentAdapter {
293268 item : ContentItem ,
294269 newName : string ,
295270 ) : Promise < ContentItem | undefined > {
296- throw new Error ( "Method not implemented." ) ;
271+ const filePath = this . trimComputePrefix ( item . uri ) ;
272+
273+ const isDirectory = item . fileStat ?. type === FileType . Directory ;
274+ const parsedFilePath = filePath . split ( "~fs~" ) ;
275+ parsedFilePath . pop ( ) ;
276+ const path = parsedFilePath . join ( "/" ) ;
277+
278+ const response = await this . fileSystemApi . updateFileOrDirectoryOnSystem ( {
279+ sessionId : this . sessionId ,
280+ fileOrDirectoryPath : filePath ,
281+ ifMatch : "" ,
282+ fileProperties : { name : newName , path, isDirectory } ,
283+ } ) ;
284+
285+ this . updateFileMetadata ( filePath , response ) ;
286+
287+ return this . filePropertiesToContentItem ( response . data ) ;
297288 }
298289
299290 public async restoreItem ( item : ContentItem ) : Promise < boolean > {
300291 throw new Error ( "Method not implemented." ) ;
301292 }
302293
303294 public async updateContentOfItem ( uri : Uri , content : string ) : Promise < void > {
304- throw new Error ( "Method not implemented." ) ;
305- }
295+ const filePath = this . trimComputePrefix ( getResourceId ( uri ) ) ;
296+ const { etag } = this . getFileInfo ( filePath ) ;
306297
307- private enrichWithDataProviderProperties (
308- item : ContentItem ,
309- flags ?: ContentItem [ "flags" ] ,
310- ) : ContentItem {
311- item . flags = flags ;
312- return {
313- ...item ,
314- permission : getPermission ( item ) ,
315- contextValue : resourceType ( item ) ,
316- fileStat : {
317- ctime : item . creationTimeStamp ,
318- mtime : item . modifiedTimeStamp ,
319- size : 0 ,
320- type : getIsContainer ( item ) ? FileType . Directory : FileType . File ,
321- } ,
322- isReference : isReference ( item ) ,
323- resourceId : getResourceIdFromItem ( item ) ,
324- vscUri : getSasServerUri ( item , flags ?. isInRecycleBin || false ) ,
325- typeName : getTypeName ( item ) ,
326- } ;
298+ const response = await this . fileSystemApi . updateFileContentOnSystem ( {
299+ sessionId : this . sessionId ,
300+ filePath,
301+ // updateFileContentOnSystem requires body to be a File type. However, the
302+ // underlying code is expecting a string. This forces compute to accept
303+ // a string.
304+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
305+ body : content as unknown as File ,
306+ ifMatch : etag ,
307+ } ) ;
327308
328- function getIsContainer ( item : ContentItem ) : boolean {
329- if ( item . fileStat ?. type === FileType . Directory ) {
330- return true ;
331- }
332-
333- const typeName = getTypeName ( item ) ;
334- if ( isItemInRecycleBin ( item ) && isReference ( item ) ) {
335- return false ;
336- }
337- if ( FOLDER_TYPES . indexOf ( typeName ) >= 0 ) {
338- return true ;
339- }
340- return false ;
341- }
309+ this . updateFileMetadata ( filePath , response ) ;
342310 }
343311
344312 private filePropertiesToContentItem (
345313 fileProperties : FileProperties ,
314+ flags ?: ContentItem [ "flags" ] ,
346315 ) : ContentItem {
347316 const links = fileProperties . links . map ( ( link ) => ( {
348317 method : link . method ,
@@ -353,25 +322,65 @@ class RestSASServerAdapter implements ContentAdapter {
353322 } ) ) ;
354323
355324 const id = getLink ( links , "GET" , "self" ) . uri ;
356- return {
325+ const isRootFolder = [ SERVER_FOLDER_ID , SAS_SERVER_HOME_DIRECTORY ] . includes (
326+ id ,
327+ ) ;
328+ const item = {
357329 id,
358330 uri : id ,
359331 name : fileProperties . name ,
360332 creationTimeStamp : 0 ,
361333 modifiedTimeStamp : new Date ( fileProperties . modifiedTimeStamp ) . getTime ( ) ,
362334 links,
363- // These will be overwritten
364335 permission : {
365- write : false ,
366- delete : false ,
367- addMember : false ,
336+ write : ! isRootFolder && ! fileProperties . readOnly ,
337+ delete : ! isRootFolder && ! fileProperties . readOnly ,
338+ addMember :
339+ ! ! getLink ( links , "POST" , "makeDirectory" ) ||
340+ ! ! getLink ( links , "POST" , "createFile" ) ,
368341 } ,
342+ flags,
343+ } ;
344+
345+ const typeName = getTypeName ( item ) ;
346+
347+ return {
348+ ...item ,
349+ contextValue : resourceType ( item ) ,
369350 fileStat : {
370- type : fileProperties . isDirectory ? FileType . Directory : FileType . File ,
371- ctime : 0 ,
372- mtime : 0 ,
351+ ctime : item . creationTimeStamp ,
352+ mtime : item . modifiedTimeStamp ,
373353 size : 0 ,
354+ type :
355+ fileProperties . isDirectory ||
356+ FOLDER_TYPES . indexOf ( typeName ) >= 0 ||
357+ isRootFolder
358+ ? FileType . Directory
359+ : FileType . File ,
374360 } ,
361+ isReference : isReference ( item ) ,
362+ resourceId : getResourceIdFromItem ( item ) ,
363+ vscUri : getSasServerUri ( item , flags ?. isInRecycleBin || false ) ,
364+ typeName : getTypeName ( item ) ,
365+ } ;
366+ }
367+
368+ private trimComputePrefix ( uri : string ) : string {
369+ return uri . replace ( `/compute/sessions/${ this . sessionId } /files/` , "" ) ;
370+ }
371+
372+ private updateFileMetadata ( id : string , { headers } : AxiosResponse ) {
373+ this . fileMetadataMap [ id ] = {
374+ etag : headers . etag ,
375+ } ;
376+ }
377+
378+ private getFileInfo ( resourceId : string ) {
379+ if ( resourceId in this . fileMetadataMap ) {
380+ return this . fileMetadataMap [ resourceId ] ;
381+ }
382+ return {
383+ etag : "" ,
375384 } ;
376385 }
377386}
0 commit comments