From dda91720c359644d86bd1944f705fb2b1345524b Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Fri, 18 Sep 2026 20:03:57 +0700 Subject: [PATCH] fix(ios): clear leftover sample journal files before installing the sample --- .../Platform/SampleDatabaseInstaller.swift | 26 +++++++++++++------ .../SampleDatabaseInstallerTests.swift | 22 ++++++++++++++-- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/TableProMobile/TableProMobile/Platform/SampleDatabaseInstaller.swift b/TableProMobile/TableProMobile/Platform/SampleDatabaseInstaller.swift index 6fdc20502..3f624a92d 100644 --- a/TableProMobile/TableProMobile/Platform/SampleDatabaseInstaller.swift +++ b/TableProMobile/TableProMobile/Platform/SampleDatabaseInstaller.swift @@ -45,6 +45,7 @@ nonisolated struct SampleDatabaseInstaller: Sendable { func installIfNeeded() throws -> URL { let installed = installedURL guard !FileManager.default.fileExists(atPath: installed.path) else { return installed } + try removeSidecars(of: installed) try copyBundledFile(to: installed) Self.logger.info("Installed the sample database") return installed @@ -53,19 +54,28 @@ nonisolated struct SampleDatabaseInstaller: Sendable { @discardableResult func reset() throws -> URL { let installed = installedURL - for url in [installed] + Self.sidecarSuffixes.map({ URL(fileURLWithPath: installed.path + $0) }) { - guard FileManager.default.fileExists(atPath: url.path) else { continue } - do { - try FileManager.default.removeItem(at: url) - } catch { - throw SampleDatabaseError.copyFailed(message: error.localizedDescription) - } - } + try removeItemIfPresent(at: installed) + try removeSidecars(of: installed) try copyBundledFile(to: installed) Self.logger.info("Reset the sample database") return installed } + private func removeSidecars(of database: URL) throws { + for suffix in Self.sidecarSuffixes { + try removeItemIfPresent(at: URL(fileURLWithPath: database.path + suffix)) + } + } + + private func removeItemIfPresent(at url: URL) throws { + guard FileManager.default.fileExists(atPath: url.path) else { return } + do { + try FileManager.default.removeItem(at: url) + } catch { + throw SampleDatabaseError.copyFailed(message: error.localizedDescription) + } + } + private func copyBundledFile(to destination: URL) throws { guard let bundledURL else { Self.logger.error("Chinook.sqlite is not in the app bundle") diff --git a/TableProMobile/TableProMobileTests/Onboarding/SampleDatabaseInstallerTests.swift b/TableProMobile/TableProMobileTests/Onboarding/SampleDatabaseInstallerTests.swift index 6d02e3fca..db8ae1c1c 100644 --- a/TableProMobile/TableProMobileTests/Onboarding/SampleDatabaseInstallerTests.swift +++ b/TableProMobile/TableProMobileTests/Onboarding/SampleDatabaseInstallerTests.swift @@ -4,6 +4,8 @@ import Testing @Suite("Sample database installer") struct SampleDatabaseInstallerTests { + private static let sqliteSidecarSuffixes = ["-journal", "-wal", "-shm"] + private let directory: URL private let bundled: URL @@ -38,14 +40,30 @@ struct SampleDatabaseInstallerTests { let installer = SampleDatabaseInstaller(bundledURL: bundled, directory: directory) let installed = try installer.installIfNeeded() try Data("edited".utf8).write(to: installed) - for suffix in SampleDatabaseInstaller.sidecarSuffixes { + for suffix in Self.sqliteSidecarSuffixes { try Data("stale".utf8).write(to: URL(fileURLWithPath: installed.path + suffix)) } try installer.reset() #expect(try String(contentsOf: installed, encoding: .utf8) == "original") - for suffix in SampleDatabaseInstaller.sidecarSuffixes { + for suffix in Self.sqliteSidecarSuffixes { + #expect(!FileManager.default.fileExists(atPath: installed.path + suffix)) + } + } + + @Test("Installing over journal files a failed reset left behind removes them first") + func installRemovesOrphanedSidecars() throws { + let installer = SampleDatabaseInstaller(bundledURL: bundled, directory: directory) + try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) + for suffix in Self.sqliteSidecarSuffixes { + try Data("stale".utf8).write(to: URL(fileURLWithPath: installer.installedURL.path + suffix)) + } + + let installed = try installer.installIfNeeded() + + #expect(try String(contentsOf: installed, encoding: .utf8) == "original") + for suffix in Self.sqliteSidecarSuffixes { #expect(!FileManager.default.fileExists(atPath: installed.path + suffix)) } }