F1 step 1: names, read-only rules and the packed listing (host-tested)

lib/files: path parts; names checked for rename and new folder; why
something can't be renamed, moved or deleted (the Gemini cache, a file
being written or a folder holding one, the firmware's top-level
folders), and why it can't go into a folder; the viewer for a file by
its name, with a sniff for text. FileList keeps a folder's entries
packed, 256 at most, the first 256 by name whatever order the card lists
them in, and sorts by name, date or size with folders first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
2026-10-06 07:37:36 +02:00
co-authored by Claude Opus 5.5
parent 63bae576ef
commit 7ab8f043d0
6 changed files with 557 additions and 0 deletions
+114
View File
@@ -0,0 +1,114 @@
#include <unity.h>
#include <cstdlib>
#include <ctime>
#include <string>
#include "file_list.h"
using namespace roro::files;
void setUp() {}
void tearDown() {}
void test_entries() {
FileList list;
TEST_ASSERT_EQUAL_size_t(0, list.count());
list.add("b.txt", 1200, 1791230400, false);
list.add("saved", 0, 1791230000, true);
TEST_ASSERT_EQUAL_size_t(2, list.count());
list.sort(FileSort::Name);
TEST_ASSERT_EQUAL_STRING("saved", list.name(0)); // folders first
TEST_ASSERT_TRUE(list.folder(0));
TEST_ASSERT_EQUAL_STRING("b.txt", list.name(1));
TEST_ASSERT_EQUAL_UINT32(1200, list.size(1));
TEST_ASSERT_EQUAL_UINT32(1791230400u, list.modified(1));
}
// Q129: folders first, then by name whatever the case; `s` cycles name, date, size.
void test_sorting() {
FileList list;
list.add("zeta.log", 10, 300, false);
list.add("Alpha.log", 3000, 100, false);
list.add("beta.log", 200, 200, false);
list.add("tracks", 0, 50, true);
list.add("Captures", 0, 60, true);
list.sort(FileSort::Name);
const char* byName[] = {"Captures", "tracks", "Alpha.log", "beta.log", "zeta.log"};
for (int i = 0; i < 5; i++) TEST_ASSERT_EQUAL_STRING(byName[i], list.name(i));
list.sort(FileSort::Date); // newest first, folders still first
const char* byDate[] = {"Captures", "tracks", "zeta.log", "beta.log", "Alpha.log"};
for (int i = 0; i < 5; i++) TEST_ASSERT_EQUAL_STRING(byDate[i], list.name(i));
list.sort(FileSort::Size); // biggest first
const char* bySize[] = {"Captures", "tracks", "Alpha.log", "beta.log", "zeta.log"};
for (int i = 0; i < 5; i++) TEST_ASSERT_EQUAL_STRING(bySize[i], list.name(i));
}
// Q136: 256 entries at most. A bigger folder keeps the first 256 by name, whatever order the
// card lists them in, and says how many more there are.
void test_more_than_fits() {
FileList list;
char name[24];
for (int i = 399; i >= 0; i--) { // the card's order: backwards
std::snprintf(name, sizeof name, "2026-%03d.log", i);
list.add(name, static_cast<uint32_t>(i), 1000 + i, false);
}
TEST_ASSERT_EQUAL_size_t(256, list.count());
TEST_ASSERT_EQUAL_size_t(144, list.more());
list.sort(FileSort::Name);
TEST_ASSERT_EQUAL_STRING("2026-000.log", list.name(0));
TEST_ASSERT_EQUAL_STRING("2026-255.log", list.name(255));
TEST_ASSERT_EQUAL_UINT32(255, list.size(255));
// And it stays small: 400 names went through, the memory is for 256.
TEST_ASSERT_TRUE(list.bytes() < 12 * 1024);
}
void test_folders_are_kept_before_files_when_full() {
FileList list;
char name[24];
for (int i = 0; i < 300; i++) {
std::snprintf(name, sizeof name, "a%03d.log", i);
list.add(name, 1, 1, false);
}
list.add("zz-folder", 0, 1, true); // sorts first, so it's among the 256
list.sort(FileSort::Name);
TEST_ASSERT_EQUAL_STRING("zz-folder", list.name(0));
TEST_ASSERT_EQUAL_size_t(45, list.more());
}
void test_find_and_clear() {
FileList list;
list.add("a.txt", 1, 1, false);
list.add("b.txt", 2, 2, false);
list.sort(FileSort::Name);
TEST_ASSERT_EQUAL_INT(1, list.find("b.txt"));
TEST_ASSERT_EQUAL_INT(-1, list.find("c.txt"));
list.clear();
TEST_ASSERT_EQUAL_size_t(0, list.count());
TEST_ASSERT_EQUAL_size_t(0, list.more());
}
// Q129, Q137: what a row shows on the right. A date before 2020 was never set.
void test_display() {
setenv("TZ", "UTC", 1);
tzset();
TEST_ASSERT_EQUAL_STRING("folder", rowDetail(true, 0, 0).c_str());
TEST_ASSERT_EQUAL_STRING("1.2 KB 2026-10-05", rowDetail(false, 1200, 1791230400).c_str());
TEST_ASSERT_EQUAL_STRING("0 B -", rowDetail(false, 0, 315532800).c_str()); // 1980-01-01, FAT's zero
TEST_ASSERT_EQUAL_STRING("2026-10-05 20:00", formatStamp(1791230400).c_str());
TEST_ASSERT_EQUAL_STRING("-", formatStamp(0).c_str());
}
int main() {
UNITY_BEGIN();
RUN_TEST(test_entries);
RUN_TEST(test_sorting);
RUN_TEST(test_more_than_fits);
RUN_TEST(test_folders_are_kept_before_files_when_full);
RUN_TEST(test_find_and_clear);
RUN_TEST(test_display);
return UNITY_END();
}