Merge branch 'main' of codeberg.org:cool-tech-zone/tangara-fw

custom
jacqueline 7 months ago
commit 8ec8ba2bd8
  1. 143
      src/tangara/audio/playlist.cpp
  2. 12
      src/tangara/audio/playlist.hpp
  3. 10
      src/tangara/audio/track_queue.cpp
  4. 5
      src/tangara/audio/track_queue.hpp
  5. 1
      src/tangara/system_fsm/running.cpp

@ -4,9 +4,12 @@
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*/ */
#include "playlist.hpp" #include "playlist.hpp"
#include <stdint.h>
#include <string> #include <string>
#include "cppbor.h"
#include "cppbor_parse.h"
#include "esp_log.h" #include "esp_log.h"
#include "ff.h" #include "ff.h"
@ -29,6 +32,7 @@ Playlist::Playlist(const std::string& playlistFilepath)
auto Playlist::open() -> bool { auto Playlist::open() -> bool {
std::unique_lock<std::mutex> lock(mutex_); std::unique_lock<std::mutex> lock(mutex_);
if (file_open_) { if (file_open_) {
return true; return true;
} }
@ -42,10 +46,12 @@ auto Playlist::open() -> bool {
file_open_ = true; file_open_ = true;
file_error_ = false; file_error_ = false;
if (!deserialiseCache()) {
// Count the playlist size and build our offset cache. // Count the playlist size and build our offset cache.
countItems(); countItems();
// Advance to the first item. // Advance to the first item.
skipToWithoutCache(0); skipToWithoutCache(0);
}
return !file_error_; return !file_error_;
} }
@ -100,6 +106,106 @@ auto Playlist::skipTo(size_t position) -> void {
skipToLocked(position); skipToLocked(position);
} }
// Serialise the cache to a file to avoid having to rescan
// the entire queue when resuming
auto Playlist::serialiseCache() -> bool {
std::unique_lock<std::mutex> lock(mutex_);
if (!file_open_) {
return false;
}
FIL file;
// Open the cache file
std::string cache_file = filepath_ + ".cache";
FRESULT res =
f_open(&file, cache_file.c_str(), FA_READ | FA_WRITE | FA_CREATE_ALWAYS);
if (res != FR_OK) {
ESP_LOGE(kTag, "failed to open cache file! res: %i", res);
return false;
}
cppbor::Array data;
// First item = file size of queue file (for checking this file matches)
data.add(f_size(&file_));
// Next item = number of tracks in this queue
data.add(total_size_);
// Next, write out every cached offset
for (uint64_t offset : offset_cache_) {
data.add(offset);
}
auto encoded = data.encode();
UINT bytes_written = 0;
f_write(&file, encoded.data(), encoded.size(), &bytes_written);
if (bytes_written != encoded.size()) {
return false;
}
f_close(&file);
return true;
}
auto Playlist::deserialiseCache() -> bool {
if (!file_open_) {
return false;
}
FIL file;
// Open the cache file
std::string cache_file = filepath_ + ".cache";
FRESULT res =
f_open(&file, cache_file.c_str(), FA_READ | FA_WRITE | FA_OPEN_ALWAYS);
if (res != FR_OK) {
ESP_LOGE(kTag, "failed to open cache file! res: %i", res);
return false;
}
std::vector<uint8_t> encoded;
encoded.resize(f_size(&file));
UINT bytes_read;
f_read(&file, encoded.data(), encoded.size(), &bytes_read);
if (bytes_read != encoded.size()) {
return false;
}
auto [data, unused, err] = cppbor::parse(encoded);
if (!data || data->type() != cppbor::ARRAY) {
return false;
}
auto entries = data->asArray();
// Double check the expected file size matches.
if (entries->get(0)->asUint()->unsignedValue() != f_size(&file_)) {
return false;
}
total_size_ = entries->get(1)->asUint()->unsignedValue();
// In case we have existing entries
offset_cache_.clear();
// Read in the cache
for (size_t i = 2; i < entries->size(); i++) {
offset_cache_.push_back(entries->get(i)->asUint()->unsignedValue());
}
f_close(&file);
return true;
}
auto Playlist::close() -> void {
if (file_open_) {
f_close(&file_);
file_open_ = false;
file_error_ = false;
}
}
auto Playlist::skipToLocked(size_t position) -> void { auto Playlist::skipToLocked(size_t position) -> void {
if (!file_open_ || file_error_) { if (!file_open_ || file_error_) {
return; return;
@ -210,9 +316,46 @@ auto Playlist::nextItem(std::span<TCHAR> buf)
MutablePlaylist::MutablePlaylist(const std::string& playlistFilepath) MutablePlaylist::MutablePlaylist(const std::string& playlistFilepath)
: Playlist(playlistFilepath) {} : Playlist(playlistFilepath) {}
auto MutablePlaylist::open() -> bool {
std::unique_lock<std::mutex> lock(mutex_);
if (file_open_) {
return true;
}
FRESULT res =
f_open(&file_, filepath_.c_str(), FA_READ | FA_WRITE | FA_OPEN_ALWAYS);
if (res != FR_OK) {
ESP_LOGE(kTag, "failed to open file! res: %i", res);
return false;
}
file_open_ = true;
file_error_ = false;
auto queue_filesize = f_size(&file_);
if (!deserialiseCache()) {
// If there's no cache (or deserialising failed) and the queue is
// sufficiently large, abort and clear the queue
if (queue_filesize > 50000) {
clearLocked();
} else {
// Otherwise, read in the existing entries
countItems();
// Advance to the first item.
skipToWithoutCache(0);
}
}
return !file_error_;
}
auto MutablePlaylist::clear() -> bool { auto MutablePlaylist::clear() -> bool {
std::unique_lock<std::mutex> lock(mutex_); std::unique_lock<std::mutex> lock(mutex_);
return clearLocked();
}
auto MutablePlaylist::clearLocked() -> bool {
// Try to recover from any IO errors. // Try to recover from any IO errors.
if (file_error_ && file_open_) { if (file_error_ && file_open_) {
file_error_ = false; file_error_ = false;

@ -33,7 +33,7 @@ class Playlist {
virtual ~Playlist(); virtual ~Playlist();
using Item = using Item =
std::variant<database::TrackId, database::TrackIterator, std::string>; std::variant<database::TrackId, database::TrackIterator, std::string>;
auto open() -> bool; virtual auto open() -> bool;
auto filepath() const -> std::string; auto filepath() const -> std::string;
auto currentPosition() const -> size_t; auto currentPosition() const -> size_t;
@ -45,6 +45,10 @@ class Playlist {
auto prev() -> void; auto prev() -> void;
auto skipTo(size_t position) -> void; auto skipTo(size_t position) -> void;
auto serialiseCache() -> bool;
auto deserialiseCache() -> bool;
auto close() -> void;
protected: protected:
const std::string filepath_; const std::string filepath_;
@ -68,7 +72,7 @@ class Playlist {
*/ */
const uint32_t sample_size_; const uint32_t sample_size_;
private: protected:
auto skipToLocked(size_t position) -> void; auto skipToLocked(size_t position) -> void;
auto countItems() -> void; auto countItems() -> void;
auto advanceBy(ssize_t amt) -> bool; auto advanceBy(ssize_t amt) -> bool;
@ -79,9 +83,13 @@ class Playlist {
class MutablePlaylist : public Playlist { class MutablePlaylist : public Playlist {
public: public:
MutablePlaylist(const std::string& playlistFilepath); MutablePlaylist(const std::string& playlistFilepath);
auto open() -> bool override;
auto clear() -> bool; auto clear() -> bool;
auto append(Item i) -> void; auto append(Item i) -> void;
private:
auto clearLocked() -> bool;
}; };
} // namespace audio } // namespace audio

@ -159,6 +159,13 @@ auto TrackQueue::open() -> bool {
return playlist_.open(); return playlist_.open();
} }
auto TrackQueue::close() -> void {
playlist_.close();
if (opened_playlist_) {
opened_playlist_->close();
}
}
auto TrackQueue::openPlaylist(const std::string& playlist_file, bool notify) auto TrackQueue::openPlaylist(const std::string& playlist_file, bool notify)
-> bool { -> bool {
opened_playlist_.emplace(playlist_file); opened_playlist_.emplace(playlist_file);
@ -422,6 +429,9 @@ auto TrackQueue::serialise() -> std::string {
cppbor::Uint{shuffle_->pos()}, cppbor::Uint{shuffle_->pos()},
}); });
} }
playlist_.serialiseCache();
return encoded.toString(); return encoded.toString();
} }

@ -76,8 +76,9 @@ class TrackQueue {
auto currentPosition(size_t position) -> bool; auto currentPosition(size_t position) -> bool;
auto totalSize() const -> size_t; auto totalSize() const -> size_t;
auto open() -> bool; auto open() -> bool;
auto openPlaylist(const std::string& playlist_file, bool notify = true) auto close() -> void;
-> bool; auto openPlaylist(const std::string& playlist_file,
bool notify = true) -> bool;
auto playFromPosition(const std::string& filepath, uint32_t position) -> void; auto playFromPosition(const std::string& filepath, uint32_t position) -> void;
using Item = using Item =

@ -214,6 +214,7 @@ void Running::react(const internal::Mount& ev) {
auto Running::unmountStorage() -> void { auto Running::unmountStorage() -> void {
ESP_LOGW(kTag, "unmounting storage"); ESP_LOGW(kTag, "unmounting storage");
sServices->track_queue().close();
sServices->database({}); sServices->database({});
sStorage.reset(); sStorage.reset();
updateSdState(drivers::SdState::kNotMounted); updateSdState(drivers::SdState::kNotMounted);

Loading…
Cancel
Save