diff --git a/drive/api/embed.py b/drive/api/embed.py index a759a4f9..5fa8d7aa 100644 --- a/drive/api/embed.py +++ b/drive/api/embed.py @@ -11,12 +11,12 @@ from io import BytesIO from drive.utils.files import get_user_directory from drive.api.files import create_drive_entity - +import uuid def create_user_embeds_directory(user=None): user_directory_name = get_user_directory(user).name user_directory_embeds_path = Path( - frappe.get_site_path("private/files"), user_directory_name, "embeds" + frappe.get_site_path("private/files/FD_data"), user_directory_name, "embeds" ) user_directory_embeds_path.mkdir(exist_ok=True) return user_directory_embeds_path @@ -25,7 +25,7 @@ def create_user_embeds_directory(user=None): def get_user_embeds_directory(user=None): user_directory_name = get_user_directory(user).name user_directory_embeds_path = Path( - frappe.get_site_path("private/files"), user_directory_name, "embeds" + frappe.get_site_path("private/files/FD_data"), user_directory_name, "embeds" ) if not os.path.exists(user_directory_embeds_path): try: @@ -61,7 +61,7 @@ def upload_chunked_file(fullpath=None, parent=None, last_modified=None): except FileNotFoundError: embed_directory = create_user_embeds_directory(user=drive_entity.owner) embed_directory = Path( - frappe.get_site_path("private/files"), + frappe.get_site_path("private/files/FD_data"), get_user_directory(user=drive_entity.owner).name, "embeds", ) @@ -72,7 +72,7 @@ def upload_chunked_file(fullpath=None, parent=None, last_modified=None): file = frappe.request.files["file"] - name = frappe.form_dict.uuid + name = uuid.uuid4().hex title, file_ext = os.path.splitext(frappe.form_dict.file_name) mime_type = frappe.form_dict.mime_type current_chunk = int(frappe.form_dict.chunk_index) @@ -95,8 +95,10 @@ def upload_chunked_file(fullpath=None, parent=None, last_modified=None): if file_size != int(frappe.form_dict.total_file_size): save_path.unlink() frappe.throw("Size on disk does not match specified filesize", ValueError) + fs_name = name + file_ext + db_path = f"embed/{fs_name}" drive_entity = create_drive_entity( - name, title, parent, save_path, file_size, file_ext, mime_type, last_modified + name, title, parent, db_path, file_size, file_ext, mime_type, last_modified ) return str(name + file_ext) diff --git a/drive/api/files.py b/drive/api/files.py index da9c692b..9dbcd3e9 100644 --- a/drive/api/files.py +++ b/drive/api/files.py @@ -135,7 +135,7 @@ def create_whiteboard_entity(title, content, parent=None): def create_uploads_directory(user=None): user_directory_name = get_user_directory(user).name user_directory_uploads_path = Path( - frappe.get_site_path("private/files"), user_directory_name, "uploads" + frappe.get_site_path("private/files/FD_data"), user_directory_name, "uploads" ) user_directory_uploads_path.mkdir(exist_ok=True) return user_directory_uploads_path @@ -144,7 +144,7 @@ def create_uploads_directory(user=None): def get_user_uploads_directory(user=None): user_directory_name = get_user_directory(user).name user_directory_uploads_path = Path( - frappe.get_site_path("private/files"), user_directory_name, "uploads" + frappe.get_site_path("private/files/FD_data"), user_directory_name, "uploads" ) if not os.path.exists(user_directory_uploads_path): try: @@ -191,7 +191,6 @@ def upload_file(fullpath=None, parent=None, last_modified=None): current_chunk = int(frappe.form_dict.chunk_index) total_chunks = int(frappe.form_dict.total_chunk_count) - save_path = Path(user_directory.path) / f"{parent}_{secure_filename(title)}" temp_path = ( Path(get_user_uploads_directory(user=frappe.session.user)) / f"{upload_session}_{secure_filename(title)}" @@ -213,22 +212,22 @@ def upload_file(fullpath=None, parent=None, last_modified=None): if file_size != int(frappe.form_dict.total_file_size): temp_path.unlink() frappe.throw("Size on disk does not match specified filesize", ValueError) - else: - os.rename(temp_path, save_path) mime_type, _ = mimetypes.guess_type(temp_path) if mime_type is None: # Read the first 2KB of the binary stream to determine the file type if string checking failed # Do a rejection workflow to reject undesired mime types - mime_type = magic.from_buffer(open(save_path, "rb").read(2048), mime=True) + mime_type = magic.from_buffer(open(temp_path, "rb").read(2048), mime=True) - file_name, file_ext = os.path.splitext(title) + _, file_ext = os.path.splitext(title) name = uuid.uuid4().hex - path = save_path.parent / f"{name}{save_path.suffix}" - save_path.rename(path) + fs_name = name + file_ext + db_path = f"files/{fs_name}" + fs_path = frappe.get_site_path("private/files/FD_data", user_directory.name, db_path) + temp_path.rename(fs_path) drive_entity = create_drive_entity( - name, title, parent, path, file_size, file_ext, mime_type, last_modified + name, title, parent, db_path, file_size, file_ext, mime_type, last_modified ) if mime_type.startswith(("image", "video")): @@ -240,7 +239,7 @@ def upload_file(fullpath=None, parent=None, last_modified=None): at_front=True, # will set to false once reactivity in new UI is solved entity_name=name, - path=path, + path=fs_path, mime_type=mime_type, ) return drive_entity @@ -456,6 +455,7 @@ def get_file_content(entity_name, trigger_download=0): # "allow_download", "is_active", "owner", + "parent_drive_entity", ], as_dict=1, ) @@ -464,10 +464,11 @@ def get_file_content(entity_name, trigger_download=0): # raise ValueError if drive_entity.is_active != 1: raise FileNotFoundError - + dir = get_user_directory(frappe.session.user).name + path = frappe.get_site_path("private/files/FD_data", dir, drive_entity.path) with DistributedLock(drive_entity.path, exclusive=False): return send_file( - drive_entity.path, + path, mimetype=drive_entity.mime_type, as_attachment=trigger_download, conditional=True, @@ -1388,7 +1389,7 @@ def search(query, home_dir): ) return result except Exception as e: - frappe.log_error(frappe.get_traceback(), 'Frappe Drive Search Error') + frappe.log_error(frappe.get_traceback(), "Frappe Drive Search Error") return {"error": str(e)} diff --git a/drive/drive/doctype/drive_entity/patches/drive_root_folder.py b/drive/drive/doctype/drive_entity/patches/drive_root_folder.py new file mode 100644 index 00000000..b9189638 --- /dev/null +++ b/drive/drive/doctype/drive_entity/patches/drive_root_folder.py @@ -0,0 +1,146 @@ +import frappe +import os +import shutil +from pathlib import Path + + +def execute(): + """ + Add a root directory for Frappe Drive + Move all files to this new directory + Update all file DB records to point to these files + """ + root_name = "FD_data" + create_root_directory(root_name) + root_folders = get_root_entities() + files = get_non_root_entities() + site_root = frappe.get_site_path("private/files") + move_root_folders(root_folders, site_root, root_name) + move_non_root(files, site_root, root_name) + + +def create_root_directory(root_name): + """ + Create new root FD_data + """ + file_path = frappe.get_site_path("private/files/") + file_path += root_name + os.makedirs(file_path, exist_ok=True) + + +def get_root_entities(): + """ + Fetch all user directories + """ + DriveEntity = frappe.qb.DocType("Drive Entity") + selectedFields = [ + DriveEntity.title, + DriveEntity.name, + DriveEntity.path, + DriveEntity.owner, + DriveEntity.title, + DriveEntity.is_group, + ] + query = ( + frappe.qb.from_(DriveEntity) + .select(*selectedFields) + .where((DriveEntity.parent_drive_entity.isnull()) & (DriveEntity.is_group == 1)) + ) + result = query.run(as_dict=True) + return result + + +def get_non_root_entities(): + """ + Get all non root entities + """ + DriveEntity = frappe.qb.DocType("Drive Entity") + selectedFields = [ + DriveEntity.name, + DriveEntity.path, + DriveEntity.owner, + DriveEntity.title, + DriveEntity.is_group, + ] + query = ( + frappe.qb.from_(DriveEntity) + .select(*selectedFields) + .where( + (DriveEntity.parent_drive_entity.notnull()) + & (DriveEntity.path.notnull()) + & (DriveEntity.is_active == 1) + ) + ) + result = query.run(as_dict=True) + return result + + +def move_root_folders(root_folders, site_root, root_name): + """ + Move user directories to /FD_data + Make /files inside each + Update DB path for user dirs + """ + for folder in root_folders: + folder_name = Path(folder["path"]).parts[-1] + new_db_path = f"/{root_name}/{folder_name}/" + curr_fs_path = site_root + f"/{folder_name}/" + new_fs_path = site_root + new_db_path + + os.makedirs(curr_fs_path + "files", exist_ok=True) + + shutil.move(curr_fs_path, new_fs_path) + frappe.db.set_value( + "Drive Entity", folder["name"], "path", new_db_path, update_modified=False + ) + + +def move_non_root(files, site_root, root_name): + """ + move all user files to /FD_data/files + update embeds DB path + update db path to relative /files/$NAME + """ + for file in files: + entity_name = file["name"] + + curr_db_path = Path(file["path"]) + curr_db_split = list(curr_db_path.parts) + + idx = curr_db_split.index("files") + user_dir = curr_db_split[idx + 1] if idx + 1 < len(curr_db_split) else None + + if "embeds" in curr_db_split: + new_db_path = list(curr_db_split[-2:]) + + elif "uploads" in curr_db_split: + new_db_path = list(curr_db_split[-2:]) + else: + # Fix current src FC directory + curr_fs_path = curr_db_split[-1:] + curr_fs_path.insert(0, user_dir) + curr_fs_path.insert(0, f"/{root_name}") + + # single level DB path + new_db_path = curr_db_split[-1:] + new_db_path.insert(0, "files") + + # new FS path at FD_data/$user_dir/files + new_fs_path = new_db_path.copy() + new_fs_path.insert(0, user_dir) + new_fs_path.insert(0, f"/{root_name}") + + new_fs_path = Path(*new_fs_path) + curr_fs_path = Path(*curr_fs_path) + + # str to preserve "./" + new = site_root + str(new_fs_path) + curr = site_root + str(curr_fs_path) + + shutil.move(curr, new) + + new_db_path = Path(*new_db_path) + db = str(new_db_path) + frappe.db.set_value( + "Drive Entity", entity_name, "path", db, update_modified=False + ) diff --git a/drive/patches.txt b/drive/patches.txt index 2ab4ce37..acba7a0c 100644 --- a/drive/patches.txt +++ b/drive/patches.txt @@ -5,4 +5,5 @@ drive.drive.doctype.drive_entity.patches.set_file_kind drive.drive.doctype.drive_docshare.patches.update_protected drive.drive.doctype.drive_entity_activity_log.patches.initialize_creation drive.drive.doctype.drive_entity_activity_log.patches.share_creation -drive.drive.doctype.drive_entity.patches.init_search_idx \ No newline at end of file +drive.drive.doctype.drive_entity.patches.init_search_idx +drive.drive.doctype.drive_entity.patches.drive_root_folder \ No newline at end of file diff --git a/drive/utils/files.py b/drive/utils/files.py index b2364392..749d4276 100644 --- a/drive/utils/files.py +++ b/drive/utils/files.py @@ -23,9 +23,9 @@ def create_user_directory(): if "Drive User" not in user_roles: raise frappe.PermissionError("You do not have access to Frappe Drive") user_directory_name = _get_user_directory_name() - user_directory_path = Path(frappe.get_site_path("private/files"), user_directory_name) + user_directory_path = Path(frappe.get_site_path("private/files/FD_data"), user_directory_name) user_directory_path.mkdir(exist_ok=True) - + db_path = "FD_data/" + user_directory_name full_name = frappe.get_value("User", frappe.session.user, "full_name") user_directory = frappe.get_doc( { @@ -33,7 +33,7 @@ def create_user_directory(): "name": user_directory_name, "title": f"{full_name}'s Drive", "is_group": 1, - "path": user_directory_path, + "path": db_path, } ) user_directory.flags.file_created = True @@ -115,7 +115,7 @@ def get_new_title(title, parent_name, document=False, folder=False): def create_user_thumbnails_directory(): user_directory_name = _get_user_directory_name() user_directory_thumnails_path = Path( - frappe.get_site_path("private/files"), user_directory_name, "thumbnails" + frappe.get_site_path("private/files/FD_data"), user_directory_name, "thumbnails" ) user_directory_thumnails_path.mkdir(exist_ok=True) return user_directory_thumnails_path @@ -124,7 +124,7 @@ def create_user_thumbnails_directory(): def get_user_thumbnails_directory(user=None): user_directory_name = _get_user_directory_name(user) user_directory_thumnails_path = Path( - frappe.get_site_path("private/files"), user_directory_name, "thumbnails" + frappe.get_site_path("private/files/FD_data"), user_directory_name, "thumbnails" ) if not os.path.exists(user_directory_thumnails_path): try: