mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-08-15 15:20:03 +00:00
300 lines
14 KiB
C++
300 lines
14 KiB
C++
// Copyright (c) 2026 Pyxis contributors
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#include "MapTileStoreSD.h"
|
|
|
|
#ifdef ARDUINO
|
|
#include <cerrno>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
namespace Hardware {
|
|
namespace TDeck {
|
|
|
|
namespace {
|
|
bool makeMountedPath(const char* name, char* mounted, std::size_t capacity) {
|
|
if ((name == NULL) || (mounted == NULL)) return false;
|
|
const int written = std::snprintf(mounted, capacity, "/sd%s", name);
|
|
return (written >= 0) && (static_cast<std::size_t>(written) < capacity);
|
|
}
|
|
TileStoreResult statMountedPathLocked(const char* name, struct stat& info) {
|
|
char mounted[MapTileStore::PATH_CAPACITY + 4U] = {};
|
|
if (!makeMountedPath(name, mounted, sizeof(mounted))) return TileStoreResult::INVALID_ARGUMENT;
|
|
errno = 0;
|
|
if (::stat(mounted, &info) == 0) return TileStoreResult::OK;
|
|
return (errno == ENOENT) ? TileStoreResult::MISS : TileStoreResult::IO_ERROR;
|
|
}
|
|
}
|
|
|
|
MapTileStoreSD::MapTileStoreSD()
|
|
: stream_(), list_root_(), list_zoom_(), list_x_(), write_fd_(-1), writing_(false), abort_pending_(false), healthy_(true) {}
|
|
MapTileStoreSD::~MapTileStoreSD() { abortWrite(); endRead(); endList(); abortWrite(); }
|
|
|
|
bool MapTileStoreSD::cardPresentLocked() { return SD.cardType() != CARD_NONE; }
|
|
|
|
TileStoreResult MapTileStoreSD::servicePendingAbortLocked() {
|
|
if (!abort_pending_) return TileStoreResult::OK;
|
|
const bool closed = (write_fd_ < 0) || (::close(write_fd_) == 0);
|
|
write_fd_ = -1;
|
|
writing_ = false;
|
|
abort_pending_ = false;
|
|
if (!closed) { healthy_ = false; return TileStoreResult::IO_ERROR; }
|
|
return TileStoreResult::OK;
|
|
}
|
|
|
|
bool MapTileStoreSD::isAvailable() const {
|
|
if (!healthy_) return false;
|
|
if (!SDAccess::is_ready() || !SDAccess::acquire_bus(100U)) return false;
|
|
MapTileStoreSD* self = const_cast<MapTileStoreSD*>(this);
|
|
const bool cleaned = self->servicePendingAbortLocked() == TileStoreResult::OK;
|
|
const bool present = cleaned && cardPresentLocked();
|
|
SDAccess::release_bus();
|
|
return present;
|
|
}
|
|
|
|
TileStoreResult MapTileStoreSD::copyName(const char* source, char* output, std::size_t capacity) {
|
|
if ((source == NULL) || (output == NULL)) return TileStoreResult::INVALID_ARGUMENT;
|
|
const std::size_t length = std::strlen(source);
|
|
if ((length + 1U) > capacity) return TileStoreResult::INDEX_MISMATCH;
|
|
std::memcpy(output, source, length + 1U);
|
|
return TileStoreResult::OK;
|
|
}
|
|
|
|
bool MapTileStoreSD::makeParentDirectoriesLocked(const char* name) {
|
|
char part[MapTileStore::PATH_CAPACITY] = {};
|
|
const std::size_t length = std::strlen(name);
|
|
if (length >= sizeof(part)) return false;
|
|
std::memcpy(part, name, length + 1U);
|
|
for (std::size_t i = 1U; i < length; ++i) {
|
|
if (part[i] == '/') {
|
|
part[i] = '\0';
|
|
if (!SD.exists(part) && !SD.mkdir(part)) return false;
|
|
part[i] = '/';
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
TileStoreResult MapTileStoreSD::beginRead(const char* name, std::uint32_t& size) {
|
|
if (!healthy_ || !SDAccess::is_ready() || !SDAccess::acquire_bus(500U)) return TileStoreResult::STORAGE_UNAVAILABLE;
|
|
const TileStoreResult cleanup = servicePendingAbortLocked();
|
|
if (cleanup != TileStoreResult::OK) { SDAccess::release_bus(); return cleanup; }
|
|
if (!cardPresentLocked()) { SDAccess::release_bus(); return TileStoreResult::STORAGE_UNAVAILABLE; }
|
|
struct stat info = {};
|
|
const TileStoreResult present = statMountedPathLocked(name, info);
|
|
if (present != TileStoreResult::OK) { SDAccess::release_bus(); return present; }
|
|
stream_ = SD.open(name, FILE_READ);
|
|
if (!stream_) { SDAccess::release_bus(); return TileStoreResult::IO_ERROR; }
|
|
const std::size_t file_size = stream_.size();
|
|
if (file_size > UINT32_MAX) { stream_.close(); SDAccess::release_bus(); return TileStoreResult::TOO_LARGE; }
|
|
size = static_cast<std::uint32_t>(file_size);
|
|
writing_ = false;
|
|
SDAccess::release_bus();
|
|
return TileStoreResult::OK;
|
|
}
|
|
|
|
TileStoreResult MapTileStoreSD::readChunk(std::uint8_t* output, std::size_t capacity, std::size_t& count) {
|
|
if (!healthy_) return TileStoreResult::IO_ERROR;
|
|
if (!SDAccess::acquire_bus(500U)) return TileStoreResult::STORAGE_UNAVAILABLE;
|
|
const TileStoreResult cleanup = servicePendingAbortLocked();
|
|
if (cleanup != TileStoreResult::OK) { SDAccess::release_bus(); return cleanup; }
|
|
if (!stream_) { SDAccess::release_bus(); return TileStoreResult::IO_ERROR; }
|
|
if (!cardPresentLocked()) { SDAccess::release_bus(); return TileStoreResult::STORAGE_UNAVAILABLE; }
|
|
count = stream_.read(output, capacity);
|
|
const bool failed = (count == 0U) && stream_.available();
|
|
SDAccess::release_bus();
|
|
return failed ? TileStoreResult::IO_ERROR : TileStoreResult::OK;
|
|
}
|
|
|
|
void MapTileStoreSD::endRead() {
|
|
if ((!stream_ || writing_) && !abort_pending_) return;
|
|
if (SDAccess::acquire_bus(500U)) {
|
|
servicePendingAbortLocked();
|
|
stream_.close();
|
|
SDAccess::release_bus();
|
|
} else {
|
|
healthy_ = false;
|
|
}
|
|
}
|
|
|
|
TileStoreResult MapTileStoreSD::beginWrite(const char* name) {
|
|
if (!healthy_ || !SDAccess::is_ready() || !SDAccess::acquire_bus(500U)) return TileStoreResult::STORAGE_UNAVAILABLE;
|
|
const TileStoreResult cleanup = servicePendingAbortLocked();
|
|
if (cleanup != TileStoreResult::OK) { SDAccess::release_bus(); return cleanup; }
|
|
if (!cardPresentLocked()) { SDAccess::release_bus(); return TileStoreResult::STORAGE_UNAVAILABLE; }
|
|
if (!makeParentDirectoriesLocked(name)) { SDAccess::release_bus(); return TileStoreResult::IO_ERROR; }
|
|
char mounted[MapTileStore::PATH_CAPACITY + 4U] = {};
|
|
if (!makeMountedPath(name, mounted, sizeof(mounted))) { SDAccess::release_bus(); return TileStoreResult::INVALID_ARGUMENT; }
|
|
write_fd_ = ::open(mounted, O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
|
writing_ = (write_fd_ >= 0);
|
|
SDAccess::release_bus();
|
|
return writing_ ? TileStoreResult::OK : TileStoreResult::IO_ERROR;
|
|
}
|
|
|
|
TileStoreResult MapTileStoreSD::writeChunk(const std::uint8_t* data, std::size_t size, std::size_t& written) {
|
|
if (!healthy_) return TileStoreResult::IO_ERROR;
|
|
if (!SDAccess::acquire_bus(500U)) return TileStoreResult::STORAGE_UNAVAILABLE;
|
|
const TileStoreResult cleanup = servicePendingAbortLocked();
|
|
if (cleanup != TileStoreResult::OK) { SDAccess::release_bus(); return cleanup; }
|
|
if ((write_fd_ < 0) || !writing_) { SDAccess::release_bus(); return TileStoreResult::IO_ERROR; }
|
|
if (!cardPresentLocked()) { SDAccess::release_bus(); return TileStoreResult::STORAGE_UNAVAILABLE; }
|
|
const ssize_t result = ::write(write_fd_, data, size);
|
|
written = (result < 0) ? 0U : static_cast<std::size_t>(result);
|
|
SDAccess::release_bus();
|
|
return (written == size) ? TileStoreResult::OK : TileStoreResult::IO_ERROR;
|
|
}
|
|
|
|
TileStoreResult MapTileStoreSD::commitWrite() {
|
|
if (!healthy_) return TileStoreResult::IO_ERROR;
|
|
if (!SDAccess::acquire_bus(500U)) return TileStoreResult::STORAGE_UNAVAILABLE;
|
|
const TileStoreResult cleanup = servicePendingAbortLocked();
|
|
if (cleanup != TileStoreResult::OK) { SDAccess::release_bus(); return cleanup; }
|
|
if ((write_fd_ < 0) || !writing_) { SDAccess::release_bus(); return TileStoreResult::IO_ERROR; }
|
|
if (!cardPresentLocked()) { SDAccess::release_bus(); return TileStoreResult::STORAGE_UNAVAILABLE; }
|
|
const bool synced = (::fsync(write_fd_) == 0);
|
|
const bool closed = (::close(write_fd_) == 0);
|
|
write_fd_ = -1;
|
|
writing_ = false;
|
|
SDAccess::release_bus();
|
|
return (synced && closed) ? TileStoreResult::OK : TileStoreResult::IO_ERROR;
|
|
}
|
|
|
|
void MapTileStoreSD::abortWrite() {
|
|
if ((write_fd_ < 0) || !writing_) return;
|
|
if (SDAccess::acquire_bus(500U)) {
|
|
if (::close(write_fd_) != 0) healthy_ = false;
|
|
write_fd_ = -1;
|
|
writing_ = false;
|
|
SDAccess::release_bus();
|
|
} else {
|
|
abort_pending_ = true;
|
|
}
|
|
}
|
|
|
|
TileStoreResult MapTileStoreSD::remove(const char* name) {
|
|
if (!healthy_ || !SDAccess::is_ready() || !SDAccess::acquire_bus(500U)) return TileStoreResult::STORAGE_UNAVAILABLE;
|
|
const TileStoreResult cleanup = servicePendingAbortLocked();
|
|
if (cleanup != TileStoreResult::OK) { SDAccess::release_bus(); return cleanup; }
|
|
if (!cardPresentLocked()) { SDAccess::release_bus(); return TileStoreResult::STORAGE_UNAVAILABLE; }
|
|
struct stat info = {};
|
|
const TileStoreResult present = statMountedPathLocked(name, info);
|
|
if (present != TileStoreResult::OK) { SDAccess::release_bus(); return present; }
|
|
const bool removed = SD.remove(name);
|
|
SDAccess::release_bus();
|
|
return removed ? TileStoreResult::OK : TileStoreResult::IO_ERROR;
|
|
}
|
|
|
|
TileStoreResult MapTileStoreSD::rename(const char* from, const char* to) {
|
|
if (!healthy_ || !SDAccess::is_ready() || !SDAccess::acquire_bus(500U)) return TileStoreResult::STORAGE_UNAVAILABLE;
|
|
const TileStoreResult cleanup = servicePendingAbortLocked();
|
|
if (cleanup != TileStoreResult::OK) { SDAccess::release_bus(); return cleanup; }
|
|
if (!cardPresentLocked()) { SDAccess::release_bus(); return TileStoreResult::STORAGE_UNAVAILABLE; }
|
|
struct stat source_info = {}, destination_info = {};
|
|
const TileStoreResult source = statMountedPathLocked(from, source_info);
|
|
if (source != TileStoreResult::OK) { SDAccess::release_bus(); return source; }
|
|
const TileStoreResult destination = statMountedPathLocked(to, destination_info);
|
|
if ((destination != TileStoreResult::MISS) && (destination != TileStoreResult::OK)) {
|
|
SDAccess::release_bus(); return destination;
|
|
}
|
|
if ((destination == TileStoreResult::OK) && !SD.remove(to)) { SDAccess::release_bus(); return TileStoreResult::IO_ERROR; }
|
|
const bool renamed = SD.rename(from, to);
|
|
SDAccess::release_bus();
|
|
return renamed ? TileStoreResult::OK : TileStoreResult::IO_ERROR;
|
|
}
|
|
|
|
TileStoreResult MapTileStoreSD::stat(const char* name, std::uint32_t& size) {
|
|
if (!healthy_ || !SDAccess::is_ready() || !SDAccess::acquire_bus(500U)) return TileStoreResult::STORAGE_UNAVAILABLE;
|
|
const TileStoreResult cleanup = servicePendingAbortLocked();
|
|
if (cleanup != TileStoreResult::OK) { SDAccess::release_bus(); return cleanup; }
|
|
if (!cardPresentLocked()) { SDAccess::release_bus(); return TileStoreResult::STORAGE_UNAVAILABLE; }
|
|
struct stat info = {};
|
|
const TileStoreResult present = statMountedPathLocked(name, info);
|
|
SDAccess::release_bus();
|
|
if (present != TileStoreResult::OK) return present;
|
|
if ((info.st_size < 0) || (static_cast<std::uint64_t>(info.st_size) > UINT32_MAX)) return TileStoreResult::TOO_LARGE;
|
|
size = static_cast<std::uint32_t>(info.st_size);
|
|
return TileStoreResult::OK;
|
|
}
|
|
|
|
TileStoreResult MapTileStoreSD::beginList() {
|
|
endList();
|
|
if (!healthy_ || !SDAccess::is_ready() || !SDAccess::acquire_bus(500U)) return TileStoreResult::STORAGE_UNAVAILABLE;
|
|
const TileStoreResult cleanup = servicePendingAbortLocked();
|
|
if (cleanup != TileStoreResult::OK) { SDAccess::release_bus(); return cleanup; }
|
|
if (!cardPresentLocked()) { SDAccess::release_bus(); return TileStoreResult::STORAGE_UNAVAILABLE; }
|
|
struct stat info = {};
|
|
const TileStoreResult present = statMountedPathLocked("/pyxis-map/tiles", info);
|
|
if (present == TileStoreResult::MISS) { SDAccess::release_bus(); return TileStoreResult::OK; }
|
|
if ((present != TileStoreResult::OK) || !S_ISDIR(info.st_mode)) {
|
|
SDAccess::release_bus(); return TileStoreResult::IO_ERROR;
|
|
}
|
|
list_root_ = SD.open("/pyxis-map/tiles", FILE_READ);
|
|
if (!list_root_) { SDAccess::release_bus(); return TileStoreResult::IO_ERROR; }
|
|
SDAccess::release_bus();
|
|
return TileStoreResult::OK; // An absent cache directory is an empty cache.
|
|
}
|
|
|
|
TileStoreResult MapTileStoreSD::nextList(char* name, std::size_t capacity, bool& done) {
|
|
done = false;
|
|
if (!healthy_) return TileStoreResult::STORAGE_UNAVAILABLE;
|
|
if (!SDAccess::acquire_bus(500U)) return TileStoreResult::STORAGE_UNAVAILABLE;
|
|
const TileStoreResult cleanup = servicePendingAbortLocked();
|
|
if (cleanup != TileStoreResult::OK) { SDAccess::release_bus(); return cleanup; }
|
|
if (!list_root_) { done = true; SDAccess::release_bus(); return TileStoreResult::OK; }
|
|
if (!cardPresentLocked()) { SDAccess::release_bus(); return TileStoreResult::STORAGE_UNAVAILABLE; }
|
|
while (true) {
|
|
if (list_x_) {
|
|
errno = 0;
|
|
fs::File item = list_x_.openNextFile();
|
|
if (item) {
|
|
const TileStoreResult copied = copyName(item.path(), name, capacity);
|
|
item.close(); SDAccess::release_bus(); return copied;
|
|
}
|
|
if (errno != 0) { SDAccess::release_bus(); return TileStoreResult::IO_ERROR; }
|
|
list_x_.close();
|
|
}
|
|
if (list_zoom_) {
|
|
errno = 0;
|
|
fs::File item = list_zoom_.openNextFile();
|
|
if (item) {
|
|
if (item.isDirectory()) { list_x_ = item; continue; }
|
|
const TileStoreResult copied = copyName(item.path(), name, capacity);
|
|
item.close(); SDAccess::release_bus(); return copied;
|
|
}
|
|
if (errno != 0) { SDAccess::release_bus(); return TileStoreResult::IO_ERROR; }
|
|
list_zoom_.close();
|
|
}
|
|
errno = 0;
|
|
fs::File item = list_root_.openNextFile();
|
|
if (item) {
|
|
if (item.isDirectory()) { list_zoom_ = item; continue; }
|
|
const TileStoreResult copied = copyName(item.path(), name, capacity);
|
|
item.close(); SDAccess::release_bus(); return copied;
|
|
}
|
|
if (errno != 0) { SDAccess::release_bus(); return TileStoreResult::IO_ERROR; }
|
|
done = true;
|
|
SDAccess::release_bus();
|
|
return TileStoreResult::OK;
|
|
}
|
|
}
|
|
|
|
void MapTileStoreSD::endList() {
|
|
if (!list_root_ && !list_zoom_ && !list_x_ && !abort_pending_) return;
|
|
if (SDAccess::acquire_bus(500U)) {
|
|
servicePendingAbortLocked();
|
|
if (list_x_) list_x_.close();
|
|
if (list_zoom_) list_zoom_.close();
|
|
if (list_root_) list_root_.close();
|
|
SDAccess::release_bus();
|
|
} else {
|
|
healthy_ = false;
|
|
}
|
|
}
|
|
|
|
} // namespace TDeck
|
|
} // namespace Hardware
|
|
#endif // ARDUINO
|