Skip to content
Merged
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
10 changes: 0 additions & 10 deletions .claude/ralph-loop.local.md

This file was deleted.

16 changes: 16 additions & 0 deletions Arithmetic.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
8A2AE91B2E06FFC900A99591 /* MultiplicationTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A2AE91C2E06FFC900A99591 /* MultiplicationTableView.swift */; };
8A2AE91D2E06FFC900A99591 /* AboutMeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A2AE91E2E06FFC900A99591 /* AboutMeView.swift */; };
8A2AE91F2E06FFC900A99591 /* OtherOptionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A2AE9202E06FFC900A99591 /* OtherOptionsView.swift */; };
8A600A332F10930500B4AAD0 /* SoundEffectsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A600A322F10930500B4AAD0 /* SoundEffectsHelper.swift */; };
8A600A342F10930500B4AAD0 /* HapticFeedbackHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A600A312F10930500B4AAD0 /* HapticFeedbackHelper.swift */; };
8A600A362F10931C00B4AAD0 /* ConfettiCelebrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A600A352F10931C00B4AAD0 /* ConfettiCelebrationView.swift */; };
8A600A382F10932400B4AAD0 /* Color+Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A600A372F10932400B4AAD0 /* Color+Theme.swift */; };
8A87B6212EF67C5800366D4D /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = 8A87B6202EF67C5800366D4D /* FirebaseAnalytics */; };
8A87B6232EF67C5800366D4D /* FirebaseCore in Frameworks */ = {isa = PBXBuildFile; productRef = 8A87B6222EF67C5800366D4D /* FirebaseCore */; };
8A87B6252EF67C5800366D4D /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = 8A87B6242EF67C5800366D4D /* FirebaseCrashlytics */; };
Expand Down Expand Up @@ -71,6 +75,10 @@
8A2AE91C2E06FFC900A99591 /* MultiplicationTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiplicationTableView.swift; sourceTree = "<group>"; };
8A2AE91E2E06FFC900A99591 /* AboutMeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutMeView.swift; sourceTree = "<group>"; };
8A2AE9202E06FFC900A99591 /* OtherOptionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherOptionsView.swift; sourceTree = "<group>"; };
8A600A312F10930500B4AAD0 /* HapticFeedbackHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticFeedbackHelper.swift; sourceTree = "<group>"; };
8A600A322F10930500B4AAD0 /* SoundEffectsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoundEffectsHelper.swift; sourceTree = "<group>"; };
8A600A352F10931C00B4AAD0 /* ConfettiCelebrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfettiCelebrationView.swift; sourceTree = "<group>"; };
8A600A372F10932400B4AAD0 /* Color+Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Theme.swift"; sourceTree = "<group>"; };
8A8AEBC72EBB7FDE008D53E6 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = "<group>"; };
8A8FA9B02EA36B730075A3D1 /* SystemInfoManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemInfoManager.swift; sourceTree = "<group>"; };
8A8FA9B22EA36BA90075A3D1 /* SystemInfoComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemInfoComponents.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -171,6 +179,7 @@
8AF3AB8F2E05AE3600F67CDE /* Extensions */ = {
isa = PBXGroup;
children = (
8A600A372F10932400B4AAD0 /* Color+Theme.swift */,
8AD2D9502E77175B004603C4 /* View+Navigation.swift */,
8AF3AB8C2E05AE3600F67CDE /* CGFloat+Adaptive.swift */,
8AF3AB8D2E05AE3600F67CDE /* Font+Adaptive.swift */,
Expand Down Expand Up @@ -200,6 +209,8 @@
8AF3AB9C2E05AE3600F67CDE /* Utils */ = {
isa = PBXGroup;
children = (
8A600A312F10930500B4AAD0 /* HapticFeedbackHelper.swift */,
8A600A322F10930500B4AAD0 /* SoundEffectsHelper.swift */,
8A0C5AEE2EC9C9510020200A /* MathBankPDFGenerator.swift */,
8A8FA9B02EA36B730075A3D1 /* SystemInfoManager.swift */,
8AF18A482E8BB16A007B05D0 /* ProgressViewUtils.swift */,
Expand All @@ -224,6 +235,7 @@
8AF3ABA32E05AE3600F67CDE /* Views */ = {
isa = PBXGroup;
children = (
8A600A352F10931C00B4AAD0 /* ConfettiCelebrationView.swift */,
8AFF11772EEE7AC300CA7987 /* QrCodeToolView.swift */,
8A0C5AEC2EC9C92D0020200A /* MathBankView.swift */,
8A0C5AE02EC8D88E0020200A /* SettingsView.swift */,
Expand Down Expand Up @@ -357,6 +369,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8A600A332F10930500B4AAD0 /* SoundEffectsHelper.swift in Sources */,
8A600A342F10930500B4AAD0 /* HapticFeedbackHelper.swift in Sources */,
8AF3ABA42E05AE3600F67CDE /* ContentView.swift in Sources */,
8A2AE9132E06A88A00A99591 /* GameProgressManager.swift in Sources */,
8A8FA9B32EA36BA90075A3D1 /* SystemInfoComponents.swift in Sources */,
Expand All @@ -378,6 +392,8 @@
8AF3ABAB2E05AE3600F67CDE /* LanguageSelectorView.swift in Sources */,
8A0C5AED2EC9C92D0020200A /* MathBankView.swift in Sources */,
8AF3ABAC2E05AE3600F67CDE /* CGFloat+Adaptive.swift in Sources */,
8A600A362F10931C00B4AAD0 /* ConfettiCelebrationView.swift in Sources */,
8A600A382F10932400B4AAD0 /* Color+Theme.swift in Sources */,
8ABE41362EA0778E007EC146 /* FormulaGuideView.swift in Sources */,
8AF3ABAD2E05AE3600F67CDE /* Font+Adaptive.swift in Sources */,
8AD2D94F2E77174C004603C4 /* CachedAsyncImageView.swift in Sources */,
Expand Down
121 changes: 91 additions & 30 deletions CoreData/CoreDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,59 @@ import CoreData

class CoreDataManager {
static let shared = CoreDataManager()


private var isInitialized = false
private var initializationError: Error?

private init() {
// 确保CoreData模型在初始化时创建
setupCoreDataStack()

// 迁移现有数据
migrateExistingData()

// Only migrate if initialization succeeded
if isInitialized {
// 迁移现有数据
migrateExistingData()
}
}

private func setupCoreDataStack() {
// 初始化CoreData堆栈
_ = persistentContainer

// If initialization failed, attempt recovery
if initializationError != nil, let storeURL = getStoreURL() {
print("Core Data initialization had error, attempting recovery...")
resetStore(at: storeURL)
}
}


private func getStoreURL() -> URL? {
let storeURL = persistentContainer.persistentStoreCoordinator.persistentStores.first?.url

// If no store exists yet, compute the default URL
if storeURL == nil {
let directory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
return directory?.appendingPathComponent("ArithmeticModel.sqlite")
}

return storeURL
}

// 迁移现有数据以适应模型更改
private func migrateExistingData() {
guard isInitialized else {
print("Cannot migrate: Core Data not initialized")
return
}

// 检查是否有需要迁移的错题
let fetchRequest: NSFetchRequest<WrongQuestionEntity> = WrongQuestionEntity.fetchRequest()

do {
let existingQuestions = try context.fetch(fetchRequest)

// 为现有错题添加解析方法和步骤
for question in existingQuestions {
// 尝试 accessing solutionMethod property - it should not throw an error
// So we don't need the do-catch block
// Ensure that each question has solutionMethod and solutionSteps
if question.solutionMethod.isEmpty {
if let questionObj = question.toQuestion() {
Expand All @@ -41,47 +68,53 @@ class CoreDataManager {
}
}
}

// 保存更改
if context.hasChanges {
try context.save()
print("成功迁移 \(existingQuestions.count) 个错题")
}
} catch {
print("迁移数据时出错: \(error)")

// 如果迁移失败,尝试重置数据库
resetCoreDataStore()
if let storeURL = getStoreURL() {
resetStore(at: storeURL)
}
}
}

// 重置Core Data存储(如果迁移失败)
private func resetCoreDataStore() {
private func resetStore(at storeURL: URL) {
print("尝试重置Core Data存储...")

// 获取持久化存储的URL
guard let storeURL = persistentContainer.persistentStoreCoordinator.persistentStores.first?.url else {
print("无法获取持久化存储URL")
return
}


do {
// 删除所有持久化存储
for store in persistentContainer.persistentStoreCoordinator.persistentStores {
try persistentContainer.persistentStoreCoordinator.remove(store)
}
// 删除存储文件

// 删除存储文件 and related files
try FileManager.default.removeItem(at: storeURL)


// Remove WAL and SHM files if they exist
let walURL = storeURL.deletingLastPathComponent().appendingPathComponent(storeURL.lastPathComponent + "-wal")
let shmURL = storeURL.deletingLastPathComponent().appendingPathComponent(storeURL.lastPathComponent + "-shm")

try? FileManager.default.removeItem(at: walURL)
try? FileManager.default.removeItem(at: shmURL)

// 重新加载持久化存储
try persistentContainer.persistentStoreCoordinator.addPersistentStore(
ofType: NSSQLiteStoreType,
configurationName: nil,
at: storeURL,
options: nil
options: [
NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true
]
)

print("Core Data存储已重置")
} catch {
print("重置Core Data存储失败: \(error)")
Expand Down Expand Up @@ -267,14 +300,31 @@ class CoreDataManager {
lazy var persistentContainer: NSPersistentContainer = {
// 创建内存中的模型
let model = createManagedObjectModel()

// 创建持久化容器
let container = NSPersistentContainer(name: "ArithmeticModel", managedObjectModel: model)

// Configure persistent store descriptions with migration options
let storeDescription = NSPersistentStoreDescription()
storeDescription.type = NSSQLiteStoreType
storeDescription.shouldInferMappingModelAutomatically = true
storeDescription.shouldMigrateStoreAutomatically = true

container.persistentStoreDescriptions = [storeDescription]

// Load persistent stores with proper error handling
container.loadPersistentStores { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
print("Core Data store loading failed: \(error), \(error.userInfo)")
// Don't use fatalError - instead set the error for later handling
self.initializationError = error
self.isInitialized = false
} else {
self.isInitialized = true
print("Core Data store loaded successfully at: \(storeDescription.url?.absoluteString ?? "unknown")")
}
}

return container
}()

Expand All @@ -283,12 +333,23 @@ class CoreDataManager {
}

func saveContext() {
guard isInitialized else {
print("Cannot save: Core Data not initialized")
return
}

if context.hasChanges {
do {
try context.save()
print("Context saved successfully")
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
print("Failed to save context: \(nserror), \(nserror.userInfo)")

// Attempt recovery by resetting the store
if let storeURL = getStoreURL() {
resetStore(at: storeURL)
}
}
}
}
Expand Down
Loading
Loading