Merge develop into main (#343)

- Refactor `AppManifest`: add new fields and rename existing ones
- Parse and validate the manifest from an app that is being installed.
- Remove deprecated `scoped()` from `Lock`
- Create `Tactility/Paths.h`
- App loading at boot now properly parses the manifest files of external apps
- Properly lock both source and destination locations during app install
- Remove LVGL path variants from `AppPaths` and `ServicePaths`
- Removed `xPath` base classes for apps and services. There's now `AppPaths` and `ServicePaths`.
- Renamed app and service paths: "data" and "system" paths are now "user data" and "assets"
This commit is contained in:
Ken Van Hoeylandt
2025-09-22 08:03:21 +02:00
committed by GitHub
parent a4d15b2a1e
commit bab3eb19bc
108 changed files with 817 additions and 757 deletions
+8 -6
View File
@@ -1,17 +1,19 @@
#include "Tactility/service/ServiceInstance.h"
#include "Tactility/service/ServiceInstancePaths.h"
#include <Tactility/service/ServiceInstance.h>
#include <Tactility/service/ServiceManifest.h>
#include <Tactility/service/ServicePaths.h>
namespace tt::service {
ServiceInstance::ServiceInstance(std::shared_ptr<const service::ServiceManifest> manifest) :
ServiceInstance::ServiceInstance(std::shared_ptr<const ServiceManifest> manifest) :
manifest(manifest),
service(manifest->createService())
{}
const service::ServiceManifest& ServiceInstance::getManifest() const { return *manifest; }
const ServiceManifest& ServiceInstance::getManifest() const { return *manifest; }
std::unique_ptr<Paths> ServiceInstance::getPaths() const {
return std::make_unique<ServiceInstancePaths>(manifest);
std::unique_ptr<ServicePaths> ServiceInstance::getPaths() const {
return std::make_unique<ServicePaths>(manifest);
}
}
@@ -1,50 +0,0 @@
#include "Tactility/service/ServiceInstancePaths.h"
#include "Tactility/MountPoints.h"
#define LVGL_PATH_PREFIX std::string("A:/")
#ifdef ESP_PLATFORM
#define PARTITION_PREFIX std::string("/")
#else
#define PARTITION_PREFIX std::string("")
#endif
namespace tt::service {
std::string ServiceInstancePaths::getDataDirectory() const {
return PARTITION_PREFIX + file::DATA_PARTITION_NAME + "/service/" + manifest->id;
}
std::string ServiceInstancePaths::getDataDirectoryLvgl() const {
return LVGL_PATH_PREFIX + file::DATA_PARTITION_NAME + "/service/" + manifest->id;
}
std::string ServiceInstancePaths::getDataPath(const std::string& childPath) const {
assert(!childPath.starts_with('/'));
return PARTITION_PREFIX + file::DATA_PARTITION_NAME + "/service/" + manifest->id + '/' + childPath;
}
std::string ServiceInstancePaths::getDataPathLvgl(const std::string& childPath) const {
assert(!childPath.starts_with('/'));
return LVGL_PATH_PREFIX + file::DATA_PARTITION_NAME + "/service/" + manifest->id + '/' + childPath;
}
std::string ServiceInstancePaths::getSystemDirectory() const {
return PARTITION_PREFIX + file::SYSTEM_PARTITION_NAME + "/service/" + manifest->id;
}
std::string ServiceInstancePaths::getSystemDirectoryLvgl() const {
return LVGL_PATH_PREFIX + file::SYSTEM_PARTITION_NAME + "/service/" + manifest->id;
}
std::string ServiceInstancePaths::getSystemPath(const std::string& childPath) const {
assert(!childPath.starts_with('/'));
return PARTITION_PREFIX + file::SYSTEM_PARTITION_NAME + "/service/" + manifest->id + '/' + childPath;
}
std::string ServiceInstancePaths::getSystemPathLvgl(const std::string& childPath) const {
return LVGL_PATH_PREFIX + file::SYSTEM_PARTITION_NAME + "/service/" + manifest->id + '/' + childPath;
}
}
+35
View File
@@ -0,0 +1,35 @@
#include <Tactility/service/ServicePaths.h>
#include <Tactility/service/ServiceManifest.h>
#include <Tactility/MountPoints.h>
#include <cassert>
#include <format>
#ifdef ESP_PLATFORM
constexpr auto PARTITION_PREFIX = std::string("/");
#else
constexpr auto PARTITION_PREFIX = std::string("");
#endif
namespace tt::service {
std::string ServicePaths::getUserDataDirectory() const {
return std::format("{}{}/service/{}", PARTITION_PREFIX, file::DATA_PARTITION_NAME, manifest->id);
}
std::string ServicePaths::getUserDataPath(const std::string& childPath) const {
assert(!childPath.starts_with('/'));
return std::format("{}/{}", getUserDataDirectory(), childPath);
}
std::string ServicePaths::getAssetsDirectory() const {
return std::format("{}{}/service/{}/assets", PARTITION_PREFIX, file::SYSTEM_PARTITION_NAME, manifest->id);
}
std::string ServicePaths::getAssetsPath(const std::string& childPath) const {
assert(!childPath.starts_with('/'));
return std::format("{}/{}", getAssetsDirectory(), childPath);
}
}
@@ -4,10 +4,10 @@
#include <Tactility/app/App.h>
#include <Tactility/app/AppRegistration.h>
#include <Tactility/app/ElfApp.h>
#include <Tactility/file/File.h>
#include <Tactility/network/HttpdReq.h>
#include <Tactility/network/Url.h>
#include <Tactility/Paths.h>
#include <Tactility/service/development/DevelopmentSettings.h>
#include <Tactility/service/ServiceManifest.h>
#include <Tactility/service/ServiceRegistration.h>
@@ -247,7 +247,7 @@ esp_err_t DevelopmentService::handleAppInstall(httpd_req_t* request) {
}
content_left -= content_read;
const std::string tmp_path = app::getTempPath();
const std::string tmp_path = getTempPath();
auto lock = file::getLock(tmp_path)->asScopedLock();
lock.lock();
@@ -1,6 +1,6 @@
#include "Tactility/service/gps/GpsService.h"
#include "Tactility/file/ObjectFile.h"
#include <Tactility/file/ObjectFile.h>
#include <Tactility/service/gps/GpsService.h>
#include <Tactility/service/ServicePaths.h>
#include <cstring>
#include <unistd.h>
@@ -17,12 +17,12 @@ bool GpsService::getConfigurationFilePath(std::string& output) const {
return false;
}
if (!file::findOrCreateDirectory(paths->getDataDirectory(), 0777)) {
TT_LOG_E(TAG, "Failed to find or create path %s", paths->getDataDirectory().c_str());
if (!file::findOrCreateDirectory(paths->getUserDataDirectory(), 0777)) {
TT_LOG_E(TAG, "Failed to find or create path %s", paths->getUserDataDirectory().c_str());
return false;
}
output = paths->getDataPath("config.bin");
output = paths->getUserDataPath("config.bin");
return true;
}
+5 -4
View File
@@ -1,9 +1,10 @@
#include "Tactility/service/gps/GpsService.h"
#include "Tactility/service/ServiceManifest.h"
#include "Tactility/service/ServiceRegistration.h"
#include <Tactility/service/gps/GpsService.h>
#include <Tactility/Log.h>
#include <Tactility/file/File.h>
#include <Tactility/Log.h>
#include <Tactility/service/ServicePaths.h>
#include <Tactility/service/ServiceManifest.h>
#include <Tactility/service/ServiceRegistration.h>
using tt::hal::gps::GpsDevice;
+1 -1
View File
@@ -193,7 +193,7 @@ void GuiService::requestDraw() {
void GuiService::showApp(std::shared_ptr<app::AppContext> app) {
lock();
if (!isStarted) {
TT_LOG_W(TAG, "Failed to show app %s: GUI not started", app->getManifest().id.c_str());
TT_LOG_W(TAG, "Failed to show app %s: GUI not started", app->getManifest().appId.c_str());
} else {
// Ensure previous app triggers onHide() logic
if (appToRender != nullptr) {
+8 -8
View File
@@ -103,7 +103,7 @@ void LoaderService::onStartAppMessage(const std::string& id, app::LaunchId launc
auto previous_app = !appStack.empty() ? appStack.top() : nullptr;
auto new_app = std::make_shared<app::AppInstance>(app_manifest, launchId, parameters);
new_app->mutableFlags().hideStatusbar = (app_manifest->flags & app::AppManifest::Flags::HideStatusBar);
new_app->mutableFlags().hideStatusbar = (app_manifest->appFlags & app::AppManifest::Flags::HideStatusBar);
appStack.push(new_app);
transitionAppToState(new_app, app::State::Initial);
@@ -136,12 +136,12 @@ void LoaderService::onStopAppMessage(const std::string& id) {
// Stop current app
auto app_to_stop = appStack.top();
if (app_to_stop->getManifest().id != id) {
TT_LOG_E(TAG, "Stop app: id mismatch (wanted %s but found %s on top of stack)", id.c_str(), app_to_stop->getManifest().id.c_str());
if (app_to_stop->getManifest().appId != id) {
TT_LOG_E(TAG, "Stop app: id mismatch (wanted %s but found %s on top of stack)", id.c_str(), app_to_stop->getManifest().appId.c_str());
return;
}
if (original_stack_size == 1 && app_to_stop->getManifest().name != "Boot") {
if (original_stack_size == 1 && app_to_stop->getManifest().appName != "Boot") {
TT_LOG_E(TAG, "Stop app: can't stop root app");
return;
}
@@ -162,12 +162,12 @@ void LoaderService::onStopAppMessage(const std::string& id) {
// We only expect the app to be referenced within the current scope
if (app_to_stop.use_count() > 1) {
TT_LOG_W(TAG, "Memory leak: Stopped %s, but use count is %ld", app_to_stop->getManifest().id.c_str(), app_to_stop.use_count() - 1);
TT_LOG_W(TAG, "Memory leak: Stopped %s, but use count is %ld", app_to_stop->getManifest().appId.c_str(), app_to_stop.use_count() - 1);
}
// Refcount is expected to be 2: 1 within app_to_stop and 1 within the current scope
if (app_to_stop->getApp().use_count() > 2) {
TT_LOG_W(TAG, "Memory leak: Stopped %s, but use count is %ld", app_to_stop->getManifest().id.c_str(), app_to_stop->getApp().use_count() - 2);
TT_LOG_W(TAG, "Memory leak: Stopped %s, but use count is %ld", app_to_stop->getManifest().appId.c_str(), app_to_stop->getApp().use_count() - 2);
}
#ifdef ESP_PLATFORM
@@ -224,7 +224,7 @@ void LoaderService::transitionAppToState(const std::shared_ptr<app::AppInstance>
TT_LOG_I(
TAG,
"App \"%s\" state: %s -> %s",
app_manifest.id.c_str(),
app_manifest.appId.c_str(),
appStateToString(old_state),
appStateToString(state)
);
@@ -263,7 +263,7 @@ app::LaunchId LoaderService::startApp(const std::string& id, std::shared_ptr<con
void LoaderService::stopApp() {
TT_LOG_I(TAG, "stopApp()");
auto id = getCurrentAppContext()->getManifest().id;
auto id = getCurrentAppContext()->getManifest().appId;
dispatcherThread->dispatch([this, id]() {
onStopAppMessage(id);
});
@@ -84,10 +84,10 @@ void ScreenshotTask::taskMain() {
auto appContext = app::getCurrentAppContext();
if (appContext != nullptr) {
const app::AppManifest& manifest = appContext->getManifest();
if (manifest.id != last_app_id) {
if (manifest.appId != last_app_id) {
kernel::delayMillis(100);
last_app_id = manifest.id;
auto filename = std::format("{}/screenshot-{}.png", work.path, manifest.id);
last_app_id = manifest.appId;
auto filename = std::format("{}/screenshot-{}.png", work.path, manifest.appId);
makeScreenshot(filename);
}
}
@@ -1,16 +1,16 @@
#include <Tactility/lvgl/Statusbar.h>
#include <Tactility/lvgl/LvglSync.h>
#include <Tactility/hal/power/PowerDevice.h>
#include <Tactility/hal/sdcard/SdCardDevice.h>
#include <Tactility/lvgl/Lvgl.h>
#include <Tactility/service/gps/GpsService.h>
#include <Tactility/lvgl/LvglSync.h>
#include <Tactility/Mutex.h>
#include <Tactility/Tactility.h>
#include <Tactility/Timer.h>
#include <Tactility/service/gps/GpsService.h>
#include <Tactility/service/ServiceContext.h>
#include <Tactility/service/ServicePaths.h>
#include <Tactility/service/ServiceRegistration.h>
#include <Tactility/service/wifi/Wifi.h>
#include <Tactility/Timer.h>
namespace tt::service::statusbar {
@@ -148,7 +148,7 @@ class StatusbarService final : public Service {
int8_t power_icon_id;
const char* power_last_icon = nullptr;
std::unique_ptr<Paths> paths;
std::unique_ptr<ServicePaths> paths;
void lock() const {
mutex.lock();
@@ -163,7 +163,7 @@ class StatusbarService final : public Service {
bool show_icon = (gps_state == gps::State::OnPending) || (gps_state == gps::State::On);
if (gps_last_state != show_icon) {
if (show_icon) {
auto icon_path = paths->getSystemPathLvgl(STATUSBAR_ICON_GPS);
auto icon_path = "A:" + paths->getAssetsPath(STATUSBAR_ICON_GPS);
lvgl::statusbar_icon_set_image(gps_icon_id, icon_path);
lvgl::statusbar_icon_set_visibility(gps_icon_id, true);
} else {
@@ -179,7 +179,7 @@ class StatusbarService final : public Service {
const char* desired_icon = getWifiStatusIcon(radio_state, is_secure);
if (wifi_last_icon != desired_icon) {
if (desired_icon != nullptr) {
auto icon_path = paths->getSystemPathLvgl(desired_icon);
auto icon_path = "A:" + paths->getAssetsPath(desired_icon);
lvgl::statusbar_icon_set_image(wifi_icon_id, icon_path);
lvgl::statusbar_icon_set_visibility(wifi_icon_id, true);
} else {
@@ -193,7 +193,7 @@ class StatusbarService final : public Service {
const char* desired_icon = getPowerStatusIcon();
if (power_last_icon != desired_icon) {
if (desired_icon != nullptr) {
auto icon_path = paths->getSystemPathLvgl(desired_icon);
auto icon_path = "A:" + paths->getAssetsPath(desired_icon);
lvgl::statusbar_icon_set_image(power_icon_id, icon_path);
lvgl::statusbar_icon_set_visibility(power_icon_id, true);
} else {
@@ -212,7 +212,7 @@ class StatusbarService final : public Service {
if (state != hal::sdcard::SdCardDevice::State::Timeout) {
auto* desired_icon = getSdCardStatusIcon(state);
if (sdcard_last_icon != desired_icon) {
auto icon_path = paths->getSystemPathLvgl(desired_icon);
auto icon_path = "A:" + paths->getAssetsPath(desired_icon);
lvgl::statusbar_icon_set_image(sdcard_icon_id, icon_path);
lvgl::statusbar_icon_set_visibility(sdcard_icon_id, true);
sdcard_last_icon = desired_icon;
+6 -11
View File
@@ -1,24 +1,19 @@
#include "Tactility/service/wifi/Wifi.h"
#ifndef ESP_PLATFORM
#include "Tactility/service/ServiceContext.h"
#include <Tactility/service/wifi/Wifi.h>
#include <Tactility/Check.h>
#include <Tactility/Log.h>
#include <Tactility/Mutex.h>
#include <Tactility/PubSub.h>
#include <Tactility/service/Service.h>
#include <Tactility/service/ServiceManifest.h>
namespace tt::service::wifi {
#define TAG "wifi"
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
constexpr auto* TAG = "Wifi";
struct Wifi {
Wifi() = default;
~Wifi() = default;
/** @brief Locking mechanism for modifying the Wifi instance */
Mutex mutex = Mutex(Mutex::Type::Recursive);
/** @brief The public event bus */
@@ -144,13 +139,13 @@ class WifiService final : public Service {
public:
bool onStart(TT_UNUSED ServiceContext& service) final {
bool onStart(TT_UNUSED ServiceContext& service) override {
tt_check(wifi == nullptr);
wifi = new Wifi();
return true;
}
void onStop(TT_UNUSED ServiceContext& service) final {
void onStop(TT_UNUSED ServiceContext& service) override {
tt_check(wifi != nullptr);
delete wifi;
wifi = nullptr;