Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/nano-7g-firmware-1-0-1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@podkit/libgpod-node': patch
---

The iPod nano 7th generation on firmware 1.0.1 can now be written. That firmware lists only SQLite post-process command set 9, not set 25, so writes failed with "no command set 25"; libgpod now falls back to set 9 and skips carrying over track choice columns that set 9 does not create.
10 changes: 6 additions & 4 deletions tools/nano7/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@ Audiobooks, Rentals) in `Library.itdb` and in the `iTunesCDB` dataset 5,
whose header fields are kept as read, and iTunes' schema level: its
additional columns, `user_version` 26 (`Library.itdb`) and 2
(`Genius.itdb`) and the `version_info` row. The device's SQLite post-process
command set 25 completes that schema. A missing set, a missing or failing
command, or an insert or commit that fails while the databases are generated
fails the write and leaves the library unchanged. A nano 5th generation keeps
libgpod's previous layout.
command set 25 completes that schema. Firmware 1.0.1 lists only its
predecessor, set 9, which lacks the track choice columns and
`FakePragmaITunes26`; set 9 is used when set 25 is absent. A missing set, a
missing or failing command, or an insert or commit that fails while the
databases are generated fails the write and leaves the library unchanged. A
nano 5th generation keeps libgpod's previous layout.

A write keeps what a nano 6th/7th generation recorded in `Dynamic.itdb`.
Plays, skips, ratings, bookmarks, recent counts and their dates become the
Expand Down
19 changes: 19 additions & 0 deletions tools/nano7/test_libgpod.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,25 @@ def test_nano_post_process_commands_are_required(self):
self.assertIn("post-process commands", result.stderr)
self.assertEqual(self.snapshot(), library)

def test_nano_firmware_1_0_1_command_set_9(self):
# Firmware 1.0.1 lists only set 9: set 25 without its later commands,
# and device update level 1107. It has no track choice columns.
sets, sql = self.commands["UserVersionCommandSets"], self.commands["SQLCommands"]
later = {"CreateIsHomeVideo", "FakePragmaITunes26", "UpdateDeviceUpdateLevelTo1109"}
names = [name for name in sets["25"]["Commands"]
if name not in later and "ItemStatsAudio" not in name and "ItemStatsSubtitle" not in name]
set_9 = dict(sets["25"], Commands=names + ["UpdateDeviceUpdateLevelTo1107"])
self.setup_synthetic("MC526", 5, dict(
self.commands, UserVersionCommandSets={"9": set_9},
SQLCommands=dict(sql, UpdateDeviceUpdateLevelTo1107=
"UPDATE version_info SET device_update_level = 1107;")))
self.run_helper("create")
self.run_helper("resave")
self.check_library(1)
self.assertEqual(rows(self.db("Library"), "SELECT device_update_level FROM version_info"),
[(1107,)])
self.assertNotIn("audio_language", columns(self.db("Dynamic"), "item_stats"))

def test_nano_runs_the_first_genre_count_command(self):
# Set 25 lists AddGenreMapCompilationCount twice; only the repeat is skipped.
sql = dict(self.commands["SQLCommands"])
Expand Down
25 changes: 18 additions & 7 deletions tools/prebuild/libgpod-nano.patch
Original file line number Diff line number Diff line change
Expand Up @@ -955,9 +955,9 @@
while (subnode) {
unsigned long int intval = strtoul(key, NULL, 0);
- if ((intval > 0) && (intval > maxver)) {
+ /* nano 6G/7G: set 25 fits libgpod's schema plus
+ Library_nano_columns and raises it to iTunes' level 26 */
+ if (nano ? (intval == 25) : ((intval > 0) && (intval > maxver))) {
+ /* nano 6G/7G: set 25 fits libgpod's schema plus Library_nano_columns;
+ firmware 1.0.1 lists only its predecessor, set 9 */
+ if (nano ? (intval == 25 || (intval == 9 && maxver != 25)) : ((intval > 0) && (intval > maxver))) {
user_ver_cmds = subnode;
maxver = intval;
}
Expand Down Expand Up @@ -1094,7 +1094,7 @@

if (!mk_Locations_cbk(itdb, outpath)) {
g_set_error (error, ITDB_ERROR, ITDB_ERROR_SQLITE,
@@ -2250,6 +2476,187 @@ static void rmdir_recursive(gchar *path)
@@ -2250,6 +2476,198 @@ static void rmdir_recursive(gchar *path)
g_rmdir(path);
}

Expand Down Expand Up @@ -1241,6 +1241,17 @@
+ g_free(itlpdir);
+}
+
+/* Command set 9 (nano 7G firmware 1.0.1) creates no track choice columns */
+static gboolean has_track_choices(const char *dbf)
+{
+ sqlite3 *db = NULL;
+ gboolean present = sqlite3_open(dbf, &db) == SQLITE_OK
+ && sqlite3_exec(db, "SELECT audio_language FROM item_stats LIMIT 0;", NULL, NULL, NULL) == SQLITE_OK;
+
+ sqlite3_close(db);
+ return present;
+}
+
+/* iPod nano 6G/7G: keep the audio and subtitle track choices the device
+ recorded for items still in the library; the post-process commands
+ reset them. */
Expand All @@ -1258,7 +1269,7 @@
+ "the plays, ratings and bookmarks it holds; remove it to write without them",
+ device_file);
+ res = -1;
+ } else if (g_file_test(device_file, G_FILE_TEST_EXISTS)) {
+ } else if (g_file_test(device_file, G_FILE_TEST_EXISTS) && has_track_choices(dbf)) {
+ /* The columns are unqualified: one that the device's item_stats
+ lacks names the new row's own, which keeps its value. */
+ char *sql = sqlite3_mprintf("ATTACH DATABASE %Q AS device;"
Expand All @@ -1282,15 +1293,15 @@
int itdb_sqlite_generate_itdbs(FExport *fexp)
{
int res = 0;
@@ -2277,6 +2684,7 @@ int itdb_sqlite_generate_itdbs(FExport *fexp)
@@ -2277,6 +2695,7 @@ int itdb_sqlite_generate_itdbs(FExport *fexp)
g_assert(fexp->itdb->playlists != NULL);

tzoffset = fexp->itdb->tzoffset;
+ utc_dates = itdb_device_is_hashab_nano(fexp->itdb->device);

tmpdir = g_build_path(g_get_tmp_dir(), tmpnam(NULL), NULL);
if (g_mkdir(tmpdir, 0755) != 0) {
@@ -2289,7 +2697,8 @@ int itdb_sqlite_generate_itdbs(FExport *fexp)
@@ -2289,7 +2708,8 @@ int itdb_sqlite_generate_itdbs(FExport *fexp)

/* generate itdb files in temporary directory */
if (build_itdb_files(fexp->itdb, fexp->albums, fexp->artists, fexp->composers, tmpdir,
Expand Down