#include "file_names.h" #include namespace roro::files { const char* const kFirmwareFolders[] = {"/irc", "/wifi", "/updates", "/gnss", "/gemini", "/captures", "/notes", "/screenshots"}; const size_t kFirmwareFolderCount = sizeof kFirmwareFolders / sizeof kFirmwareFolders[0]; std::string parentOf(const std::string& path) { size_t slash = path.rfind('/'); return slash == std::string::npos || slash == 0 ? "/" : path.substr(0, slash); } std::string baseName(const std::string& path) { size_t slash = path.rfind('/'); return slash == std::string::npos ? path : path.substr(slash + 1); } std::string joinPath(const std::string& folder, const std::string& name) { return folder == "/" ? "/" + name : folder + "/" + name; } std::string extensionOf(const std::string& name) { size_t dot = name.rfind('.'); if (dot == std::string::npos || dot == 0) return ""; std::string ext = name.substr(dot + 1); for (char& c : ext) c = static_cast(std::tolower(static_cast(c))); return ext; } bool isInside(const std::string& path, const std::string& folder) { if (folder == "/") return true; if (path.compare(0, folder.size(), folder) != 0) return false; return path.size() == folder.size() || path[folder.size()] == '/'; } std::string checkName(const std::string& name) { if (name.empty()) return "A name can't be empty"; if (name == "." || name == "..") return "\".\" and \"..\" aren't names"; if (name.size() > 64) return "A name can be 64 characters at most"; for (char c : name) { if (static_cast(c) < 0x20) return "A name can't contain control characters"; for (char bad : std::string("/\\:*?\"<>|")) if (c == bad) return std::string("A name can't contain ") + c; } if (name.back() == '.' || name.back() == ' ') return "A name can't end with a dot or a space"; return ""; } std::string whyReadOnly(const std::string& path, const std::vector& inUse) { if (path == "/" || path.empty()) return "That's the card itself"; if (isInside(path, kGeminiCache)) return std::string(kGeminiCache) + " is the Gemini App's working space"; for (const std::string& open : inUse) { if (open == path) return "It's being written right now"; if (isInside(open, path)) return "It holds a file that's being written right now"; } for (size_t i = 0; i < kFirmwareFolderCount; i++) if (path == kFirmwareFolders[i]) return std::string("The firmware keeps its files in ") + path; return ""; } std::string whyNotInto(const std::string& source, const std::string& into) { if (isInside(into, kGeminiCache)) return std::string(kGeminiCache) + " is the Gemini App's working space"; if (isInside(into, source)) return "A folder can't go inside itself"; if (parentOf(source) == into) return "It's already there"; return ""; } std::string copyName(const std::string& name, int n) { size_t dot = name.rfind('.'); std::string suffix = " (" + std::to_string(n) + ")"; if (dot == std::string::npos || dot == 0) return name + suffix; return name.substr(0, dot) + suffix + name.substr(dot); } FileKind kindOf(const std::string& name) { std::string ext = extensionOf(name); if (ext == "gpx") return FileKind::Gpx; if (ext == "pcap") return FileKind::Pcap; if (ext == "ota") return FileKind::Ota; for (const char* text : {"txt", "log", "gmi", "csv", "md", "json", "ini", "conf", "ir"}) if (ext == text) return FileKind::Text; return FileKind::Unknown; } bool opensAtEnd(const std::string& name) { return extensionOf(name) == "log"; } bool looksLikeText(const uint8_t* data, size_t len) { size_t odd = 0; for (size_t i = 0; i < len; i++) { uint8_t b = data[i]; if (b == 0) return false; if (b < 0x20 && b != '\n' && b != '\r' && b != '\t') odd++; } return odd * 20 <= len; // a stray control character or two is still text } RmArgs parseRm(const std::string& args) { RmArgs out; size_t at = 0; while (at < args.size()) { while (at < args.size() && args[at] == ' ') at++; if (at >= args.size() || args[at] != '-') break; size_t end = args.find(' ', at); std::string flags = args.substr(at + 1, end == std::string::npos ? std::string::npos : end - at - 1); bool known = !flags.empty(); for (char c : flags) known = known && (c == 'r' || c == 'R' || c == 'f'); if (!known) break; // a name that starts with a dash for (char c : flags) (c == 'f' ? out.force : out.recursive) = true; at = end == std::string::npos ? args.size() : end; } out.path = at < args.size() ? args.substr(at) : ""; while (!out.path.empty() && out.path.back() == ' ') out.path.pop_back(); return out; } bool hasGlob(const std::string& text) { return text.find_first_of("*?") != std::string::npos; } bool globMatch(const std::string& pattern, const std::string& name) { auto lower = [](char c) { return c >= 'A' && c <= 'Z' ? static_cast(c - 'A' + 'a') : c; }; size_t p = 0, n = 0, star = std::string::npos, mark = 0; while (n < name.size()) { if (p < pattern.size() && (pattern[p] == '?' || lower(pattern[p]) == lower(name[n]))) { p++; n++; } else if (p < pattern.size() && pattern[p] == '*') { star = p++; // try it as nothing first; come back here to let it take one more mark = n; } else if (star != std::string::npos) { p = star + 1; n = ++mark; } else return false; } while (p < pattern.size() && pattern[p] == '*') p++; return p == pattern.size(); } bool splitGlob(const std::string& path, std::string& folder, std::string& pattern) { size_t slash = path.rfind('/'); if (slash == std::string::npos) return false; pattern = path.substr(slash + 1); folder = slash == 0 ? "/" : path.substr(0, slash); return hasGlob(pattern) && !hasGlob(folder); } } // namespace roro::files