Add a basic database reindex screen
This commit is contained in:
@@ -136,14 +136,18 @@ auto Database::Update() -> std::future<void> {
|
||||
|
||||
// Stage 1: verify all existing tracks are still valid.
|
||||
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)};
|
||||
OwningSlice prefix = EncodeDataPrefix();
|
||||
it->Seek(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());
|
||||
if (!track) {
|
||||
// The value was malformed. Drop this record.
|
||||
@@ -195,10 +199,14 @@ auto Database::Update() -> std::future<void> {
|
||||
|
||||
// Stage 2: search for newly added files.
|
||||
ESP_LOGI(kTag, "scanning for new tracks");
|
||||
events::Ui().Dispatch(event::UpdateProgress{
|
||||
.stage = event::UpdateProgress::Stage::kScanningForNewTracks,
|
||||
});
|
||||
uint64_t num_processed = 0;
|
||||
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);
|
||||
if (!tags || tags->encoding() == Container::kUnsupported) {
|
||||
// No parseable tags; skip this fiile.
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "ff.h"
|
||||
|
||||
#include "memory_resource.hpp"
|
||||
#include "spi.hpp"
|
||||
|
||||
namespace database {
|
||||
|
||||
@@ -30,7 +31,11 @@ auto FileGathererImpl::FindFiles(
|
||||
const TCHAR* next_path = static_cast<const TCHAR*>(next_path_str.c_str());
|
||||
|
||||
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) {
|
||||
// TODO: log.
|
||||
continue;
|
||||
@@ -38,7 +43,10 @@ auto FileGathererImpl::FindFiles(
|
||||
|
||||
for (;;) {
|
||||
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) {
|
||||
// No more files in the directory.
|
||||
break;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "tinyfsm.hpp"
|
||||
|
||||
namespace database {
|
||||
@@ -21,6 +22,7 @@ struct UpdateProgress : tinyfsm::Event {
|
||||
kScanningForNewTracks,
|
||||
};
|
||||
Stage stage;
|
||||
uint64_t val;
|
||||
};
|
||||
|
||||
} // namespace event
|
||||
|
||||
@@ -19,10 +19,15 @@ namespace modals {
|
||||
|
||||
class Progress : public Modal {
|
||||
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:
|
||||
lv_obj_t* container_;
|
||||
lv_obj_t* title_;
|
||||
lv_obj_t* subtitle_;
|
||||
};
|
||||
|
||||
} // namespace modals
|
||||
|
||||
@@ -37,6 +37,7 @@ struct IndexSelected : tinyfsm::Event {
|
||||
};
|
||||
|
||||
struct ControlSchemeChanged : tinyfsm::Event {};
|
||||
struct ReindexDatabase : tinyfsm::Event {};
|
||||
|
||||
struct BackPressed : tinyfsm::Event {};
|
||||
struct ShowNowPlaying : tinyfsm::Event {};
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "audio_events.hpp"
|
||||
#include "battery.hpp"
|
||||
#include "bindey/property.h"
|
||||
#include "db_events.hpp"
|
||||
#include "gpios.hpp"
|
||||
#include "lvgl_task.hpp"
|
||||
#include "model_playback.hpp"
|
||||
@@ -75,6 +76,11 @@ class UiState : public tinyfsm::Fsm<UiState> {
|
||||
}
|
||||
virtual void react(const internal::OnboardingNavigate&) {}
|
||||
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::BootComplete&) {}
|
||||
@@ -130,6 +136,7 @@ class Browse : public UiState {
|
||||
|
||||
void react(const internal::ShowNowPlaying&) 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::BluetoothDevicesChanged&) override;
|
||||
@@ -150,6 +157,18 @@ class Playing : public UiState {
|
||||
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 {};
|
||||
|
||||
} // namespace states
|
||||
|
||||
@@ -28,18 +28,39 @@
|
||||
namespace ui {
|
||||
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_flex_flow(root_, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_flex_align(root_, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_CENTER,
|
||||
LV_FLEX_ALIGN_CENTER);
|
||||
|
||||
lv_obj_t* 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);
|
||||
title_ = lv_label_create(root_);
|
||||
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_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
|
||||
|
||||
@@ -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_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_bind(reset_btn, LV_EVENT_CLICKED, [&](lv_obj_t*) {
|
||||
events::Ui().Dispatch(internal::ReindexDatabase{});
|
||||
});
|
||||
}
|
||||
|
||||
FirmwareUpdate::FirmwareUpdate(models::TopBar& bar)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "lvgl_task.hpp"
|
||||
#include "modal_add_to_queue.hpp"
|
||||
#include "modal_confirm.hpp"
|
||||
#include "modal_progress.hpp"
|
||||
#include "model_playback.hpp"
|
||||
#include "nvs.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;
|
||||
|
||||
void Playing::entry() {
|
||||
@@ -347,6 +352,47 @@ void Playing::react(const internal::BackPressed& ev) {
|
||||
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 ui
|
||||
|
||||
|
||||
Reference in New Issue
Block a user