Public Access
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:
@@ -0,0 +1,131 @@
|
||||
#include "file_list.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
|
||||
namespace roro::files {
|
||||
|
||||
namespace {
|
||||
|
||||
// Names compare letters only, whatever the case; ties by the bytes, so the order is total.
|
||||
int compareNames(const char* a, const char* b) {
|
||||
for (const char *x = a, *y = b;; ++x, ++y) {
|
||||
int cx = std::tolower(static_cast<unsigned char>(*x)), cy = std::tolower(static_cast<unsigned char>(*y));
|
||||
if (cx != cy) return cx < cy ? -1 : 1;
|
||||
if (!cx) break;
|
||||
}
|
||||
return std::strcmp(a, b);
|
||||
}
|
||||
|
||||
constexpr uint32_t kYear2020 = 1577836800;
|
||||
|
||||
std::string sizeText(uint32_t bytes) {
|
||||
char s[16];
|
||||
if (bytes < 1024) std::snprintf(s, sizeof s, "%u B", static_cast<unsigned>(bytes));
|
||||
else if (bytes < 10 * 1024) std::snprintf(s, sizeof s, "%.1f KB", bytes / 1024.0);
|
||||
else if (bytes < 1024 * 1024) std::snprintf(s, sizeof s, "%u KB", static_cast<unsigned>(bytes / 1024));
|
||||
else if (bytes < 10u * 1024 * 1024) std::snprintf(s, sizeof s, "%.1f MB", bytes / 1048576.0);
|
||||
else if (bytes < 1024u * 1024 * 1024) std::snprintf(s, sizeof s, "%u MB", static_cast<unsigned>(bytes / 1048576));
|
||||
else std::snprintf(s, sizeof s, "%.1f GB", bytes / 1073741824.0);
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void FileList::clear() {
|
||||
std::vector<Entry>().swap(entries_);
|
||||
std::vector<uint16_t>().swap(order_);
|
||||
std::vector<char>().swap(names_);
|
||||
more_ = wasted_ = 0;
|
||||
}
|
||||
|
||||
bool FileList::before(const Entry& a, const Entry& b, FileSort by) const {
|
||||
if (a.folder != b.folder) return a.folder;
|
||||
if (!a.folder) {
|
||||
if (by == FileSort::Date && a.modified != b.modified) return a.modified > b.modified;
|
||||
if (by == FileSort::Size && a.size != b.size) return a.size > b.size;
|
||||
}
|
||||
return compareNames(names_.data() + a.name, names_.data() + b.name) < 0;
|
||||
}
|
||||
|
||||
void FileList::add(const char* name, uint32_t size, uint32_t modified, bool folder) {
|
||||
size_t len = std::strlen(name) + 1;
|
||||
if (entries_.size() >= kMax) {
|
||||
// Full: the new entry takes the place of the one that sorts last by name, if it sorts
|
||||
// before it. The old name's bytes stay in the buffer until there's enough waste to pack.
|
||||
more_++;
|
||||
size_t last = 0;
|
||||
for (size_t i = 1; i < entries_.size(); i++)
|
||||
if (before(entries_[last], entries_[i], FileSort::Name)) last = i;
|
||||
Entry candidate{static_cast<uint32_t>(names_.size()), size, modified, folder};
|
||||
names_.insert(names_.end(), name, name + len);
|
||||
if (!before(candidate, entries_[last], FileSort::Name)) {
|
||||
names_.resize(names_.size() - len);
|
||||
return;
|
||||
}
|
||||
wasted_ += std::strlen(names_.data() + entries_[last].name) + 1;
|
||||
entries_[last] = candidate;
|
||||
if (wasted_ > 2048) compact();
|
||||
return;
|
||||
}
|
||||
if (entries_.empty()) {
|
||||
entries_.reserve(32);
|
||||
names_.reserve(512);
|
||||
}
|
||||
entries_.push_back({static_cast<uint32_t>(names_.size()), size, modified, folder});
|
||||
names_.insert(names_.end(), name, name + len);
|
||||
order_.push_back(static_cast<uint16_t>(order_.size()));
|
||||
}
|
||||
|
||||
void FileList::compact() {
|
||||
std::vector<char> packed;
|
||||
packed.reserve(names_.size() - wasted_);
|
||||
for (Entry& e : entries_) {
|
||||
const char* n = names_.data() + e.name;
|
||||
e.name = static_cast<uint32_t>(packed.size());
|
||||
packed.insert(packed.end(), n, n + std::strlen(n) + 1);
|
||||
}
|
||||
names_.swap(packed);
|
||||
wasted_ = 0;
|
||||
}
|
||||
|
||||
void FileList::sort(FileSort by) {
|
||||
if (wasted_) compact();
|
||||
names_.shrink_to_fit();
|
||||
entries_.shrink_to_fit();
|
||||
order_.resize(entries_.size());
|
||||
for (size_t i = 0; i < order_.size(); i++) order_[i] = static_cast<uint16_t>(i);
|
||||
std::sort(order_.begin(), order_.end(), [&](uint16_t a, uint16_t b) { return before(entries_[a], entries_[b], by); });
|
||||
}
|
||||
|
||||
int FileList::find(const std::string& name) const {
|
||||
for (size_t i = 0; i < order_.size(); i++)
|
||||
if (name == this->name(i)) return static_cast<int>(i);
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t FileList::bytes() const {
|
||||
return entries_.capacity() * sizeof(Entry) + order_.capacity() * sizeof(uint16_t) + names_.capacity();
|
||||
}
|
||||
|
||||
std::string formatStamp(uint32_t modified) {
|
||||
if (modified < kYear2020) return "-";
|
||||
time_t t = static_cast<time_t>(modified);
|
||||
struct tm local;
|
||||
localtime_r(&t, &local);
|
||||
char s[20];
|
||||
std::snprintf(s, sizeof s, "%04d-%02d-%02d %02d:%02d", local.tm_year + 1900, local.tm_mon + 1, local.tm_mday, local.tm_hour,
|
||||
local.tm_min);
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string rowDetail(bool folder, uint32_t size, uint32_t modified) {
|
||||
if (folder) return "folder";
|
||||
std::string stamp = formatStamp(modified);
|
||||
return sizeText(size) + " " + (stamp == "-" ? stamp : stamp.substr(0, 10));
|
||||
}
|
||||
|
||||
} // namespace roro::files
|
||||
Reference in New Issue
Block a user