Public Access
- 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
68 lines
2.1 KiB
C++
68 lines
2.1 KiB
C++
#include "storage_paths.h"
|
|
|
|
#include <cctype>
|
|
|
|
namespace roro::storage {
|
|
|
|
namespace {
|
|
|
|
// Howard Hinnant's days_from_civil.
|
|
int daysFromCivil(int y, int m, int d) {
|
|
y -= m <= 2;
|
|
int era = (y >= 0 ? y : y - 399) / 400;
|
|
int yoe = y - era * 400;
|
|
int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
|
|
int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
|
|
return era * 146097 + doe - 719468;
|
|
}
|
|
|
|
bool digits(const std::string& s, size_t pos, size_t n, int& out) {
|
|
if (pos + n > s.size()) return false;
|
|
out = 0;
|
|
for (size_t i = pos; i < pos + n; i++) {
|
|
if (!std::isdigit(static_cast<unsigned char>(s[i]))) return false;
|
|
out = out * 10 + (s[i] - '0');
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int validDay(int y, int m, int d) {
|
|
static const int kDays[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
|
if (y < 1970 || m < 1 || m > 12 || d < 1 || d > kDays[m - 1]) return -1;
|
|
return daysFromCivil(y, m, d);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::string sanitize(const std::string& name) {
|
|
std::string out;
|
|
for (char c : name) {
|
|
bool forbidden = c == '\\' || c == '/' || c == ':' || c == '*' || c == '?' || c == '"' || c == '<' ||
|
|
c == '>' || c == '|' || static_cast<unsigned char>(c) < 0x20;
|
|
out += forbidden ? '_' : c;
|
|
}
|
|
if (out.empty() || out == ".") return "_";
|
|
if (out == "..") return "_.";
|
|
return out;
|
|
}
|
|
|
|
std::string dailyLogPath(std::initializer_list<std::string> folders, const std::string& date) {
|
|
std::string path;
|
|
for (auto& f : folders) path += "/" + sanitize(f);
|
|
return path + "/" + (date.empty() ? "undated" : date) + ".log";
|
|
}
|
|
|
|
int fileDay(const std::string& name) {
|
|
int y, m, d;
|
|
if (digits(name, 0, 4, y) && name.size() > 10 && name[4] == '-' && digits(name, 5, 2, m) && name[7] == '-' &&
|
|
digits(name, 8, 2, d))
|
|
return validDay(y, m, d);
|
|
if (digits(name, 0, 4, y) && digits(name, 4, 2, m) && digits(name, 6, 2, d) && name.size() > 8 && name[8] == '-')
|
|
return validDay(y, m, d);
|
|
return -1;
|
|
}
|
|
|
|
int dayFromUtc(int64_t utcSeconds) { return static_cast<int>(utcSeconds / 86400); }
|
|
|
|
} // namespace roro::storage
|