Skip to content

Commit ea27266

Browse files
committed
add logic to copy object in backend and frontend content manager
1 parent ff3426e commit ea27266

File tree

4 files changed

+196
-72
lines changed

4 files changed

+196
-72
lines changed

jupyter_drives/handlers.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@ async def patch(self, drive: str = "", path: str = ""):
8787
@tornado.web.authenticated
8888
async def put(self, drive: str = "", path: str = ""):
8989
body = self.get_json_body()
90-
result = await self._manager.save_file(drive, path, **body)
90+
if 'content' in body:
91+
result = await self._manager.save_file(drive, path, **body)
92+
elif 'to_path' in body:
93+
result = await self._manager.copy_file(drive, path, **body)
9194
self.finish(result)
9295

9396
@tornado.web.authenticated

jupyter_drives/manager.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,38 @@ async def check_file(self, drive_name, path):
381381
}
382382
return response
383383

384+
async def copy_file(self, drive_name, path, to_path):
385+
"""Save file with new content.
386+
387+
Args:
388+
drive_name: name of drive where file exists
389+
path: path where original content exists
390+
to_path: path where object should be copied
391+
"""
392+
data = {}
393+
try:
394+
# eliminate leading and trailing backslashes
395+
path = path.strip('/')
396+
397+
await obs.copy_async(self._content_managers[drive_name], path, to_path)
398+
metadata = await obs.head_async(self._content_managers[drive_name], to_path)
399+
400+
data = {
401+
"path": to_path,
402+
"last_modified": metadata["last_modified"].isoformat(),
403+
"size": metadata["size"]
404+
}
405+
except Exception as e:
406+
raise tornado.web.HTTPError(
407+
status_code= httpx.codes.BAD_REQUEST,
408+
reason=f"The following error occured when copying the: {e}",
409+
)
410+
411+
response = {
412+
"data": data
413+
}
414+
return response
415+
384416
async def _call_provider(
385417
self,
386418
url: str,

src/contents.ts

Lines changed: 65 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import {
1212
checkObject,
1313
deleteObjects,
1414
countObjectNameAppearances,
15-
renameObjects
15+
renameObjects,
16+
copyObjects
1617
} from './requests';
1718

1819
let data: Contents.IModel = {
@@ -573,88 +574,83 @@ export class Drive implements Contents.IDrive {
573574
}
574575

575576
/**
576-
* Copy a file into a given directory.
577+
* Helping function for copying an object.
577578
*
578-
* @param path - The original file path.
579+
* @param copiedItemPath - The original file path.
579580
*
580-
* @param toDir - The destination directory path.
581+
* @param toPath - The path where item will be copied.
581582
*
582-
* @returns A promise which resolves with the new contents model when the
583+
* @param driveName - The name of the drive where content is moved.
584+
*
585+
* @returns A promise which resolves with the new name when the
583586
* file is copied.
584587
*/
588+
async incrementCopyName(
589+
copiedItemPath: string,
590+
toPath: string,
591+
driveName: string
592+
) {
593+
// extracting original file name
594+
const originalFileName = PathExt.basename(copiedItemPath);
585595

586-
incrementCopyName(contents: Contents.IModel, copiedItemPath: string): string {
587-
const content: Array<Contents.IModel> = contents.content;
588-
let name: string = '';
589-
let countText = 0;
590-
let countDir = 0;
591-
let countNotebook = 0;
592-
let ext = undefined;
593-
const list1 = copiedItemPath.split('/');
594-
const copiedItemName = list1[list1.length - 1];
596+
// constructing new file name and path with -Copy string
597+
const newFileName =
598+
PathExt.extname(originalFileName) === ''
599+
? originalFileName + '-Copy'
600+
: originalFileName.split('.')[0] +
601+
'-Copy.' +
602+
originalFileName.split('.')[1];
595603

596-
const list2 = copiedItemName.split('.');
597-
let rootName = list2[0];
604+
const newFilePath = PathExt.join(toPath, newFileName);
605+
// copiedItemPath.substring(0, copiedItemPath.lastIndexOf('/') + 1) + newFileName;
598606

599-
content.forEach(item => {
600-
if (item.name.includes(rootName) && item.name.includes('.txt')) {
601-
ext = '.txt';
602-
if (rootName.includes('-Copy')) {
603-
const list3 = rootName.split('-Copy');
604-
countText = parseInt(list3[1]) + 1;
605-
rootName = list3[0];
606-
} else {
607-
countText = countText + 1;
608-
}
609-
}
610-
if (item.name.includes(rootName) && item.name.includes('.ipynb')) {
611-
ext = '.ipynb';
612-
if (rootName.includes('-Copy')) {
613-
const list3 = rootName.split('-Copy');
614-
countNotebook = parseInt(list3[1]) + 1;
615-
rootName = list3[0];
616-
} else {
617-
countNotebook = countNotebook + 1;
618-
}
619-
} else if (item.name.includes(rootName)) {
620-
if (rootName.includes('-Copy')) {
621-
const list3 = rootName.split('-Copy');
622-
countDir = parseInt(list3[1]) + 1;
623-
rootName = list3[0];
624-
} else {
625-
countDir = countDir + 1;
626-
}
627-
}
628-
});
607+
// getting incremented name of Copy in case of duplicates
608+
const incrementedName = await this.incrementName(newFilePath, driveName);
629609

630-
if (ext === '.txt') {
631-
name = rootName + '-Copy' + countText + ext;
632-
}
633-
if (ext === 'ipynb') {
634-
name = rootName + '-Copy' + countText + ext;
635-
} else if (ext === undefined) {
636-
name = rootName + '-Copy' + countDir;
637-
}
638-
639-
return name;
610+
return incrementedName;
640611
}
612+
613+
/**
614+
* Copy a file into a given directory.
615+
*
616+
* @param path - The original file path.
617+
*
618+
* @param toDir - The destination directory path.
619+
*
620+
* @returns A promise which resolves with the new contents model when the
621+
* file is copied.
622+
*/
641623
async copy(
642-
fromFile: string,
624+
path: string,
643625
toDir: string,
644626
options: Contents.ICreateOptions = {}
645627
): Promise<Contents.IModel> {
646-
/*const settings = this.serverSettings;
647-
const url = this._getUrl(toDir);
648-
const init = {
649-
method: 'POST',
650-
body: JSON.stringify({ copy_from: fromFile })
651-
};
652-
const response = await ServerConnection.makeRequest(url, init, settings);
653-
if (response.status !== 201) {
654-
const err = await ServerConnection.ResponseError.create(response);
655-
throw err;
656-
}
657-
const data = await response.json();*/
628+
// extract current drive name
629+
const currentDrive = this._drivesList.filter(
630+
x =>
631+
x.name ===
632+
(path.indexOf('/') !== -1 ? path.substring(0, path.indexOf('/')) : path)
633+
)[0];
634+
635+
// eliminate drive name from path
636+
const relativePath =
637+
path.indexOf('/') !== -1 ? path.substring(path.indexOf('/') + 1) : '';
638+
const toRelativePath =
639+
toDir.indexOf('/') !== -1 ? toDir.substring(toDir.indexOf('/') + 1) : '';
640+
641+
// construct new file or directory name for the copy
642+
const newFileName = await this.incrementCopyName(
643+
relativePath,
644+
toRelativePath,
645+
currentDrive.name
646+
);
647+
648+
data = await copyObjects(currentDrive.name, {
649+
path: relativePath,
650+
toPath: toRelativePath,
651+
newFileName: newFileName,
652+
registeredFileTypes: this._registeredFileTypes
653+
});
658654

659655
this._fileChanged.emit({
660656
type: 'new',

src/requests.ts

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,78 @@ export async function renameObjects(
335335
return data;
336336
}
337337

338+
/**
339+
* Copy an object.
340+
*
341+
* @param driveName
342+
* @param options.path The path of object.
343+
* @param options.toPath The path where object should be copied.
344+
* @param options.newFileName The name of the item to be copied.
345+
* @param options.registeredFileTypes The list containing all registered file types.
346+
*
347+
* @returns A promise which resolves with the contents model.
348+
*/
349+
export async function copyObjects(
350+
driveName: string,
351+
options: {
352+
path: string;
353+
toPath: string;
354+
newFileName: string;
355+
registeredFileTypes: IRegisteredFileTypes;
356+
}
357+
) {
358+
const formattedNewPath = PathExt.join(options.toPath, options.newFileName);
359+
360+
const [fileType, fileMimeType, fileFormat] = getFileType(
361+
PathExt.extname(PathExt.basename(options.newFileName)),
362+
options.registeredFileTypes
363+
);
364+
365+
// get list of contents with given prefix (path)
366+
const response = await requestAPI<any>(
367+
'drives/' + driveName + '/' + options.path,
368+
'GET'
369+
);
370+
371+
// copying contents of a directory
372+
if (response.data.length !== undefined && response.data.length !== 0) {
373+
await Promise.all(
374+
response.data.map(async (c: any) => {
375+
const remainingFilePath = c.path.substring(options.path.length);
376+
Private.copySingleObject(
377+
driveName,
378+
PathExt.join(options.path, remainingFilePath),
379+
PathExt.join(formattedNewPath, remainingFilePath)
380+
);
381+
})
382+
);
383+
}
384+
// always copy the main object (file or directory)
385+
try {
386+
const copiedObject = await Private.copySingleObject(
387+
driveName,
388+
options.path,
389+
formattedNewPath
390+
);
391+
data = {
392+
name: options.newFileName,
393+
path: PathExt.join(driveName, formattedNewPath),
394+
last_modified: copiedObject.data.last_modified,
395+
created: '',
396+
content: PathExt.extname(options.newFileName) !== '' ? null : [], // TODO: add dir check
397+
format: fileFormat as Contents.FileFormat,
398+
mimetype: fileMimeType,
399+
size: copiedObject.data.size,
400+
writable: true,
401+
type: fileType
402+
};
403+
} catch (error) {
404+
// copied failed if directory didn't exist and was only part of a path
405+
}
406+
407+
return data;
408+
}
409+
338410
/**
339411
* Check existance of an object.
340412
*
@@ -380,7 +452,7 @@ export const countObjectNameAppearances = async (
380452

381453
if (response.data && response.data.length !== 0) {
382454
response.data.forEach((c: any) => {
383-
const fileName = c.row.replace(path ? path + '/' : '', '').split('/')[0];
455+
const fileName = c.path.replace(path ? path + '/' : '', '').split('/')[0];
384456
if (
385457
fileName.substring(0, originalName.length + 1).includes(originalName)
386458
) {
@@ -412,7 +484,7 @@ namespace Private {
412484
* a directory, in the case of deleting the directory.
413485
*
414486
* @param driveName
415-
* @param objectPath complete path of object to delete
487+
* @param objectPath complete path of object to rename
416488
*/
417489
export async function renameSingleObject(
418490
driveName: string,
@@ -427,4 +499,25 @@ namespace Private {
427499
}
428500
);
429501
}
502+
503+
/**
504+
* Helping function for copying files inside
505+
* a directory, in the case of deleting the directory.
506+
*
507+
* @param driveName
508+
* @param objectPath complete path of object to copy
509+
*/
510+
export async function copySingleObject(
511+
driveName: string,
512+
objectPath: string,
513+
newObjectPath: string
514+
) {
515+
return await requestAPI<any>(
516+
'drives/' + driveName + '/' + objectPath,
517+
'PUT',
518+
{
519+
to_path: newObjectPath
520+
}
521+
);
522+
}
430523
}

0 commit comments

Comments
 (0)