fix: robust iOpenPod imports and sync error handling
_import_iop() now permanently evicts local ipod_device from sys.modules on first call (instead of restoring it), caches all iOpenPod modules, and avoids the spurious ModuleNotFoundError cascade on rapid calls. sync_itunescdb() wrapped in try/except — no longer propagates import/sync errors to add_track() callers. ipod_device.py fallback no longer duplicates files to /Music/ when add_track() already copied them to F00/.
This commit is contained in:
parent
4f7bd2709c
commit
0f12c4985f
@ -543,9 +543,10 @@ class IPodDevice:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
self.logger.warning("ipod_nano7_db not available, using simple file copy")
|
self.logger.warning("ipod_nano7_db not available, using simple file copy")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Nano7 database transfer failed: {e}, falling back to simple copy")
|
self.logger.error(f"Nano7 database transfer failed: {e}")
|
||||||
|
return False # Don't fall back — file was already copied to F00/
|
||||||
|
|
||||||
# Fallback: simple file copy (won't show in iPod interface)
|
# Fallback: simple file copy — only on ImportError
|
||||||
if relative_path:
|
if relative_path:
|
||||||
dest_dir = os.path.join(self.mount_point, relative_path)
|
dest_dir = os.path.join(self.mount_point, relative_path)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -36,34 +36,43 @@ from mutagen import File as MutagenFile
|
|||||||
from mutagen.easyid3 import EasyID3
|
from mutagen.easyid3 import EasyID3
|
||||||
|
|
||||||
_IOP_ROOT = "/tmp/iOpenPod"
|
_IOP_ROOT = "/tmp/iOpenPod"
|
||||||
|
_IOP_CACHE: dict[str, Any] = {}
|
||||||
|
_IOP_READY = False
|
||||||
|
|
||||||
|
|
||||||
def _import_iop(module_name: str):
|
def _import_iop(module_name: str):
|
||||||
"""Import a module from iOpenPod, bypassing any locally-cached conflict.
|
"""Import a module from iOpenPod, evicting our local ipod_device once.
|
||||||
|
|
||||||
main.py imports our local ``ipod_device`` first, which caches it in
|
main.py imports our local ``ipod_device`` early, caching it in
|
||||||
``sys.modules``. When ipod_nano7_db later tries ``from ipod_device
|
``sys.modules``. On the FIRST call here we permanently evict the
|
||||||
import ChecksumType``, Python returns the cached *local* module
|
local module so iOpenPod's ``ipod_device`` takes its place in
|
||||||
(which lacks ChecksumType). This helper temporarily evicts the
|
``sys.modules``. Our local classes (IPodDevice) were already
|
||||||
conflicting local modules, adds the iOpenPod root to sys.path, loads
|
bound by ``from ipod_device import IPodDevice`` in main.py before
|
||||||
the requested module from there, then restores the path.
|
the eviction, so they stay alive through their own references.
|
||||||
|
|
||||||
|
Subsequent calls are served from a simple module cache.
|
||||||
"""
|
"""
|
||||||
# Modules that may be cached as our local copies and need eviction.
|
global _IOP_READY
|
||||||
_conflicting = {"ipod_device"}
|
if module_name in _IOP_CACHE:
|
||||||
_saved = {}
|
return _IOP_CACHE[module_name]
|
||||||
for name in _conflicting & set(sys.modules):
|
|
||||||
_saved[name] = sys.modules.pop(name)
|
if not _IOP_READY:
|
||||||
|
# Evict local shadows ONCE so iOpenPod modules resolve correctly.
|
||||||
|
sys.modules.pop("ipod_device", None)
|
||||||
|
_IOP_READY = True
|
||||||
|
logger.debug("iOpenPod boot: evicted local ipod_device")
|
||||||
|
|
||||||
had_iop = _IOP_ROOT in sys.path
|
had_iop = _IOP_ROOT in sys.path
|
||||||
if not had_iop:
|
if not had_iop:
|
||||||
sys.path.insert(0, _IOP_ROOT)
|
sys.path.insert(0, _IOP_ROOT)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return importlib.import_module(module_name)
|
mod = importlib.import_module(module_name)
|
||||||
finally:
|
finally:
|
||||||
if not had_iop:
|
if not had_iop:
|
||||||
sys.path.remove(_IOP_ROOT)
|
sys.path.remove(_IOP_ROOT)
|
||||||
sys.modules.update(_saved)
|
|
||||||
|
_IOP_CACHE[module_name] = mod
|
||||||
|
return mod
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -463,12 +472,13 @@ class Nano7Database:
|
|||||||
# ── sync: regenerate everything via iOpenPod ──────────────────────
|
# ── sync: regenerate everything via iOpenPod ──────────────────────
|
||||||
|
|
||||||
def sync_itunescdb(self) -> None:
|
def sync_itunescdb(self) -> None:
|
||||||
"""Regenerate ALL databases using iOpenPod's writers.
|
"""Regenerate ALL databases using iOpenPod's writers."""
|
||||||
|
try:
|
||||||
|
self._sync_itunescdb_impl()
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("iTunesDB sync failed: %s", e)
|
||||||
|
|
||||||
Scans all audio files on iPod, builds correct TrackInfo list,
|
def _sync_itunescdb_impl(self) -> None:
|
||||||
writes SQLite databases (Library/Locations/Dynamic/Extras/Genius),
|
|
||||||
writes binary iTunesDB, writes iTunesCDB.ext and Locations.cbk.
|
|
||||||
"""
|
|
||||||
# 1. Scan all audio files on iPod
|
# 1. Scan all audio files on iPod
|
||||||
scanned = self._scan_ipod_files()
|
scanned = self._scan_ipod_files()
|
||||||
if not scanned:
|
if not scanned:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user