From cc429eed31a552aa6411a77c164345efe20953e8 Mon Sep 17 00:00:00 2001 From: Maksim Totmin Date: Sun, 31 May 2026 18:31:26 +0700 Subject: [PATCH] Add _ensure_schema() to create SQLite tables if missing - Creates all required tables (item, artist, album, avformat_info, store_info, item_to_container, track_size_calc) in Library.itdb - Creates location table in Locations.itdb - Called from Nano7Database.__init__(), so empty DB is always valid - Fixes 'no such table: item' error on iPod with wiped DB --- src/ipod_nano7_db.py | 89 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/src/ipod_nano7_db.py b/src/ipod_nano7_db.py index a4edca6..7feb1cc 100644 --- a/src/ipod_nano7_db.py +++ b/src/ipod_nano7_db.py @@ -31,6 +31,76 @@ logger = logging.getLogger(__name__) MASTER_CONTAINER_PID = 203092939621887772 # "iPod" master playlist +LIBRARY_SCHEMA_SQL = """ +CREATE TABLE IF NOT EXISTS item ( + pid INTEGER PRIMARY KEY, media_kind INTEGER, is_song INTEGER, + date_modified INTEGER, year INTEGER, is_compilation INTEGER, + is_user_disabled INTEGER, remember_bookmark INTEGER, + exclude_from_shuffle INTEGER, part_of_gapless_album INTEGER, + chosen_by_auto_fill INTEGER, artwork_status INTEGER, + artwork_cache_id INTEGER, total_time_ms INTEGER, + track_number INTEGER, track_count INTEGER, disc_number INTEGER, + disc_count INTEGER, album_pid INTEGER, artist_pid INTEGER, + title TEXT, artist TEXT, album TEXT, album_artist TEXT, + sort_title TEXT, sort_artist TEXT, sort_album TEXT, + sort_album_artist TEXT, title_order INTEGER, artist_order INTEGER, + album_order INTEGER, physical_order INTEGER +); + +CREATE TABLE IF NOT EXISTS artist ( + pid INTEGER PRIMARY KEY, kind INTEGER, artwork_status INTEGER, + artwork_album_pid INTEGER, name TEXT, name_order INTEGER, + sort_name TEXT, is_unknown INTEGER, has_songs INTEGER, + has_music_videos INTEGER, has_non_compilation_tracks INTEGER, + album_count INTEGER +); + +CREATE TABLE IF NOT EXISTS album ( + pid INTEGER PRIMARY KEY, kind INTEGER, artwork_status INTEGER, + artwork_item_pid INTEGER, artist_pid INTEGER, user_rating INTEGER, + name TEXT, name_order INTEGER, all_compilations INTEGER, + feed_url TEXT, season_number INTEGER, is_unknown INTEGER, + has_songs INTEGER, has_music_videos INTEGER, sort_order INTEGER, + artist_order INTEGER, has_any_compilations INTEGER, sort_name TEXT, + artist_count_calc INTEGER, has_movies INTEGER, item_count INTEGER, + min_volume_normalization_energy INTEGER +); + +CREATE TABLE IF NOT EXISTS avformat_info ( + item_pid INTEGER, sub_id INTEGER, audio_format INTEGER, + bit_rate INTEGER, channels INTEGER, sample_rate INTEGER, + duration INTEGER, gapless_heuristic_info INTEGER, + gapless_encoding_delay INTEGER, gapless_encoding_drain INTEGER, + gapless_last_frame_resynch INTEGER, analysis_inhibit_flags INTEGER, + audio_fingerprint INTEGER, volume_normalization_energy INTEGER +); + +CREATE TABLE IF NOT EXISTS store_info ( + item_pid INTEGER PRIMARY KEY +); + +CREATE TABLE IF NOT EXISTS item_to_container ( + item_pid INTEGER, container_pid INTEGER, physical_order INTEGER, + shuffle_order INTEGER +); + +CREATE TABLE IF NOT EXISTS track_size_calc ( + kind TEXT PRIMARY KEY, size INTEGER DEFAULT 0 +); + +INSERT OR IGNORE INTO track_size_calc (kind, size) VALUES ('audio', 0); +""" + +LOCATIONS_SCHEMA_SQL = """ +CREATE TABLE IF NOT EXISTS location ( + item_pid INTEGER, sub_id INTEGER, base_location_id INTEGER, + location_type INTEGER, location TEXT, extension INTEGER, + kind_id INTEGER, date_created INTEGER, file_size INTEGER, + file_creator INTEGER, file_type INTEGER, + num_dir_levels_file INTEGER, num_dir_levels_lib INTEGER +); +""" + class Nano7Database: """Manages iPod Nano 7 SQLite database and iTunesCDB""" @@ -62,6 +132,25 @@ class Nano7Database: ".mp4": struct.unpack(">I", b"MP4 ")[0], } + self._ensure_schema() + + def _ensure_schema(self) -> None: + """Create required tables in Library.itdb and Locations.itdb if missing.""" + sqlite_path = self.sqlite_path + locations_path = self.locations_path + + for db_path, schema in [(sqlite_path, LIBRARY_SCHEMA_SQL), + (locations_path, LOCATIONS_SCHEMA_SQL)]: + try: + os.makedirs(os.path.dirname(db_path), exist_ok=True) + conn = sqlite3.connect(db_path) + conn.executescript(schema) + conn.commit() + conn.close() + logger.info(f"Schema ensured in {os.path.basename(db_path)}") + except Exception as e: + logger.warning(f"Failed to ensure schema in {db_path}: {e}") + def _detect_firewire_id(self) -> bytes: """Parse FirewireGuid from SysInfo file on the iPod.""" sysinfo_path = os.path.join(