#include "file_list.h" #include #include #include #include #include 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(*x)), cy = std::tolower(static_cast(*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(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(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(bytes / 1048576)); else std::snprintf(s, sizeof s, "%.1f GB", bytes / 1073741824.0); return s; } } // namespace void FileList::clear() { std::vector().swap(entries_); std::vector().swap(order_); std::vector().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(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(names_.size()), size, modified, folder}); names_.insert(names_.end(), name, name + len); order_.push_back(static_cast(order_.size())); } void FileList::compact() { std::vector packed; packed.reserve(names_.size() - wasted_); for (Entry& e : entries_) { const char* n = names_.data() + e.name; e.name = static_cast(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(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(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(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)); } std::string fitName(const std::string& name, size_t maxChars) { if (name.size() <= maxChars || maxChars < 8) return name.substr(0, maxChars); size_t tail = std::min(6, maxChars / 3), head = maxChars - tail - 2; return name.substr(0, head) + ".." + name.substr(name.size() - tail); } } // namespace roro::files