|
| 1 | +import os |
| 2 | +import pathlib |
| 3 | +from googleapiclient.errors import HttpError |
| 4 | +from googleapiclient.http import MediaFileUpload |
| 5 | + |
| 6 | + |
| 7 | +def get_existing_files_recursive(folder_id, service): |
| 8 | + query = f"'{folder_id}' in parents and trashed=false" |
| 9 | + files = {} |
| 10 | + folders = {} |
| 11 | + |
| 12 | + def recurse(current_id, path_prefix): |
| 13 | + results = service.files().list(q=f"'{current_id}' in parents and trashed=false", |
| 14 | + fields="files(id, name, mimeType)").execute() |
| 15 | + for item in results['files']: |
| 16 | + full_path = os.path.join(path_prefix, item['name']) |
| 17 | + if item['mimeType'] == 'application/vnd.google-apps.folder': |
| 18 | + folders[full_path] = item['id'] |
| 19 | + recurse(item['id'], full_path) |
| 20 | + else: |
| 21 | + files[full_path] = item['id'] |
| 22 | + |
| 23 | + recurse(folder_id, "") |
| 24 | + return {"files": files, "folders": folders} |
| 25 | + |
| 26 | +def ensure_folder(path, parent_id, drive_service, existing_items): |
| 27 | + folders = existing_items["folders"] |
| 28 | + if path in folders: |
| 29 | + return folders[path] |
| 30 | + |
| 31 | + folder_name = os.path.basename(path) |
| 32 | + parent_path = os.path.dirname(path) |
| 33 | + if parent_path != "": |
| 34 | + parent_folder_id = ensure_folder(parent_path, parent_id, drive_service, existing_items) |
| 35 | + else: |
| 36 | + parent_folder_id = parent_id |
| 37 | + |
| 38 | + file_metadata = { |
| 39 | + "name": folder_name, |
| 40 | + "mimeType": "application/vnd.google-apps.folder", |
| 41 | + "parents": [parent_folder_id] |
| 42 | + } |
| 43 | + folder = drive_service.files().create(body=file_metadata, fields="id").execute() |
| 44 | + folders[path] = folder["id"] |
| 45 | + return folder["id"] |
| 46 | + |
| 47 | +def upload_file_or_folder(local_base_path, parent_drive_folder_id, drive_service, existing_items): |
| 48 | + for local_path in pathlib.Path(local_base_path).rglob("*"): |
| 49 | + relative_path = str(local_path.relative_to(local_base_path)) |
| 50 | + |
| 51 | + if local_path.is_dir(): |
| 52 | + ensure_folder(relative_path, parent_drive_folder_id, drive_service, existing_items) |
| 53 | + print(f"✅ Added folder: {relative_path}") |
| 54 | + |
| 55 | + elif local_path.is_file(): |
| 56 | + |
| 57 | + parent_path = os.path.dirname(relative_path) |
| 58 | + if parent_path == "": |
| 59 | + parent_id = parent_drive_folder_id |
| 60 | + else: |
| 61 | + parent_id = ensure_folder(parent_path, parent_drive_folder_id, drive_service, existing_items) |
| 62 | + |
| 63 | + if relative_path in existing_items["files"]: |
| 64 | + continue # Already exists, skip |
| 65 | + file_metadata = { |
| 66 | + "name": os.path.basename(local_path), |
| 67 | + "parents": [parent_id] |
| 68 | + } |
| 69 | + media = MediaFileUpload(str(local_path)) |
| 70 | + file = drive_service.files().create( |
| 71 | + body=file_metadata, |
| 72 | + media_body=media, |
| 73 | + fields="id" |
| 74 | + ).execute() |
| 75 | + existing_items["files"][relative_path] = file["id"] |
| 76 | + print(f"✅ Added file: {relative_path}") |
| 77 | + |
| 78 | + |
| 79 | +def delete_items_not_in_local(local_base_path, parent_drive_folder_id, drive_service, existing_items): |
| 80 | + local_files = set() |
| 81 | + local_folders = set() |
| 82 | + |
| 83 | + for path in pathlib.Path(local_base_path).rglob("*"): |
| 84 | + rel = str(path.relative_to(local_base_path)) |
| 85 | + if path.is_file(): |
| 86 | + local_files.add(rel) |
| 87 | + elif path.is_dir(): |
| 88 | + local_folders.add(rel) |
| 89 | + |
| 90 | + for remote_path, file_id in existing_items["files"].items(): |
| 91 | + if remote_path not in local_files: |
| 92 | + try: |
| 93 | + drive_service.files().delete(fileId=file_id).execute() |
| 94 | + print(f"❌ Removed file: {remote_path}") |
| 95 | + except HttpError as e: |
| 96 | + print(f"Error when removing {remote_path}: {e}") |
| 97 | + |
| 98 | + # Remove folders from the deepest one |
| 99 | + for remote_path in sorted(existing_items["folders"].keys(), key=lambda x: -x.count("/")): |
| 100 | + if remote_path not in local_folders: |
| 101 | + try: |
| 102 | + drive_service.files().delete(fileId=existing_items["folders"][remote_path]).execute() |
| 103 | + print(f"❌ Removed directory: {remote_path}") |
| 104 | + except HttpError as e: |
| 105 | + print(f"Error when removing directory {remote_path}: {e}") |
0 commit comments