Import source: sort_root_full
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
@@ -0,0 +1,89 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Full sort of gdrive root — classifies every loose file and moves it to the right folder."""
|
||||||
|
import json, subprocess, sys
|
||||||
|
|
||||||
|
REMOTE = "gdrive:"
|
||||||
|
BASE = ["rclone"]
|
||||||
|
# Folders that should never be touched (already organised or system folders)
|
||||||
|
KEEP_DIRS = {"Google AI Studio", "Photos", "Untitled folder",
|
||||||
|
"Videos", "Music", "Software", "Archives", "Bin",
|
||||||
|
"Docs", "Config", "Models", "ZIM", "SPH"}
|
||||||
|
|
||||||
|
def run(*args):
|
||||||
|
cmd = list(args)
|
||||||
|
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
if r.returncode != 0:
|
||||||
|
print(f" ERROR: {r.stderr.strip()[:120]}")
|
||||||
|
return r.returncode == 0
|
||||||
|
|
||||||
|
def classify(name: str, mime: str) -> str:
|
||||||
|
ext = name.rsplit(".", 1)[-1].lower() if "." in name else ""
|
||||||
|
if mime.startswith("video/") or ext in ("mp4","mkv","avi","mov","webm","flv"):
|
||||||
|
return "Videos"
|
||||||
|
if mime.startswith("audio/") or ext in ("mp3","flac","wav","ogg","m4a","aac"):
|
||||||
|
return "Music"
|
||||||
|
if mime.startswith("image/") or ext in ("jpg","jpeg","png","gif","webp","bmp","heic","heif"):
|
||||||
|
return "Photos"
|
||||||
|
if ext in ("exe","msi","deb","rpm","dmg","appimage"):
|
||||||
|
return "Software"
|
||||||
|
if ext in ("zip","rar","7z","tar","gz","bz2","xz","zst"):
|
||||||
|
return "Archives"
|
||||||
|
if ext == "gguf":
|
||||||
|
return "Models"
|
||||||
|
if ext in ("safetensors","pt","ckpt","pth"):
|
||||||
|
return "Models"
|
||||||
|
if ext == "zim":
|
||||||
|
return "ZIM"
|
||||||
|
if ext in ("conf","ovpn","cfg","ini","toml"):
|
||||||
|
return "Config"
|
||||||
|
if ext in ("pdf","doc","docx","txt","md","csv","xlsx","pptx","json"):
|
||||||
|
return "Docs"
|
||||||
|
if ext == "bin":
|
||||||
|
return "Bin"
|
||||||
|
# Google Docs / Sheets / Slides (no extension, mime-based)
|
||||||
|
gdoc_mimes = ("application/vnd.google-apps.document",
|
||||||
|
"application/vnd.google-apps.spreadsheet",
|
||||||
|
"application/vnd.google-apps.presentation",
|
||||||
|
"application/vnd.google-apps.form",
|
||||||
|
"application/vnd.openxmlformats")
|
||||||
|
if any(mime.startswith(m) for m in gdoc_mimes):
|
||||||
|
return "Docs"
|
||||||
|
return "Other"
|
||||||
|
|
||||||
|
print("Listing root (depth 0)...")
|
||||||
|
r = subprocess.run(
|
||||||
|
BASE + ["lsjson", REMOTE, "--max-depth", "0"],
|
||||||
|
capture_output=True, text=True
|
||||||
|
)
|
||||||
|
items = json.loads(r.stdout)
|
||||||
|
|
||||||
|
loose_files = [f for f in items if not f["IsDir"]]
|
||||||
|
dirs = [f["Name"] for f in items if f["IsDir"]]
|
||||||
|
|
||||||
|
print(f"Found {len(loose_files)} loose files, {len(dirs)} folders\n")
|
||||||
|
|
||||||
|
groups: dict[str, list] = {}
|
||||||
|
for f in loose_files:
|
||||||
|
folder = classify(f["Name"], f["MimeType"])
|
||||||
|
groups.setdefault(folder, []).append(f["Name"])
|
||||||
|
|
||||||
|
print("=== Move plan ===")
|
||||||
|
for folder, names in sorted(groups.items()):
|
||||||
|
print(f" {folder}/ ← {len(names)} files")
|
||||||
|
|
||||||
|
print("\n=== Executing ===")
|
||||||
|
created: set = set()
|
||||||
|
ok = err = 0
|
||||||
|
|
||||||
|
for folder, names in sorted(groups.items()):
|
||||||
|
if folder not in created:
|
||||||
|
run("rclone", "mkdir", f"{REMOTE}{folder}")
|
||||||
|
created.add(folder)
|
||||||
|
for name in names:
|
||||||
|
print(f" → {folder}/{name[:65]}")
|
||||||
|
if run("rclone", "moveto", f"{REMOTE}{name}", f"{REMOTE}{folder}/{name}"):
|
||||||
|
ok += 1
|
||||||
|
else:
|
||||||
|
err += 1
|
||||||
|
|
||||||
|
print(f"\n=== Done: {ok} moved, {err} errors ===")
|
||||||
Reference in New Issue
Block a user