Skip to content

Commit bc5c206

Browse files
authored
Merge pull request #34 from martinRenou/rename_copy
Implement rename and copy
2 parents 06f3e91 + 9daeb16 commit bc5c206

File tree

5 files changed

+122
-92
lines changed

5 files changed

+122
-92
lines changed

MANIFEST.in

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ include docs/environment.yml
1717

1818
# Javascript files
1919
graft src
20-
graft schema
2120
graft style
2221
prune **/node_modules
2322
prune lib

package.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818
},
1919
"files": [
2020
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
21-
"style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
22-
"schema/*.json"
21+
"style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}"
2322
],
2423
"main": "lib/index.js",
2524
"types": "lib/index.d.ts",
@@ -90,8 +89,7 @@
9089
},
9190
"jupyterlab": {
9291
"extension": true,
93-
"outputDir": "jupyterlab_filesystem_access/labextension",
94-
"schemaDir": "schema"
92+
"outputDir": "jupyterlab_filesystem_access/labextension"
9593
},
9694
"jupyter-releaser": {
9795
"hooks": {

schema/plugin.json

Lines changed: 0 additions & 67 deletions
This file was deleted.

src/drive.ts

Lines changed: 120 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { PathExt } from '@jupyterlab/coreutils';
44

55
import { ISignal, Signal } from '@lumino/signaling';
66

7+
export const DRIVE_NAME = 'FileSystem';
8+
const DRIVE_PREFIX = `${DRIVE_NAME}:`;
9+
710
function arrayBufferToBase64(buffer: ArrayBuffer): string {
811
let binary = '';
912
const bytes = new Uint8Array(buffer);
@@ -41,7 +44,7 @@ export class FileSystemDrive implements Contents.IDrive {
4144
}
4245

4346
get name(): string {
44-
return 'FileSystem';
47+
return DRIVE_NAME;
4548
}
4649

4750
get serverSettings(): ServerConnection.ISettings {
@@ -64,6 +67,8 @@ export class FileSystemDrive implements Contents.IDrive {
6467
path: string,
6568
options?: Contents.IFetchOptions
6669
): Promise<Contents.IModel> {
70+
path = this.cleanPath(path);
71+
6772
const root = this._rootHandle;
6873

6974
if (!root) {
@@ -72,11 +77,11 @@ export class FileSystemDrive implements Contents.IDrive {
7277
path: '',
7378
created: new Date().toISOString(),
7479
last_modified: new Date().toISOString(),
75-
format: 'json',
80+
format: null,
81+
mimetype: '',
7682
content: null,
7783
writable: true,
78-
type: 'directory',
79-
mimetype: 'application/json'
84+
type: 'directory'
8085
};
8186
}
8287

@@ -109,11 +114,11 @@ export class FileSystemDrive implements Contents.IDrive {
109114
path: PathExt.join(parentPath, localPath, value.name),
110115
created: '',
111116
last_modified: '',
112-
format: 'json',
117+
format: null,
118+
mimetype: '',
113119
content: null,
114120
writable: true,
115-
type: 'directory',
116-
mimetype: 'application/json'
121+
type: 'directory'
117122
});
118123
}
119124
}
@@ -123,8 +128,8 @@ export class FileSystemDrive implements Contents.IDrive {
123128
path: PathExt.join(parentPath, localPath),
124129
last_modified: '',
125130
created: '',
126-
format: 'json',
127-
mimetype: 'application/json',
131+
format: null,
132+
mimetype: '',
128133
content,
129134
size: undefined,
130135
writable: true,
@@ -140,16 +145,20 @@ export class FileSystemDrive implements Contents.IDrive {
140145
async newUntitled(
141146
options?: Contents.ICreateOptions
142147
): Promise<Contents.IModel> {
148+
let parentPath = '';
149+
if (options && options.path) {
150+
parentPath = this.cleanPath(options.path);
151+
}
152+
143153
const type = options?.type || 'directory';
144154
const path = PathExt.join(
145-
options?.path || '',
155+
parentPath,
146156
type === 'directory' ? 'Untitled Folder' : 'untitled'
147157
);
148158
const ext = options?.ext || 'txt';
149159

150160
const parentHandle = await this.getParentHandle(path);
151161

152-
const parentPath = PathExt.dirname(path);
153162
let localPath = PathExt.basename(path);
154163
const name = localPath;
155164

@@ -186,6 +195,8 @@ export class FileSystemDrive implements Contents.IDrive {
186195
}
187196

188197
async delete(path: string): Promise<void> {
198+
path = this.cleanPath(path);
199+
189200
const parentHandle = await this.getParentHandle(path);
190201

191202
await parentHandle.removeEntry(PathExt.basename(path), { recursive: true });
@@ -197,17 +208,37 @@ export class FileSystemDrive implements Contents.IDrive {
197208
});
198209
}
199210

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);
202221
}
203222

204223
async save(
205224
path: string,
206225
options?: Partial<Contents.IModel>
207226
): Promise<Contents.IModel> {
227+
path = this.cleanPath(path);
228+
208229
const parentHandle = await this.getParentHandle(path);
209230

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+
});
211242
const writable = await handle.createWritable({});
212243

213244
const format = options?.format;
@@ -223,8 +254,32 @@ export class FileSystemDrive implements Contents.IDrive {
223254
return this.get(path);
224255
}
225256

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);
228283
}
229284

230285
async createCheckpoint(path: string): Promise<Contents.ICheckpointModel> {
@@ -261,7 +316,6 @@ export class FileSystemDrive implements Contents.IDrive {
261316
}
262317

263318
let parentHandle = root;
264-
// If saving a file that is not under root, we need the right directory handle
265319
for (const subPath of path.split('/').slice(0, -1)) {
266320
parentHandle = await parentHandle.getDirectoryHandle(subPath);
267321
}
@@ -336,6 +390,55 @@ export class FileSystemDrive implements Contents.IDrive {
336390
};
337391
}
338392

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+
339442
private _isDisposed = false;
340443
private _fileChanged = new Signal<Contents.IDrive, Contents.IChangedArgs>(
341444
this

src/index.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,6 @@ const plugin: JupyterFrontEndPlugin<void> = {
5757
widget.title.caption = trans.__('Local File System');
5858
widget.title.icon = listIcon;
5959

60-
// Adding a data attribute
61-
widget.node.setAttribute('data-is-filesystem-access', '');
62-
6360
const openDirectoryButton = new ToolbarButton({
6461
icon: folderIcon,
6562
onClick: async () => {

0 commit comments

Comments
 (0)