Add a basic database reindex screen

custom
jacqueline 2 years ago
parent 28633e857f
commit ee8e523456
  1. 20
      src/database/database.cpp
  2. 12
      src/database/file_gatherer.cpp
  3. 2
      src/database/include/db_events.hpp
  4. 7
      src/ui/include/modal_progress.hpp
  5. 1
      src/ui/include/ui_events.hpp
  6. 19
      src/ui/include/ui_fsm.hpp
  7. 29
      src/ui/modal_progress.cpp
  8. 6
      src/ui/screen_settings.cpp
  9. 46
      src/ui/ui_fsm.cpp

@ -136,14 +136,18 @@ auto Database::Update() -> std::future<void> {
// Stage 1: verify all existing tracks are still valid. // Stage 1: verify all existing tracks are still valid.
ESP_LOGI(kTag, "verifying existing tracks"); ESP_LOGI(kTag, "verifying existing tracks");
events::Ui().Dispatch(event::UpdateProgress{
.stage = event::UpdateProgress::Stage::kVerifyingExistingTracks,
});
{ {
uint64_t num_processed = 0;
std::unique_ptr<leveldb::Iterator> it{db_->NewIterator(read_options)}; std::unique_ptr<leveldb::Iterator> it{db_->NewIterator(read_options)};
OwningSlice prefix = EncodeDataPrefix(); OwningSlice prefix = EncodeDataPrefix();
it->Seek(prefix.slice); it->Seek(prefix.slice);
while (it->Valid() && it->key().starts_with(prefix.slice)) { while (it->Valid() && it->key().starts_with(prefix.slice)) {
num_processed++;
events::Ui().Dispatch(event::UpdateProgress{
.stage = event::UpdateProgress::Stage::kVerifyingExistingTracks,
.val = num_processed,
});
std::shared_ptr<TrackData> track = ParseDataValue(it->value()); std::shared_ptr<TrackData> track = ParseDataValue(it->value());
if (!track) { if (!track) {
// The value was malformed. Drop this record. // The value was malformed. Drop this record.
@ -195,10 +199,14 @@ auto Database::Update() -> std::future<void> {
// Stage 2: search for newly added files. // Stage 2: search for newly added files.
ESP_LOGI(kTag, "scanning for new tracks"); ESP_LOGI(kTag, "scanning for new tracks");
events::Ui().Dispatch(event::UpdateProgress{ uint64_t num_processed = 0;
.stage = event::UpdateProgress::Stage::kScanningForNewTracks,
});
file_gatherer_.FindFiles("", [&](const std::pmr::string& path) { file_gatherer_.FindFiles("", [&](const std::pmr::string& path) {
num_processed++;
events::Ui().Dispatch(event::UpdateProgress{
.stage = event::UpdateProgress::Stage::kScanningForNewTracks,
.val = num_processed,
});
std::shared_ptr<TrackTags> tags = tag_parser_.ReadAndParseTags(path); std::shared_ptr<TrackTags> tags = tag_parser_.ReadAndParseTags(path);
if (!tags || tags->encoding() == Container::kUnsupported) { if (!tags || tags->encoding() == Container::kUnsupported) {
// No parseable tags; skip this fiile. // No parseable tags; skip this fiile.

@ -14,6 +14,7 @@
#include "ff.h" #include "ff.h"
#include "memory_resource.hpp" #include "memory_resource.hpp"
#include "spi.hpp"
namespace database { namespace database {
@ -30,7 +31,11 @@ auto FileGathererImpl::FindFiles(
const TCHAR* next_path = static_cast<const TCHAR*>(next_path_str.c_str()); const TCHAR* next_path = static_cast<const TCHAR*>(next_path_str.c_str());
FF_DIR dir; FF_DIR dir;
FRESULT res = f_opendir(&dir, next_path); FRESULT res;
{
auto lock = drivers::acquire_spi();
res = f_opendir(&dir, next_path);
}
if (res != FR_OK) { if (res != FR_OK) {
// TODO: log. // TODO: log.
continue; continue;
@ -38,7 +43,10 @@ auto FileGathererImpl::FindFiles(
for (;;) { for (;;) {
FILINFO info; FILINFO info;
res = f_readdir(&dir, &info); {
auto lock = drivers::acquire_spi();
res = f_readdir(&dir, &info);
}
if (res != FR_OK || info.fname[0] == 0) { if (res != FR_OK || info.fname[0] == 0) {
// No more files in the directory. // No more files in the directory.
break; break;

@ -6,6 +6,7 @@
#pragma once #pragma once
#include <stdint.h>
#include "tinyfsm.hpp" #include "tinyfsm.hpp"
namespace database { namespace database {
@ -21,6 +22,7 @@ struct UpdateProgress : tinyfsm::Event {
kScanningForNewTracks, kScanningForNewTracks,
}; };
Stage stage; Stage stage;
uint64_t val;
}; };
} // namespace event } // namespace event

@ -19,10 +19,15 @@ namespace modals {
class Progress : public Modal { class Progress : public Modal {
public: public:
Progress(Screen*, std::pmr::string title); Progress(Screen*, std::pmr::string title, std::pmr::string subtitle = "");
void title(const std::pmr::string&);
void subtitle(const std::pmr::string&);
private: private:
lv_obj_t* container_; lv_obj_t* container_;
lv_obj_t* title_;
lv_obj_t* subtitle_;
}; };
} // namespace modals } // namespace modals

@ -37,6 +37,7 @@ struct IndexSelected : tinyfsm::Event {
}; };
struct ControlSchemeChanged : tinyfsm::Event {}; struct ControlSchemeChanged : tinyfsm::Event {};
struct ReindexDatabase : tinyfsm::Event {};
struct BackPressed : tinyfsm::Event {}; struct BackPressed : tinyfsm::Event {};
struct ShowNowPlaying : tinyfsm::Event {}; struct ShowNowPlaying : tinyfsm::Event {};

@ -14,6 +14,7 @@
#include "audio_events.hpp" #include "audio_events.hpp"
#include "battery.hpp" #include "battery.hpp"
#include "bindey/property.h" #include "bindey/property.h"
#include "db_events.hpp"
#include "gpios.hpp" #include "gpios.hpp"
#include "lvgl_task.hpp" #include "lvgl_task.hpp"
#include "model_playback.hpp" #include "model_playback.hpp"
@ -75,6 +76,11 @@ class UiState : public tinyfsm::Fsm<UiState> {
} }
virtual void react(const internal::OnboardingNavigate&) {} virtual void react(const internal::OnboardingNavigate&) {}
void react(const internal::ControlSchemeChanged&); void react(const internal::ControlSchemeChanged&);
virtual void react(const internal::ReindexDatabase&){};
virtual void react(const database::event::UpdateStarted&){};
virtual void react(const database::event::UpdateProgress&){};
virtual void react(const database::event::UpdateFinished&){};
virtual void react(const system_fsm::DisplayReady&) {} virtual void react(const system_fsm::DisplayReady&) {}
virtual void react(const system_fsm::BootComplete&) {} virtual void react(const system_fsm::BootComplete&) {}
@ -130,6 +136,7 @@ class Browse : public UiState {
void react(const internal::ShowNowPlaying&) override; void react(const internal::ShowNowPlaying&) override;
void react(const internal::ShowSettingsPage&) override; void react(const internal::ShowSettingsPage&) override;
void react(const internal::ReindexDatabase&) override;
void react(const system_fsm::StorageMounted&) override; void react(const system_fsm::StorageMounted&) override;
void react(const system_fsm::BluetoothDevicesChanged&) override; void react(const system_fsm::BluetoothDevicesChanged&) override;
@ -150,6 +157,18 @@ class Playing : public UiState {
using UiState::react; using UiState::react;
}; };
class Indexing : public UiState {
public:
void entry() override;
void exit() override;
void react(const database::event::UpdateStarted&) override;
void react(const database::event::UpdateProgress&) override;
void react(const database::event::UpdateFinished&) override;
using UiState::react;
};
class FatalError : public UiState {}; class FatalError : public UiState {};
} // namespace states } // namespace states

@ -28,18 +28,39 @@
namespace ui { namespace ui {
namespace modals { namespace modals {
Progress::Progress(Screen* host, std::pmr::string title_text) : Modal(host) { Progress::Progress(Screen* host,
std::pmr::string title_text,
std::pmr::string subtitle_text)
: Modal(host) {
lv_obj_set_layout(root_, LV_LAYOUT_FLEX); lv_obj_set_layout(root_, LV_LAYOUT_FLEX);
lv_obj_set_flex_flow(root_, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_flow(root_, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(root_, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_CENTER, lv_obj_set_flex_align(root_, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_CENTER); LV_FLEX_ALIGN_CENTER);
lv_obj_t* title = lv_label_create(root_); title_ = lv_label_create(root_);
lv_label_set_text(title, title_text.c_str()); lv_obj_set_size(title_, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_obj_set_size(title, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
subtitle_ = lv_label_create(root_);
lv_obj_set_size(subtitle_, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_obj_t* spinner = lv_spinner_create(root_, 3000, 45); lv_obj_t* spinner = lv_spinner_create(root_, 3000, 45);
lv_obj_set_size(spinner, 16, 16); lv_obj_set_size(spinner, 16, 16);
title(title_text);
subtitle(subtitle_text);
}
void Progress::title(const std::pmr::string& s) {
lv_label_set_text(title_, s.c_str());
}
void Progress::subtitle(const std::pmr::string& s) {
if (s.empty()) {
lv_obj_add_flag(subtitle_, LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_clear_flag(subtitle_, LV_OBJ_FLAG_HIDDEN);
lv_label_set_text(subtitle_, s.c_str());
}
} }
} // namespace modals } // namespace modals

@ -469,8 +469,12 @@ Storage::Storage(models::TopBar& bar) : MenuScreen(bar, "Storage") {
lv_obj_t* reset_btn = lv_btn_create(content_); lv_obj_t* reset_btn = lv_btn_create(content_);
lv_obj_t* reset_label = lv_label_create(reset_btn); lv_obj_t* reset_label = lv_label_create(reset_btn);
lv_label_set_text(reset_label, "Reset Database"); lv_label_set_text(reset_label, "Update Database");
lv_group_add_obj(group_, reset_btn); lv_group_add_obj(group_, reset_btn);
lv_bind(reset_btn, LV_EVENT_CLICKED, [&](lv_obj_t*) {
events::Ui().Dispatch(internal::ReindexDatabase{});
});
} }
FirmwareUpdate::FirmwareUpdate(models::TopBar& bar) FirmwareUpdate::FirmwareUpdate(models::TopBar& bar)

@ -21,6 +21,7 @@
#include "lvgl_task.hpp" #include "lvgl_task.hpp"
#include "modal_add_to_queue.hpp" #include "modal_add_to_queue.hpp"
#include "modal_confirm.hpp" #include "modal_confirm.hpp"
#include "modal_progress.hpp"
#include "model_playback.hpp" #include "model_playback.hpp"
#include "nvs.hpp" #include "nvs.hpp"
#include "relative_wheel.hpp" #include "relative_wheel.hpp"
@ -328,6 +329,10 @@ void Browse::react(const system_fsm::BluetoothDevicesChanged&) {
} }
} }
void Browse::react(const internal::ReindexDatabase& ev) {
transit<Indexing>();
}
static std::shared_ptr<screens::Playing> sPlayingScreen; static std::shared_ptr<screens::Playing> sPlayingScreen;
void Playing::entry() { void Playing::entry() {
@ -347,6 +352,47 @@ void Playing::react(const internal::BackPressed& ev) {
transit<Browse>(); transit<Browse>();
} }
static std::shared_ptr<modals::Progress> sIndexProgress;
void Indexing::entry() {
sIndexProgress.reset(new modals::Progress(sCurrentScreen.get(), "Indexing",
"Preparing database"));
sCurrentModal = sIndexProgress;
auto db = sServices->database().lock();
if (!db) {
// TODO: Hmm.
return;
}
db->Update();
}
void Indexing::exit() {
sCurrentModal.reset();
sIndexProgress.reset();
}
void Indexing::react(const database::event::UpdateStarted&) {}
void Indexing::react(const database::event::UpdateProgress& ev) {
std::ostringstream str;
switch (ev.stage) {
case database::event::UpdateProgress::Stage::kVerifyingExistingTracks:
sIndexProgress->title("Verifying");
str << "Tracks checked: " << ev.val;
sIndexProgress->subtitle(str.str().c_str());
break;
case database::event::UpdateProgress::Stage::kScanningForNewTracks:
sIndexProgress->title("Scanning");
str << "Files checked: " << ev.val;
sIndexProgress->subtitle(str.str().c_str());
break;
}
}
void Indexing::react(const database::event::UpdateFinished&) {
transit<Browse>();
}
} // namespace states } // namespace states
} // namespace ui } // namespace ui

Loading…
Cancel
Save