diff --git a/Conjuga/Conjuga/Services/DataLoader.swift b/Conjuga/Conjuga/Services/DataLoader.swift index 10bbbd4..f2db7c2 100644 --- a/Conjuga/Conjuga/Services/DataLoader.swift +++ b/Conjuga/Conjuga/Services/DataLoader.swift @@ -9,7 +9,7 @@ actor DataLoader { static let textbookDataVersion = 14 static let textbookDataKey = "textbookDataVersion" - static let bookDataVersion = 2 // bump: vocab
  • bullets now extracted + static let bookDataVersion = 3 // bump: robust bundle lookup with explicit-slug fallback static let bookDataKey = "bookDataVersion" /// Quick check: does the DB need seeding or course data refresh? @@ -163,7 +163,10 @@ actor DataLoader { let shared = UserDefaults.standard let context = ModelContext(container) let existingCount = (try? context.fetchCount(FetchDescriptor())) ?? 0 - let versionCurrent = shared.integer(forKey: bookDataKey) >= bookDataVersion + let storedVersion = shared.integer(forKey: bookDataKey) + let versionCurrent = storedVersion >= bookDataVersion + + print("[DataLoader] refreshBooksDataIfNeeded: existing=\(existingCount) stored=\(storedVersion) target=\(bookDataVersion) versionCurrent=\(versionCurrent)") if versionCurrent && existingCount > 0 { return } @@ -182,7 +185,9 @@ actor DataLoader { if seedBooks(context: context) { shared.set(bookDataVersion, forKey: bookDataKey) - print("Book data re-seeded to version \(bookDataVersion)") + print("[DataLoader] Book data re-seeded to version \(bookDataVersion)") + } else { + print("[DataLoader] Book reseed produced no rows — leaving version key untouched") } } @@ -637,18 +642,45 @@ actor DataLoader { return true } - /// Find every `book_*.json` resource in the app bundle. + /// Slugs of books bundled with the app. Kept explicit so device installs + /// don't depend on `Bundle.urls(forResourcesWithExtension:subdirectory:)` + /// successfully enumerating the bundle — that API has been observed to + /// return empty for some iOS configurations even when the resource is + /// present, matching the same `bundleURL.appendingPathComponent` fallback + /// used by the textbook seed. + private static let bundledBookSlugs: [String] = [ + "olly-vol2", + ] + + /// Resolve URLs for every bundled book. Uses the explicit-slug fast path + /// first (mirrors `seedTextbookData`'s lookup pattern), then falls back to + /// directory enumeration so newly-bundled books are picked up without a + /// code change. private static func bundledBookJSONURLs() -> [URL] { var seen = Set() var out: [URL] = [] let bundle = Bundle.main - for ext in ["json"] { - if let urls = bundle.urls(forResourcesWithExtension: ext, subdirectory: nil) { - for url in urls where url.lastPathComponent.hasPrefix("book_") { - if seen.insert(url.lastPathComponent).inserted { out.append(url) } + + for slug in bundledBookSlugs { + let filename = "book_\(slug).json" + let url = bundle.url(forResource: "book_\(slug)", withExtension: "json") + ?? bundle.bundleURL.appendingPathComponent(filename) + if FileManager.default.fileExists(atPath: url.path), + seen.insert(filename).inserted { + out.append(url) + } + } + + if let urls = bundle.urls(forResourcesWithExtension: "json", subdirectory: nil) { + for url in urls where url.lastPathComponent.hasPrefix("book_") { + if seen.insert(url.lastPathComponent).inserted { + out.append(url) } } } + + let names = out.map(\.lastPathComponent).joined(separator: ", ") + print("[DataLoader] bundledBookJSONURLs found \(out.count) files: [\(names)]") return out.sorted { $0.lastPathComponent < $1.lastPathComponent } }