committed by
GitHub
parent
cca7224252
commit
f4f91f81d6
@@ -1,15 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <tactility/service/service_instance.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace tt::service {
|
namespace tt::service {
|
||||||
|
|
||||||
enum class State {
|
using State = ::ServiceState;
|
||||||
Starting,
|
|
||||||
Started,
|
|
||||||
Stopping,
|
|
||||||
Stopped
|
|
||||||
};
|
|
||||||
|
|
||||||
// Forward declaration
|
// Forward declaration
|
||||||
class ServiceContext;
|
class ServiceContext;
|
||||||
@@ -26,6 +23,6 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
std::shared_ptr<Service> create() { return std::shared_ptr<T>(new T); }
|
void* create(const ::ServiceManifest* /*manifest*/) { return new std::shared_ptr<Service>(std::shared_ptr<T>(new T)); }
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <tactility/service/service_manifest.h>
|
||||||
|
|
||||||
|
struct ServiceInstance;
|
||||||
|
|
||||||
namespace tt::service {
|
namespace tt::service {
|
||||||
|
|
||||||
@@ -8,23 +11,23 @@ struct ServiceManifest;
|
|||||||
class ServicePaths;
|
class ServicePaths;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The public representation of a service instance.
|
* Thin, non-owning wrapper around the running TactilityKernel service instance
|
||||||
|
* (ServiceInstance, which the kernel API also calls ServiceContext).
|
||||||
* @warning Do not store references or pointers to these! You can retrieve them via the Loader service.
|
* @warning Do not store references or pointers to these! You can retrieve them via the Loader service.
|
||||||
*/
|
*/
|
||||||
class ServiceContext {
|
class ServiceContext final {
|
||||||
|
|
||||||
protected:
|
::ServiceInstance* service_instance;
|
||||||
|
|
||||||
virtual ~ServiceContext() = default;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
explicit ServiceContext(::ServiceInstance* instance) : service_instance(instance) {}
|
||||||
|
|
||||||
/** @return a reference to the service's manifest */
|
/** @return a reference to the service's manifest */
|
||||||
virtual const ServiceManifest& getManifest() const = 0;
|
const ::ServiceManifest* getManifest() const;
|
||||||
|
|
||||||
/** Retrieve the paths that are relevant to this service */
|
/** Retrieve the paths that are relevant to this service */
|
||||||
virtual std::unique_ptr<ServicePaths> getPaths() const = 0;
|
std::unique_ptr<ServicePaths> getPaths() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace tt::service {
|
|||||||
// Forward declarations
|
// Forward declarations
|
||||||
class ServiceContext;
|
class ServiceContext;
|
||||||
|
|
||||||
typedef std::shared_ptr<Service>(*CreateService)();
|
using CreateService = ::ServiceCreate;
|
||||||
|
|
||||||
/** A ledger that describes the main parts of a service. */
|
/** A ledger that describes the main parts of a service. */
|
||||||
struct ServiceManifest {
|
struct ServiceManifest {
|
||||||
|
|||||||
@@ -1,45 +1,67 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <tactility/check.h>
|
||||||
|
#include <tactility/error.h>
|
||||||
|
#include <tactility/service/service_manifest.h>
|
||||||
|
#include <tactility/service/service_paths.h>
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace tt::service {
|
namespace tt::service {
|
||||||
|
|
||||||
// Forward declarations
|
constexpr size_t PATH_BUFFER_SIZE = 255;
|
||||||
class ServiceManifest;
|
|
||||||
|
|
||||||
class ServicePaths {
|
class ServicePaths {
|
||||||
|
|
||||||
std::shared_ptr<const ServiceManifest> manifest;
|
const ::ServiceManifest* manifest;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
explicit ServicePaths(std::shared_ptr<const ServiceManifest> manifest) : manifest(std::move(manifest)) {}
|
explicit ServicePaths(const ::ServiceManifest* manifest) : manifest(manifest) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The user data directory is intended to survive OS upgrades.
|
* The user data directory is intended to survive OS upgrades.
|
||||||
* The path will not end with a "/".
|
* The path will not end with a "/".
|
||||||
*/
|
*/
|
||||||
std::string getUserDataDirectory() const;
|
std::string getUserDataDirectory() const {
|
||||||
|
char buffer[PATH_BUFFER_SIZE];
|
||||||
|
check(service_paths_get_user_data_directory(manifest->id, buffer, sizeof(buffer)) == ERROR_NONE);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The user data directory is intended to survive OS upgrades.
|
* The user data directory is intended to survive OS upgrades.
|
||||||
* Configuration data should be stored here.
|
* Configuration data should be stored here.
|
||||||
* @param[in] childPath the path without a "/" prefix
|
* @param[in] childPath the path without a "/" prefix
|
||||||
*/
|
*/
|
||||||
std::string getUserDataPath(const std::string& childPath) const;
|
std::string getUserDataPath(const std::string& childPath) const {
|
||||||
|
assert(!childPath.starts_with('/'));
|
||||||
|
char buffer[PATH_BUFFER_SIZE];
|
||||||
|
check(service_paths_get_user_data_path(manifest->id, childPath.c_str(), buffer, sizeof(buffer)) == ERROR_NONE);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* You should not store configuration data here.
|
* You should not store configuration data here.
|
||||||
* The path will not end with a "/".
|
* The path will not end with a "/".
|
||||||
*/
|
*/
|
||||||
std::string getAssetsDirectory() const;
|
std::string getAssetsDirectory() const {
|
||||||
|
char buffer[PATH_BUFFER_SIZE];
|
||||||
|
check(service_paths_get_assets_directory(manifest->id, buffer, sizeof(buffer)) == ERROR_NONE);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* You should not store configuration data here.
|
* You should not store configuration data here.
|
||||||
* @param[in] childPath the path without a "/" prefix
|
* @param[in] childPath the path without a "/" prefix
|
||||||
*/
|
*/
|
||||||
std::string getAssetsPath(const std::string& childPath) const;
|
std::string getAssetsPath(const std::string& childPath) const {
|
||||||
|
assert(!childPath.starts_with('/'));
|
||||||
|
char buffer[PATH_BUFFER_SIZE];
|
||||||
|
check(service_paths_get_assets_path(manifest->id, childPath.c_str(), buffer, sizeof(buffer)) == ERROR_NONE);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ State getState(const std::string& id);
|
|||||||
* @param[in] id the id as defined in the manifest
|
* @param[in] id the id as defined in the manifest
|
||||||
* @return the matching manifest or nullptr when it wasn't found
|
* @return the matching manifest or nullptr when it wasn't found
|
||||||
*/
|
*/
|
||||||
std::shared_ptr<const ServiceManifest> findManifestById(const std::string& id);
|
const ::ServiceManifest* findManifestById(const std::string& id);
|
||||||
|
|
||||||
/** Find a ServiceContext by its manifest id.
|
/** Find a ServiceContext by its manifest id.
|
||||||
* @param[in] id the id as defined in the manifest
|
* @param[in] id the id as defined in the manifest
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Tactility/service/ServiceContext.h>
|
|
||||||
#include <Tactility/service/Service.h>
|
|
||||||
#include <Tactility/Mutex.h>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace tt::service {
|
|
||||||
|
|
||||||
class ServiceInstance final : public ServiceContext {
|
|
||||||
|
|
||||||
Mutex mutex;
|
|
||||||
std::shared_ptr<const ServiceManifest> manifest;
|
|
||||||
std::shared_ptr<Service> service;
|
|
||||||
State state = State::Stopped;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
explicit ServiceInstance(std::shared_ptr<const ServiceManifest> manifest);
|
|
||||||
~ServiceInstance() override = default;
|
|
||||||
|
|
||||||
/** @return a reference to the service's manifest */
|
|
||||||
const ServiceManifest& getManifest() const override;
|
|
||||||
|
|
||||||
/** Retrieve the paths that are relevant to this service */
|
|
||||||
std::unique_ptr<ServicePaths> getPaths() const override;
|
|
||||||
|
|
||||||
std::shared_ptr<Service> getService() const { return service; }
|
|
||||||
|
|
||||||
State getState() const { return state; }
|
|
||||||
|
|
||||||
void setState(State newState) { state = newState; }
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#include <Tactility/bluetooth/BluetoothPairedDevice.h>
|
#include <Tactility/bluetooth/BluetoothPairedDevice.h>
|
||||||
|
|
||||||
|
#include "Tactility/Paths.h"
|
||||||
|
|
||||||
#include <Tactility/file/File.h>
|
#include <Tactility/file/File.h>
|
||||||
#include <Tactility/file/PropertiesFile.h>
|
#include <Tactility/file/PropertiesFile.h>
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
@@ -16,13 +18,16 @@ namespace tt::bluetooth::settings {
|
|||||||
constexpr auto* TAG = "BluetoothPairedDevice";
|
constexpr auto* TAG = "BluetoothPairedDevice";
|
||||||
|
|
||||||
// Use the same directory as the old service for backward compatibility.
|
// Use the same directory as the old service for backward compatibility.
|
||||||
constexpr auto* DATA_DIR = "/data/service/bluetooth";
|
|
||||||
constexpr auto* DEVICE_SETTINGS_FORMAT = "{}/{}.device.properties";
|
constexpr auto* DEVICE_SETTINGS_FORMAT = "{}/{}.device.properties";
|
||||||
constexpr auto* KEY_NAME = "name";
|
constexpr auto* KEY_NAME = "name";
|
||||||
constexpr auto* KEY_ADDR = "addr";
|
constexpr auto* KEY_ADDR = "addr";
|
||||||
constexpr auto* KEY_AUTO_CONNECT = "autoConnect";
|
constexpr auto* KEY_AUTO_CONNECT = "autoConnect";
|
||||||
constexpr auto* KEY_PROFILE_ID = "profileId";
|
constexpr auto* KEY_PROFILE_ID = "profileId";
|
||||||
|
|
||||||
|
static std::string getSettingsFilePath() {
|
||||||
|
return getUserDataPath() + "/service/bluetooth";
|
||||||
|
}
|
||||||
|
|
||||||
std::string addrToHex(const std::array<uint8_t, 6>& addr) {
|
std::string addrToHex(const std::array<uint8_t, 6>& addr) {
|
||||||
std::stringstream stream;
|
std::stringstream stream;
|
||||||
stream << std::hex;
|
stream << std::hex;
|
||||||
@@ -52,7 +57,7 @@ static bool hexToAddr(const std::string& hex, std::array<uint8_t, 6>& addr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static std::string getFilePath(const std::string& addr_hex) {
|
static std::string getFilePath(const std::string& addr_hex) {
|
||||||
return std::format(DEVICE_SETTINGS_FORMAT, DATA_DIR, addr_hex);
|
return std::format(DEVICE_SETTINGS_FORMAT, getSettingsFilePath(), addr_hex);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasFileForDevice(const std::string& addr_hex) {
|
bool hasFileForDevice(const std::string& addr_hex) {
|
||||||
@@ -102,7 +107,10 @@ bool remove(const std::string& addr_hex) {
|
|||||||
|
|
||||||
std::vector<PairedDevice> loadAll() {
|
std::vector<PairedDevice> loadAll() {
|
||||||
std::vector<dirent> entries;
|
std::vector<dirent> entries;
|
||||||
file::scandir(DATA_DIR, entries, [](const dirent* entry) -> int {
|
if (!file::isDirectory(getSettingsFilePath())) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
file::scandir(getSettingsFilePath(), entries, [](const dirent* entry) -> int {
|
||||||
if (entry->d_type != file::TT_DT_REG && entry->d_type != file::TT_DT_UNKNOWN) return -1;
|
if (entry->d_type != file::TT_DT_REG && entry->d_type != file::TT_DT_UNKNOWN) return -1;
|
||||||
std::string name = entry->d_name;
|
std::string name = entry->d_name;
|
||||||
return name.ends_with(".device.properties") ? 0 : -1;
|
return name.ends_with(".device.properties") ? 0 : -1;
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ void attachDevices() {
|
|||||||
// We search for the manifest first, because during the initial start() during boot
|
// We search for the manifest first, because during the initial start() during boot
|
||||||
// the service won't be registered yet.
|
// the service won't be registered yet.
|
||||||
if (service::findManifestById("Gui") != nullptr) {
|
if (service::findManifestById("Gui") != nullptr) {
|
||||||
if (service::getState("Gui") == service::State::Stopped) {
|
if (service::getState("Gui") == SERVICE_STATE_STOPPED) {
|
||||||
service::startService("Gui");
|
service::startService("Gui");
|
||||||
} else {
|
} else {
|
||||||
LOG_E(TAG, "Gui service is not in Stopped state");
|
LOG_E(TAG, "Gui service is not in Stopped state");
|
||||||
@@ -112,7 +112,7 @@ void attachDevices() {
|
|||||||
// We search for the manifest first, because during the initial start() during boot
|
// We search for the manifest first, because during the initial start() during boot
|
||||||
// the service won't be registered yet.
|
// the service won't be registered yet.
|
||||||
if (service::findManifestById("Statusbar") != nullptr) {
|
if (service::findManifestById("Statusbar") != nullptr) {
|
||||||
if (service::getState("Statusbar") == service::State::Stopped) {
|
if (service::getState("Statusbar") == SERVICE_STATE_STOPPED) {
|
||||||
service::startService("Statusbar");
|
service::startService("Statusbar");
|
||||||
} else {
|
} else {
|
||||||
LOG_E(TAG, "Statusbar service is not in Stopped state");
|
LOG_E(TAG, "Statusbar service is not in Stopped state");
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#include <Tactility/service/ServiceContext.h>
|
||||||
|
|
||||||
|
#include <Tactility/service/ServicePaths.h>
|
||||||
|
|
||||||
|
#include <tactility/service/service_instance.h>
|
||||||
|
|
||||||
|
namespace tt::service {
|
||||||
|
|
||||||
|
const ::ServiceManifest* ServiceContext::getManifest() const {
|
||||||
|
return service_instance_get_manifest(service_instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ServicePaths> ServiceContext::getPaths() const {
|
||||||
|
return std::make_unique<ServicePaths>(getManifest());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#include <Tactility/service/ServiceInstance.h>
|
|
||||||
|
|
||||||
#include <Tactility/service/ServiceManifest.h>
|
|
||||||
#include <Tactility/service/ServicePaths.h>
|
|
||||||
|
|
||||||
namespace tt::service {
|
|
||||||
|
|
||||||
ServiceInstance::ServiceInstance(std::shared_ptr<const ServiceManifest> manifest) :
|
|
||||||
manifest(manifest),
|
|
||||||
service(manifest->createService())
|
|
||||||
{}
|
|
||||||
|
|
||||||
const ServiceManifest& ServiceInstance::getManifest() const { return *manifest; }
|
|
||||||
|
|
||||||
std::unique_ptr<ServicePaths> ServiceInstance::getPaths() const {
|
|
||||||
return std::make_unique<ServicePaths>(manifest);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
#include <Tactility/service/ServicePaths.h>
|
|
||||||
|
|
||||||
#include <Tactility/service/ServiceManifest.h>
|
|
||||||
#include <Tactility/Paths.h>
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <format>
|
|
||||||
|
|
||||||
namespace tt::service {
|
|
||||||
|
|
||||||
std::string ServicePaths::getUserDataDirectory() const {
|
|
||||||
return std::format("{}/service/{}", tt::getUserDataPath(), 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", tt::getUserDataPath(), manifest->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ServicePaths::getAssetsPath(const std::string& childPath) const {
|
|
||||||
assert(!childPath.starts_with('/'));
|
|
||||||
return std::format("{}/{}", getAssetsDirectory(), childPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,43 +1,68 @@
|
|||||||
#include <Tactility/service/ServiceRegistration.h>
|
#include <Tactility/service/ServiceRegistration.h>
|
||||||
|
|
||||||
#include <Tactility/Mutex.h>
|
#include <Tactility/service/ServiceContext.h>
|
||||||
#include <Tactility/service/ServiceInstance.h>
|
|
||||||
#include <Tactility/service/ServiceManifest.h>
|
#include <Tactility/service/ServiceManifest.h>
|
||||||
|
|
||||||
#include <string>
|
#include <tactility/error.h>
|
||||||
|
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
|
#include <tactility/service/service_manager.h>
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace tt::service {
|
namespace tt::service {
|
||||||
|
|
||||||
constexpr auto* TAG = "ServiceRegistration";
|
constexpr auto* TAG = "ServiceRegistration";
|
||||||
|
|
||||||
typedef std::unordered_map<std::string, std::shared_ptr<const ServiceManifest>> ManifestMap;
|
// Bridges the kernel's C ServiceManifest/Service callbacks to the C++ Service
|
||||||
typedef std::unordered_map<std::string, std::shared_ptr<ServiceInstance>> ServiceInstanceMap;
|
// instances they wrap. Declared extern "C" to match the linkage of the C
|
||||||
|
// function-pointer types they're assigned to (see e.g. gpio_controller.cpp).
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
static ManifestMap service_manifest_map;
|
static void cppDestroyServiceTrampoline(const ::ServiceManifest* /*manifest*/, void* data) {
|
||||||
static ServiceInstanceMap service_instance_map;
|
delete static_cast<std::shared_ptr<Service>*>(data);
|
||||||
|
}
|
||||||
|
|
||||||
static Mutex manifest_mutex;
|
static error_t cppOnStartTrampoline(::ServiceInstance* instance, void* data) {
|
||||||
static Mutex instance_mutex;
|
auto& servicePtr = *static_cast<std::shared_ptr<Service>*>(data);
|
||||||
|
ServiceContext context(instance);
|
||||||
|
return servicePtr->onStart(context) ? ERROR_NONE : ERROR_RESOURCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cppOnStopTrampoline(::ServiceInstance* instance, void* data) {
|
||||||
|
auto& servicePtr = *static_cast<std::shared_ptr<Service>*>(data);
|
||||||
|
ServiceContext context(instance);
|
||||||
|
servicePtr->onStop(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
|
|
||||||
void addService(std::shared_ptr<const ServiceManifest> manifest, bool autoStart) {
|
void addService(std::shared_ptr<const ServiceManifest> manifest, bool autoStart) {
|
||||||
assert(manifest != nullptr);
|
assert(manifest != nullptr);
|
||||||
// We'll move the manifest pointer, but we'll need to id later
|
|
||||||
const auto& id = manifest->id;
|
const auto& id = manifest->id;
|
||||||
|
|
||||||
LOG_I(TAG, "Adding %s", id.c_str());
|
LOG_I(TAG, "Adding %s", id.c_str());
|
||||||
|
|
||||||
manifest_mutex.lock();
|
if (service_manager_find_manifest(id.c_str()) != nullptr) {
|
||||||
if (service_manifest_map[id] == nullptr) {
|
|
||||||
service_manifest_map[id] = std::move(manifest);
|
|
||||||
} else {
|
|
||||||
LOG_E(TAG, "Service id in use: %s", id.c_str());
|
LOG_E(TAG, "Service id in use: %s", id.c_str());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
manifest_mutex.unlock();
|
|
||||||
|
|
||||||
if (autoStart) {
|
// Intentionally never freed: services are registered once and live for the
|
||||||
startService(id);
|
// process lifetime (there is no removeService()). Keeps id's backing string
|
||||||
|
// alive for cManifest.id below.
|
||||||
|
auto* persistentManifest = new std::shared_ptr(manifest);
|
||||||
|
auto* cManifest = new ::ServiceManifest {
|
||||||
|
.id = (*persistentManifest)->id.c_str(),
|
||||||
|
.create_service = (*persistentManifest)->createService,
|
||||||
|
.destroy_service = cppDestroyServiceTrampoline,
|
||||||
|
.on_start = cppOnStartTrampoline,
|
||||||
|
.on_stop = cppOnStopTrampoline,
|
||||||
|
};
|
||||||
|
|
||||||
|
error_t error = service_manager_add(cManifest, autoStart);
|
||||||
|
if (error != ERROR_NONE) {
|
||||||
|
LOG_E(TAG, "Failed to add service %s: %s", id.c_str(), error_to_string(error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,94 +70,50 @@ void addService(const ServiceManifest& manifest, bool autoStart) {
|
|||||||
addService(std::make_shared<const ServiceManifest>(manifest), autoStart);
|
addService(std::make_shared<const ServiceManifest>(manifest), autoStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<const ServiceManifest> findManifestById(const std::string& id) {
|
const ::ServiceManifest* findManifestById(const std::string& id) {
|
||||||
manifest_mutex.lock();
|
return service_manager_find_manifest(id.c_str());
|
||||||
auto iterator = service_manifest_map.find(id);
|
|
||||||
auto manifest = iterator != service_manifest_map.end() ? iterator->second : nullptr;
|
|
||||||
manifest_mutex.unlock();
|
|
||||||
return manifest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::shared_ptr<ServiceInstance> findServiceInstanceById(const std::string& id) {
|
|
||||||
manifest_mutex.lock();
|
|
||||||
auto iterator = service_instance_map.find(id);
|
|
||||||
auto service = iterator != service_instance_map.end() ? iterator->second : nullptr;
|
|
||||||
manifest_mutex.unlock();
|
|
||||||
return service;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Return proper error/status instead of BOOL?
|
|
||||||
bool startService(const std::string& id) {
|
bool startService(const std::string& id) {
|
||||||
LOG_I(TAG, "Starting %s", id.c_str());
|
LOG_I(TAG, "Starting %s", id.c_str());
|
||||||
auto manifest = findManifestById(id);
|
const error_t error = service_manager_start(id.c_str());
|
||||||
if (manifest == nullptr) {
|
if (error != ERROR_NONE) {
|
||||||
LOG_E(TAG, "manifest not found for service %s", id.c_str());
|
LOG_E(TAG, "Starting %s failed: %s", id.c_str(), error_to_string(error));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto service_instance = std::make_shared<ServiceInstance>(manifest);
|
|
||||||
|
|
||||||
// Register first, so that a service can retrieve itself during onStart()
|
|
||||||
instance_mutex.lock();
|
|
||||||
service_instance_map[manifest->id] = service_instance;
|
|
||||||
instance_mutex.unlock();
|
|
||||||
|
|
||||||
service_instance->setState(State::Starting);
|
|
||||||
if (service_instance->getService()->onStart(*service_instance)) {
|
|
||||||
service_instance->setState(State::Started);
|
|
||||||
} else {
|
|
||||||
LOG_E(TAG, "Starting %s failed", id.c_str());
|
|
||||||
service_instance->setState(State::Stopped);
|
|
||||||
instance_mutex.lock();
|
|
||||||
service_instance_map.erase(manifest->id);
|
|
||||||
instance_mutex.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_I(TAG, "Started %s", id.c_str());
|
LOG_I(TAG, "Started %s", id.c_str());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<ServiceContext> findServiceContextById(const std::string& id) {
|
std::shared_ptr<ServiceContext> findServiceContextById(const std::string& id) {
|
||||||
return findServiceInstanceById(id);
|
auto* instance = service_manager_find_instance(id.c_str());
|
||||||
|
if (instance == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return std::make_shared<ServiceContext>(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Service> findServiceById(const std::string& id) {
|
std::shared_ptr<Service> findServiceById(const std::string& id) {
|
||||||
auto instance = findServiceInstanceById(id);
|
auto* instance = service_manager_find_instance(id.c_str());
|
||||||
return instance != nullptr ? instance->getService() : nullptr;
|
if (instance == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return *static_cast<std::shared_ptr<Service>*>(instance->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool stopService(const std::string& id) {
|
bool stopService(const std::string& id) {
|
||||||
LOG_I(TAG, "Stopping %s", id.c_str());
|
LOG_I(TAG, "Stopping %s", id.c_str());
|
||||||
auto service_instance = findServiceInstanceById(id);
|
error_t error = service_manager_stop(id.c_str());
|
||||||
if (service_instance == nullptr) {
|
if (error != ERROR_NONE) {
|
||||||
LOG_W(TAG, "Service not running: %s", id.c_str());
|
LOG_W(TAG, "Service not running: %s", id.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
service_instance->setState(State::Stopping);
|
|
||||||
service_instance->getService()->onStop(*service_instance);
|
|
||||||
service_instance->setState(State::Stopped);
|
|
||||||
|
|
||||||
instance_mutex.lock();
|
|
||||||
service_instance_map.erase(id);
|
|
||||||
instance_mutex.unlock();
|
|
||||||
|
|
||||||
if (service_instance.use_count() > 1) {
|
|
||||||
LOG_W(TAG, "Possible memory leak: service %s still has %d references", service_instance->getManifest().id.c_str(), (int)(service_instance.use_count() - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_I(TAG, "Stopped %s", id.c_str());
|
LOG_I(TAG, "Stopped %s", id.c_str());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
State getState(const std::string& id) {
|
State getState(const std::string& id) {
|
||||||
auto service_instance = findServiceInstanceById(id);
|
return service_manager_get_state(id.c_str());
|
||||||
if (service_instance == nullptr) {
|
|
||||||
return State::Stopped;
|
|
||||||
}
|
|
||||||
return service_instance->getState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <tactility/error.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the root path for user data. Survives OS upgrades.
|
||||||
|
* @param[out] out_path buffer to store the path (no trailing "/")
|
||||||
|
* @param[in] out_path_size size of the output buffer
|
||||||
|
* @retval ERROR_NOT_FOUND if the configured storage location isn't available (e.g. no SD card)
|
||||||
|
* @retval ERROR_BUFFER_OVERFLOW if out_path_size is too small
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t paths_get_user_data_path(char* out_path, size_t out_path_size);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <tactility/error.h>
|
||||||
|
#include <tactility/service/service_manifest.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct ServiceInstanceInternal;
|
||||||
|
|
||||||
|
/** The lifecycle state of a ServiceInstance. */
|
||||||
|
typedef enum {
|
||||||
|
SERVICE_STATE_STARTING,
|
||||||
|
SERVICE_STATE_STARTED,
|
||||||
|
SERVICE_STATE_STOPPING,
|
||||||
|
SERVICE_STATE_STOPPED,
|
||||||
|
} ServiceState;
|
||||||
|
|
||||||
|
/** Represents a running (or about-to-run) instance of a service. */
|
||||||
|
struct ServiceInstance {
|
||||||
|
/** The manifest that spawned this instance. */
|
||||||
|
const struct ServiceManifest* manifest;
|
||||||
|
/** Service-specific data, owned by the service implementation. Can be NULL. */
|
||||||
|
void* data;
|
||||||
|
/**
|
||||||
|
* Internal state managed by the kernel.
|
||||||
|
* ServiceInstance implementers should initialize this to NULL.
|
||||||
|
* Do not access or modify directly; use service_instance_* functions.
|
||||||
|
*/
|
||||||
|
struct ServiceInstanceInternal* internal;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a service instance.
|
||||||
|
* @details This calls manifest->create_service() to build the instance's data.
|
||||||
|
* @param[in,out] instance a service instance with manifest set and internal set to NULL
|
||||||
|
* @param[in] manifest non-null manifest to construct the instance from
|
||||||
|
* @retval ERROR_OUT_OF_MEMORY if internal data allocation failed
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t service_instance_construct(struct ServiceInstance* instance, const struct ServiceManifest* manifest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destruct a service instance.
|
||||||
|
* @details This calls manifest->destroy_service() on the instance's data.
|
||||||
|
* @param[in,out] instance non-null service instance pointer
|
||||||
|
* @retval ERROR_INVALID_STATE if the instance is not stopped
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t service_instance_destruct(struct ServiceInstance* instance);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the manifest of a service instance.
|
||||||
|
* @param[in] instance non-null service instance pointer
|
||||||
|
* @return the manifest
|
||||||
|
*/
|
||||||
|
const struct ServiceManifest* service_instance_get_manifest(struct ServiceInstance* instance);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the data of a service instance.
|
||||||
|
* @param[in] instance non-null service instance pointer
|
||||||
|
* @return the data (can be NULL)
|
||||||
|
*/
|
||||||
|
void* service_instance_get_data(struct ServiceInstance* instance);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the state of a service instance.
|
||||||
|
* @param[in] instance non-null service instance pointer
|
||||||
|
* @return the current state
|
||||||
|
*/
|
||||||
|
ServiceState service_instance_get_state(struct ServiceInstance* instance);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <tactility/error.h>
|
||||||
|
#include <tactility/service/service_instance.h>
|
||||||
|
#include <tactility/service/service_manifest.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register a service manifest.
|
||||||
|
* @param[in] manifest non-null manifest to register
|
||||||
|
* @param[in] auto_start if true, the service is started immediately after registration
|
||||||
|
* @retval ERROR_INVALID_ARGUMENT if a manifest with the same id is already registered
|
||||||
|
* @retval ERROR_RESOURCE if auto_start is true and starting the service failed
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t service_manager_add(const struct ServiceManifest* manifest, bool auto_start);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unregister a previously-added manifest.
|
||||||
|
* @param[in] id non-null service id
|
||||||
|
* @retval ERROR_INVALID_STATE if the service is still running
|
||||||
|
* @retval ERROR_NOT_FOUND if no manifest with this id is registered
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t service_manager_remove(const char* id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Find a registered manifest by id.
|
||||||
|
* @param[in] id non-null service id
|
||||||
|
* @return the manifest, or NULL if not found
|
||||||
|
*/
|
||||||
|
const struct ServiceManifest* service_manager_find_manifest(const char* id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Start a registered service by id.
|
||||||
|
* @param[in] id non-null service id
|
||||||
|
* @retval ERROR_NOT_FOUND if no manifest with this id is registered
|
||||||
|
* @retval ERROR_INVALID_STATE if the service is already running
|
||||||
|
* @retval ERROR_RESOURCE if the service's on_start callback failed
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t service_manager_start(const char* id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stop a running service by id.
|
||||||
|
* @param[in] id non-null service id
|
||||||
|
* @retval ERROR_NOT_FOUND if no service with this id is running
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t service_manager_stop(const char* id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the state of a service by id.
|
||||||
|
* @param[in] id non-null service id
|
||||||
|
* @return the current state, or SERVICE_STATE_STOPPED if the id is unknown
|
||||||
|
*/
|
||||||
|
ServiceState service_manager_get_state(const char* id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Find the service instance
|
||||||
|
* @param[in] id non-null service id
|
||||||
|
* @return the instance when found, otherwise return NULL
|
||||||
|
*/
|
||||||
|
struct ServiceInstance* service_manager_find_instance(const char* id);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <tactility/error.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ServiceContext (declared in service_context.h) is a typedef alias of ServiceInstance,
|
||||||
|
// so the callback types below are declared directly in terms of ServiceInstance to avoid
|
||||||
|
// a conflicting forward-declaration of an unrelated "ServiceContext" struct tag.
|
||||||
|
struct ServiceInstance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates and initializes the service's custom data.
|
||||||
|
* @param[in] manifest the manifest that owns this callback
|
||||||
|
* @return the service's custom data, or NULL if it has none
|
||||||
|
*/
|
||||||
|
typedef void* (*ServiceCreate)(const struct ServiceManifest* manifest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees custom data that was set up by the matching ServiceCreate function.
|
||||||
|
* @param[in] data the custom data returned by create_service
|
||||||
|
* @param[in] manifest the manifest that owns this callback
|
||||||
|
*/
|
||||||
|
typedef void (*ServiceDestroy)(const struct ServiceManifest* manifest, void* data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a service instance is starting.
|
||||||
|
* Can be NULL, in which case starting always succeeds.
|
||||||
|
* @param[in,out] instance the starting service instance (also its own ServiceContext)
|
||||||
|
* @param[in] data the custom data returned by create_service
|
||||||
|
* @return ERROR_NONE if the service started successfully
|
||||||
|
*/
|
||||||
|
typedef error_t (*ServiceOnStart)(struct ServiceInstance* instance, void* data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a service instance is stopping.
|
||||||
|
* Can be NULL, in which case stopping is a no-op.
|
||||||
|
* @param[in,out] instance the stopping service instance (also its own ServiceContext)
|
||||||
|
* @param[in] data the custom data returned by create_service
|
||||||
|
*/
|
||||||
|
typedef void (*ServiceOnStop)(struct ServiceInstance* instance, void* data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes a registrable service type.
|
||||||
|
* One manifest exists per service id, shared across start/stop cycles.
|
||||||
|
*/
|
||||||
|
struct ServiceManifest {
|
||||||
|
/** Unique service identifier. Should never be NULL. */
|
||||||
|
const char* id;
|
||||||
|
/** Allocates the service's custom data. Should never be NULL. */
|
||||||
|
ServiceCreate create_service;
|
||||||
|
/** Frees the service's custom data. Should never be NULL. */
|
||||||
|
ServiceDestroy destroy_service;
|
||||||
|
/** Called when a service instance starts. Can be NULL. */
|
||||||
|
ServiceOnStart on_start;
|
||||||
|
/** Called when a service instance stops. Can be NULL. */
|
||||||
|
ServiceOnStop on_stop;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <tactility/error.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the user data directory for a service. Survives OS upgrades. No trailing "/".
|
||||||
|
* @param[in] service_id non-null service id
|
||||||
|
* @param[out] out_path buffer to store the path
|
||||||
|
* @param[in] out_path_size size of the output buffer
|
||||||
|
* @retval ERROR_BUFFER_OVERFLOW if out_path_size is too small
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t service_paths_get_user_data_directory(const char* service_id, char* out_path, size_t out_path_size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get a path within the user data directory for a service.
|
||||||
|
* @param[in] service_id non-null service id
|
||||||
|
* @param[in] child_path path without a "/" prefix
|
||||||
|
* @param[out] out_path buffer to store the path
|
||||||
|
* @param[in] out_path_size size of the output buffer
|
||||||
|
* @retval ERROR_BUFFER_OVERFLOW if out_path_size is too small
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t service_paths_get_user_data_path(const char* service_id, const char* child_path, char* out_path, size_t out_path_size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the assets directory for a service. Do not store configuration data here. No trailing "/".
|
||||||
|
* @param[in] service_id non-null service id
|
||||||
|
* @param[out] out_path buffer to store the path
|
||||||
|
* @param[in] out_path_size size of the output buffer
|
||||||
|
* @retval ERROR_BUFFER_OVERFLOW if out_path_size is too small
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t service_paths_get_assets_directory(const char* service_id, char* out_path, size_t out_path_size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get a path within the assets directory for a service.
|
||||||
|
* @param[in] service_id non-null service id
|
||||||
|
* @param[in] child_path path without a "/" prefix
|
||||||
|
* @param[out] out_path buffer to store the path
|
||||||
|
* @param[in] out_path_size size of the output buffer
|
||||||
|
* @retval ERROR_BUFFER_OVERFLOW if out_path_size is too small
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t service_paths_get_assets_path(const char* service_id, const char* child_path, char* out_path, size_t out_path_size);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -21,6 +21,9 @@
|
|||||||
#include <tactility/error.h>
|
#include <tactility/error.h>
|
||||||
#include <tactility/filesystem/file_system.h>
|
#include <tactility/filesystem/file_system.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
#include <tactility/service/service_instance.h>
|
||||||
|
#include <tactility/service/service_manager.h>
|
||||||
|
#include <tactility/service/service_paths.h>
|
||||||
|
|
||||||
#ifndef ESP_PLATFORM
|
#ifndef ESP_PLATFORM
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
@@ -247,6 +250,25 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = {
|
|||||||
DEFINE_MODULE_SYMBOL(module_is_started),
|
DEFINE_MODULE_SYMBOL(module_is_started),
|
||||||
DEFINE_MODULE_SYMBOL(module_resolve_symbol),
|
DEFINE_MODULE_SYMBOL(module_resolve_symbol),
|
||||||
DEFINE_MODULE_SYMBOL(module_resolve_symbol_global),
|
DEFINE_MODULE_SYMBOL(module_resolve_symbol_global),
|
||||||
|
// service/service_instance
|
||||||
|
DEFINE_MODULE_SYMBOL(service_instance_construct),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_instance_destruct),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_instance_get_manifest),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_instance_get_data),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_instance_get_state),
|
||||||
|
// service/service_manager
|
||||||
|
DEFINE_MODULE_SYMBOL(service_manager_add),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_manager_remove),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_manager_find_manifest),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_manager_start),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_manager_stop),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_manager_get_state),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_manager_find_instance),
|
||||||
|
// service/service_paths
|
||||||
|
DEFINE_MODULE_SYMBOL(service_paths_get_user_data_directory),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_paths_get_user_data_path),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_paths_get_assets_directory),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_paths_get_assets_path),
|
||||||
// terminator
|
// terminator
|
||||||
MODULE_SYMBOL_TERMINATOR
|
MODULE_SYMBOL_TERMINATOR
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
#include <tactility/paths.h>
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/drivers/sdcard.h>
|
||||||
|
#include <tactility/filesystem/file_system.h>
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
static error_t get_user_data_root_path(char* out_path, size_t out_path_size) {
|
||||||
|
#if defined(CONFIG_TT_USER_DATA_LOCATION_INTERNAL)
|
||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
const char* mount_point = "/data";
|
||||||
|
#else
|
||||||
|
const char* mount_point = "data";
|
||||||
|
#endif
|
||||||
|
if (std::strlen(mount_point) + 1 > out_path_size) {
|
||||||
|
return ERROR_BUFFER_OVERFLOW;
|
||||||
|
}
|
||||||
|
std::strcpy(out_path, mount_point);
|
||||||
|
return ERROR_NONE;
|
||||||
|
#elif defined(CONFIG_TT_USER_DATA_LOCATION_SD)
|
||||||
|
struct FileSystem* found = nullptr;
|
||||||
|
file_system_for_each(&found, [](FileSystem* fs, void* context) {
|
||||||
|
auto* owner = file_system_get_owner(fs);
|
||||||
|
if (owner == nullptr || device_get_type(owner) != &SDCARD_TYPE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
*static_cast<FileSystem**>(context) = fs;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (found == nullptr) {
|
||||||
|
return ERROR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
return file_system_get_path(found, out_path, out_path_size);
|
||||||
|
#else
|
||||||
|
#error CONFIG_TT_USER_DATA_* not set or unsupported
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
error_t paths_get_user_data_path(char* out_path, size_t out_path_size) {
|
||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
char root[64];
|
||||||
|
error_t error = get_user_data_root_path(root, sizeof(root));
|
||||||
|
if (error != ERROR_NONE) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
int written = std::snprintf(out_path, out_path_size, "%s/tactility", root);
|
||||||
|
if (written < 0 || (size_t)written >= out_path_size) {
|
||||||
|
return ERROR_BUFFER_OVERFLOW;
|
||||||
|
}
|
||||||
|
return ERROR_NONE;
|
||||||
|
#else
|
||||||
|
const char* fixed_path = "data";
|
||||||
|
if (std::strlen(fixed_path) + 1 > out_path_size) {
|
||||||
|
return ERROR_BUFFER_OVERFLOW;
|
||||||
|
}
|
||||||
|
std::strcpy(out_path, fixed_path);
|
||||||
|
return ERROR_NONE;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
#include <tactility/service/service_instance.h>
|
||||||
|
#include <tactility/concurrent/mutex.h>
|
||||||
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
#include <new>
|
||||||
|
|
||||||
|
#define TAG "service_instance"
|
||||||
|
|
||||||
|
struct ServiceInstanceInternal {
|
||||||
|
Mutex mutex {};
|
||||||
|
ServiceState state = SERVICE_STATE_STOPPED;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
error_t service_instance_construct(ServiceInstance* instance, const ServiceManifest* manifest) {
|
||||||
|
instance->internal = new(std::nothrow) ServiceInstanceInternal;
|
||||||
|
if (instance->internal == nullptr) {
|
||||||
|
return ERROR_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
mutex_construct(&instance->internal->mutex);
|
||||||
|
|
||||||
|
instance->manifest = manifest;
|
||||||
|
instance->data = manifest->create_service(manifest);
|
||||||
|
|
||||||
|
LOG_D(TAG, "construct %s", manifest->id);
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t service_instance_destruct(ServiceInstance* instance) {
|
||||||
|
auto* internal = instance->internal;
|
||||||
|
|
||||||
|
mutex_lock(&internal->mutex);
|
||||||
|
if (internal->state != SERVICE_STATE_STOPPED) {
|
||||||
|
mutex_unlock(&internal->mutex);
|
||||||
|
return ERROR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
mutex_unlock(&internal->mutex);
|
||||||
|
|
||||||
|
LOG_D(TAG, "destruct %s", instance->manifest->id);
|
||||||
|
|
||||||
|
instance->manifest->destroy_service(instance->manifest, instance->data);
|
||||||
|
instance->data = nullptr;
|
||||||
|
|
||||||
|
instance->internal = nullptr;
|
||||||
|
mutex_destruct(&internal->mutex);
|
||||||
|
delete internal;
|
||||||
|
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ServiceManifest* service_instance_get_manifest(ServiceInstance* instance) {
|
||||||
|
return instance->manifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* service_instance_get_data(ServiceInstance* instance) {
|
||||||
|
return instance->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServiceState service_instance_get_state(ServiceInstance* instance) {
|
||||||
|
mutex_lock(&instance->internal->mutex);
|
||||||
|
ServiceState state = instance->internal->state;
|
||||||
|
mutex_unlock(&instance->internal->mutex);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Internal-only: mutate the state of a service instance. Used by service_registration.cpp. */
|
||||||
|
void service_instance_set_state(ServiceInstance* instance, ServiceState state) {
|
||||||
|
mutex_lock(&instance->internal->mutex);
|
||||||
|
instance->internal->state = state;
|
||||||
|
mutex_unlock(&instance->internal->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
#include <tactility/service/service_manager.h>
|
||||||
|
#include <tactility/concurrent/mutex.h>
|
||||||
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
#include <new>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#define TAG "service_registration"
|
||||||
|
|
||||||
|
// Defined in service_instance.cpp. Internal-only: lets the registry drive state
|
||||||
|
// transitions without exposing mutation on the public ServiceInstance API.
|
||||||
|
extern "C" void service_instance_set_state(ServiceInstance* instance, ServiceState state);
|
||||||
|
|
||||||
|
struct ManifestLedger {
|
||||||
|
std::unordered_map<std::string, const ServiceManifest*> manifests;
|
||||||
|
Mutex mutex {};
|
||||||
|
|
||||||
|
ManifestLedger() { mutex_construct(&mutex); }
|
||||||
|
~ManifestLedger() { mutex_destruct(&mutex); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InstanceLedger {
|
||||||
|
std::unordered_map<std::string, ServiceInstance*> instances;
|
||||||
|
Mutex mutex {};
|
||||||
|
|
||||||
|
InstanceLedger() { mutex_construct(&mutex); }
|
||||||
|
~InstanceLedger() { mutex_destruct(&mutex); }
|
||||||
|
};
|
||||||
|
|
||||||
|
static ManifestLedger& get_manifest_ledger() {
|
||||||
|
static ManifestLedger ledger;
|
||||||
|
return ledger;
|
||||||
|
}
|
||||||
|
|
||||||
|
static InstanceLedger& get_instance_ledger() {
|
||||||
|
static InstanceLedger ledger;
|
||||||
|
return ledger;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define manifest_ledger get_manifest_ledger()
|
||||||
|
#define instance_ledger get_instance_ledger()
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
error_t service_manager_add(const ServiceManifest* manifest, bool auto_start) {
|
||||||
|
mutex_lock(&manifest_ledger.mutex);
|
||||||
|
if (manifest_ledger.manifests.contains(manifest->id)) {
|
||||||
|
mutex_unlock(&manifest_ledger.mutex);
|
||||||
|
LOG_E(TAG, "Manifest with id '%s' is already registered", manifest->id);
|
||||||
|
return ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
manifest_ledger.manifests[manifest->id] = manifest;
|
||||||
|
mutex_unlock(&manifest_ledger.mutex);
|
||||||
|
|
||||||
|
LOG_I(TAG, "add %s", manifest->id);
|
||||||
|
|
||||||
|
if (auto_start) {
|
||||||
|
return service_manager_start(manifest->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t service_manager_remove(const char* id) {
|
||||||
|
if (service_manager_get_state(id) != SERVICE_STATE_STOPPED) {
|
||||||
|
return ERROR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_lock(&manifest_ledger.mutex);
|
||||||
|
const auto iterator = manifest_ledger.manifests.find(id);
|
||||||
|
if (iterator == manifest_ledger.manifests.end()) {
|
||||||
|
mutex_unlock(&manifest_ledger.mutex);
|
||||||
|
return ERROR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
manifest_ledger.manifests.erase(iterator);
|
||||||
|
mutex_unlock(&manifest_ledger.mutex);
|
||||||
|
|
||||||
|
LOG_I(TAG, "remove %s", id);
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t service_manager_start(const char* id) {
|
||||||
|
mutex_lock(&manifest_ledger.mutex);
|
||||||
|
const auto manifest_iterator = manifest_ledger.manifests.find(id);
|
||||||
|
if (manifest_iterator == manifest_ledger.manifests.end()) {
|
||||||
|
mutex_unlock(&manifest_ledger.mutex);
|
||||||
|
return ERROR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
const ServiceManifest* manifest = manifest_iterator->second;
|
||||||
|
mutex_unlock(&manifest_ledger.mutex);
|
||||||
|
|
||||||
|
mutex_lock(&instance_ledger.mutex);
|
||||||
|
if (instance_ledger.instances.contains(id)) {
|
||||||
|
mutex_unlock(&instance_ledger.mutex);
|
||||||
|
return ERROR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* instance = new(std::nothrow) ServiceInstance { .manifest = nullptr, .data = nullptr, .internal = nullptr };
|
||||||
|
if (instance == nullptr) {
|
||||||
|
mutex_unlock(&instance_ledger.mutex);
|
||||||
|
return ERROR_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t error = service_instance_construct(instance, manifest);
|
||||||
|
if (error != ERROR_NONE) {
|
||||||
|
mutex_unlock(&instance_ledger.mutex);
|
||||||
|
delete instance;
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register before on_start() so a service can find itself while starting.
|
||||||
|
instance_ledger.instances[id] = instance;
|
||||||
|
mutex_unlock(&instance_ledger.mutex);
|
||||||
|
|
||||||
|
service_instance_set_state(instance, SERVICE_STATE_STARTING);
|
||||||
|
|
||||||
|
LOG_I(TAG, "start %s", id);
|
||||||
|
error = (manifest->on_start != nullptr) ? manifest->on_start(instance, instance->data) : ERROR_NONE;
|
||||||
|
|
||||||
|
if (error == ERROR_NONE) {
|
||||||
|
service_instance_set_state(instance, SERVICE_STATE_STARTED);
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_E(TAG, "Failed to start %s: %s", id, error_to_string(error));
|
||||||
|
service_instance_set_state(instance, SERVICE_STATE_STOPPED);
|
||||||
|
|
||||||
|
mutex_lock(&instance_ledger.mutex);
|
||||||
|
instance_ledger.instances.erase(id);
|
||||||
|
mutex_unlock(&instance_ledger.mutex);
|
||||||
|
|
||||||
|
service_instance_destruct(instance);
|
||||||
|
delete instance;
|
||||||
|
|
||||||
|
return ERROR_RESOURCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t service_manager_stop(const char* id) {
|
||||||
|
mutex_lock(&instance_ledger.mutex);
|
||||||
|
const auto iterator = instance_ledger.instances.find(id);
|
||||||
|
if (iterator == instance_ledger.instances.end()) {
|
||||||
|
mutex_unlock(&instance_ledger.mutex);
|
||||||
|
return ERROR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
ServiceInstance* instance = iterator->second;
|
||||||
|
mutex_unlock(&instance_ledger.mutex);
|
||||||
|
|
||||||
|
LOG_I(TAG, "stop %s", id);
|
||||||
|
|
||||||
|
service_instance_set_state(instance, SERVICE_STATE_STOPPING);
|
||||||
|
|
||||||
|
if (instance->manifest->on_stop != nullptr) {
|
||||||
|
instance->manifest->on_stop(instance, instance->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
service_instance_set_state(instance, SERVICE_STATE_STOPPED);
|
||||||
|
|
||||||
|
mutex_lock(&instance_ledger.mutex);
|
||||||
|
instance_ledger.instances.erase(id);
|
||||||
|
mutex_unlock(&instance_ledger.mutex);
|
||||||
|
|
||||||
|
service_instance_destruct(instance);
|
||||||
|
delete instance;
|
||||||
|
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServiceState service_manager_get_state(const char* id) {
|
||||||
|
mutex_lock(&instance_ledger.mutex);
|
||||||
|
const auto iterator = instance_ledger.instances.find(id);
|
||||||
|
if (iterator == instance_ledger.instances.end()) {
|
||||||
|
mutex_unlock(&instance_ledger.mutex);
|
||||||
|
return SERVICE_STATE_STOPPED;
|
||||||
|
}
|
||||||
|
ServiceInstance* instance = iterator->second;
|
||||||
|
mutex_unlock(&instance_ledger.mutex);
|
||||||
|
|
||||||
|
return service_instance_get_state(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ServiceManifest* service_manager_find_manifest(const char* id) {
|
||||||
|
mutex_lock(&manifest_ledger.mutex);
|
||||||
|
const auto iterator = manifest_ledger.manifests.find(id);
|
||||||
|
const ServiceManifest* manifest = (iterator != manifest_ledger.manifests.end()) ? iterator->second : nullptr;
|
||||||
|
mutex_unlock(&manifest_ledger.mutex);
|
||||||
|
return manifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServiceInstance* service_manager_find_instance(const char* id) {
|
||||||
|
mutex_lock(&instance_ledger.mutex);
|
||||||
|
const auto iterator = instance_ledger.instances.find(id);
|
||||||
|
auto* instance = (iterator != instance_ledger.instances.end()) ? iterator->second : nullptr;
|
||||||
|
mutex_unlock(&instance_ledger.mutex);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
#include <tactility/service/service_paths.h>
|
||||||
|
#include <tactility/paths.h>
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
error_t service_paths_get_user_data_directory(const char* service_id, char* out_path, size_t out_path_size) {
|
||||||
|
char root[192];
|
||||||
|
error_t error = paths_get_user_data_path(root, sizeof(root));
|
||||||
|
if (error != ERROR_NONE) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
int written = std::snprintf(out_path, out_path_size, "%s/service/%s", root, service_id);
|
||||||
|
if (written < 0 || (size_t)written >= out_path_size) {
|
||||||
|
return ERROR_BUFFER_OVERFLOW;
|
||||||
|
}
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t service_paths_get_user_data_path(const char* service_id, const char* child_path, char* out_path, size_t out_path_size) {
|
||||||
|
char directory[224];
|
||||||
|
error_t error = service_paths_get_user_data_directory(service_id, directory, sizeof(directory));
|
||||||
|
if (error != ERROR_NONE) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
int written = std::snprintf(out_path, out_path_size, "%s/%s", directory, child_path);
|
||||||
|
if (written < 0 || (size_t)written >= out_path_size) {
|
||||||
|
return ERROR_BUFFER_OVERFLOW;
|
||||||
|
}
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t service_paths_get_assets_directory(const char* service_id, char* out_path, size_t out_path_size) {
|
||||||
|
char directory[224];
|
||||||
|
error_t error = service_paths_get_user_data_directory(service_id, directory, sizeof(directory));
|
||||||
|
if (error != ERROR_NONE) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
int written = std::snprintf(out_path, out_path_size, "%s/assets", directory);
|
||||||
|
if (written < 0 || (size_t)written >= out_path_size) {
|
||||||
|
return ERROR_BUFFER_OVERFLOW;
|
||||||
|
}
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t service_paths_get_assets_path(const char* service_id, const char* child_path, char* out_path, size_t out_path_size) {
|
||||||
|
char directory[224];
|
||||||
|
error_t error = service_paths_get_assets_directory(service_id, directory, sizeof(directory));
|
||||||
|
if (error != ERROR_NONE) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
int written = std::snprintf(out_path, out_path_size, "%s/%s", directory, child_path);
|
||||||
|
if (written < 0 || (size_t)written >= out_path_size) {
|
||||||
|
return ERROR_BUFFER_OVERFLOW;
|
||||||
|
}
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
#include "doctest.h"
|
||||||
|
#include <tactility/paths.h>
|
||||||
|
#include <tactility/service/service_paths.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
TEST_CASE("paths_get_user_data_path returns a non-empty path") {
|
||||||
|
char buffer[192];
|
||||||
|
CHECK_EQ(paths_get_user_data_path(buffer, sizeof(buffer)), ERROR_NONE);
|
||||||
|
CHECK_GT(std::strlen(buffer), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("paths_get_user_data_path reports overflow for a too-small buffer") {
|
||||||
|
char buffer[1];
|
||||||
|
CHECK_EQ(paths_get_user_data_path(buffer, sizeof(buffer)), ERROR_BUFFER_OVERFLOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("service_paths_get_user_data_directory includes the service id") {
|
||||||
|
char root[192];
|
||||||
|
REQUIRE_EQ(paths_get_user_data_path(root, sizeof(root)), ERROR_NONE);
|
||||||
|
|
||||||
|
char buffer[224];
|
||||||
|
CHECK_EQ(service_paths_get_user_data_directory("my-service", buffer, sizeof(buffer)), ERROR_NONE);
|
||||||
|
|
||||||
|
std::string expected = std::string(root) + "/service/my-service";
|
||||||
|
CHECK_EQ(std::string(buffer), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("service_paths_get_user_data_path appends the child path") {
|
||||||
|
char directory[224];
|
||||||
|
REQUIRE_EQ(service_paths_get_user_data_directory("my-service", directory, sizeof(directory)), ERROR_NONE);
|
||||||
|
|
||||||
|
char buffer[256];
|
||||||
|
CHECK_EQ(service_paths_get_user_data_path("my-service", "settings.properties", buffer, sizeof(buffer)), ERROR_NONE);
|
||||||
|
|
||||||
|
std::string expected = std::string(directory) + "/settings.properties";
|
||||||
|
CHECK_EQ(std::string(buffer), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("service_paths_get_assets_directory is nested under the user data directory") {
|
||||||
|
char directory[224];
|
||||||
|
REQUIRE_EQ(service_paths_get_user_data_directory("my-service", directory, sizeof(directory)), ERROR_NONE);
|
||||||
|
|
||||||
|
char buffer[256];
|
||||||
|
CHECK_EQ(service_paths_get_assets_directory("my-service", buffer, sizeof(buffer)), ERROR_NONE);
|
||||||
|
|
||||||
|
std::string expected = std::string(directory) + "/assets";
|
||||||
|
CHECK_EQ(std::string(buffer), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("service_paths_get_assets_path appends the child path") {
|
||||||
|
char directory[224];
|
||||||
|
REQUIRE_EQ(service_paths_get_assets_directory("my-service", directory, sizeof(directory)), ERROR_NONE);
|
||||||
|
|
||||||
|
char buffer[256];
|
||||||
|
CHECK_EQ(service_paths_get_assets_path("my-service", "icon.png", buffer, sizeof(buffer)), ERROR_NONE);
|
||||||
|
|
||||||
|
std::string expected = std::string(directory) + "/icon.png";
|
||||||
|
CHECK_EQ(std::string(buffer), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("service_paths functions report overflow for a too-small buffer") {
|
||||||
|
char buffer[1];
|
||||||
|
CHECK_EQ(service_paths_get_user_data_directory("my-service", buffer, sizeof(buffer)), ERROR_BUFFER_OVERFLOW);
|
||||||
|
CHECK_EQ(service_paths_get_user_data_path("my-service", "child", buffer, sizeof(buffer)), ERROR_BUFFER_OVERFLOW);
|
||||||
|
CHECK_EQ(service_paths_get_assets_directory("my-service", buffer, sizeof(buffer)), ERROR_BUFFER_OVERFLOW);
|
||||||
|
CHECK_EQ(service_paths_get_assets_path("my-service", "child", buffer, sizeof(buffer)), ERROR_BUFFER_OVERFLOW);
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
#include "doctest.h"
|
||||||
|
#include <tactility/service/service_manager.h>
|
||||||
|
|
||||||
|
// Defined in service_instance.cpp. Internal-only, exposed here to test try_get/put gating.
|
||||||
|
extern "C" void service_instance_set_state(ServiceInstance* instance, ServiceState state);
|
||||||
|
|
||||||
|
static int create_called = 0;
|
||||||
|
static int destroy_called = 0;
|
||||||
|
static int on_start_called = 0;
|
||||||
|
static int on_stop_called = 0;
|
||||||
|
static error_t on_start_result = ERROR_NONE;
|
||||||
|
static const ServiceManifest* last_create_manifest = nullptr;
|
||||||
|
static const ServiceManifest* last_destroy_manifest = nullptr;
|
||||||
|
|
||||||
|
static void* test_create_service(const ServiceManifest* manifest) {
|
||||||
|
create_called++;
|
||||||
|
last_create_manifest = manifest;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_destroy_service(const ServiceManifest* manifest, void*) {
|
||||||
|
destroy_called++;
|
||||||
|
last_destroy_manifest = manifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t test_on_start(ServiceInstance*, void*) {
|
||||||
|
on_start_called++;
|
||||||
|
return on_start_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_on_stop(ServiceInstance*, void*) {
|
||||||
|
on_stop_called++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void reset_counters() {
|
||||||
|
create_called = 0;
|
||||||
|
destroy_called = 0;
|
||||||
|
on_start_called = 0;
|
||||||
|
on_stop_called = 0;
|
||||||
|
on_start_result = ERROR_NONE;
|
||||||
|
last_create_manifest = nullptr;
|
||||||
|
last_destroy_manifest = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("ServiceInstance construction and destruction") {
|
||||||
|
reset_counters();
|
||||||
|
|
||||||
|
static const ServiceManifest manifest = {
|
||||||
|
.id = "instance-test",
|
||||||
|
.create_service = test_create_service,
|
||||||
|
.destroy_service = test_destroy_service,
|
||||||
|
.on_start = test_on_start,
|
||||||
|
.on_stop = test_on_stop
|
||||||
|
};
|
||||||
|
|
||||||
|
ServiceInstance instance = { .manifest = nullptr, .data = nullptr, .internal = nullptr };
|
||||||
|
|
||||||
|
CHECK_EQ(service_instance_construct(&instance, &manifest), ERROR_NONE);
|
||||||
|
CHECK_NE(instance.internal, nullptr);
|
||||||
|
CHECK_EQ(instance.manifest, &manifest);
|
||||||
|
CHECK_EQ(create_called, 1);
|
||||||
|
CHECK_EQ(last_create_manifest, &manifest);
|
||||||
|
CHECK_EQ(service_instance_get_state(&instance), SERVICE_STATE_STOPPED);
|
||||||
|
|
||||||
|
CHECK_EQ(service_instance_destruct(&instance), ERROR_NONE);
|
||||||
|
CHECK_EQ(instance.internal, nullptr);
|
||||||
|
CHECK_EQ(destroy_called, 1);
|
||||||
|
CHECK_EQ(last_destroy_manifest, &manifest);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("service_manager_add rejects duplicate ids") {
|
||||||
|
reset_counters();
|
||||||
|
|
||||||
|
static const ServiceManifest manifest = {
|
||||||
|
.id = "duplicate-test",
|
||||||
|
.create_service = test_create_service,
|
||||||
|
.destroy_service = test_destroy_service
|
||||||
|
};
|
||||||
|
|
||||||
|
CHECK_EQ(service_manager_add(&manifest, false), ERROR_NONE);
|
||||||
|
CHECK_EQ(service_manager_add(&manifest, false), ERROR_INVALID_ARGUMENT);
|
||||||
|
|
||||||
|
CHECK_EQ(service_manager_remove("duplicate-test"), ERROR_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("service_registration start/stop lifecycle") {
|
||||||
|
reset_counters();
|
||||||
|
|
||||||
|
static const ServiceManifest manifest = {
|
||||||
|
.id = "lifecycle-test",
|
||||||
|
.create_service = test_create_service,
|
||||||
|
.destroy_service = test_destroy_service,
|
||||||
|
.on_start = test_on_start,
|
||||||
|
.on_stop = test_on_stop
|
||||||
|
};
|
||||||
|
|
||||||
|
CHECK_EQ(service_manager_add(&manifest, false), ERROR_NONE);
|
||||||
|
CHECK_EQ(service_manager_get_state("lifecycle-test"), SERVICE_STATE_STOPPED);
|
||||||
|
|
||||||
|
CHECK_EQ(service_manager_start("lifecycle-test"), ERROR_NONE);
|
||||||
|
CHECK_EQ(on_start_called, 1);
|
||||||
|
CHECK_EQ(service_manager_get_state("lifecycle-test"), SERVICE_STATE_STARTED);
|
||||||
|
CHECK_NE(service_manager_find_instance("lifecycle-test"), nullptr);
|
||||||
|
|
||||||
|
// Starting again while already started should fail
|
||||||
|
CHECK_EQ(service_manager_start("lifecycle-test"), ERROR_INVALID_STATE);
|
||||||
|
|
||||||
|
// Removing while running should fail
|
||||||
|
CHECK_EQ(service_manager_remove("lifecycle-test"), ERROR_INVALID_STATE);
|
||||||
|
|
||||||
|
CHECK_EQ(service_manager_stop("lifecycle-test"), ERROR_NONE);
|
||||||
|
CHECK_EQ(on_stop_called, 1);
|
||||||
|
CHECK_EQ(service_manager_get_state("lifecycle-test"), SERVICE_STATE_STOPPED);
|
||||||
|
CHECK_EQ(service_manager_find_instance("lifecycle-test"), nullptr);
|
||||||
|
|
||||||
|
// Stopping again while already stopped should fail
|
||||||
|
CHECK_EQ(service_manager_stop("lifecycle-test"), ERROR_NOT_FOUND);
|
||||||
|
|
||||||
|
CHECK_EQ(service_manager_remove("lifecycle-test"), ERROR_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("service_manager_add with auto_start") {
|
||||||
|
reset_counters();
|
||||||
|
|
||||||
|
static const ServiceManifest manifest = {
|
||||||
|
.id = "auto-start-test",
|
||||||
|
.create_service = test_create_service,
|
||||||
|
.destroy_service = test_destroy_service,
|
||||||
|
.on_start = test_on_start,
|
||||||
|
.on_stop = test_on_stop
|
||||||
|
};
|
||||||
|
|
||||||
|
CHECK_EQ(service_manager_add(&manifest, true), ERROR_NONE);
|
||||||
|
CHECK_EQ(on_start_called, 1);
|
||||||
|
CHECK_EQ(service_manager_get_state("auto-start-test"), SERVICE_STATE_STARTED);
|
||||||
|
|
||||||
|
CHECK_EQ(service_manager_stop("auto-start-test"), ERROR_NONE);
|
||||||
|
CHECK_EQ(service_manager_remove("auto-start-test"), ERROR_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("service_manager_start failure leaves service stopped") {
|
||||||
|
reset_counters();
|
||||||
|
on_start_result = ERROR_RESOURCE;
|
||||||
|
|
||||||
|
static const ServiceManifest manifest = {
|
||||||
|
.id = "failing-start-test",
|
||||||
|
.create_service = test_create_service,
|
||||||
|
.destroy_service = test_destroy_service,
|
||||||
|
.on_start = test_on_start,
|
||||||
|
.on_stop = test_on_stop
|
||||||
|
};
|
||||||
|
|
||||||
|
CHECK_EQ(service_manager_add(&manifest, false), ERROR_NONE);
|
||||||
|
CHECK_EQ(service_manager_start("failing-start-test"), ERROR_RESOURCE);
|
||||||
|
CHECK_EQ(service_manager_get_state("failing-start-test"), SERVICE_STATE_STOPPED);
|
||||||
|
CHECK_EQ(service_manager_find_instance("failing-start-test"), nullptr);
|
||||||
|
|
||||||
|
CHECK_EQ(service_manager_remove("failing-start-test"), ERROR_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("service_registration lookup functions with unknown id") {
|
||||||
|
CHECK_EQ(service_manager_get_state("unknown-service-id"), SERVICE_STATE_STOPPED);
|
||||||
|
CHECK_EQ(service_manager_find_manifest("unknown-service-id"), nullptr);
|
||||||
|
CHECK_EQ(service_manager_find_instance("unknown-service-id"), nullptr);
|
||||||
|
CHECK_EQ(service_manager_start("unknown-service-id"), ERROR_NOT_FOUND);
|
||||||
|
CHECK_EQ(service_manager_stop("unknown-service-id"), ERROR_NOT_FOUND);
|
||||||
|
CHECK_EQ(service_manager_remove("unknown-service-id"), ERROR_NOT_FOUND);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user