Require SD card or >4MB flash (#545)
This commit is contained in:
committed by
GitHub
parent
90afba647e
commit
05720821f8
@@ -1,21 +1,15 @@
|
||||
#include <Tactility/service/ServicePaths.h>
|
||||
|
||||
#include <Tactility/service/ServiceManifest.h>
|
||||
#include <Tactility/MountPoints.h>
|
||||
#include <Tactility/Paths.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);
|
||||
return std::format("{}/service/{}", tt::getUserDataPath(), manifest->id);
|
||||
}
|
||||
|
||||
std::string ServicePaths::getUserDataPath(const std::string& childPath) const {
|
||||
@@ -24,7 +18,7 @@ std::string ServicePaths::getUserDataPath(const std::string& childPath) const {
|
||||
}
|
||||
|
||||
std::string ServicePaths::getAssetsDirectory() const {
|
||||
return std::format("{}{}/service/{}/assets", PARTITION_PREFIX, file::SYSTEM_PARTITION_NAME, manifest->id);
|
||||
return std::format("{}/service/{}/assets", tt::getUserDataPath(), manifest->id);
|
||||
}
|
||||
|
||||
std::string ServicePaths::getAssetsPath(const std::string& childPath) const {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <Tactility/file/PropertiesFile.h>
|
||||
#include <Tactility/Logger.h>
|
||||
#include <Tactility/Paths.h>
|
||||
#include <Tactility/service/development/DevelopmentSettings.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
@@ -9,7 +10,10 @@ namespace tt::service::development {
|
||||
|
||||
static const auto LOGGER = Logger("DevSettings");
|
||||
|
||||
constexpr auto* SETTINGS_FILE = "/data/settings/development.properties";
|
||||
static std::string getSettingsFilePath() {
|
||||
return getUserDataPath() + "/settings/development.properties";
|
||||
}
|
||||
|
||||
constexpr auto* SETTINGS_KEY_ENABLE_ON_BOOT = "enableOnBoot";
|
||||
|
||||
struct DevelopmentSettings {
|
||||
@@ -18,7 +22,7 @@ struct DevelopmentSettings {
|
||||
|
||||
static bool load(DevelopmentSettings& settings) {
|
||||
std::map<std::string, std::string> map;
|
||||
if (!file::loadPropertiesFile(SETTINGS_FILE, map)) {
|
||||
if (!file::loadPropertiesFile(getSettingsFilePath(), map)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -34,13 +38,13 @@ static bool load(DevelopmentSettings& settings) {
|
||||
static bool save(const DevelopmentSettings& settings) {
|
||||
std::map<std::string, std::string> map;
|
||||
map[SETTINGS_KEY_ENABLE_ON_BOOT] = settings.enableOnBoot ? "true" : "false";
|
||||
return file::savePropertiesFile(SETTINGS_FILE, map);
|
||||
return file::savePropertiesFile(getSettingsFilePath(), map);
|
||||
}
|
||||
|
||||
void setEnableOnBoot(bool enable) {
|
||||
DevelopmentSettings properties { .enableOnBoot = enable };
|
||||
if (!save(properties)) {
|
||||
LOGGER.error("Failed to save {}", SETTINGS_FILE);
|
||||
LOGGER.error("Failed to save {}", getSettingsFilePath());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
namespace tt::service::webserver {
|
||||
|
||||
static const auto LOGGER = tt::Logger("AssetVersion");
|
||||
constexpr auto* DATA_VERSION_FILE = "/data/webserver/version.json";
|
||||
constexpr auto* DATA_VERSION_FILE = "/system/app/WebServer/version.json";
|
||||
constexpr auto* SD_VERSION_FILE = "/sdcard/tactility/webserver/version.json";
|
||||
constexpr auto* DATA_ASSETS_DIR = "/data/webserver";
|
||||
constexpr auto* DATA_ASSETS_DIR = "/system/app/WebServer";
|
||||
constexpr auto* SD_ASSETS_DIR = "/sdcard/tactility/webserver";
|
||||
|
||||
static bool loadVersionFromFile(const char* path, AssetVersion& version) {
|
||||
@@ -349,17 +349,6 @@ bool syncAssets() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// POST-FLASH RECOVERY: Data empty but SD card exists
|
||||
if (!dataExists) {
|
||||
LOGGER.info("Data partition empty - copying from SD card (recovery mode)");
|
||||
if (!copyDirectory(SD_ASSETS_DIR, DATA_ASSETS_DIR)) {
|
||||
LOGGER.error("Failed to copy assets from SD card to Data");
|
||||
return false;
|
||||
}
|
||||
LOGGER.info("Recovery complete - assets restored from SD card");
|
||||
return true;
|
||||
}
|
||||
|
||||
// NORMAL OPERATION: Both exist - compare versions
|
||||
AssetVersion dataVersion, sdVersion;
|
||||
bool hasDataVer = loadDataVersion(dataVersion);
|
||||
|
||||
@@ -207,11 +207,6 @@ bool WebServerService::onStart(ServiceContext& service) {
|
||||
statusbarIconId = lvgl::statusbar_icon_add();
|
||||
lvgl::statusbar_icon_set_visibility(statusbarIconId, false);
|
||||
|
||||
// Run asset synchronization on startup
|
||||
if (!syncAssets()) {
|
||||
LOGGER.warn("Asset sync failed, but continuing with available assets");
|
||||
}
|
||||
|
||||
// Load and cache settings once at boot
|
||||
bool serverEnabled;
|
||||
{
|
||||
@@ -619,7 +614,7 @@ static bool isAllowedBasePath(const std::string& path, bool allowRoot = false) {
|
||||
return false;
|
||||
}
|
||||
if (allowRoot && path == "/") return true;
|
||||
return path == "/data" || path.starts_with("/data/") || path == "/sdcard" || path.starts_with("/sdcard/");
|
||||
return path.starts_with("/data") || path.starts_with("/system/app/WebServer") || path.starts_with("/sdcard");
|
||||
}
|
||||
|
||||
// Normalize client-supplied path: URL-decode, trim quotes/control chars, ensure leading slash, collapse duplicate slashes
|
||||
@@ -990,7 +985,6 @@ esp_err_t WebServerService::handleAdminPost(httpd_req_t* request) {
|
||||
}
|
||||
|
||||
const char* uri = request->uri;
|
||||
if (strncmp(uri, "/admin/sync", 11) == 0) return handleSync(request);
|
||||
if (strncmp(uri, "/admin/reboot", 13) == 0) return handleReboot(request);
|
||||
LOGGER.info("POST {} - not found in admin dispatcher", uri);
|
||||
httpd_resp_send_err(request, HTTPD_404_NOT_FOUND, "not found");
|
||||
@@ -1460,10 +1454,7 @@ esp_err_t WebServerService::handleApiScreenshot(httpd_req_t* request) {
|
||||
|
||||
#if TT_FEATURE_SCREENSHOT_ENABLED
|
||||
// Determine save location: prefer SD card root if mounted, otherwise /data
|
||||
std::string save_path;
|
||||
if (!findFirstMountedSdCardPath(save_path)) {
|
||||
save_path = file::MOUNT_POINT_DATA;
|
||||
}
|
||||
std::string save_path = getUserDataRootPath();
|
||||
|
||||
// Find next available filename with incrementing number
|
||||
std::string screenshot_path;
|
||||
@@ -1683,25 +1674,9 @@ esp_err_t WebServerService::handleFsRename(httpd_req_t* request) {
|
||||
|
||||
// endregion
|
||||
|
||||
esp_err_t WebServerService::handleSync(httpd_req_t* request) {
|
||||
|
||||
LOGGER.info("POST /sync");
|
||||
|
||||
bool success = syncAssets();
|
||||
|
||||
if (success) {
|
||||
httpd_resp_sendstr(request, "Assets synchronized successfully");
|
||||
} else {
|
||||
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Asset sync failed");
|
||||
}
|
||||
|
||||
return success ? ESP_OK : ESP_FAIL;
|
||||
}
|
||||
|
||||
esp_err_t WebServerService::handleReboot(httpd_req_t* request) {
|
||||
|
||||
LOGGER.info("POST /reboot");
|
||||
|
||||
httpd_resp_sendstr(request, "Rebooting...");
|
||||
|
||||
// Reboot after a short delay to allow response to be sent
|
||||
@@ -1724,7 +1699,7 @@ esp_err_t WebServerService::handleAssets(httpd_req_t* request) {
|
||||
|
||||
// Special case: serve favicon from system assets
|
||||
if (strcmp(uri, "/favicon.ico") == 0) {
|
||||
const char* faviconPath = "/data/system/spinner.png";
|
||||
const char* faviconPath = "/system/spinner.png";
|
||||
if (file::isFile(faviconPath)) {
|
||||
httpd_resp_set_type(request, "image/png");
|
||||
httpd_resp_set_hdr(request, "Cache-Control", "public, max-age=86400");
|
||||
@@ -1767,11 +1742,9 @@ esp_err_t WebServerService::handleAssets(httpd_req_t* request) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
std::string dataPath = std::string("/data/webserver") + requestedPath;
|
||||
std::string dataPath = std::string("/system/app/WebServer") + requestedPath;
|
||||
|
||||
if (requestedPath == "/dashboard.html" && !file::isFile(dataPath.c_str())) {
|
||||
// Dashboard doesn't exist, try default.html
|
||||
dataPath = "/data/webserver/default.html";
|
||||
LOGGER.info("dashboard.html not found, serving default.html");
|
||||
}
|
||||
|
||||
@@ -1854,6 +1827,11 @@ void setWebServerEnabled(bool enabled) {
|
||||
}
|
||||
}
|
||||
|
||||
bool isWebServerEnabled() {
|
||||
WebServerService* instance = g_webServerInstance.load();
|
||||
return instance != nullptr && instance->isEnabled();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // ESP_PLATFORM
|
||||
|
||||
@@ -181,6 +181,10 @@ bool save(const WifiApSettings& apSettings) {
|
||||
}
|
||||
|
||||
const auto file_path = getApPropertiesFilePath(service_context->getPaths(), apSettings.ssid);
|
||||
if (!file::findOrCreateParentDirectory(file_path, 0755)) {
|
||||
LOGGER.error("Failed to create {}", file_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> map;
|
||||
|
||||
|
||||
@@ -124,16 +124,9 @@ void bootSplashInit() {
|
||||
getMainDispatcher().dispatch([] {
|
||||
LOGGER.info("bootSplashInit dispatch begin");
|
||||
// First import any provisioning files placed on the system data partition.
|
||||
const std::string data_settings_path = file::getChildPath(file::MOUNT_POINT_DATA, "settings");
|
||||
const std::string data_settings_path = file::getChildPath(getUserDataPath(), "provisioning");
|
||||
importWifiApSettingsFromDir(data_settings_path);
|
||||
|
||||
// Then scan attached SD cards as before.
|
||||
std::string sdcard_path;
|
||||
if (findFirstMountedSdCardPath((sdcard_path))) {
|
||||
const std::string sd_settings_path = file::getChildPath(sdcard_path, "settings");
|
||||
importWifiApSettingsFromDir(sd_settings_path);
|
||||
}
|
||||
|
||||
if (settings::shouldEnableOnBoot()) {
|
||||
LOGGER.info("Auto-enabling due to setting");
|
||||
getMainDispatcher().dispatch([] -> void { setEnabled(true); });
|
||||
|
||||
@@ -21,14 +21,14 @@ static WifiSettings cachedSettings {
|
||||
|
||||
static bool cached = false;
|
||||
|
||||
static bool load(WifiSettings& settings) {
|
||||
auto service_context = findServiceContext();
|
||||
if (service_context == nullptr) {
|
||||
return false;
|
||||
}
|
||||
static bool hasWifiSettingsFile(std::shared_ptr<ServiceContext> context) {
|
||||
std::string settings_path = context->getPaths()->getUserDataPath("settings.properties");
|
||||
return file::isFile(settings_path);
|
||||
}
|
||||
|
||||
static bool load(std::shared_ptr<ServiceContext> context, WifiSettings& settings) {
|
||||
std::map<std::string, std::string> map;
|
||||
std::string settings_path = service_context->getPaths()->getUserDataPath("settings.properties");
|
||||
std::string settings_path = context->getPaths()->getUserDataPath("settings.properties");
|
||||
if (!file::loadPropertiesFile(settings_path, map)) {
|
||||
return false;
|
||||
}
|
||||
@@ -42,23 +42,26 @@ static bool load(WifiSettings& settings) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool save(const WifiSettings& settings) {
|
||||
auto service_context = findServiceContext();
|
||||
if (service_context == nullptr) {
|
||||
return false;
|
||||
}
|
||||
static bool save(std::shared_ptr<ServiceContext> context, const WifiSettings& settings) {
|
||||
std::map<std::string, std::string> map;
|
||||
map[SETTINGS_KEY_ENABLE_ON_BOOT] = settings.enableOnBoot ? "true" : "false";
|
||||
std::string settings_path = service_context->getPaths()->getUserDataPath("settings.properties");
|
||||
std::string settings_path = context->getPaths()->getUserDataPath("settings.properties");
|
||||
if (!file::findOrCreateParentDirectory(settings_path, 0755)) {
|
||||
LOGGER.error("Failed to create {}", settings_path);
|
||||
return false;
|
||||
}
|
||||
return file::savePropertiesFile(settings_path, map);
|
||||
}
|
||||
|
||||
WifiSettings getCachedOrLoad() {
|
||||
if (!cached) {
|
||||
if (!load(cachedSettings)) {
|
||||
LOGGER.error("Failed to load");
|
||||
} else {
|
||||
cached = true;
|
||||
auto context = findServiceContext();
|
||||
if (context && hasWifiSettingsFile(context)) {
|
||||
if (load(context, cachedSettings)) {
|
||||
cached = true;
|
||||
} else {
|
||||
LOGGER.info("Failed to load settings, using defaults");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,8 +70,9 @@ WifiSettings getCachedOrLoad() {
|
||||
|
||||
void setEnableOnBoot(bool enable) {
|
||||
cachedSettings.enableOnBoot = enable;
|
||||
if (!save(cachedSettings)) {
|
||||
LOGGER.error("Failed to save");
|
||||
auto context = findServiceContext();
|
||||
if (context && !save(context, cachedSettings)) {
|
||||
LOGGER.error("Failed to save settings");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user