@@ -4,6 +4,9 @@ import { PathExt } from '@jupyterlab/coreutils';
4
4
5
5
import { ISignal , Signal } from '@lumino/signaling' ;
6
6
7
+ export const DRIVE_NAME = 'FileSystem' ;
8
+ const DRIVE_PREFIX = `${ DRIVE_NAME } :` ;
9
+
7
10
function arrayBufferToBase64 ( buffer : ArrayBuffer ) : string {
8
11
let binary = '' ;
9
12
const bytes = new Uint8Array ( buffer ) ;
@@ -41,7 +44,7 @@ export class FileSystemDrive implements Contents.IDrive {
41
44
}
42
45
43
46
get name ( ) : string {
44
- return 'FileSystem' ;
47
+ return DRIVE_NAME ;
45
48
}
46
49
47
50
get serverSettings ( ) : ServerConnection . ISettings {
@@ -64,6 +67,8 @@ export class FileSystemDrive implements Contents.IDrive {
64
67
path : string ,
65
68
options ?: Contents . IFetchOptions
66
69
) : Promise < Contents . IModel > {
70
+ path = this . cleanPath ( path ) ;
71
+
67
72
const root = this . _rootHandle ;
68
73
69
74
if ( ! root ) {
@@ -72,11 +77,11 @@ export class FileSystemDrive implements Contents.IDrive {
72
77
path : '' ,
73
78
created : new Date ( ) . toISOString ( ) ,
74
79
last_modified : new Date ( ) . toISOString ( ) ,
75
- format : 'json' ,
80
+ format : null ,
81
+ mimetype : '' ,
76
82
content : null ,
77
83
writable : true ,
78
- type : 'directory' ,
79
- mimetype : 'application/json'
84
+ type : 'directory'
80
85
} ;
81
86
}
82
87
@@ -109,11 +114,11 @@ export class FileSystemDrive implements Contents.IDrive {
109
114
path : PathExt . join ( parentPath , localPath , value . name ) ,
110
115
created : '' ,
111
116
last_modified : '' ,
112
- format : 'json' ,
117
+ format : null ,
118
+ mimetype : '' ,
113
119
content : null ,
114
120
writable : true ,
115
- type : 'directory' ,
116
- mimetype : 'application/json'
121
+ type : 'directory'
117
122
} ) ;
118
123
}
119
124
}
@@ -123,8 +128,8 @@ export class FileSystemDrive implements Contents.IDrive {
123
128
path : PathExt . join ( parentPath , localPath ) ,
124
129
last_modified : '' ,
125
130
created : '' ,
126
- format : 'json' ,
127
- mimetype : 'application/json ' ,
131
+ format : null ,
132
+ mimetype : '' ,
128
133
content,
129
134
size : undefined ,
130
135
writable : true ,
@@ -140,16 +145,20 @@ export class FileSystemDrive implements Contents.IDrive {
140
145
async newUntitled (
141
146
options ?: Contents . ICreateOptions
142
147
) : Promise < Contents . IModel > {
148
+ let parentPath = '' ;
149
+ if ( options && options . path ) {
150
+ parentPath = this . cleanPath ( options . path ) ;
151
+ }
152
+
143
153
const type = options ?. type || 'directory' ;
144
154
const path = PathExt . join (
145
- options ?. path || '' ,
155
+ parentPath ,
146
156
type === 'directory' ? 'Untitled Folder' : 'untitled'
147
157
) ;
148
158
const ext = options ?. ext || 'txt' ;
149
159
150
160
const parentHandle = await this . getParentHandle ( path ) ;
151
161
152
- const parentPath = PathExt . dirname ( path ) ;
153
162
let localPath = PathExt . basename ( path ) ;
154
163
const name = localPath ;
155
164
@@ -186,6 +195,8 @@ export class FileSystemDrive implements Contents.IDrive {
186
195
}
187
196
188
197
async delete ( path : string ) : Promise < void > {
198
+ path = this . cleanPath ( path ) ;
199
+
189
200
const parentHandle = await this . getParentHandle ( path ) ;
190
201
191
202
await parentHandle . removeEntry ( PathExt . basename ( path ) , { recursive : true } ) ;
@@ -197,17 +208,37 @@ export class FileSystemDrive implements Contents.IDrive {
197
208
} ) ;
198
209
}
199
210
200
- rename ( oldPath : string , newPath : string ) : Promise < Contents . IModel > {
201
- throw new Error ( 'Method not implemented.' ) ;
211
+ async rename ( oldPath : string , newPath : string ) : Promise < Contents . IModel > {
212
+ // Best effort, we are lacking proper APIs for renaming
213
+ oldPath = this . cleanPath ( oldPath ) ;
214
+ newPath = this . cleanPath ( newPath ) ;
215
+
216
+ await this . doCopy ( oldPath , newPath ) ;
217
+
218
+ await this . delete ( oldPath ) ;
219
+
220
+ return this . get ( newPath ) ;
202
221
}
203
222
204
223
async save (
205
224
path : string ,
206
225
options ?: Partial < Contents . IModel >
207
226
) : Promise < Contents . IModel > {
227
+ path = this . cleanPath ( path ) ;
228
+
208
229
const parentHandle = await this . getParentHandle ( path ) ;
209
230
210
- const handle = await parentHandle . getFileHandle ( PathExt . basename ( path ) ) ;
231
+ if ( options ?. type === 'directory' ) {
232
+ await parentHandle . getDirectoryHandle ( PathExt . basename ( path ) , {
233
+ create : true
234
+ } ) ;
235
+
236
+ return this . get ( path ) ;
237
+ }
238
+
239
+ const handle = await parentHandle . getFileHandle ( PathExt . basename ( path ) , {
240
+ create : true
241
+ } ) ;
211
242
const writable = await handle . createWritable ( { } ) ;
212
243
213
244
const format = options ?. format ;
@@ -223,8 +254,32 @@ export class FileSystemDrive implements Contents.IDrive {
223
254
return this . get ( path ) ;
224
255
}
225
256
226
- copy ( path : string , toLocalDir : string ) : Promise < Contents . IModel > {
227
- throw new Error ( 'Method not implemented.' ) ;
257
+ async copy ( path : string , toLocalDir : string ) : Promise < Contents . IModel > {
258
+ // Best effort, we are lacking proper APIs for copying
259
+ path = this . cleanPath ( path ) ;
260
+
261
+ const toCopy = await this . get ( path ) ;
262
+ const parentPath = PathExt . dirname ( path ) ;
263
+
264
+ let newName = toCopy . name ;
265
+ if ( parentPath === toLocalDir ) {
266
+ const ext = PathExt . extname ( toCopy . name ) ;
267
+
268
+ if ( ext ) {
269
+ newName = `${ newName . slice (
270
+ 0 ,
271
+ newName . length - ext . length
272
+ ) } (Copy)${ ext } `;
273
+ } else {
274
+ newName = `${ newName } (Copy)` ;
275
+ }
276
+ }
277
+
278
+ const newPath = PathExt . join ( toLocalDir , newName ) ;
279
+
280
+ await this . doCopy ( path , newPath ) ;
281
+
282
+ return this . get ( newPath ) ;
228
283
}
229
284
230
285
async createCheckpoint ( path : string ) : Promise < Contents . ICheckpointModel > {
@@ -261,7 +316,6 @@ export class FileSystemDrive implements Contents.IDrive {
261
316
}
262
317
263
318
let parentHandle = root ;
264
- // If saving a file that is not under root, we need the right directory handle
265
319
for ( const subPath of path . split ( '/' ) . slice ( 0 , - 1 ) ) {
266
320
parentHandle = await parentHandle . getDirectoryHandle ( subPath ) ;
267
321
}
@@ -336,6 +390,55 @@ export class FileSystemDrive implements Contents.IDrive {
336
390
} ;
337
391
}
338
392
393
+ private async doCopy ( oldPath : string , newPath : string ) : Promise < void > {
394
+ // Best effort, we are lacking proper APIs for copying
395
+ const oldParentHandle = await this . getParentHandle ( oldPath ) ;
396
+
397
+ const oldLocalPath = PathExt . basename ( oldPath ) ;
398
+
399
+ let oldHandle : FileSystemDirectoryHandle | FileSystemFileHandle ;
400
+
401
+ if ( oldLocalPath ) {
402
+ oldHandle = await this . getHandle ( oldParentHandle , oldLocalPath ) ;
403
+ } else {
404
+ oldHandle = oldParentHandle ;
405
+ }
406
+
407
+ const newParentHandle = await this . getParentHandle ( newPath ) ;
408
+
409
+ const newLocalPath = PathExt . basename ( newPath ) ;
410
+
411
+ if ( oldHandle . kind === 'directory' ) {
412
+ // If it's a directory, create directory, then doCopy for the directory content
413
+ await newParentHandle . getDirectoryHandle ( newLocalPath , { create : true } ) ;
414
+
415
+ for await ( const content of oldHandle . values ( ) ) {
416
+ await this . doCopy (
417
+ PathExt . join ( oldPath , content . name ) ,
418
+ PathExt . join ( newPath , content . name )
419
+ ) ;
420
+ }
421
+ } else {
422
+ // If it's a file, copy the file content
423
+ const newFileHandle = await newParentHandle . getFileHandle ( newLocalPath , {
424
+ create : true
425
+ } ) ;
426
+
427
+ const writable = await newFileHandle . createWritable ( { } ) ;
428
+ const file = await oldHandle . getFile ( ) ;
429
+ const data = await file . arrayBuffer ( ) ;
430
+ writable . write ( data ) ;
431
+ await writable . close ( ) ;
432
+ }
433
+ }
434
+
435
+ private cleanPath ( path : string ) : string {
436
+ if ( path . includes ( DRIVE_PREFIX ) ) {
437
+ return path . replace ( DRIVE_PREFIX , '' ) ;
438
+ }
439
+ return path ;
440
+ }
441
+
339
442
private _isDisposed = false ;
340
443
private _fileChanged = new Signal < Contents . IDrive , Contents . IChangedArgs > (
341
444
this
0 commit comments