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 }
}