#include "file_names.h" #include namespace roro::files { const char* const kFirmwareFolders[] = {"/irc", "/wifi", "/updates", "/gnss", "/gemini", "/captures", "/notes"}; 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 } } // namespace roro::files