Vendored 5 packages from iOpenPod (TheRealSavi/iOpenPod): iTunesDB_Writer/ — binary iTunesDB generation iTunesDB_Shared/ — field defs, constants, album identity SQLiteDB_Writer/ — SQLite databases for Nano 6G/7G iTunesDB_Parser/ — play stats reading from iPod ipod_device/ — device detection, FireWire ID, capabilities Rationale: iOpenPod is a standalone iPod manager app, not a library. Pulling it as a pip dependency is heavy and confusing for users. Changes: + src/vendor/ — 77 .py files + calcHashAB.wasm (506 KB) ~ ipod_nano7_db.py — _find_iop_root() now returns src/vendor/ ~ requirements.txt — iopenpod replaced with wasmtime + pycryptodome ~ .gitignore — removed iOpenPod/ exclusion (setup.py reads requirements.txt automatically)
133 lines
4.0 KiB
Python
133 lines
4.0 KiB
Python
"""
|
|
PyUSB backend resolution helpers.
|
|
|
|
PyUSB is pure Python, but on Windows it still needs a native
|
|
``libusb-1.0.dll``. The app vendors the official 64-bit libusb DLL under
|
|
``vendor/libusb/windows/x64`` and falls back to system/package locations.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ctypes.util
|
|
import logging
|
|
import os
|
|
import platform
|
|
import sys
|
|
from collections.abc import Callable
|
|
from pathlib import Path
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def get_libusb_backend():
|
|
"""Return a PyUSB libusb1 backend, or ``None`` if no backend can load."""
|
|
try:
|
|
import usb.backend.libusb1
|
|
except ImportError:
|
|
return None
|
|
|
|
backend = usb.backend.libusb1.get_backend()
|
|
if backend is not None:
|
|
return backend
|
|
|
|
for candidate in _candidate_libusb_paths():
|
|
backend = _backend_from_path(usb.backend.libusb1.get_backend, candidate)
|
|
if backend is not None:
|
|
logger.debug("PyUSB libusb backend loaded from %s", candidate)
|
|
return backend
|
|
|
|
return None
|
|
|
|
|
|
def backend_diagnostic() -> str:
|
|
"""Return a short human-readable backend diagnostic string."""
|
|
try:
|
|
import usb.backend.libusb1
|
|
except ImportError:
|
|
return "pyusb is not installed"
|
|
|
|
if usb.backend.libusb1.get_backend() is not None:
|
|
return "system libusb backend available"
|
|
|
|
candidates = list(_candidate_libusb_paths())
|
|
if not candidates:
|
|
return "no libusb-1.0 library candidates found"
|
|
|
|
existing = [str(path) for path in candidates if path.exists()]
|
|
if existing:
|
|
return "libusb candidates exist but failed to load: " + ", ".join(existing)
|
|
return "libusb candidates missing: " + ", ".join(str(path) for path in candidates)
|
|
|
|
|
|
def _backend_from_path(get_backend: Callable, path: Path):
|
|
if not path.exists():
|
|
return None
|
|
|
|
def _find_library(_name: str) -> str:
|
|
return str(path)
|
|
|
|
try:
|
|
return get_backend(find_library=_find_library)
|
|
except Exception as exc:
|
|
logger.debug("PyUSB backend load failed from %s: %s", path, exc)
|
|
return None
|
|
|
|
|
|
def _candidate_libusb_paths() -> list[Path]:
|
|
candidates: list[Path] = []
|
|
|
|
for env_name in ("IOPENPOD_LIBUSB_DLL", "PYUSB_LIBUSB_DLL"):
|
|
env_path = os.environ.get(env_name, "").strip()
|
|
if env_path:
|
|
candidates.append(Path(env_path))
|
|
|
|
# Optional helper from the ``libusb-package`` wheel when available.
|
|
try:
|
|
import libusb_package # type: ignore
|
|
|
|
path = libusb_package.find_library()
|
|
if path:
|
|
candidates.append(Path(path))
|
|
except Exception:
|
|
pass
|
|
|
|
system_path = ctypes.util.find_library("usb-1.0")
|
|
if system_path:
|
|
candidates.append(Path(system_path))
|
|
|
|
root = Path(__file__).resolve().parent.parent
|
|
exe_dir = Path(sys.executable).resolve().parent
|
|
if sys.platform == "win32":
|
|
arch = platform.architecture()[0]
|
|
if arch == "64bit":
|
|
candidates.extend([
|
|
root / "vendor" / "libusb" / "windows" / "x64" / "libusb-1.0.dll",
|
|
exe_dir / "libusb-1.0.dll",
|
|
exe_dir / "vendor" / "libusb" / "windows" / "x64" / "libusb-1.0.dll",
|
|
])
|
|
else:
|
|
candidates.extend([
|
|
root / "vendor" / "libusb" / "windows" / "x86" / "libusb-1.0.dll",
|
|
exe_dir / "libusb-1.0.dll",
|
|
exe_dir / "vendor" / "libusb" / "windows" / "x86" / "libusb-1.0.dll",
|
|
])
|
|
elif sys.platform == "darwin":
|
|
candidates.extend([
|
|
root / "vendor" / "libusb" / "macos" / "libusb-1.0.dylib",
|
|
exe_dir / "libusb-1.0.dylib",
|
|
])
|
|
else:
|
|
candidates.extend([
|
|
root / "vendor" / "libusb" / "linux" / "libusb-1.0.so",
|
|
exe_dir / "libusb-1.0.so",
|
|
])
|
|
|
|
unique: list[Path] = []
|
|
seen: set[str] = set()
|
|
for candidate in candidates:
|
|
key = str(candidate).casefold()
|
|
if key not in seen:
|
|
unique.append(candidate)
|
|
seen.add(key)
|
|
return unique
|