fix: resolve ipod_device import conflict with lazy imports

main.py imports our local ipod_device first, which caches it in
sys.modules.  When ipod_nano7_db later tried 'from ipod_device import
ChecksumType', Python returned the cached local module (which lacks
ChecksumType), causing 'ipod_nano7_db not available' and falling back
to simple file copy into wrong /Music/ directory.

Fix: replace top-level iOpenPod imports with lazy _import_iop() helper
that temporarily evicts local ipod_device from sys.modules, adds
/tmp/iOpenPod to sys.path, loads the iOpenPod module, then restores
the path.  All iOpenPod imports now happen inside method bodies only.
This commit is contained in:
Maksim Totmin 2026-05-31 21:03:11 +07:00
parent 4c4a19640d
commit 4f7bd2709c

View File

@ -20,6 +20,7 @@ Public API preserved for main.py compatibility:
delete_orphaned_files(paths) remove orphan files delete_orphaned_files(paths) remove orphan files
""" """
import importlib
import os import os
import sys import sys
import hashlib import hashlib
@ -31,25 +32,38 @@ import struct
import time import time
from typing import Any, Optional from typing import Any, Optional
os.environ.setdefault("IOPENOD_HOME", "/tmp/iOpenPod")
_iop = "/tmp/iOpenPod"
if _iop not in sys.path:
sys.path.insert(0, _iop)
from mutagen import File as MutagenFile from mutagen import File as MutagenFile
from mutagen.easyid3 import EasyID3 from mutagen.easyid3 import EasyID3
from iTunesDB_Writer.mhit_writer import TrackInfo as IopTrackInfo _IOP_ROOT = "/tmp/iOpenPod"
from iTunesDB_Writer.mhyp_writer import PlaylistInfo
from iTunesDB_Writer import write_itunesdb as iop_write_itunesdb
from SQLiteDB_Writer import write_sqlite_databases def _import_iop(module_name: str):
from ipod_device import ( """Import a module from iOpenPod, bypassing any locally-cached conflict.
DeviceCapabilities,
ChecksumType, main.py imports our local ``ipod_device`` first, which caches it in
get_firewire_id, ``sys.modules``. When ipod_nano7_db later tries ``from ipod_device
capabilities_for_family_gen, import ChecksumType``, Python returns the cached *local* module
get_current_device, (which lacks ChecksumType). This helper temporarily evicts the
) conflicting local modules, adds the iOpenPod root to sys.path, loads
the requested module from there, then restores the path.
"""
# Modules that may be cached as our local copies and need eviction.
_conflicting = {"ipod_device"}
_saved = {}
for name in _conflicting & set(sys.modules):
_saved[name] = sys.modules.pop(name)
had_iop = _IOP_ROOT in sys.path
if not had_iop:
sys.path.insert(0, _IOP_ROOT)
try:
return importlib.import_module(module_name)
finally:
if not had_iop:
sys.path.remove(_IOP_ROOT)
sys.modules.update(_saved)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -165,9 +179,10 @@ def _read_audio_tags(filepath: str) -> dict[str, Any]:
def _scanned_tracks_to_iop(mountpoint: str, def _scanned_tracks_to_iop(mountpoint: str,
scanned: list[dict]) -> list[IopTrackInfo]: scanned: list[dict]) -> list:
"""Convert our scanned track dicts to iOpenPod TrackInfo objects.""" """Convert our scanned track dicts to iOpenPod TrackInfo objects."""
tracks: list[IopTrackInfo] = [] IopTrackInfo = _import_iop("iTunesDB_Writer.mhit_writer").TrackInfo
tracks: list = []
for s in scanned: for s in scanned:
fpath = s["file_path"] fpath = s["file_path"]
fname = os.path.basename(fpath) fname = os.path.basename(fpath)
@ -467,11 +482,12 @@ class Nano7Database:
logger.info("Built %d iOpenPod TrackInfo objects", len(tracks)) logger.info("Built %d iOpenPod TrackInfo objects", len(tracks))
# 3. Determine capabilities for this device # 3. Determine capabilities for this device
ipod_device = _import_iop("ipod_device")
capabilities = None capabilities = None
try: try:
dev = get_current_device() dev = ipod_device.get_current_device()
if dev and dev.model_family: if dev and dev.model_family:
capabilities = capabilities_for_family_gen( capabilities = ipod_device.capabilities_for_family_gen(
dev.model_family, dev.generation or "", dev.model_family, dev.generation or "",
) )
if capabilities: if capabilities:
@ -481,10 +497,11 @@ class Nano7Database:
# 4. Generate and write binary iTunesDB first (to get db_pid) # 4. Generate and write binary iTunesDB first (to get db_pid)
try: try:
firewire_id = get_firewire_id(self.mountpoint) firewire_id = ipod_device.get_firewire_id(self.mountpoint)
except Exception: except Exception:
firewire_id = self.firewire_id firewire_id = self.firewire_id
iop_write_itunesdb = _import_iop("iTunesDB_Writer").write_itunesdb
ok = iop_write_itunesdb( ok = iop_write_itunesdb(
self.mountpoint, self.mountpoint,
tracks, tracks,
@ -501,8 +518,8 @@ class Nano7Database:
# 5. Extract db_pid from the binary DB for SQLite cross-reference # 5. Extract db_pid from the binary DB for SQLite cross-reference
db_pid = 0 db_pid = 0
try: try:
from ipod_device import resolve_itdb_path ipod_dev = _import_iop("ipod_device")
cdb_path = resolve_itdb_path(self.mountpoint) cdb_path = ipod_dev.resolve_itdb_path(self.mountpoint)
if cdb_path: if cdb_path:
with open(cdb_path, "rb") as f: with open(cdb_path, "rb") as f:
hdr = f.read(0x20) hdr = f.read(0x20)
@ -514,6 +531,7 @@ class Nano7Database:
# 6. Write SQLite databases (Nano 6G/7G require this) # 6. Write SQLite databases (Nano 6G/7G require this)
try: try:
write_sqlite_databases = _import_iop("SQLiteDB_Writer").write_sqlite_databases
sqlite_ok = write_sqlite_databases( sqlite_ok = write_sqlite_databases(
ipod_path=self.mountpoint, ipod_path=self.mountpoint,
tracks=tracks, tracks=tracks,
@ -532,23 +550,26 @@ class Nano7Database:
def _write_empty_databases(self) -> None: def _write_empty_databases(self) -> None:
"""Write minimal empty databases when no tracks exist.""" """Write minimal empty databases when no tracks exist."""
empty_tracks: list[IopTrackInfo] = [] ipod_device = _import_iop("ipod_device")
iop_write_itunesdb = _import_iop("iTunesDB_Writer").write_itunesdb
write_sqlite_databases = _import_iop("SQLiteDB_Writer").write_sqlite_databases
try: try:
fwid = get_firewire_id(self.mountpoint) fwid = ipod_device.get_firewire_id(self.mountpoint)
except Exception: except Exception:
fwid = self.firewire_id fwid = self.firewire_id
capabilities = None capabilities = None
try: try:
dev = get_current_device() dev = ipod_device.get_current_device()
if dev and dev.model_family: if dev and dev.model_family:
capabilities = capabilities_for_family_gen( capabilities = ipod_device.capabilities_for_family_gen(
dev.model_family, dev.generation or "") dev.model_family, dev.generation or "")
except Exception: except Exception:
pass pass
iop_write_itunesdb(self.mountpoint, empty_tracks, firewire_id=fwid, iop_write_itunesdb(self.mountpoint, [], firewire_id=fwid,
backup=True, capabilities=capabilities) backup=True, capabilities=capabilities)
write_sqlite_databases( write_sqlite_databases(
ipod_path=self.mountpoint, tracks=empty_tracks, ipod_path=self.mountpoint, tracks=[],
master_playlist_name="iPod", master_playlist_name="iPod",
db_pid=random.getrandbits(64), db_pid=random.getrandbits(64),
capabilities=capabilities, firewire_id=fwid, backup=True, capabilities=capabilities, firewire_id=fwid, backup=True,