#include "storage_page.h" #include "ui/widgets.h" namespace roro { namespace { constexpr int kAges = sizeof(kCleanupAgeLabels) / sizeof(kCleanupAgeLabels[0]); constexpr int kCategories = sizeof(kCleanupCategories) / sizeof(kCleanupCategories[0]); } // namespace void StoragePage::enter() { view_ = View::Main; dialog_.reset(); main_.setCount(kRows); main_.select(kCleanUp); } void StoragePage::warn(const char* text) { bus_.publish(Event::withText(EventType::Notification, text, static_cast(NotificationLevel::Warning))); } uint64_t StoragePage::categoryBytes(int category) const { uint64_t sum = 0; if (category < static_cast(listing_.size())) for (auto& f : listing_[category]) sum += f.bytes; return sum; } CleanupPlan StoragePage::planFor(int age) const { int today = clock_.today(); auto cutoff = static_cast(age); // Without a clock only "Everything" can be judged. if (today < 0 && cutoff != CleanupAge::Everything) return {}; return CleanupPlan::make(listing_[categories_.selected()], cutoff, today); } bool StoragePage::onKey(const KeyEvent& e) { if (dialog_) { dialog_->onKey(e); int result = dialog_->result(); if (result == 1) { if (view_ == View::Main) { storage_.requestFormat(); } else { storage_.requestDelete(pending_.paths); enter(); } } if (result != DialogModel::kPending) dialog_.reset(); return true; } switch (view_) { case View::Main: switch (e.key) { case Key::Up: main_.up(); break; case Key::Down: main_.down(); break; case Key::Back: return false; case Key::Select: if (!storage_.state().present) { warn("No SD card"); } else if (main_.selected() == kCleanUp) { storage_.requestListing(); haveListing_ = false; categories_.setCount(kCategories); view_ = View::Categories; } else if (main_.selected() == kErase) { dialog_.reset(new DialogModel({"Cancel", "Erase"})); } break; default: break; } return true; case View::Categories: switch (e.key) { case Key::Up: categories_.up(); break; case Key::Down: categories_.down(); break; case Key::Back: view_ = View::Main; break; case Key::Select: if (!haveListing_) break; if (listing_[categories_.selected()].empty()) { warn("Nothing to clean up here"); break; } ages_.setCount(kAges); view_ = View::Ages; break; default: break; } return true; case View::Ages: switch (e.key) { case Key::Up: ages_.up(); break; case Key::Down: ages_.down(); break; case Key::Back: view_ = View::Categories; break; case Key::Select: pending_ = planFor(ages_.selected()); if (pending_.paths.empty()) warn(clock_.today() < 0 ? "Clock not set: only Everything works" : "No files that old"); else dialog_.reset(new DialogModel({"Cancel", "Delete"})); break; default: break; } return true; } return true; } void StoragePage::draw(Canvas& c) { const auto& area = theme::kContent; if (!haveListing_ && storage_.listingReady()) { listing_ = storage_.listing(); haveListing_ = true; } switch (view_) { case View::Main: { auto s = storage_.state(); widgets::list( c, main_, area, [](int i) -> std::string { const char* labels[] = {"SD card", "Logs", "Captures", "Clean up", "Erase SD card"}; return labels[i]; }, [&](int i) -> std::string { if (storage_.formatting() && i == kCard) return "erasing..."; if (!s.present) return i == kCard ? "none" : ""; switch (i) { case kCard: return formatBytes(s.totalBytes) + ", " + std::to_string(s.usedPercent) + "% used"; case kLogs: return s.logsAllowed ? "recording" : "paused (over 90%)"; case kCaptures: return s.capturesAllowed ? "allowed" : "stopped (full)"; default: return ">"; } }); if (dialog_) widgets::dialog(c, "Erase SD card?", "Everything on the card is deleted. It becomes one FAT32 partition.", *dialog_); break; } case View::Categories: if (!haveListing_) { widgets::textLines(c, {"Listing files..."}, 0, area); break; } widgets::list( c, categories_, area, [](int i) { return std::string(kCleanupCategories[i].label); }, [this](int i) { return std::to_string(listing_[i].size()) + " files, " + formatBytes(categoryBytes(i)); }); break; case View::Ages: widgets::list( c, ages_, area, [](int i) { return std::string(kCleanupAgeLabels[i]); }, [this](int i) { auto plan = planFor(i); return plan.paths.empty() ? std::string("-") : formatBytes(plan.bytes); }); if (dialog_) { std::string message = "Delete " + std::to_string(pending_.paths.size()) + " files from " + kCleanupCategories[categories_.selected()].label + ", freeing " + formatBytes(pending_.bytes) + "?"; widgets::dialog(c, "Clean up", message, *dialog_); } break; } } } // namespace roro