diff options
Diffstat (limited to 'src/tangara/audio/playlist.cpp')
| -rw-r--r-- | src/tangara/audio/playlist.cpp | 151 |
1 files changed, 147 insertions, 4 deletions
diff --git a/src/tangara/audio/playlist.cpp b/src/tangara/audio/playlist.cpp index 1436e2d2..f57f078d 100644 --- a/src/tangara/audio/playlist.cpp +++ b/src/tangara/audio/playlist.cpp @@ -4,9 +4,12 @@ * SPDX-License-Identifier: GPL-3.0-only */ #include "playlist.hpp" +#include <stdint.h> #include <string> +#include "cppbor.h" +#include "cppbor_parse.h" #include "esp_log.h" #include "ff.h" @@ -29,6 +32,7 @@ Playlist::Playlist(const std::string& playlistFilepath) auto Playlist::open() -> bool { std::unique_lock<std::mutex> lock(mutex_); + if (file_open_) { return true; } @@ -42,10 +46,12 @@ auto Playlist::open() -> bool { file_open_ = true; file_error_ = false; - // Count the playlist size and build our offset cache. - countItems(); - // Advance to the first item. - skipToWithoutCache(0); + if (!deserialiseCache()) { + // Count the playlist size and build our offset cache. + countItems(); + // Advance to the first item. + skipToWithoutCache(0); + } return !file_error_; } @@ -100,6 +106,106 @@ auto Playlist::skipTo(size_t position) -> void { 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 { if (!file_open_ || file_error_) { return; @@ -210,9 +316,46 @@ auto Playlist::nextItem(std::span<TCHAR> buf) MutablePlaylist::MutablePlaylist(const std::string& 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 { std::unique_lock<std::mutex> lock(mutex_); + return clearLocked(); +} +auto MutablePlaylist::clearLocked() -> bool { // Try to recover from any IO errors. if (file_error_ && file_open_) { file_error_ = false; |
