Add auto-compact LMDB schedule setting with UI and tests

Closes: https://github.com/damus-io/damus/issues/3718
Changelog-Added: Automatic lossless storage optimization
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit is contained in:
Daniel D’Aquino
2026-04-06 15:01:06 -07:00
parent bfad604e5b
commit 9285504d9a
4 changed files with 276 additions and 2 deletions
+119
View File
@@ -17,6 +17,9 @@ final class NdbCompactionTests: XCTestCase {
try FileManager.default.createDirectory(at: testDirectory, withIntermediateDirectories: true)
// Ensure the flag is cleared before each test.
UserDefaults.standard.set(false, forKey: Ndb.compact_on_next_launch_key)
// Reset scheduling-related keys so tests start from a clean state.
UserDefaults.standard.removeObject(forKey: Ndb.auto_compact_schedule_key)
UserDefaults.standard.removeObject(forKey: Ndb.last_compact_date_key)
}
override func tearDown() async throws {
@@ -25,6 +28,9 @@ final class NdbCompactionTests: XCTestCase {
}
// Leave the flag cleared after each test.
UserDefaults.standard.set(false, forKey: Ndb.compact_on_next_launch_key)
// Clean up scheduling keys.
UserDefaults.standard.removeObject(forKey: Ndb.auto_compact_schedule_key)
UserDefaults.standard.removeObject(forKey: Ndb.last_compact_date_key)
try await super.tearDown()
}
@@ -171,4 +177,117 @@ final class NdbCompactionTests: XCTestCase {
"lock.mdb should be recreated by LMDB after opening the compacted database"
)
}
// MARK: - AutoCompactSchedule: interval values
func testAutoCompactSchedule_intervalValues() {
XCTAssertEqual(AutoCompactSchedule.daily.interval, 60 * 60 * 24, "daily interval should be 24 hours")
XCTAssertEqual(AutoCompactSchedule.weekly.interval, 60 * 60 * 24 * 7, "weekly interval should be 7 days")
XCTAssertEqual(AutoCompactSchedule.monthly.interval, 60 * 60 * 24 * 30, "monthly interval should be 30 days")
XCTAssertNil(AutoCompactSchedule.never.interval, ".never should have no interval")
}
// MARK: - get/set auto-compact schedule
func testGetAutoCompactSchedule_returnsWeeklyByDefault() {
// No key stored default should be .weekly
XCTAssertEqual(Ndb.get_auto_compact_schedule(), .weekly)
}
func testSetAndGetAutoCompactSchedule_roundTrips() {
for schedule in AutoCompactSchedule.allCases {
Ndb.set_auto_compact_schedule(schedule)
XCTAssertEqual(Ndb.get_auto_compact_schedule(), schedule,
"Round-trip failed for schedule: \(schedule)")
}
}
// MARK: - schedule_auto_compact_if_needed
func testScheduleAutoCompact_doesNothingWhenNever() {
Ndb.set_auto_compact_schedule(.never)
Ndb.schedule_auto_compact_if_needed()
XCTAssertFalse(
UserDefaults.standard.bool(forKey: Ndb.compact_on_next_launch_key),
"schedule=never should never set the compact flag"
)
}
func testScheduleAutoCompact_setsFlag_whenIntervalHasElapsed() {
// Given: schedule is weekly, last compaction was 8 days ago
Ndb.set_auto_compact_schedule(.weekly)
let eightDaysAgo = Date().addingTimeInterval(-(60 * 60 * 24 * 8))
UserDefaults.standard.set(eightDaysAgo, forKey: Ndb.last_compact_date_key)
// When
Ndb.schedule_auto_compact_if_needed()
// Then: flag should be set
XCTAssertTrue(
UserDefaults.standard.bool(forKey: Ndb.compact_on_next_launch_key),
"compact flag should be set when scheduled interval has elapsed"
)
}
func testScheduleAutoCompact_doesNotSetFlag_whenIntervalHasNotElapsed() {
// Given: schedule is weekly, last compaction was 1 day ago
Ndb.set_auto_compact_schedule(.weekly)
let oneDayAgo = Date().addingTimeInterval(-(60 * 60 * 24))
UserDefaults.standard.set(oneDayAgo, forKey: Ndb.last_compact_date_key)
// When
Ndb.schedule_auto_compact_if_needed()
// Then: flag should NOT be set
XCTAssertFalse(
UserDefaults.standard.bool(forKey: Ndb.compact_on_next_launch_key),
"compact flag should not be set when the scheduled interval has not yet elapsed"
)
}
func testScheduleAutoCompact_setsFlag_whenNoLastCompactDate() {
// Given: schedule is daily, no last compact date stored (first launch)
Ndb.set_auto_compact_schedule(.daily)
// last_compact_date_key is absent (cleaned in setUp)
// When
Ndb.schedule_auto_compact_if_needed()
// Then: flag should be set (distantPast triggers compaction on first launch)
XCTAssertTrue(
UserDefaults.standard.bool(forKey: Ndb.compact_on_next_launch_key),
"compact flag should be set on first launch (no previous compaction date)"
)
}
// MARK: - compact_if_needed: records last compact date
func testCompactIfNeeded_recordsLastCompactDate_afterSuccessfulCompaction() {
let dbPath = testDirectory.appendingPathComponent("date_record_db").path
try? FileManager.default.createDirectory(atPath: dbPath, withIntermediateDirectories: true)
guard let ndb = Ndb(path: dbPath) else {
XCTFail("Could not open Ndb at \(dbPath)")
return
}
ndb.close()
XCTAssertNil(Ndb.get_last_compact_date(), "No last compact date should exist before first compaction")
Ndb.set_compact_on_next_launch()
let beforeCompact = Date()
Ndb.compact_if_needed(db_path: dbPath)
let afterCompact = Date()
guard let lastDate = Ndb.get_last_compact_date() else {
XCTFail("Last compact date should be set after successful compaction")
return
}
XCTAssertGreaterThanOrEqual(lastDate, beforeCompact, "last compact date should be >= time before compaction started")
XCTAssertLessThanOrEqual(lastDate, afterCompact, "last compact date should be <= time after compaction ended")
}
}