From 4f7bd2709cb6997436798d73c5145683965d92e2 Mon Sep 17 00:00:00 2001 From: Maksim Totmin Date: Sun, 31 May 2026 21:03:11 +0700 Subject: [PATCH] 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. --- src/ipod_nano7_db.py | 79 ++++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/src/ipod_nano7_db.py b/src/ipod_nano7_db.py index 3737a49..e84452a 100644 --- a/src/ipod_nano7_db.py +++ b/src/ipod_nano7_db.py @@ -20,6 +20,7 @@ Public API preserved for main.py compatibility: delete_orphaned_files(paths) — remove orphan files """ +import importlib import os import sys import hashlib @@ -31,25 +32,38 @@ import struct import time 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.easyid3 import EasyID3 -from iTunesDB_Writer.mhit_writer import TrackInfo as IopTrackInfo -from iTunesDB_Writer.mhyp_writer import PlaylistInfo -from iTunesDB_Writer import write_itunesdb as iop_write_itunesdb -from SQLiteDB_Writer import write_sqlite_databases -from ipod_device import ( - DeviceCapabilities, - ChecksumType, - get_firewire_id, - capabilities_for_family_gen, - get_current_device, -) +_IOP_ROOT = "/tmp/iOpenPod" + + +def _import_iop(module_name: str): + """Import a module from iOpenPod, bypassing any locally-cached conflict. + + main.py imports our local ``ipod_device`` first, which caches it in + ``sys.modules``. When ipod_nano7_db later tries ``from ipod_device + import ChecksumType``, Python returns the cached *local* module + (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__) @@ -165,9 +179,10 @@ def _read_audio_tags(filepath: str) -> dict[str, Any]: 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.""" - tracks: list[IopTrackInfo] = [] + IopTrackInfo = _import_iop("iTunesDB_Writer.mhit_writer").TrackInfo + tracks: list = [] for s in scanned: fpath = s["file_path"] fname = os.path.basename(fpath) @@ -467,11 +482,12 @@ class Nano7Database: logger.info("Built %d iOpenPod TrackInfo objects", len(tracks)) # 3. Determine capabilities for this device + ipod_device = _import_iop("ipod_device") capabilities = None try: - dev = get_current_device() + dev = ipod_device.get_current_device() if dev and dev.model_family: - capabilities = capabilities_for_family_gen( + capabilities = ipod_device.capabilities_for_family_gen( dev.model_family, dev.generation or "", ) if capabilities: @@ -481,10 +497,11 @@ class Nano7Database: # 4. Generate and write binary iTunesDB first (to get db_pid) try: - firewire_id = get_firewire_id(self.mountpoint) + firewire_id = ipod_device.get_firewire_id(self.mountpoint) except Exception: firewire_id = self.firewire_id + iop_write_itunesdb = _import_iop("iTunesDB_Writer").write_itunesdb ok = iop_write_itunesdb( self.mountpoint, tracks, @@ -501,8 +518,8 @@ class Nano7Database: # 5. Extract db_pid from the binary DB for SQLite cross-reference db_pid = 0 try: - from ipod_device import resolve_itdb_path - cdb_path = resolve_itdb_path(self.mountpoint) + ipod_dev = _import_iop("ipod_device") + cdb_path = ipod_dev.resolve_itdb_path(self.mountpoint) if cdb_path: with open(cdb_path, "rb") as f: hdr = f.read(0x20) @@ -514,6 +531,7 @@ class Nano7Database: # 6. Write SQLite databases (Nano 6G/7G require this) try: + write_sqlite_databases = _import_iop("SQLiteDB_Writer").write_sqlite_databases sqlite_ok = write_sqlite_databases( ipod_path=self.mountpoint, tracks=tracks, @@ -532,23 +550,26 @@ class Nano7Database: def _write_empty_databases(self) -> None: """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: - fwid = get_firewire_id(self.mountpoint) + fwid = ipod_device.get_firewire_id(self.mountpoint) except Exception: fwid = self.firewire_id capabilities = None try: - dev = get_current_device() + dev = ipod_device.get_current_device() if dev and dev.model_family: - capabilities = capabilities_for_family_gen( + capabilities = ipod_device.capabilities_for_family_gen( dev.model_family, dev.generation or "") except Exception: pass - iop_write_itunesdb(self.mountpoint, empty_tracks, firewire_id=fwid, + iop_write_itunesdb(self.mountpoint, [], firewire_id=fwid, backup=True, capabilities=capabilities) write_sqlite_databases( - ipod_path=self.mountpoint, tracks=empty_tracks, + ipod_path=self.mountpoint, tracks=[], master_playlist_name="iPod", db_pid=random.getrandbits(64), capabilities=capabilities, firewire_id=fwid, backup=True,