mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-02-20 07:25:06 +00:00
Compare commits
2 Commits
a05a6afaaf
...
15de4e20b8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15de4e20b8 | ||
|
|
3802679de4 |
@ -13,20 +13,8 @@ namespace tt::file {
|
||||
|
||||
/**
|
||||
* @param[in] path the path to find a lock for
|
||||
* @return a non-null lock for the specified path.
|
||||
* @return a lock instance when a lock was found, otherwise nullptr
|
||||
*/
|
||||
std::shared_ptr<Lock> getLock(const std::string& path);
|
||||
|
||||
/**
|
||||
* Acquires a lock, calls the function, then releases the lock.
|
||||
* @param[in] path the path to find a lock for
|
||||
* @param[in] fn the code to execute while the lock is acquired
|
||||
*/
|
||||
template<typename ReturnType>
|
||||
ReturnType withLock(const std::string& path, std::function<ReturnType()> fn) {
|
||||
const auto lock = getLock(path)->asScopedLock();
|
||||
lock.lock();
|
||||
return fn();
|
||||
}
|
||||
std::shared_ptr<Lock> _Nullable findLock(const std::string& path);
|
||||
|
||||
}
|
||||
|
||||
@ -24,6 +24,8 @@ std::map<std::string, std::string> parseContentDisposition(const std::vector<std
|
||||
|
||||
bool readAndDiscardOrSendError(httpd_req_t* request, const std::string& toRead);
|
||||
|
||||
size_t receiveFile(httpd_req_t* request, size_t length, const std::string& filePath);
|
||||
|
||||
}
|
||||
|
||||
#endif // ESP_PLATFORM
|
||||
@ -17,6 +17,7 @@
|
||||
|
||||
#include <map>
|
||||
#include <format>
|
||||
#include <Tactility/file/FileLock.h>
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <Tactility/InitEsp.h>
|
||||
@ -188,11 +189,9 @@ static void registerInstalledApps(const std::string& path) {
|
||||
static void registerInstalledAppsFromSdCard(const std::shared_ptr<hal::sdcard::SdCardDevice>& sdcard) {
|
||||
auto sdcard_root_path = sdcard->getMountPath();
|
||||
auto app_path = std::format("{}/app", sdcard_root_path);
|
||||
sdcard->getLock()->lock();
|
||||
if (file::isDirectory(app_path)) {
|
||||
registerInstalledApps(app_path);
|
||||
}
|
||||
sdcard->getLock()->unlock();
|
||||
}
|
||||
|
||||
static void registerInstalledAppsFromSdCards() {
|
||||
@ -269,6 +268,7 @@ void run(const Configuration& config) {
|
||||
#ifdef ESP_PLATFORM
|
||||
initEsp();
|
||||
#endif
|
||||
file::setFindLockFunction(file::findLock);
|
||||
settings::initTimeZone();
|
||||
hal::init(*config.hardware);
|
||||
network::ntp::init();
|
||||
|
||||
@ -106,12 +106,9 @@ static bool untar(const std::string& tarPath, const std::string& destinationPath
|
||||
}
|
||||
|
||||
void cleanupInstallDirectory(const std::string& path) {
|
||||
const auto lock = file::getLock(path);
|
||||
lock->lock();
|
||||
if (!file::deleteRecursively(path)) {
|
||||
TT_LOG_W(TAG, "Failed to delete existing installation at %s", path.c_str());
|
||||
}
|
||||
lock->unlock();
|
||||
}
|
||||
|
||||
bool install(const std::string& path) {
|
||||
@ -121,23 +118,18 @@ bool install(const std::string& path) {
|
||||
auto app_parent_path = getAppInstallPath();
|
||||
TT_LOG_I(TAG, "Installing app %s to %s", path.c_str(), app_parent_path.c_str());
|
||||
|
||||
auto target_path_lock = file::getLock(app_parent_path)->asScopedLock();
|
||||
|
||||
target_path_lock.lock();
|
||||
auto filename = file::getLastPathSegment(path);
|
||||
const std::string app_target_path = std::format("{}/{}", app_parent_path, filename);
|
||||
if (file::isDirectory(app_target_path) && !file::deleteRecursively(app_target_path)) {
|
||||
TT_LOG_W(TAG, "Failed to delete %s", app_target_path.c_str());
|
||||
}
|
||||
target_path_lock.unlock();
|
||||
|
||||
target_path_lock.lock();
|
||||
if (!file::findOrCreateDirectory(app_target_path, 0777)) {
|
||||
TT_LOG_I(TAG, "Failed to create directory %s", app_target_path.c_str());
|
||||
return false;
|
||||
}
|
||||
target_path_lock.unlock();
|
||||
|
||||
auto target_path_lock = file::getLock(app_parent_path)->asScopedLock();
|
||||
auto source_path_lock = file::getLock(path)->asScopedLock();
|
||||
target_path_lock.lock();
|
||||
source_path_lock.lock();
|
||||
@ -149,23 +141,19 @@ bool install(const std::string& path) {
|
||||
source_path_lock.unlock();
|
||||
target_path_lock.unlock();
|
||||
|
||||
target_path_lock.lock();
|
||||
auto manifest_path = app_target_path + "/manifest.properties";
|
||||
if (!file::isFile(manifest_path)) {
|
||||
TT_LOG_E(TAG, "Manifest not found at %s", manifest_path.c_str());
|
||||
cleanupInstallDirectory(app_target_path);
|
||||
return false;
|
||||
}
|
||||
target_path_lock.unlock();
|
||||
|
||||
target_path_lock.lock();
|
||||
std::map<std::string, std::string> properties;
|
||||
if (!file::loadPropertiesFile(manifest_path, properties)) {
|
||||
TT_LOG_E(TAG, "Failed to load manifest at %s", manifest_path.c_str());
|
||||
cleanupInstallDirectory(app_target_path);
|
||||
return false;
|
||||
}
|
||||
target_path_lock.unlock();
|
||||
|
||||
AppManifest manifest;
|
||||
if (!parseManifest(properties, manifest)) {
|
||||
@ -179,7 +167,6 @@ bool install(const std::string& path) {
|
||||
stopAll(manifest.appId);
|
||||
}
|
||||
|
||||
target_path_lock.lock();
|
||||
const std::string renamed_target_path = std::format("{}/{}", app_parent_path, manifest.appId);
|
||||
if (file::isDirectory(renamed_target_path)) {
|
||||
if (!file::deleteRecursively(renamed_target_path)) {
|
||||
@ -188,15 +175,16 @@ bool install(const std::string& path) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
target_path_lock.unlock();
|
||||
|
||||
target_path_lock.lock();
|
||||
if (rename(app_target_path.c_str(), renamed_target_path.c_str()) != 0) {
|
||||
bool rename_success = rename(app_target_path.c_str(), renamed_target_path.c_str()) == 0;
|
||||
target_path_lock.unlock();
|
||||
|
||||
if (!rename_success) {
|
||||
TT_LOG_E(TAG, "Failed to rename \"%s\" to \"%s\"", app_target_path.c_str(), manifest.appId.c_str());
|
||||
cleanupInstallDirectory(app_target_path);
|
||||
return false;
|
||||
}
|
||||
target_path_lock.unlock();
|
||||
|
||||
manifest.appLocation = Location::external(renamed_target_path);
|
||||
|
||||
@ -214,22 +202,20 @@ bool uninstall(const std::string& appId) {
|
||||
}
|
||||
|
||||
auto app_path = getAppInstallPath(appId);
|
||||
return file::withLock<bool>(app_path, [&app_path, &appId] {
|
||||
if (!file::isDirectory(app_path)) {
|
||||
TT_LOG_E(TAG, "App %s not found at ", app_path.c_str());
|
||||
return false;
|
||||
}
|
||||
if (!file::isDirectory(app_path)) {
|
||||
TT_LOG_E(TAG, "App %s not found at ", app_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file::deleteRecursively(app_path)) {
|
||||
return false;
|
||||
}
|
||||
if (!file::deleteRecursively(app_path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!removeApp(appId)) {
|
||||
TT_LOG_W(TAG, "Failed to remove app %d from registry", appId.c_str());
|
||||
}
|
||||
if (!removeApp(appId)) {
|
||||
TT_LOG_W(TAG, "Failed to remove app %d from registry", appId.c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@ -57,19 +57,17 @@ bool State::setEntriesForPath(const std::string& path) {
|
||||
return true;
|
||||
} else {
|
||||
dir_entries.clear();
|
||||
return file::withLock<bool>(path, [this, &path] {
|
||||
int count = file::scandir(path, dir_entries, &file::direntFilterDotEntries, file::direntSortAlphaAndType);
|
||||
if (count >= 0) {
|
||||
TT_LOG_I(TAG, "%s has %u entries", path.c_str(), count);
|
||||
current_path = path;
|
||||
selected_child_entry = "";
|
||||
action = ActionNone;
|
||||
return true;
|
||||
} else {
|
||||
TT_LOG_E(TAG, "Failed to fetch entries for %s", path.c_str());
|
||||
return false;
|
||||
}
|
||||
});
|
||||
int count = file::scandir(path, dir_entries, &file::direntFilterDotEntries, file::direntSortAlphaAndType);
|
||||
if (count >= 0) {
|
||||
TT_LOG_I(TAG, "%s has %u entries", path.c_str(), count);
|
||||
current_path = path;
|
||||
selected_child_entry = "";
|
||||
action = ActionNone;
|
||||
return true;
|
||||
} else {
|
||||
TT_LOG_E(TAG, "Failed to fetch entries for %s", path.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -317,17 +317,18 @@ void View::onResult(LaunchId launchId, Result result, std::unique_ptr<Bundle> bu
|
||||
switch (state->getPendingAction()) {
|
||||
case State::ActionDelete: {
|
||||
if (alertdialog::getResultIndex(*bundle) == 0) {
|
||||
file::withLock<void>(filepath, [&filepath] {
|
||||
if (file::isDirectory(filepath)) {
|
||||
if (!file::deleteRecursively(filepath)) {
|
||||
TT_LOG_W(TAG, "Failed to delete %s", filepath.c_str());
|
||||
}
|
||||
} else if (file::isFile(filepath)) {
|
||||
if (remove(filepath.c_str()) <= 0) {
|
||||
TT_LOG_W(TAG, "Failed to delete %s", filepath.c_str());
|
||||
}
|
||||
}
|
||||
});
|
||||
if (file::isDirectory(filepath)) {
|
||||
if (!file::deleteRecursively(filepath)) {
|
||||
TT_LOG_W(TAG, "Failed to delete %s", filepath.c_str());
|
||||
}
|
||||
} else if (file::isFile(filepath)) {
|
||||
auto lock = file::getLock(filepath);
|
||||
lock->lock();
|
||||
if (remove(filepath.c_str()) <= 0) {
|
||||
TT_LOG_W(TAG, "Failed to delete %s", filepath.c_str());
|
||||
}
|
||||
lock->unlock();
|
||||
}
|
||||
|
||||
state->setEntriesForPath(state->getCurrentPath());
|
||||
update();
|
||||
@ -337,14 +338,15 @@ void View::onResult(LaunchId launchId, Result result, std::unique_ptr<Bundle> bu
|
||||
case State::ActionRename: {
|
||||
auto new_name = inputdialog::getResult(*bundle);
|
||||
if (!new_name.empty() && new_name != state->getSelectedChildEntry()) {
|
||||
file::withLock<void>(filepath, [this, &filepath, &new_name] {
|
||||
std::string rename_to = file::getChildPath(state->getCurrentPath(), new_name);
|
||||
if (rename(filepath.c_str(), rename_to.c_str())) {
|
||||
TT_LOG_I(TAG, "Renamed \"%s\" to \"%s\"", filepath.c_str(), rename_to.c_str());
|
||||
} else {
|
||||
TT_LOG_E(TAG, "Failed to rename \"%s\" to \"%s\"", filepath.c_str(), rename_to.c_str());
|
||||
}
|
||||
});
|
||||
auto lock = file::getLock(filepath);
|
||||
lock->lock();
|
||||
std::string rename_to = file::getChildPath(state->getCurrentPath(), new_name);
|
||||
if (rename(filepath.c_str(), rename_to.c_str())) {
|
||||
TT_LOG_I(TAG, "Renamed \"%s\" to \"%s\"", filepath.c_str(), rename_to.c_str());
|
||||
} else {
|
||||
TT_LOG_E(TAG, "Failed to rename \"%s\" to \"%s\"", filepath.c_str(), rename_to.c_str());
|
||||
}
|
||||
lock->unlock();
|
||||
|
||||
state->setEntriesForPath(state->getCurrentPath());
|
||||
update();
|
||||
|
||||
@ -34,12 +34,6 @@ std::string State::getSelectedChildPath() const {
|
||||
}
|
||||
|
||||
bool State::setEntriesForPath(const std::string& path) {
|
||||
auto lock = mutex.asScopedLock();
|
||||
if (!lock.lock(100)) {
|
||||
TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "setEntriesForPath");
|
||||
return false;
|
||||
}
|
||||
|
||||
TT_LOG_I(TAG, "Changing path: %s -> %s", current_path.c_str(), path.c_str());
|
||||
|
||||
/**
|
||||
|
||||
@ -5,20 +5,8 @@
|
||||
|
||||
namespace tt::file {
|
||||
|
||||
class NoLock : public Lock {
|
||||
bool lock(TickType_t timeout) const override { return true; }
|
||||
bool unlock() const override { return true; }
|
||||
};
|
||||
|
||||
static std::shared_ptr<Lock> noLock = std::make_shared<NoLock>();
|
||||
|
||||
std::shared_ptr<Lock> getLock(const std::string& path) {
|
||||
auto sdcard_lock = hal::sdcard::findSdCardLock(path);
|
||||
if (sdcard_lock != nullptr) {
|
||||
return sdcard_lock;
|
||||
} else {
|
||||
return noLock;
|
||||
}
|
||||
std::shared_ptr<Lock> _Nullable findLock(const std::string& path) {
|
||||
return hal::sdcard::findSdCardLock(path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -19,28 +19,26 @@ bool getKeyValuePair(const std::string& input, std::string& key, std::string& va
|
||||
}
|
||||
|
||||
bool loadPropertiesFile(const std::string& filePath, std::function<void(const std::string& key, const std::string& value)> callback) {
|
||||
return file::withLock<bool>(filePath, [&filePath, &callback] {
|
||||
TT_LOG_I(TAG, "Reading properties file %s", filePath.c_str());
|
||||
uint16_t line_count = 0;
|
||||
std::string key_prefix = "";
|
||||
return readLines(filePath, true, [&key_prefix, &line_count, &filePath, &callback](const std::string& line) {
|
||||
line_count++;
|
||||
std::string key, value;
|
||||
auto trimmed_line = string::trim(line, " \t");
|
||||
if (!trimmed_line.starts_with("#")) {
|
||||
if (trimmed_line.starts_with("[")) {
|
||||
key_prefix = trimmed_line;
|
||||
} else {
|
||||
if (getKeyValuePair(trimmed_line, key, value)) {
|
||||
std::string trimmed_key = key_prefix + string::trim(key, " \t");
|
||||
std::string trimmed_value = string::trim(value, " \t");
|
||||
callback(trimmed_key, trimmed_value);
|
||||
} else {
|
||||
TT_LOG_E(TAG, "Failed to parse line %d of %s", line_count, filePath.c_str());
|
||||
}
|
||||
}
|
||||
TT_LOG_I(TAG, "Reading properties file %s", filePath.c_str());
|
||||
uint16_t line_count = 0;
|
||||
std::string key_prefix = "";
|
||||
return readLines(filePath, true, [&key_prefix, &line_count, &filePath, &callback](const std::string& line) {
|
||||
line_count++;
|
||||
std::string key, value;
|
||||
auto trimmed_line = string::trim(line, " \t");
|
||||
if (!trimmed_line.starts_with("#")) {
|
||||
if (trimmed_line.starts_with("[")) {
|
||||
key_prefix = trimmed_line;
|
||||
} else {
|
||||
if (getKeyValuePair(trimmed_line, key, value)) {
|
||||
std::string trimmed_key = key_prefix + string::trim(key, " \t");
|
||||
std::string trimmed_value = string::trim(value, " \t");
|
||||
callback(trimmed_key, trimmed_value);
|
||||
} else {
|
||||
TT_LOG_E(TAG, "Failed to parse line %d of %s", line_count, filePath.c_str());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -64,10 +64,8 @@ bool TextResources::load() {
|
||||
return false;
|
||||
}
|
||||
|
||||
file::withLock<void>(file_path, [&file_path, &new_data] {
|
||||
file::readLines(file_path, true, [&new_data](const char* line) {
|
||||
new_data.push_back(line);
|
||||
});
|
||||
file::readLines(file_path, true, [&new_data](const char* line) {
|
||||
new_data.push_back(line);
|
||||
});
|
||||
|
||||
if (new_data.empty()) {
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include <sstream>
|
||||
#include <Tactility/Log.h>
|
||||
#include <Tactility/StringUtils.h>
|
||||
#include <Tactility/file/File.h>
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
@ -161,6 +162,39 @@ bool readAndDiscardOrSendError(httpd_req_t* request, const std::string& toRead)
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t receiveFile(httpd_req_t* request, size_t length, const std::string& filePath) {
|
||||
constexpr auto BUFFER_SIZE = 512;
|
||||
char buffer[BUFFER_SIZE];
|
||||
size_t bytes_received = 0;
|
||||
|
||||
auto lock = file::getLock(filePath)->asScopedLock();
|
||||
lock.lock();
|
||||
|
||||
auto* file = fopen(filePath.c_str(), "wb");
|
||||
if (file == nullptr) {
|
||||
TT_LOG_E(TAG, "Failed to open file for writing: %s", filePath.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (bytes_received < length) {
|
||||
auto expected_chunk_size = std::min<size_t>(BUFFER_SIZE, length - bytes_received);
|
||||
size_t receive_chunk_size = httpd_req_recv(request, buffer, expected_chunk_size);
|
||||
if (receive_chunk_size <= 0) {
|
||||
TT_LOG_E(TAG, "Receive failed");
|
||||
break;
|
||||
}
|
||||
if (fwrite(buffer, 1, receive_chunk_size, file) != receive_chunk_size) {
|
||||
TT_LOG_E(TAG, "Failed to write all bytes");
|
||||
break;
|
||||
}
|
||||
bytes_received += receive_chunk_size;
|
||||
}
|
||||
|
||||
// Write file
|
||||
fclose(file);
|
||||
return bytes_received;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // ESP_PLATFORM
|
||||
@ -243,45 +243,30 @@ esp_err_t DevelopmentService::handleAppInstall(httpd_req_t* request) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
// Receive file
|
||||
size_t content_read;
|
||||
auto part_after_file = std::format("\r\n--{}--\r\n", boundary);
|
||||
auto file_size = content_left - part_after_file.length();
|
||||
auto buffer = network::receiveByteArray(request, file_size, content_read);
|
||||
if (content_read != file_size) {
|
||||
httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "Multipart form error: file data not received");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
content_left -= content_read;
|
||||
// Receive boundary
|
||||
auto boundary_and_newlines_after_file = std::format("\r\n--{}--\r\n", boundary);
|
||||
auto file_size = content_left - boundary_and_newlines_after_file.length();
|
||||
|
||||
// Create tmp directory
|
||||
const std::string tmp_path = getTempPath();
|
||||
auto lock = file::getLock(tmp_path)->asScopedLock();
|
||||
|
||||
lock.lock();
|
||||
if (!file::findOrCreateDirectory(tmp_path, 0777)) {
|
||||
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to save file");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
// Write file
|
||||
lock.lock();
|
||||
auto file_path = std::format("{}/{}", tmp_path, filename_entry->second);
|
||||
auto* file = fopen(file_path.c_str(), "wb");
|
||||
auto file_bytes_written = fwrite(buffer.get(), 1, file_size, file);
|
||||
fclose(file);
|
||||
if (file_bytes_written != file_size) {
|
||||
if (network::receiveFile(request, file_size, file_path) != file_size) {
|
||||
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to save file");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
content_left -= file_size;
|
||||
|
||||
// Read and verify part
|
||||
if (!network::readAndDiscardOrSendError(request, part_after_file)) {
|
||||
if (!network::readAndDiscardOrSendError(request, boundary_and_newlines_after_file)) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
content_left -= part_after_file.length();
|
||||
content_left -= boundary_and_newlines_after_file.length();
|
||||
|
||||
if (content_left != 0) {
|
||||
TT_LOG_W(TAG, "We have more bytes at the end of the request parsing?!");
|
||||
@ -292,11 +277,9 @@ esp_err_t DevelopmentService::handleAppInstall(httpd_req_t* request) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
lock.lock();
|
||||
if (remove(file_path.c_str()) != 0) {
|
||||
if (!file::deleteFile(file_path.c_str())) {
|
||||
TT_LOG_W(TAG, "Failed to delete %s", file_path.c_str());
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
TT_LOG_I(TAG, "[200] /app/install -> %s", file_path.c_str());
|
||||
|
||||
|
||||
@ -82,8 +82,6 @@ static void importWifiAp(const std::string& filePath) {
|
||||
static void importWifiApSettings(std::shared_ptr<hal::sdcard::SdCardDevice> sdcard) {
|
||||
auto path = file::getChildPath(sdcard->getMountPath(), "settings");
|
||||
|
||||
auto lock = sdcard->getLock()->asScopedLock();
|
||||
lock.lock();
|
||||
std::vector<dirent> dirent_list;
|
||||
if (file::scandir(path, dirent_list, [](const dirent* entry) {
|
||||
switch (entry->d_type) {
|
||||
@ -104,7 +102,6 @@ static void importWifiApSettings(std::shared_ptr<hal::sdcard::SdCardDevice> sdca
|
||||
}, nullptr) == 0) {
|
||||
return;
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
if (dirent_list.empty()) {
|
||||
TT_LOG_W(TAG, "No AP files found at %s", sdcard->getMountPath().c_str());
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include <Tactility/settings/SystemSettings.h>
|
||||
|
||||
#include <format>
|
||||
#include <Tactility/file/File.h>
|
||||
|
||||
namespace tt::settings {
|
||||
|
||||
@ -20,9 +21,7 @@ static bool loadSystemSettingsFromFile(SystemSettings& properties) {
|
||||
auto file_path = std::format(FILE_PATH_FORMAT, file::MOUNT_POINT_DATA);
|
||||
TT_LOG_I(TAG, "System settings loading from %s", file_path.c_str());
|
||||
std::map<std::string, std::string> map;
|
||||
if (!file::withLock<bool>(file_path, [&map, &file_path] {
|
||||
return file::loadPropertiesFile(file_path, map);
|
||||
})) {
|
||||
if (!file::loadPropertiesFile(file_path, map)) {
|
||||
TT_LOG_E(TAG, "Failed to load %s", file_path.c_str());
|
||||
return false;
|
||||
}
|
||||
@ -46,9 +45,6 @@ static bool loadSystemSettingsFromFile(SystemSettings& properties) {
|
||||
}
|
||||
|
||||
bool loadSystemSettings(SystemSettings& properties) {
|
||||
auto scoped_lock = mutex.asScopedLock();
|
||||
scoped_lock.lock();
|
||||
|
||||
if (!cached) {
|
||||
if (!loadSystemSettingsFromFile(cachedSettings)) {
|
||||
return false;
|
||||
@ -61,24 +57,19 @@ bool loadSystemSettings(SystemSettings& properties) {
|
||||
}
|
||||
|
||||
bool saveSystemSettings(const SystemSettings& properties) {
|
||||
auto scoped_lock = mutex.asScopedLock();
|
||||
scoped_lock.lock();
|
||||
|
||||
auto file_path = std::format(FILE_PATH_FORMAT, file::MOUNT_POINT_DATA);
|
||||
return file::withLock<bool>(file_path, [&properties, &file_path] {
|
||||
std::map<std::string, std::string> map;
|
||||
map["language"] = toString(properties.language);
|
||||
map["timeFormat24h"] = properties.timeFormat24h ? "true" : "false";
|
||||
std::map<std::string, std::string> map;
|
||||
map["language"] = toString(properties.language);
|
||||
map["timeFormat24h"] = properties.timeFormat24h ? "true" : "false";
|
||||
|
||||
if (!file::savePropertiesFile(file_path, map)) {
|
||||
TT_LOG_E(TAG, "Failed to save %s", file_path.c_str());
|
||||
return false;
|
||||
}
|
||||
if (!file::savePropertiesFile(file_path, map)) {
|
||||
TT_LOG_E(TAG, "Failed to save %s", file_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
cachedSettings = properties;
|
||||
cached = true;
|
||||
return true;
|
||||
});
|
||||
cachedSettings = properties;
|
||||
cached = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdio>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "tt_app_manifest.h"
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
20
TactilityC/Include/tt_hal.h
Normal file
20
TactilityC/Include/tt_hal.h
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Affects LVGL widget style */
|
||||
enum UiScale {
|
||||
/** Ideal for very small non-touch screen devices (e.g. Waveshare S3 LCD 1.3") */
|
||||
UiScaleSmallest,
|
||||
/** Nothing was changed in the LVGL UI/UX */
|
||||
UiScaleDefault
|
||||
};
|
||||
|
||||
/** @return the UI scaling setting for this device. */
|
||||
UiScale tt_hal_configuration_get_ui_scale();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <tt_kernel.h>
|
||||
|
||||
#include "tt_hal_device.h"
|
||||
#include <tt_hal_device.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
||||
@ -7,31 +7,74 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Logical GPIO pin identifier used by the HAL. Typically maps to the SoC GPIO number. */
|
||||
typedef unsigned int GpioPin;
|
||||
/** Value indicating that no GPIO pin is used/applicable. */
|
||||
#define GPIO_NO_PIN -1
|
||||
|
||||
/** @warning The order must match tt::hal::gpio::Mode */
|
||||
enum class GpioMode {
|
||||
Disable = 0,
|
||||
Input,
|
||||
Output,
|
||||
OutputOpenDrain,
|
||||
InputOutput,
|
||||
InputOutputOpenDrain
|
||||
/** GPIO pin mode used by the HAL.
|
||||
* @warning The order must match tt::hal::gpio::Mode
|
||||
*/
|
||||
enum GpioMode {
|
||||
/** Pin is disabled (high-impedance). */
|
||||
GpioModeDisable = 0,
|
||||
/** Pin configured as input only. */
|
||||
GpioModeInput,
|
||||
/** Pin configured as push-pull output only. */
|
||||
GpioModeOutput,
|
||||
/** Pin configured as open-drain output only. */
|
||||
GpioModeOutputOpenDrain,
|
||||
/** Pin configured for both input and output (push-pull). */
|
||||
GpioModeInputOutput,
|
||||
/** Pin configured for both input and output (open-drain). */
|
||||
GpioModeInputOutputOpenDrain
|
||||
};
|
||||
|
||||
/** Configure a single pin */
|
||||
/** Configure a single GPIO pin.
|
||||
* @param[in] pin GPIO number to configure.
|
||||
* @param[in] mode Desired I/O mode for the pin.
|
||||
* @param[in] pullUp Enable internal pull-up if true.
|
||||
* @param[in] pullDown Enable internal pull-down if true.
|
||||
* @return true on success, false if the pin is invalid or configuration failed.
|
||||
*/
|
||||
bool tt_hal_gpio_configure(GpioPin pin, GpioMode mode, bool pullUp, bool pullDown);
|
||||
|
||||
/** Configure a set of pins defined by their bit index */
|
||||
/** Configure a set of GPIO pins in one call.
|
||||
* The bit index of pin N is (1ULL << N).
|
||||
* @param[in] pinBitMask Bit mask of pins to configure.
|
||||
* @param[in] mode Desired I/O mode for the pins.
|
||||
* @param[in] pullUp Enable internal pull-up on the selected pins if true.
|
||||
* @param[in] pullDown Enable internal pull-down on the selected pins if true.
|
||||
* @return true on success, false if any pin is invalid or configuration failed.
|
||||
*/
|
||||
bool tt_hal_gpio_configure_with_pin_bitmask(uint64_t pinBitMask, GpioMode mode, bool pullUp, bool pullDown);
|
||||
|
||||
/** Set the input/output mode for the specified pin.
|
||||
* @param[in] pin The pin to configure.
|
||||
* @param[in] mode The mode to set.
|
||||
* @return true on success, false if the pin is invalid or mode not supported.
|
||||
*/
|
||||
bool tt_hal_gpio_set_mode(GpioPin pin, GpioMode mode);
|
||||
|
||||
/** Read the current logic level of a pin.
|
||||
* The pin should be configured for input or input/output.
|
||||
* @param[in] pin The pin to read.
|
||||
* @return true if the level is high, false if low. If the pin is invalid, the
|
||||
* behavior is implementation-defined and may return false.
|
||||
*/
|
||||
bool tt_hal_gpio_get_level(GpioPin pin);
|
||||
|
||||
/** Drive the output level of a pin.
|
||||
* The pin should be configured for output or input/output.
|
||||
* @param[in] pin The pin to drive.
|
||||
* @param[in] level Output level to set (true = high, false = low).
|
||||
* @return true on success, false if the pin is invalid or not configured as output.
|
||||
*/
|
||||
bool tt_hal_gpio_set_level(GpioPin pin, bool level);
|
||||
|
||||
/** Get the number of GPIO pins available on this platform.
|
||||
* @return The count of valid GPIO pins.
|
||||
*/
|
||||
int tt_hal_gpio_get_pin_count();
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <hal/i2c_types.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "tt_hal_device.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "tt_kernel.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
||||
@ -3,13 +3,13 @@
|
||||
#include "tt_thread.h"
|
||||
#include <freertos/FreeRTOS.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/** The handle that represents a timer instance */
|
||||
typedef void* TimerHandle;
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#include <tt_file.h>
|
||||
#include <tt_lock_private.h>
|
||||
#include <Tactility/file/FileLock.h>
|
||||
#include <Tactility/file/File.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
|
||||
13
TactilityC/Source/tt_hal.cpp
Normal file
13
TactilityC/Source/tt_hal.cpp
Normal file
@ -0,0 +1,13 @@
|
||||
#include "tt_hal.h"
|
||||
|
||||
#include <Tactility/Tactility.h>
|
||||
#include <Tactility/hal/Configuration.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
UiScale tt_hal_configuration_get_ui_scale() {
|
||||
auto scale = tt::hal::getConfiguration()->uiScale;
|
||||
return static_cast<UiScale>(scale);
|
||||
}
|
||||
|
||||
}
|
||||
@ -7,6 +7,7 @@
|
||||
#include "tt_bundle.h"
|
||||
#include "tt_file.h"
|
||||
#include "tt_gps.h"
|
||||
#include "tt_hal.h"
|
||||
#include "tt_hal_device.h"
|
||||
#include "tt_hal_display.h"
|
||||
#include "tt_hal_gpio.h"
|
||||
@ -183,6 +184,7 @@ const esp_elfsym main_symbols[] {
|
||||
ESP_ELFSYM_EXPORT(tt_bundle_put_string),
|
||||
ESP_ELFSYM_EXPORT(tt_gps_has_coordinates),
|
||||
ESP_ELFSYM_EXPORT(tt_gps_get_coordinates),
|
||||
ESP_ELFSYM_EXPORT(tt_hal_configuration_get_ui_scale),
|
||||
ESP_ELFSYM_EXPORT(tt_hal_device_find),
|
||||
ESP_ELFSYM_EXPORT(tt_hal_display_driver_alloc),
|
||||
ESP_ELFSYM_EXPORT(tt_hal_display_driver_draw_bitmap),
|
||||
|
||||
@ -5,42 +5,44 @@ struct TimerWrapper {
|
||||
std::unique_ptr<tt::Timer> timer;
|
||||
};
|
||||
|
||||
#define HANDLE_TO_WRAPPER(handle) static_cast<TimerWrapper*>(handle)
|
||||
|
||||
extern "C" {
|
||||
|
||||
TimerHandle tt_timer_alloc(TimerType type, TimerCallback callback, void* callbackContext) {
|
||||
auto wrapper = std::make_shared<TimerWrapper>();
|
||||
wrapper->timer = std::make_unique<tt::Timer>((tt::Timer::Type)type, [callback, callbackContext](){ callback(callbackContext); });
|
||||
return wrapper.get();
|
||||
auto wrapper = new TimerWrapper;
|
||||
wrapper->timer = std::make_unique<tt::Timer>(static_cast<tt::Timer::Type>(type), [callback, callbackContext](){ callback(callbackContext); });
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
void tt_timer_free(TimerHandle handle) {
|
||||
auto* wrapper = (TimerWrapper*)handle;
|
||||
auto* wrapper = static_cast<TimerWrapper*>(handle);
|
||||
wrapper->timer = nullptr;
|
||||
delete wrapper;
|
||||
}
|
||||
|
||||
bool tt_timer_start(TimerHandle handle, TickType_t intervalTicks) {
|
||||
return ((TimerWrapper*)handle)->timer->start(intervalTicks);
|
||||
return HANDLE_TO_WRAPPER(handle)->timer->start(intervalTicks);
|
||||
}
|
||||
|
||||
bool tt_timer_restart(TimerHandle handle, TickType_t intervalTicks) {
|
||||
return ((TimerWrapper*)handle)->timer->restart(intervalTicks);
|
||||
return HANDLE_TO_WRAPPER(handle)->timer->restart(intervalTicks);
|
||||
}
|
||||
|
||||
bool tt_timer_stop(TimerHandle handle) {
|
||||
return ((TimerWrapper*)handle)->timer->stop();
|
||||
return HANDLE_TO_WRAPPER(handle)->timer->stop();
|
||||
}
|
||||
|
||||
bool tt_timer_is_running(TimerHandle handle) {
|
||||
return ((TimerWrapper*)handle)->timer->isRunning();
|
||||
return HANDLE_TO_WRAPPER(handle)->timer->isRunning();
|
||||
}
|
||||
|
||||
uint32_t tt_timer_get_expire_time(TimerHandle handle) {
|
||||
return ((TimerWrapper*)handle)->timer->getExpireTime();
|
||||
return HANDLE_TO_WRAPPER(handle)->timer->getExpireTime();
|
||||
}
|
||||
|
||||
bool tt_timer_set_pending_callback(TimerHandle handle, TimerPendingCallback callback, void* callbackContext, uint32_t callbackArg, TickType_t timeoutTicks) {
|
||||
return ((TimerWrapper*)handle)->timer->setPendingCallback(
|
||||
return HANDLE_TO_WRAPPER(handle)->timer->setPendingCallback(
|
||||
callback,
|
||||
callbackContext,
|
||||
callbackArg,
|
||||
@ -49,7 +51,7 @@ bool tt_timer_set_pending_callback(TimerHandle handle, TimerPendingCallback call
|
||||
}
|
||||
|
||||
void tt_timer_set_thread_priority(TimerHandle handle, ThreadPriority priority) {
|
||||
((TimerWrapper*)handle)->timer->setThreadPriority((tt::Thread::Priority)priority);
|
||||
HANDLE_TO_WRAPPER(handle)->timer->setThreadPriority(static_cast<tt::Thread::Priority>(priority));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
/**
|
||||
* All functions in this file can be safely called without manually applying file locks.
|
||||
* For calls to C stdlib APIs such as fopen(), always call file::getLock(path) first!
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "Tactility/TactilityCore.h"
|
||||
@ -40,6 +44,28 @@ struct FileCloser {
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::function<std::shared_ptr<Lock>(const std::string&)> FindLockFunction;
|
||||
|
||||
/**
|
||||
* @param[in] path the path to get a lock for
|
||||
* @return a lock instance (never null)
|
||||
*/
|
||||
std::shared_ptr<Lock> getLock(const std::string& path);
|
||||
|
||||
void setFindLockFunction(const FindLockFunction& function);
|
||||
|
||||
/**
|
||||
* Acquires a lock, calls the function, then releases the lock.
|
||||
* @param[in] path the path to find a lock for
|
||||
* @param[in] fn the code to execute while the lock is acquired
|
||||
*/
|
||||
template<typename ReturnType>
|
||||
ReturnType withLock(const std::string& path, std::function<ReturnType()> fn) {
|
||||
const auto lock = getLock(path)->asScopedLock();
|
||||
lock.lock();
|
||||
return fn();
|
||||
}
|
||||
|
||||
long getSize(FILE* file);
|
||||
|
||||
/** Read a file and return its data.
|
||||
@ -73,6 +99,10 @@ bool findOrCreateParentDirectory(const std::string& path, mode_t mode);
|
||||
|
||||
bool deleteRecursively(const std::string& path);
|
||||
|
||||
bool deleteFile(const std::string& path);
|
||||
|
||||
bool deleteDirectory(const std::string& path);
|
||||
|
||||
/**
|
||||
* Concatenate a child path with a parent path, ensuring proper slash inbetween
|
||||
* @param basePath an absolute path with or without trailing "/"
|
||||
|
||||
@ -11,7 +11,33 @@ class SdCardDevice;
|
||||
|
||||
namespace tt::file {
|
||||
|
||||
#define TAG "file"
|
||||
constexpr auto* TAG = "file";
|
||||
|
||||
class NoLock final : public Lock {
|
||||
bool lock(TickType_t timeout) const override { return true; }
|
||||
bool unlock() const override { return true; }
|
||||
};
|
||||
|
||||
static std::shared_ptr<Lock> noLock = std::make_shared<NoLock>();
|
||||
static std::function<std::shared_ptr<Lock>(const std::string&)> findLockFunction = nullptr;
|
||||
|
||||
std::shared_ptr<Lock> getLock(const std::string& path) {
|
||||
if (findLockFunction == nullptr) {
|
||||
TT_LOG_W(TAG, "File lock function not set!");
|
||||
return noLock;
|
||||
}
|
||||
|
||||
auto lock = findLockFunction(path);
|
||||
if (lock == nullptr) {
|
||||
return noLock;
|
||||
}
|
||||
|
||||
return lock;
|
||||
}
|
||||
|
||||
void setFindLockFunction(const FindLockFunction& function) {
|
||||
findLockFunction = function;
|
||||
}
|
||||
|
||||
std::string getChildPath(const std::string& basePath, const std::string& childPath) {
|
||||
// Postfix with "/" when the current path isn't "/"
|
||||
@ -40,6 +66,9 @@ bool listDirectory(
|
||||
const std::string& path,
|
||||
std::function<void(const dirent&)> onEntry
|
||||
) {
|
||||
auto lock = getLock(path)->asScopedLock();
|
||||
lock.lock();
|
||||
|
||||
TT_LOG_I(TAG, "listDir start %s", path.c_str());
|
||||
DIR* dir = opendir(path.c_str());
|
||||
if (dir == nullptr) {
|
||||
@ -64,6 +93,9 @@ int scandir(
|
||||
ScandirFilter _Nullable filterMethod,
|
||||
ScandirSort _Nullable sortMethod
|
||||
) {
|
||||
auto lock = getLock(path)->asScopedLock();
|
||||
lock.lock();
|
||||
|
||||
TT_LOG_I(TAG, "scandir start");
|
||||
DIR* dir = opendir(path.c_str());
|
||||
if (dir == nullptr) {
|
||||
@ -187,6 +219,9 @@ bool writeString(const std::string& filepath, const std::string& content) {
|
||||
}
|
||||
|
||||
static bool findOrCreateDirectoryInternal(std::string path, mode_t mode) {
|
||||
auto lock = getLock(path)->asScopedLock();
|
||||
lock.lock();
|
||||
|
||||
struct stat dir_stat;
|
||||
if (mkdir(path.c_str(), mode) == 0) {
|
||||
return true;
|
||||
@ -277,6 +312,7 @@ bool deleteRecursively(const std::string& path) {
|
||||
TT_LOG_E(TAG, "Failed to scan directory %s", path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& entry : entries) {
|
||||
auto child_path = path + "/" + entry.d_name;
|
||||
if (!deleteRecursively(child_path)) {
|
||||
@ -284,10 +320,10 @@ bool deleteRecursively(const std::string& path) {
|
||||
}
|
||||
}
|
||||
TT_LOG_I(TAG, "Deleting %s", path.c_str());
|
||||
return rmdir(path.c_str()) == 0;
|
||||
return deleteDirectory(path);
|
||||
} else if (isFile(path)) {
|
||||
TT_LOG_I(TAG, "Deleting %s", path.c_str());
|
||||
return remove(path.c_str()) == 0;
|
||||
return deleteFile(path);
|
||||
} else if (path == "/" || path == "." || path == "..") {
|
||||
// No-op
|
||||
return true;
|
||||
@ -297,17 +333,36 @@ bool deleteRecursively(const std::string& path) {
|
||||
}
|
||||
}
|
||||
|
||||
bool deleteFile(const std::string& path) {
|
||||
auto lock = getLock(path)->asScopedLock();
|
||||
lock.lock();
|
||||
return remove(path.c_str()) == 0;
|
||||
}
|
||||
|
||||
bool deleteDirectory(const std::string& path) {
|
||||
auto lock = getLock(path)->asScopedLock();
|
||||
lock.lock();
|
||||
return rmdir(path.c_str()) == 0;
|
||||
}
|
||||
|
||||
bool isFile(const std::string& path) {
|
||||
auto lock = getLock(path)->asScopedLock();
|
||||
lock.lock();
|
||||
return access(path.c_str(), F_OK) == 0;
|
||||
}
|
||||
|
||||
bool isDirectory(const std::string& path) {
|
||||
auto lock = getLock(path)->asScopedLock();
|
||||
lock.lock();
|
||||
struct stat stat_result;
|
||||
return stat(path.c_str(), &stat_result) == 0 && S_ISDIR(stat_result.st_mode);
|
||||
}
|
||||
|
||||
bool readLines(const std::string& filepath, bool stripNewLine, std::function<void(const char* line)> callback) {
|
||||
auto* file = fopen(filepath.c_str(), "r");
|
||||
bool readLines(const std::string& filePath, bool stripNewLine, std::function<void(const char* line)> callback) {
|
||||
auto lock = getLock(filePath)->asScopedLock();
|
||||
lock.lock();
|
||||
|
||||
auto* file = fopen(filePath.c_str(), "r");
|
||||
if (file == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user