Do more to avoid and recover from partial db updates

- do not power off in an update is in progress
 - explicitly store last update time, rather than deriving it from
   unchanged tracks.
custom
jacqueline 1 year ago
parent c399199bfc
commit eacea59e8a
  1. 59
      src/database/database.cpp
  2. 6
      src/database/include/database.hpp
  3. 3
      src/system_fsm/include/system_fsm.hpp
  4. 7
      src/system_fsm/running.cpp
  5. 3
      src/system_fsm/system_fsm.cpp

@ -22,6 +22,7 @@
#include "collation.hpp" #include "collation.hpp"
#include "cppbor.h" #include "cppbor.h"
#include "cppbor_parse.h"
#include "esp_log.h" #include "esp_log.h"
#include "ff.h" #include "ff.h"
#include "freertos/projdefs.h" #include "freertos/projdefs.h"
@ -60,6 +61,7 @@ static const uint8_t kCurrentDbVersion = 5;
static const char kKeyCustom[] = "U\0"; static const char kKeyCustom[] = "U\0";
static const char kKeyCollator[] = "collator"; static const char kKeyCollator[] = "collator";
static const char kKeyTrackId[] = "next_track_id"; static const char kKeyTrackId[] = "next_track_id";
static const char kKeyLastUpdate[] = "last_update";
static std::atomic<bool> sIsDbOpen(false); static std::atomic<bool> sIsDbOpen(false);
@ -188,7 +190,8 @@ Database::Database(leveldb::DB* db,
cache_(cache), cache_(cache),
file_gatherer_(file_gatherer), file_gatherer_(file_gatherer),
tag_parser_(tag_parser), tag_parser_(tag_parser),
collator_(collator) {} collator_(collator),
is_updating_(false) {}
Database::~Database() { Database::~Database() {
// Delete db_ first so that any outstanding background work finishes before // Delete db_ first so that any outstanding background work finishes before
@ -272,11 +275,18 @@ auto Database::getIndexes() -> std::vector<IndexInfo> {
} }
auto Database::updateIndexes() -> void { auto Database::updateIndexes() -> void {
if (is_updating_.exchange(true)) {
return;
}
events::Ui().Dispatch(event::UpdateStarted{}); events::Ui().Dispatch(event::UpdateStarted{});
events::System().Dispatch(event::UpdateStarted{});
leveldb::ReadOptions read_options; leveldb::ReadOptions read_options;
read_options.fill_cache = false; read_options.fill_cache = false;
std::pair<uint16_t, uint16_t> newest_track{0, 0}; std::pair<uint16_t, uint16_t> last_update = dbGetLastUpdate();
ESP_LOGI(kTag, "last update was at %u,%u", last_update.first,
last_update.second);
// 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");
@ -317,7 +327,6 @@ auto Database::updateIndexes() -> void {
modified_at = {info.fdate, info.ftime}; modified_at = {info.fdate, info.ftime};
} }
if (modified_at == track->modified_at) { if (modified_at == track->modified_at) {
newest_track = std::max(modified_at, newest_track);
continue; continue;
} else { } else {
track->modified_at = modified_at; track->modified_at = modified_at;
@ -356,12 +365,10 @@ auto Database::updateIndexes() -> void {
} }
} }
ESP_LOGI(kTag, "newest unmodified was at %u,%u", newest_track.first,
newest_track.second);
// 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");
uint64_t num_processed = 0; uint64_t num_processed = 0;
std::pair<uint16_t, uint16_t> newest_track = last_update;
file_gatherer_.FindFiles("", [&](const std::string& path, file_gatherer_.FindFiles("", [&](const std::string& path,
const FILINFO& info) { const FILINFO& info) {
num_processed++; num_processed++;
@ -371,9 +378,10 @@ auto Database::updateIndexes() -> void {
}); });
std::pair<uint16_t, uint16_t> modified{info.fdate, info.ftime}; std::pair<uint16_t, uint16_t> modified{info.fdate, info.ftime};
if (modified < newest_track) { if (modified < last_update) {
return; return;
} }
newest_track = std::max(modified, newest_track);
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) {
@ -442,7 +450,44 @@ auto Database::updateIndexes() -> void {
tags->album().value_or("no album").c_str()); tags->album().value_or("no album").c_str());
} }
}); });
dbSetLastUpdate(newest_track);
ESP_LOGI(kTag, "newest track was at %u,%u", newest_track.first,
newest_track.second);
is_updating_ = false;
events::Ui().Dispatch(event::UpdateFinished{}); events::Ui().Dispatch(event::UpdateFinished{});
events::System().Dispatch(event::UpdateFinished{});
}
auto Database::isUpdating() -> bool {
return is_updating_;
}
auto Database::dbGetLastUpdate() -> std::pair<uint16_t, uint16_t> {
std::string raw;
if (!db_->Get(leveldb::ReadOptions{}, kKeyLastUpdate, &raw).ok()) {
return {0, 0};
}
auto [res, unused, err] = cppbor::parseWithViews(
reinterpret_cast<const uint8_t*>(raw.data()), raw.size());
if (!res || res->type() != cppbor::ARRAY) {
return {0, 0};
}
auto as_arr = res->asArray();
if (as_arr->size() != 2 || as_arr->get(0)->type() != cppbor::UINT ||
as_arr->get(1)->type() != cppbor::UINT) {
return {0, 0};
}
return {as_arr->get(0)->asUint()->unsignedValue(),
as_arr->get(1)->asUint()->unsignedValue()};
}
auto Database::dbSetLastUpdate(std::pair<uint16_t, uint16_t> time) -> void {
auto encoding = cppbor::Array{
cppbor::Uint{time.first},
cppbor::Uint{time.second},
};
db_->Put(leveldb::WriteOptions{}, kKeyLastUpdate, encoding.toString());
} }
auto Database::dbMintNewTrackId() -> TrackId { auto Database::dbMintNewTrackId() -> TrackId {

@ -78,6 +78,7 @@ class Database {
auto getIndexes() -> std::vector<IndexInfo>; auto getIndexes() -> std::vector<IndexInfo>;
auto updateIndexes() -> void; auto updateIndexes() -> void;
auto isUpdating() -> bool;
// Cannot be copied or moved. // Cannot be copied or moved.
Database(const Database&) = delete; Database(const Database&) = delete;
@ -96,12 +97,17 @@ class Database {
ITagParser& tag_parser_; ITagParser& tag_parser_;
locale::ICollator& collator_; locale::ICollator& collator_;
std::atomic<bool> is_updating_;
Database(leveldb::DB* db, Database(leveldb::DB* db,
leveldb::Cache* cache, leveldb::Cache* cache,
IFileGatherer& file_gatherer, IFileGatherer& file_gatherer,
ITagParser& tag_parser, ITagParser& tag_parser,
locale::ICollator& collator); locale::ICollator& collator);
auto dbGetLastUpdate() -> std::pair<uint16_t, uint16_t>;
auto dbSetLastUpdate(std::pair<uint16_t, uint16_t>) -> void;
auto dbMintNewTrackId() -> TrackId; auto dbMintNewTrackId() -> TrackId;
auto dbEntomb(TrackId track, uint64_t hash) -> void; auto dbEntomb(TrackId track, uint64_t hash) -> void;

@ -13,6 +13,7 @@
#include "battery.hpp" #include "battery.hpp"
#include "bluetooth.hpp" #include "bluetooth.hpp"
#include "database.hpp" #include "database.hpp"
#include "db_events.hpp"
#include "display.hpp" #include "display.hpp"
#include "gpios.hpp" #include "gpios.hpp"
#include "nvs.hpp" #include "nvs.hpp"
@ -60,6 +61,7 @@ class SystemState : public tinyfsm::Fsm<SystemState> {
virtual void react(const StorageError&) {} virtual void react(const StorageError&) {}
virtual void react(const KeyLockChanged&) {} virtual void react(const KeyLockChanged&) {}
virtual void react(const SdDetectChanged&) {} virtual void react(const SdDetectChanged&) {}
virtual void react(const database::event::UpdateFinished&) {}
virtual void react(const audio::PlaybackFinished&) {} virtual void react(const audio::PlaybackFinished&) {}
virtual void react(const internal::IdleTimeout&) {} virtual void react(const internal::IdleTimeout&) {}
@ -98,6 +100,7 @@ class Running : public SystemState {
void react(const KeyLockChanged&) override; void react(const KeyLockChanged&) override;
void react(const SdDetectChanged&) override; void react(const SdDetectChanged&) override;
void react(const audio::PlaybackFinished&) override; void react(const audio::PlaybackFinished&) override;
void react(const database::event::UpdateFinished&) override;
using SystemState::react; using SystemState::react;

@ -7,6 +7,7 @@
#include "app_console.hpp" #include "app_console.hpp"
#include "audio_events.hpp" #include "audio_events.hpp"
#include "database.hpp" #include "database.hpp"
#include "db_events.hpp"
#include "file_gatherer.hpp" #include "file_gatherer.hpp"
#include "freertos/projdefs.h" #include "freertos/projdefs.h"
#include "result.hpp" #include "result.hpp"
@ -47,6 +48,12 @@ void Running::react(const audio::PlaybackFinished& ev) {
} }
} }
void Running::react(const database::event::UpdateFinished&) {
if (IdleCondition()) {
transit<Idle>();
}
}
void Running::react(const SdDetectChanged& ev) { void Running::react(const SdDetectChanged& ev) {
if (ev.has_sd_card) { if (ev.has_sd_card) {
if (!sStorage && mountStorage()) { if (!sStorage && mountStorage()) {

@ -95,7 +95,8 @@ void SystemState::react(const internal::SamdInterrupt&) {
} }
auto SystemState::IdleCondition() -> bool { auto SystemState::IdleCondition() -> bool {
return sServices->gpios().IsLocked() && auto db = sServices->database().lock();
return sServices->gpios().IsLocked() && !(db && db->isUpdating()) &&
audio::AudioState::is_in_state<audio::states::Standby>(); audio::AudioState::is_in_state<audio::states::Standby>();
} }

Loading…
Cancel
Save