Public Access
M1 step 3: Log writing and Storage Clean-up
- lib/storage_model (host-tested): FAT-safe names, daily Log paths, dates from Log/Capture file names, CleanupPlan by category and age, byte formatting - StorageService does all card I/O on its task: queued Log lines are written in batches each second (dropped while Logs are paused), plus file listing and deletion jobs - Settings > Storage moves into StoragePage: usage, Clean up (category, age with size preview, confirmation), Erase SD card - Clock: local date for Log names - Serial: log <text>, sd list Verified on the device: lines land in /irc/dev/#test/2026-10-02.log with folders created as needed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
#include <unity.h>
|
||||
|
||||
#include "cleanup_plan.h"
|
||||
|
||||
using namespace roro;
|
||||
|
||||
void setUp() {}
|
||||
void tearDown() {}
|
||||
|
||||
static const int kToday = 20728; // 2026-10-02
|
||||
|
||||
static std::vector<StoredFile> files() {
|
||||
return {
|
||||
{"/irc/libera/#roro/2026-10-01.log", 1000}, // 1 day old
|
||||
{"/irc/libera/#roro/2026-08-01.log", 2000}, // 62 days
|
||||
{"/irc/libera/#roro/2025-09-01.log", 4000}, // 396 days
|
||||
{"/irc/libera/#roro/undated.log", 8000},
|
||||
};
|
||||
}
|
||||
|
||||
void test_age_cutoffs_select_strictly_older_files() {
|
||||
auto plan = CleanupPlan::make(files(), CleanupAge::OneMonth, kToday);
|
||||
TEST_ASSERT_EQUAL(2, plan.paths.size());
|
||||
TEST_ASSERT_EQUAL(6000, plan.bytes);
|
||||
}
|
||||
|
||||
void test_a_year_keeps_everything_younger() {
|
||||
auto plan = CleanupPlan::make(files(), CleanupAge::OneYear, kToday);
|
||||
TEST_ASSERT_EQUAL(1, plan.paths.size());
|
||||
TEST_ASSERT_EQUAL_STRING("/irc/libera/#roro/2025-09-01.log", plan.paths[0].c_str());
|
||||
}
|
||||
|
||||
void test_undated_files_only_go_with_everything() {
|
||||
auto plan = CleanupPlan::make(files(), CleanupAge::Everything, kToday);
|
||||
TEST_ASSERT_EQUAL(4, plan.paths.size());
|
||||
TEST_ASSERT_EQUAL(15000, plan.bytes);
|
||||
}
|
||||
|
||||
void test_nothing_to_delete_is_an_empty_plan() {
|
||||
auto plan = CleanupPlan::make({{"/irc/a/b/2026-10-02.log", 10}}, CleanupAge::OneMonth, kToday);
|
||||
TEST_ASSERT_TRUE(plan.paths.empty());
|
||||
TEST_ASSERT_EQUAL(0, plan.bytes);
|
||||
}
|
||||
|
||||
void test_notes_are_never_selected() {
|
||||
auto plan = CleanupPlan::make({{"/notes/2020-01-01.log", 10}}, CleanupAge::Everything, kToday);
|
||||
TEST_ASSERT_TRUE(plan.paths.empty());
|
||||
}
|
||||
|
||||
void test_categories_cover_logs_and_captures_but_not_notes() {
|
||||
bool notes = false;
|
||||
for (auto& c : kCleanupCategories) notes |= std::string(c.folder) == "/notes";
|
||||
TEST_ASSERT_FALSE(notes);
|
||||
TEST_ASSERT_TRUE(sizeof(kCleanupCategories) / sizeof(kCleanupCategories[0]) >= 3);
|
||||
}
|
||||
|
||||
void test_size_formatting() {
|
||||
TEST_ASSERT_EQUAL_STRING("512 B", formatBytes(512).c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("1.5 KB", formatBytes(1536).c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("340 MB", formatBytes(340ull * 1024 * 1024).c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("2.1 GB", formatBytes(2254857830ull).c_str());
|
||||
}
|
||||
|
||||
int main() {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_age_cutoffs_select_strictly_older_files);
|
||||
RUN_TEST(test_a_year_keeps_everything_younger);
|
||||
RUN_TEST(test_undated_files_only_go_with_everything);
|
||||
RUN_TEST(test_nothing_to_delete_is_an_empty_plan);
|
||||
RUN_TEST(test_notes_are_never_selected);
|
||||
RUN_TEST(test_categories_cover_logs_and_captures_but_not_notes);
|
||||
RUN_TEST(test_size_formatting);
|
||||
return UNITY_END();
|
||||
}
|
||||
@@ -61,6 +61,12 @@ void test_local_time_in_brussels_summer_and_winter() {
|
||||
TEST_ASSERT_EQUAL_STRING("13:00", ClockModel::formatLocalTime(k2026_01_15_1200Z).c_str());
|
||||
}
|
||||
|
||||
void test_local_date_in_brussels() {
|
||||
ClockModel::applyTimezone(kBrussels);
|
||||
// 2026-07-01 23:30Z is already July 2nd in Brussels (UTC+2)
|
||||
TEST_ASSERT_EQUAL_STRING("2026-07-02", ClockModel::formatLocalDate(k2026_07_01_1200Z + 11 * 3600 + 1800).c_str());
|
||||
}
|
||||
|
||||
int main() {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_clock_is_unset_until_a_source_sets_it);
|
||||
@@ -70,5 +76,6 @@ int main() {
|
||||
RUN_TEST(test_utc_now_survives_millis_wraparound);
|
||||
RUN_TEST(test_relative_age_formatting);
|
||||
RUN_TEST(test_local_time_in_brussels_summer_and_winter);
|
||||
RUN_TEST(test_local_date_in_brussels);
|
||||
return UNITY_END();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#include <unity.h>
|
||||
|
||||
#include "storage_paths.h"
|
||||
|
||||
using namespace roro;
|
||||
|
||||
void setUp() {}
|
||||
void tearDown() {}
|
||||
|
||||
void test_sanitize_replaces_characters_fat_forbids() {
|
||||
TEST_ASSERT_EQUAL_STRING("a_b_c_d_e_f_g_h_i", storage::sanitize("a\\b/c:d*e?f\"g<h>i").c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("#roro", storage::sanitize("#roro").c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("Cl\xC3\xA9ment", storage::sanitize("Cl\xC3\xA9ment").c_str());
|
||||
}
|
||||
|
||||
void test_sanitize_never_returns_empty_or_dot_names() {
|
||||
TEST_ASSERT_EQUAL_STRING("_", storage::sanitize("").c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("_", storage::sanitize(".").c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("_.", storage::sanitize("..").c_str());
|
||||
}
|
||||
|
||||
void test_daily_log_path() {
|
||||
TEST_ASSERT_EQUAL_STRING("/irc/libera/#roro/2026-10-02.log",
|
||||
storage::dailyLogPath({"irc", "libera", "#roro"}, "2026-10-02").c_str());
|
||||
}
|
||||
|
||||
void test_daily_log_path_sanitizes_each_part() {
|
||||
TEST_ASSERT_EQUAL_STRING("/irc/irc.libera.chat_6697/nick_name/undated.log",
|
||||
storage::dailyLogPath({"irc", "irc.libera.chat:6697", "nick/name"}, "").c_str());
|
||||
}
|
||||
|
||||
void test_parse_date_from_log_and_capture_names() {
|
||||
TEST_ASSERT_EQUAL(20728, storage::fileDay("2026-10-02.log")); // days since 1970-01-01
|
||||
TEST_ASSERT_EQUAL(20728, storage::fileDay("20261002-144500.pcap"));
|
||||
TEST_ASSERT_EQUAL(-1, storage::fileDay("undated.log"));
|
||||
TEST_ASSERT_EQUAL(-1, storage::fileDay("notes.txt"));
|
||||
TEST_ASSERT_EQUAL(-1, storage::fileDay("2026-13-02.log"));
|
||||
}
|
||||
|
||||
void test_day_from_utc_seconds() {
|
||||
TEST_ASSERT_EQUAL(20728, storage::dayFromUtc(1790942400)); // 2026-10-02 12:00Z
|
||||
TEST_ASSERT_EQUAL(0, storage::dayFromUtc(0));
|
||||
}
|
||||
|
||||
int main() {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_sanitize_replaces_characters_fat_forbids);
|
||||
RUN_TEST(test_sanitize_never_returns_empty_or_dot_names);
|
||||
RUN_TEST(test_daily_log_path);
|
||||
RUN_TEST(test_daily_log_path_sanitizes_each_part);
|
||||
RUN_TEST(test_parse_date_from_log_and_capture_names);
|
||||
RUN_TEST(test_day_from_utc_seconds);
|
||||
return UNITY_END();
|
||||
}
|
||||
Reference in New Issue
Block a user