Skip to content

Commit 4835df8

Browse files
committed
restructure rename function to deal with directories
1 parent ca91463 commit 4835df8

File tree

2 files changed

+103
-68
lines changed

2 files changed

+103
-68
lines changed

jupyter_drives/manager.py

Lines changed: 87 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import json
33
import logging
44
from typing import Dict, List, Optional, Tuple, Union, Any
5+
from datetime import datetime
56

67
import os
78
import tornado
@@ -274,7 +275,6 @@ async def new_file(self, drive_name, path, is_dir):
274275
try:
275276
# eliminate leading and trailing backslashes
276277
path = path.strip('/')
277-
print('isDir: ', is_dir)
278278

279279
if is_dir == False or self._config.provider != 's3':
280280
# TO DO: switch to mode "created", which is not implemented yet
@@ -367,18 +367,39 @@ async def rename_file(self, drive_name, path, new_path):
367367
new_path: path of new file name
368368
"""
369369
data = {}
370+
finished = False
370371
try:
371372
# eliminate leading and trailing backslashes
372373
path = path.strip('/')
373-
374-
await obs.rename_async(self._content_managers[drive_name]["store"], path, new_path)
375-
metadata = await obs.head_async(self._content_managers[drive_name]["store"], new_path)
376374

377-
data = {
378-
"path": new_path,
379-
"last_modified": metadata["last_modified"].isoformat(),
380-
"size": metadata["size"]
381-
}
375+
# get list of contents with given prefix (path)
376+
stream = obs.list(self._content_managers[drive_name]["store"], path, chunk_size=100, return_arrow=True)
377+
async for batch in stream:
378+
contents_list = pyarrow.record_batch(batch).to_pylist()
379+
# rename each object within directory
380+
for object in contents_list:
381+
finished = True
382+
remaining_path = object["path"][len(path)+1:]
383+
old_path = path if remaining_path == '' else os.path.join(path, remaining_path)
384+
formatted_new_path = new_path if remaining_path == '' else os.path.join(new_path, remaining_path)
385+
try:
386+
await obs.rename_async(self._content_managers[drive_name]["store"], old_path, formatted_new_path)
387+
except Exception as e:
388+
# we are dealing with a directory rename in S3 and obstore doesn't find the object
389+
if self._config.provider == 's3':
390+
self._rename_directory(drive_name, old_path, formatted_new_path)
391+
else:
392+
raise tornado.web.HTTPError(
393+
status_code= httpx.codes.BAD_REQUEST,
394+
reason=f"The following error occured when renaming the object: {e}",
395+
)
396+
397+
# no extra S3 directories to rename
398+
if data == {} and finished == False:
399+
# rename single file from root(won't be listed above)
400+
await obs.rename_async(self._content_managers[drive_name]["store"], path, new_path)
401+
402+
data = await self._get_metadata(drive_name, new_path)
382403
except Exception as e:
383404
raise tornado.web.HTTPError(
384405
status_code= httpx.codes.BAD_REQUEST,
@@ -573,13 +594,69 @@ async def _delete_directories(self, drive_name, path):
573594
self._s3_clients[location].delete_object(Bucket=drive_name, Key=path+'/')
574595

575596
except Exception as e:
576-
raise tornado.web.HTTPError(
597+
raise tornado.web.HTTPError(
577598
status_code= httpx.codes.BAD_REQUEST,
578599
reason=f"The following error occured when deleting the directory: {e}",
579600
)
580601

581602
return
582603

604+
def _rename_directory(self, drive_name, path, new_path):
605+
"""Helping function to rename directories, when dealing with S3 buckets.
606+
607+
Args:
608+
drive_name: name of drive where to create object
609+
path: path of object
610+
new_path: new path of object
611+
"""
612+
try:
613+
location = self._content_managers[drive_name]["location"]
614+
if location not in self._s3_clients:
615+
self._s3_clients[location] = self._s3_session.client('s3', location)
616+
617+
self._s3_clients[location].copy_object(Bucket=drive_name, CopySource=os.path.join(drive_name, path)+'/', Key=new_path + '/')
618+
self._s3_clients[location].delete_object(Bucket=drive_name, Key = path + '/')
619+
except Exception:
620+
# object is not found if we are not dealing with directory
621+
pass
622+
623+
return
624+
625+
async def _get_metadata(self, drive_name, path):
626+
"""Helping function to get metadata of object.
627+
628+
Args:
629+
drive_name: name of drive where to create object
630+
path: path of object
631+
"""
632+
try:
633+
metadata = await obs.head_async(self._content_managers[drive_name]["store"], path)
634+
data = {
635+
"path": path,
636+
"last_modified": metadata["last_modified"].isoformat(),
637+
"size": metadata["size"]
638+
}
639+
except Exception:
640+
try:
641+
location = self._content_managers[drive_name]["location"]
642+
if location not in self._s3_clients:
643+
self._s3_clients[location] = self._s3_session.client('s3', location)
644+
645+
metadata = self._s3_clients[location].head_object(Bucket=drive_name, Key=path + '/')
646+
data = {
647+
"path": path,
648+
"last_modified": metadata["last_modified"].isoformat(),
649+
"size": metadata["size"]
650+
}
651+
except Exception:
652+
data = {
653+
"path": path,
654+
"last_modified": datetime.now().isoformat(),
655+
"size": 0
656+
}
657+
658+
return data
659+
583660
async def _call_provider(
584661
self,
585662
url: str,

src/requests.ts

Lines changed: 16 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -286,47 +286,26 @@ export async function renameObjects(
286286
options.registeredFileTypes
287287
);
288288

289-
// get list of contents with given prefix (path)
290289
const response = await requestAPI<any>(
291290
'drives/' + driveName + '/' + options.path,
292-
'GET'
291+
'PATCH',
292+
{
293+
new_path: formattedNewPath
294+
}
293295
);
294296

295-
// renaming contents of a directory
296-
if (response.data.length !== undefined && response.data.length !== 0) {
297-
await Promise.all(
298-
response.data.map(async (c: any) => {
299-
const remainingFilePath = c.path.substring(options.path.length);
300-
Private.renameSingleObject(
301-
driveName,
302-
PathExt.join(options.path, remainingFilePath),
303-
PathExt.join(formattedNewPath, remainingFilePath)
304-
);
305-
})
306-
);
307-
}
308-
// always rename the object (file or main directory)
309-
try {
310-
const renamedObject = await Private.renameSingleObject(
311-
driveName,
312-
options.path,
313-
formattedNewPath
314-
);
315-
data = {
316-
name: options.newFileName,
317-
path: PathExt.join(driveName, formattedNewPath),
318-
last_modified: renamedObject.data.last_modified,
319-
created: '',
320-
content: PathExt.extname(options.newFileName) !== '' ? null : [], // TODO: add dir check
321-
format: fileFormat as Contents.FileFormat,
322-
mimetype: fileMimeType,
323-
size: renamedObject.data.size,
324-
writable: true,
325-
type: fileType
326-
};
327-
} catch (error) {
328-
// renaming failed if directory didn't exist and was only part of a path
329-
}
297+
data = {
298+
name: options.newFileName,
299+
path: PathExt.join(driveName, formattedNewPath),
300+
last_modified: response.data.last_modified,
301+
created: '',
302+
content: null,
303+
format: fileFormat as Contents.FileFormat,
304+
mimetype: fileMimeType,
305+
size: response.data.size,
306+
writable: true,
307+
type: fileType
308+
};
330309

331310
return data;
332311
}
@@ -457,27 +436,6 @@ export const countObjectNameAppearances = async (
457436
};
458437

459438
namespace Private {
460-
/**
461-
* Helping function for renaming files inside
462-
* a directory, in the case of deleting the directory.
463-
*
464-
* @param driveName
465-
* @param objectPath complete path of object to rename
466-
*/
467-
export async function renameSingleObject(
468-
driveName: string,
469-
objectPath: string,
470-
newObjectPath: string
471-
) {
472-
return await requestAPI<any>(
473-
'drives/' + driveName + '/' + objectPath,
474-
'PATCH',
475-
{
476-
new_path: newObjectPath
477-
}
478-
);
479-
}
480-
481439
/**
482440
* Helping function for copying files inside
483441
* a directory, in the case of deleting the directory.

0 commit comments

Comments
 (0)