#pragma once #include #include #include #include #include #include #include #include #include "file_list.h" #include "services/storage_service.h" namespace roro { // What the Storage App does to the card (docs/milestones/F1.md): list a folder, count what's in // one, copy, move, delete, make a folder. One operation at a time, on the storage task like every // card access, **in slices of about 150 ms**: a long copy re-queues itself between slices, so IRC // Logs, a Gemini page streaming to the card or a Capture are written in between and nobody waits // long enough to give up. The read-only rules of Q130 are checked here, whoever asks. class FileOps { public: enum class Op : uint8_t { None, List, Count, Copy, Move, Delete, MakeFolder }; // The files the firmware has open right now that matter for `path` (a folder or not). using InUse = std::function(const std::string& path, bool folder)>; struct Status { Op op = Op::None; bool running = false; bool cancelled = false; bool exists = false; // a copy or move found something with that name: ask, then `replace` std::string error; // why it failed, for a human; empty when it worked uint32_t doneBytes = 0, totalBytes = 0; // a copy's progress uint32_t files = 0; // files copied or deleted so far; counted, for Count uint32_t bytes = 0; // Count: their total size std::string path; // what it was about uint32_t ms = 0; // how long it took, once it has ended }; FileOps(StorageService& storage, InUse inUse); // Each returns "" when the operation started, or why it can't. One at a time. std::string list(const std::string& folder); std::string count(const std::string& path); std::string copy(const std::string& from, bool folder, const std::string& intoFolder, const std::string& name, bool replace = false); std::string move(const std::string& from, bool folder, const std::string& to, bool replace = false); std::string remove(const std::string& path, bool folder); std::string makeFolder(const std::string& path); void cancel() { cancel_ = true; } // Why `path` can't be renamed, moved or deleted, or "": asked before the confirmation. std::string whyReadOnly(const std::string& path, bool folder) const; bool busy() const { return busy_; } Status status() const; // while it runs bool finished(Status& out); // true once, when it has ended void takeListing(files::FileList& out); // after a List finished without error // For the console, which has no listing to know from: is `path` a folder? Waits for the card. bool isFolder(const std::string& path, bool& exists); private: static constexpr uint32_t kSliceMs = 150; static constexpr size_t kPiece = 4096; // Q133 std::string start(Op op, const std::string& path); void queue(); void slice(); // on the storage task bool sliceList(); // each: true when the operation is over bool sliceCount(); bool sliceCopy(); bool sliceDelete(); void fail(const std::string& why); void finish(); bool enter(const std::string& path); // push a folder onto the walk void closeAll(); StorageService& storage_; InUse inUse_; mutable SemaphoreHandle_t lock_; // status_, listing_ Status status_; files::FileList listing_; std::atomic busy_{false}, done_{false}, cancel_{false}; std::atomic doneBytes_{0}, files_{0}; // The operation in hand: only the storage task touches these while it runs. Op op_ = Op::None; std::string from_, to_; bool replace_ = false, sourceIsFolder_ = false, undo_ = false; int phase_ = 0; std::vector dirs_; // the folders being walked, outermost first std::vector paths_; // their paths FF_DIR listDir_; // List reads the folder straight from FatFs bool listOpen_ = false; fs::File in_, out_; // the file being copied std::string outPath_; uint32_t countBytes_ = 0, startMs_ = 0; uint32_t fileBytes_ = 0, fileSize_ = 0; // copied of the file in hand; its size when opened }; } // namespace roro