summaryrefslogtreecommitdiff
path: root/src/tangara/audio/playlist.cpp
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2024-09-06 12:40:11 +1000
committerjacqueline <me@jacqueline.id.au>2024-09-06 12:40:11 +1000
commit172d31ec6d69d7372d46e1ba712a2a2187e19d2a (patch)
tree41f08579a311a53db975f0123c3dfafbb12e95b5 /src/tangara/audio/playlist.cpp
parent0426dfd4f21861297850905bf27158659f9d959a (diff)
downloadtangara-fw-172d31ec6d69d7372d46e1ba712a2a2187e19d2a.tar.gz
Ignore comments within playlist files
Includes a general cleanup+restructure of playlist.cpp, and fixing the tests and benchmarks
Diffstat (limited to 'src/tangara/audio/playlist.cpp')
-rw-r--r--src/tangara/audio/playlist.cpp335
1 files changed, 214 insertions, 121 deletions
diff --git a/src/tangara/audio/playlist.cpp b/src/tangara/audio/playlist.cpp
index 944ad143..1436e2d2 100644
--- a/src/tangara/audio/playlist.cpp
+++ b/src/tangara/audio/playlist.cpp
@@ -5,14 +5,16 @@
*/
#include "playlist.hpp"
-#include <string.h>
+#include <string>
-#include "audio/playlist.hpp"
-#include "database/database.hpp"
#include "esp_log.h"
#include "ff.h"
+#include "audio/playlist.hpp"
+#include "database/database.hpp"
+
namespace audio {
+
[[maybe_unused]] static constexpr char kTag[] = "playlist";
Playlist::Playlist(const std::string& playlistFilepath)
@@ -20,190 +22,281 @@ Playlist::Playlist(const std::string& playlistFilepath)
mutex_(),
total_size_(0),
pos_(-1),
+ file_open_(false),
+ file_error_(false),
offset_cache_(&memory::kSpiRamResource),
sample_size_(50) {}
auto Playlist::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;
}
- // Count all entries
- consumeAndCount(-1);
- // Grab the first one
- skipTo(0);
- return true;
+ file_open_ = true;
+ file_error_ = false;
+
+ // Count the playlist size and build our offset cache.
+ countItems();
+ // Advance to the first item.
+ skipToWithoutCache(0);
+
+ return !file_error_;
}
Playlist::~Playlist() {
- f_close(&file_);
+ if (file_open_) {
+ f_close(&file_);
+ }
+}
+
+auto Playlist::filepath() const -> std::string {
+ return filepath_;
}
auto Playlist::currentPosition() const -> size_t {
- return pos_;
+ std::unique_lock<std::mutex> lock(mutex_);
+ return pos_ < 0 ? 0 : pos_;
}
auto Playlist::size() const -> size_t {
+ std::unique_lock<std::mutex> lock(mutex_);
return total_size_;
}
-auto MutablePlaylist::append(Item i) -> void {
+auto Playlist::value() const -> std::string {
std::unique_lock<std::mutex> lock(mutex_);
- auto offset = f_tell(&file_);
- bool first_entry = current_value_.empty();
- // Seek to end and append
- auto end = f_size(&file_);
- auto res = f_lseek(&file_, end);
- if (res != FR_OK) {
- ESP_LOGE(kTag, "Seek to end of file failed? Error %d", res);
- return;
- }
- // TODO: Resolve paths for track id, etc
- std::string path;
- if (std::holds_alternative<std::string>(i)) {
- path = std::get<std::string>(i);
- f_printf(&file_, "%s\n", path.c_str());
- if (total_size_ % sample_size_ == 0) {
- offset_cache_.push_back(end);
- }
- if (first_entry) {
- current_value_ = path;
- }
- total_size_++;
- }
- // Restore position
- res = f_lseek(&file_, offset);
- if (res != FR_OK) {
- ESP_LOGE(kTag, "Failed to restore file position after append?");
- return;
+ return current_value_;
+}
+
+auto Playlist::atEnd() const -> bool {
+ std::unique_lock<std::mutex> lock(mutex_);
+ return pos_ + 1 >= total_size_;
+}
+
+auto Playlist::next() -> void {
+ std::unique_lock<std::mutex> lock(mutex_);
+ if (pos_ + 1 < total_size_ && !file_error_) {
+ advanceBy(1);
}
- res = f_sync(&file_);
- if (res != FR_OK) {
- ESP_LOGE(kTag, "Failed to sync playlist file after append");
- return;
+}
+
+auto Playlist::prev() -> void {
+ std::unique_lock<std::mutex> lock(mutex_);
+ if (!file_error_) {
+ // Naive approach to see how that goes for now
+ skipToLocked(pos_ - 1);
}
}
auto Playlist::skipTo(size_t position) -> void {
std::unique_lock<std::mutex> lock(mutex_);
+ skipToLocked(position);
+}
+
+auto Playlist::skipToLocked(size_t position) -> void {
+ if (!file_open_ || file_error_) {
+ return;
+ }
+
// Check our cache and go to nearest entry
- pos_ = position;
auto remainder = position % sample_size_;
auto quotient = (position - remainder) / sample_size_;
if (offset_cache_.size() <= quotient) {
- // Fall back case
- ESP_LOGW(kTag, "File offset cache failed, falling back...");
- f_rewind(&file_);
- advanceBy(pos_);
+ skipToWithoutCache(position);
return;
}
- auto entry = offset_cache_.at(quotient);
+
// Go to byte offset
+ auto entry = offset_cache_.at(quotient);
auto res = f_lseek(&file_, entry);
if (res != FR_OK) {
- ESP_LOGW(kTag, "Error going to byte offset %llu for playlist entry index %d", entry, pos_);
+ ESP_LOGW(kTag, "error seeking %u", res);
+ file_error_ = true;
+ return;
}
- // Count ahead entries
- advanceBy(remainder+1);
+
+ // Count ahead entries.
+ advanceBy(remainder + 1);
}
-auto Playlist::next() -> void {
- if (!atEnd()) {
- pos_++;
- skipTo(pos_);
+auto Playlist::skipToWithoutCache(size_t position) -> void {
+ if (position >= pos_) {
+ advanceBy(position - pos_);
+ } else {
+ pos_ = -1;
+ FRESULT res = f_rewind(&file_);
+ if (res != FR_OK) {
+ ESP_LOGW(kTag, "error rewinding %u", res);
+ file_error_ = true;
+ return;
+ }
+ advanceBy(position + 1);
}
}
-auto Playlist::prev() -> void {
- // Naive approach to see how that goes for now
- pos_--;
- skipTo(pos_);
-}
+auto Playlist::countItems() -> void {
+ TCHAR buff[512];
-auto Playlist::value() const -> std::string {
- return current_value_;
+ for (;;) {
+ auto offset = f_tell(&file_);
+ auto next_item = nextItem(buff);
+ if (!next_item) {
+ break;
+ }
+ if (total_size_ % sample_size_ == 0) {
+ offset_cache_.push_back(offset);
+ }
+ total_size_++;
+ }
+
+ f_rewind(&file_);
}
-MutablePlaylist::MutablePlaylist(const std::string& playlistFilepath) : Playlist(playlistFilepath) {}
+auto Playlist::advanceBy(ssize_t amt) -> bool {
+ TCHAR buff[512];
+ std::optional<std::string_view> item;
-auto MutablePlaylist::clear() -> bool {
- std::unique_lock<std::mutex> lock(mutex_);
- auto res = f_close(&file_);
- if (res != FR_OK) {
- return false;
+ while (amt > 0) {
+ item = nextItem(buff);
+ if (!item) {
+ break;
+ }
+ pos_++;
+ amt--;
}
- res =
- f_open(&file_, filepath_.c_str(), FA_READ | FA_WRITE | FA_CREATE_ALWAYS);
- if (res != FR_OK) {
- return false;
+
+ if (item) {
+ current_value_ = *item;
}
- total_size_ = 0;
- current_value_.clear();
- offset_cache_.clear();
- pos_ = 0;
- return true;
-}
-auto Playlist::atEnd() -> bool {
- return pos_ + 1 >= total_size_;
+ return amt == 0;
}
-auto Playlist::filepath() -> std::string {
- return filepath_;
+auto Playlist::nextItem(std::span<TCHAR> buf)
+ -> std::optional<std::string_view> {
+ while (file_open_ && !file_error_ && !f_eof(&file_)) {
+ // FIXME: f_gets is quite slow (it does several very small reads instead of
+ // grabbing a whole sector at a time), and it doesn't work well for very
+ // long lines. We should do something smarter here.
+ TCHAR* str = f_gets(buf.data(), buf.size(), &file_);
+ if (str == NULL) {
+ ESP_LOGW(kTag, "Error consuming playlist file at offset %llu",
+ f_tell(&file_));
+ file_error_ = true;
+ return {};
+ }
+
+ std::string_view line{str};
+ if (line.starts_with("#")) {
+ continue;
+ }
+ if (line.ends_with('\n')) {
+ line = line.substr(0, line.size() - 1);
+ }
+ return line;
+ }
+
+ // Got to EOF without reading a valid line.
+ return {};
}
-auto Playlist::consumeAndCount(ssize_t upto) -> bool {
+MutablePlaylist::MutablePlaylist(const std::string& playlistFilepath)
+ : Playlist(playlistFilepath) {}
+
+auto MutablePlaylist::clear() -> bool {
std::unique_lock<std::mutex> lock(mutex_);
- TCHAR buff[512];
- size_t count = 0;
- f_rewind(&file_);
- while (!f_eof(&file_)) {
- auto offset = f_tell(&file_);
- // TODO: Correctly handle lines longer than this
- // TODO: Also correctly handle the case where the last entry doesn't end in
- // \n
- auto res = f_gets(buff, 512, &file_);
- if (res == NULL) {
- ESP_LOGW(kTag, "Error consuming playlist file at line %d", count);
+
+ // Try to recover from any IO errors.
+ if (file_error_ && file_open_) {
+ file_error_ = false;
+ file_open_ = false;
+ f_close(&file_);
+ }
+
+ FRESULT res;
+ if (file_open_) {
+ res = f_rewind(&file_);
+ if (res != FR_OK) {
+ ESP_LOGE(kTag, "error rewinding %u", res);
+ file_error_ = true;
return false;
}
- if (count % sample_size_ == 0) {
- offset_cache_.push_back(offset);
+ res = f_truncate(&file_);
+ if (res != FR_OK) {
+ ESP_LOGE(kTag, "error truncating %u", res);
+ file_error_ = true;
+ return false;
}
- count++;
-
- if (upto >= 0 && count > upto) {
- size_t len = strlen(buff);
- current_value_.assign(buff, len - 1);
- break;
+ } else {
+ res = f_open(&file_, filepath_.c_str(),
+ FA_READ | FA_WRITE | FA_CREATE_ALWAYS);
+ if (res != FR_OK) {
+ ESP_LOGE(kTag, "error opening file %u", res);
+ file_error_ = true;
+ return false;
}
+ file_open_ = true;
}
- if (upto < 0) {
- total_size_ = count;
- f_rewind(&file_);
- }
+
+ total_size_ = 0;
+ current_value_.clear();
+ offset_cache_.clear();
+ pos_ = -1;
return true;
}
-auto Playlist::advanceBy(ssize_t amt) -> bool {
- TCHAR buff[512];
- size_t count = 0;
- while (!f_eof(&file_)) {
- auto res = f_gets(buff, 512, &file_);
- if (res == NULL) {
- ESP_LOGW(kTag, "Error consuming playlist file at line %d", count);
- return false;
+auto MutablePlaylist::append(Item i) -> void {
+ std::unique_lock<std::mutex> lock(mutex_);
+ if (!file_open_ || file_error_) {
+ return;
+ }
+
+ auto offset = f_tell(&file_);
+ bool first_entry = current_value_.empty();
+
+ // Seek to end and append
+ auto end = f_size(&file_);
+ auto res = f_lseek(&file_, end);
+ if (res != FR_OK) {
+ ESP_LOGE(kTag, "Seek to end of file failed? Error %d", res);
+ file_error_ = true;
+ return;
+ }
+
+ // TODO: Resolve paths for track id, etc
+ std::string path;
+ if (std::holds_alternative<std::string>(i)) {
+ path = std::get<std::string>(i);
+ f_printf(&file_, "%s\n", path.c_str());
+ if (total_size_ % sample_size_ == 0) {
+ offset_cache_.push_back(end);
}
- count++;
- if (count >= amt) {
- size_t len = strlen(buff);
- current_value_.assign(buff, len - 1);
- break;
+ if (first_entry) {
+ current_value_ = path;
}
+ total_size_++;
+ }
+
+ // Restore position
+ res = f_lseek(&file_, offset);
+ if (res != FR_OK) {
+ ESP_LOGE(kTag, "Failed to restore file position after append?");
+ file_error_ = true;
+ return;
+ }
+ res = f_sync(&file_);
+ if (res != FR_OK) {
+ ESP_LOGE(kTag, "Failed to sync playlist file after append");
+ file_error_ = true;
+ return;
}
- return true;
}
-} // namespace audio \ No newline at end of file
+} // namespace audio