Merge develop to main (#334)
- `FileBrowser` app now supports deleting directories (recursively) - `DevelopmentService` and `tactility.py` now support the app `uninstall` action - Fix crash for `File` app: implement file locking in several places (SPI SD cards need it) - Remove I2C configuration from `M5stackCardputer.cpp` because we don't support the "Cardputer Adv" variant in that firmware.
This commit is contained in:
committed by
GitHub
parent
7027da00b8
commit
d5c94c7a8a
@@ -100,4 +100,6 @@ std::string getInstallPath();
|
||||
|
||||
bool install(const std::string& path);
|
||||
|
||||
bool uninstall(const std::string& appId);
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ struct AppManifest;
|
||||
/** Register an application with its manifest */
|
||||
void addApp(const AppManifest& manifest);
|
||||
|
||||
/** Remove an app from the registry */
|
||||
bool removeApp(const std::string& id);
|
||||
|
||||
/** Find an application manifest by its id
|
||||
* @param[in] id the manifest id
|
||||
* @return the application manifest if it was found
|
||||
|
||||
@@ -41,6 +41,13 @@ class DevelopmentService final : public Service {
|
||||
.user_ctx = this
|
||||
};
|
||||
|
||||
httpd_uri_t appUninstallEndpoint = {
|
||||
.uri = "/app/uninstall",
|
||||
.method = HTTP_PUT,
|
||||
.handler = handleAppUninstall,
|
||||
.user_ctx = this
|
||||
};
|
||||
|
||||
void onNetworkConnected();
|
||||
void onNetworkDisconnected();
|
||||
|
||||
@@ -50,6 +57,7 @@ class DevelopmentService final : public Service {
|
||||
static esp_err_t handleGetInfo(httpd_req_t* request);
|
||||
static esp_err_t handleAppRun(httpd_req_t* request);
|
||||
static esp_err_t handleAppInstall(httpd_req_t* request);
|
||||
static esp_err_t handleAppUninstall(httpd_req_t* request);
|
||||
|
||||
public:
|
||||
|
||||
|
||||
@@ -223,4 +223,25 @@ bool install(const std::string& path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool uninstall(const std::string& appId) {
|
||||
TT_LOG_I(TAG, "Uninstalling app %s", appId.c_str());
|
||||
auto app_path = getInstallPath() + "/" + 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::deleteRecursively(app_path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!removeApp(appId)) {
|
||||
TT_LOG_W(TAG, "Failed to remove app %d from registry", appId.c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -29,6 +29,15 @@ void addApp(const AppManifest& manifest) {
|
||||
hash_mutex.unlock();
|
||||
}
|
||||
|
||||
bool removeApp(const std::string& id) {
|
||||
TT_LOG_I(TAG, "Removing manifest for %s", id.c_str());
|
||||
|
||||
auto lock = hash_mutex.asScopedLock();
|
||||
lock.lock();
|
||||
|
||||
return app_manifest_map.erase(id) == 1;
|
||||
}
|
||||
|
||||
_Nullable std::shared_ptr<AppManifest> findAppById(const std::string& id) {
|
||||
hash_mutex.lock();
|
||||
auto result = app_manifest_map.find(id);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
#include <dirent.h>
|
||||
#include <Tactility/file/FileLock.h>
|
||||
|
||||
namespace tt::app::filebrowser {
|
||||
|
||||
@@ -56,18 +57,19 @@ bool State::setEntriesForPath(const std::string& path) {
|
||||
return true;
|
||||
} else {
|
||||
dir_entries.clear();
|
||||
// TODO: file Lock
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
#include <Tactility/file/FileLock.h>
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include "Tactility/service/loader/Loader.h"
|
||||
@@ -215,6 +216,8 @@ void View::showActionsForDirectory() {
|
||||
|
||||
auto* rename_button = lv_list_add_button(action_list, LV_SYMBOL_EDIT, "Rename");
|
||||
lv_obj_add_event_cb(rename_button, onRenamePressedCallback, LV_EVENT_SHORT_CLICKED, this);
|
||||
auto* delete_button = lv_list_add_button(action_list, LV_SYMBOL_TRASH, "Delete");
|
||||
lv_obj_add_event_cb(delete_button, onDeletePressedCallback, LV_EVENT_SHORT_CLICKED, this);
|
||||
|
||||
lv_obj_remove_flag(action_list, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
@@ -314,12 +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) {
|
||||
int delete_count = remove(filepath.c_str());
|
||||
if (delete_count > 0) {
|
||||
TT_LOG_I(TAG, "Deleted %d items", delete_count);
|
||||
} else {
|
||||
TT_LOG_W(TAG, "Failed to delete %s", filepath.c_str());
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
state->setEntriesForPath(state->getCurrentPath());
|
||||
update();
|
||||
}
|
||||
@@ -328,12 +337,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()) {
|
||||
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());
|
||||
}
|
||||
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());
|
||||
}
|
||||
});
|
||||
|
||||
state->setEntriesForPath(state->getCurrentPath());
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@ void DevelopmentService::startServer() {
|
||||
httpd_register_uri_handler(server, &handleGetInfoEndpoint);
|
||||
httpd_register_uri_handler(server, &appRunEndpoint);
|
||||
httpd_register_uri_handler(server, &appInstallEndpoint);
|
||||
httpd_register_uri_handler(server, &appUninstallEndpoint);
|
||||
TT_LOG_I(TAG, "Started on port %d", config.server_port);
|
||||
} else {
|
||||
TT_LOG_E(TAG, "Failed to start");
|
||||
@@ -297,6 +298,39 @@ esp_err_t DevelopmentService::handleAppInstall(httpd_req_t* request) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t DevelopmentService::handleAppUninstall(httpd_req_t* request) {
|
||||
TT_LOG_I(TAG, "PUT /app/uninstall");
|
||||
|
||||
std::string query;
|
||||
if (!network::getQueryOrSendError(request, query)) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
auto parameters = network::parseUrlQuery(query);
|
||||
auto id_key_pos = parameters.find("id");
|
||||
if (id_key_pos == parameters.end()) {
|
||||
TT_LOG_W(TAG, "[400] /app/uninstall id not specified");
|
||||
httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "id not specified");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (!app::findAppById(id_key_pos->second)) {
|
||||
TT_LOG_I(TAG, "[200] /app/uninstall %s (app wasn't installed)", id_key_pos->second.c_str());
|
||||
httpd_resp_send(request, nullptr, 0);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
if (app::uninstall(id_key_pos->second)) {
|
||||
TT_LOG_I(TAG, "[200] /app/uninstall %s", id_key_pos->second.c_str());
|
||||
httpd_resp_send(request, nullptr, 0);
|
||||
return ESP_OK;
|
||||
} else {
|
||||
TT_LOG_W(TAG, "[500] /app/uninstall %s", id_key_pos->second.c_str());
|
||||
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to uninstall");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
std::shared_ptr<DevelopmentService> findService() {
|
||||
|
||||
Reference in New Issue
Block a user