111 lines
3.2 KiB
Python
Executable File
111 lines
3.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Copy a UF2 artifact to a detected RP2040 mass-storage mount."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
INFO_FILE = "INFO_UF2.TXT"
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--source", type=Path, required=True, help="Path to the UF2 file to copy")
|
|
parser.add_argument("--timeout", type=float, default=10.0, help="Seconds to wait for the mount")
|
|
parser.add_argument(
|
|
"--mount",
|
|
type=str,
|
|
default=os.environ.get("MOUNT", ""),
|
|
help="Explicit mount point (default: auto-detect)",
|
|
)
|
|
return parser.parse_args()
|
|
|
|
|
|
def candidate_paths(explicit: str, user: str) -> list[Path]:
|
|
paths: list[Path] = []
|
|
if explicit:
|
|
paths.append(Path(explicit))
|
|
roots = [
|
|
Path("/Volumes"),
|
|
Path("/media"),
|
|
Path(f"/media/{user}"),
|
|
Path("/run/media"),
|
|
Path(f"/run/media/{user}"),
|
|
]
|
|
for root in roots:
|
|
if not root.exists() or not root.is_dir():
|
|
continue
|
|
for child in root.iterdir():
|
|
if child.is_dir():
|
|
paths.append(child)
|
|
return paths
|
|
|
|
|
|
def choose_mount(explicit: str, user: str) -> Path | None:
|
|
candidates = candidate_paths(explicit, user)
|
|
if explicit:
|
|
path = Path(explicit)
|
|
return path if path.exists() and path.is_dir() else None
|
|
info_candidates = [path for path in candidates if (path / INFO_FILE).exists()]
|
|
if info_candidates:
|
|
return info_candidates[0]
|
|
for path in candidates:
|
|
if path.exists() and path.is_dir():
|
|
return path
|
|
return None
|
|
|
|
|
|
def main() -> int:
|
|
args = parse_args()
|
|
source = args.source
|
|
if not source.exists():
|
|
print(f"UF2 source file not found: {source}", file=sys.stderr)
|
|
return 1
|
|
|
|
explicit_mount = args.mount.strip()
|
|
user = os.environ.get("USER", "")
|
|
deadline = time.time() + float(args.timeout)
|
|
|
|
while time.time() <= deadline:
|
|
mount = choose_mount(explicit_mount, user)
|
|
if mount is not None:
|
|
if not mount.exists() or not mount.is_dir():
|
|
time.sleep(1)
|
|
continue
|
|
destination = mount / source.name
|
|
try:
|
|
shutil.copy2(source, destination)
|
|
try:
|
|
if hasattr(os, "sync"):
|
|
os.sync()
|
|
except Exception:
|
|
pass
|
|
time.sleep(0.5)
|
|
except Exception as exc: # noqa: BLE001
|
|
print(f"Failed to copy UF2 to {destination}: {exc}", file=sys.stderr)
|
|
return 1
|
|
print(f"Copied {source} to {destination}")
|
|
return 0
|
|
time.sleep(1)
|
|
|
|
if explicit_mount:
|
|
print(
|
|
f"Mount point '{explicit_mount}' not found within {args.timeout} seconds",
|
|
file=sys.stderr,
|
|
)
|
|
else:
|
|
print(
|
|
"Unable to detect RP2040 UF2 mount. Pass one via mount=/path",
|
|
file=sys.stderr,
|
|
)
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__": # pragma: no cover
|
|
sys.exit(main())
|