committed by
GitHub
parent
cca7224252
commit
f4f91f81d6
@@ -1,15 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <tactility/service/service_instance.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace tt::service {
|
||||
|
||||
enum class State {
|
||||
Starting,
|
||||
Started,
|
||||
Stopping,
|
||||
Stopped
|
||||
};
|
||||
using State = ::ServiceState;
|
||||
|
||||
// Forward declaration
|
||||
class ServiceContext;
|
||||
@@ -26,6 +23,6 @@ public:
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
#include <memory>
|
||||
#include <tactility/service/service_manifest.h>
|
||||
|
||||
struct ServiceInstance;
|
||||
|
||||
namespace tt::service {
|
||||
|
||||
@@ -8,23 +11,23 @@ struct ServiceManifest;
|
||||
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.
|
||||
*/
|
||||
class ServiceContext {
|
||||
class ServiceContext final {
|
||||
|
||||
protected:
|
||||
|
||||
virtual ~ServiceContext() = default;
|
||||
::ServiceInstance* service_instance;
|
||||
|
||||
public:
|
||||
|
||||
explicit ServiceContext(::ServiceInstance* instance) : service_instance(instance) {}
|
||||
|
||||
/** @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 */
|
||||
virtual std::unique_ptr<ServicePaths> getPaths() const = 0;
|
||||
std::unique_ptr<ServicePaths> getPaths() const;
|
||||
};
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace tt::service {
|
||||
// Forward declarations
|
||||
class ServiceContext;
|
||||
|
||||
typedef std::shared_ptr<Service>(*CreateService)();
|
||||
using CreateService = ::ServiceCreate;
|
||||
|
||||
/** A ledger that describes the main parts of a service. */
|
||||
struct ServiceManifest {
|
||||
|
||||
@@ -1,45 +1,67 @@
|
||||
#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 <memory>
|
||||
|
||||
namespace tt::service {
|
||||
|
||||
// Forward declarations
|
||||
class ServiceManifest;
|
||||
constexpr size_t PATH_BUFFER_SIZE = 255;
|
||||
|
||||
class ServicePaths {
|
||||
|
||||
std::shared_ptr<const ServiceManifest> manifest;
|
||||
const ::ServiceManifest* manifest;
|
||||
|
||||
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 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.
|
||||
* Configuration data should be stored here.
|
||||
* @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.
|
||||
* 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.
|
||||
* @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
|
||||
* @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.
|
||||
* @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/Paths.h"
|
||||
|
||||
#include <Tactility/file/File.h>
|
||||
#include <Tactility/file/PropertiesFile.h>
|
||||
#include <tactility/log.h>
|
||||
@@ -16,13 +18,16 @@ namespace tt::bluetooth::settings {
|
||||
constexpr auto* TAG = "BluetoothPairedDevice";
|
||||
|
||||
// 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* KEY_NAME = "name";
|
||||
constexpr auto* KEY_ADDR = "addr";
|
||||
constexpr auto* KEY_AUTO_CONNECT = "autoConnect";
|
||||
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::stringstream stream;
|
||||
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) {
|
||||
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) {
|
||||
@@ -102,7 +107,10 @@ bool remove(const std::string& addr_hex) {
|
||||
|
||||
std::vector<PairedDevice> loadAll() {
|
||||
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;
|
||||
std::string name = entry->d_name;
|
||||
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
|
||||
// the service won't be registered yet.
|
||||
if (service::findManifestById("Gui") != nullptr) {
|
||||
if (service::getState("Gui") == service::State::Stopped) {
|
||||
if (service::getState("Gui") == SERVICE_STATE_STOPPED) {
|
||||
service::startService("Gui");
|
||||
} else {
|
||||
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
|
||||
// the service won't be registered yet.
|
||||
if (service::findManifestById("Statusbar") != nullptr) {
|
||||
if (service::getState("Statusbar") == service::State::Stopped) {
|
||||
if (service::getState("Statusbar") == SERVICE_STATE_STOPPED) {
|
||||
service::startService("Statusbar");
|
||||
} else {
|
||||
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/Mutex.h>
|
||||
#include <Tactility/service/ServiceInstance.h>
|
||||
#include <Tactility/service/ServiceContext.h>
|
||||
#include <Tactility/service/ServiceManifest.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/log.h>
|
||||
#include <tactility/service/service_manager.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
|
||||
namespace tt::service {
|
||||
|
||||
constexpr auto* TAG = "ServiceRegistration";
|
||||
|
||||
typedef std::unordered_map<std::string, std::shared_ptr<const ServiceManifest>> ManifestMap;
|
||||
typedef std::unordered_map<std::string, std::shared_ptr<ServiceInstance>> ServiceInstanceMap;
|
||||
// Bridges the kernel's C ServiceManifest/Service callbacks to the C++ Service
|
||||
// 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 ServiceInstanceMap service_instance_map;
|
||||
static void cppDestroyServiceTrampoline(const ::ServiceManifest* /*manifest*/, void* data) {
|
||||
delete static_cast<std::shared_ptr<Service>*>(data);
|
||||
}
|
||||
|
||||
static Mutex manifest_mutex;
|
||||
static Mutex instance_mutex;
|
||||
static error_t cppOnStartTrampoline(::ServiceInstance* instance, void* data) {
|
||||
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) {
|
||||
assert(manifest != nullptr);
|
||||
// We'll move the manifest pointer, but we'll need to id later
|
||||
const auto& id = manifest->id;
|
||||
|
||||
LOG_I(TAG, "Adding %s", id.c_str());
|
||||
|
||||
manifest_mutex.lock();
|
||||
if (service_manifest_map[id] == nullptr) {
|
||||
service_manifest_map[id] = std::move(manifest);
|
||||
} else {
|
||||
if (service_manager_find_manifest(id.c_str()) != nullptr) {
|
||||
LOG_E(TAG, "Service id in use: %s", id.c_str());
|
||||
return;
|
||||
}
|
||||
manifest_mutex.unlock();
|
||||
|
||||
if (autoStart) {
|
||||
startService(id);
|
||||
// Intentionally never freed: services are registered once and live for the
|
||||
// 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);
|
||||
}
|
||||
|
||||
std::shared_ptr<const ServiceManifest> findManifestById(const std::string& id) {
|
||||
manifest_mutex.lock();
|
||||
auto iterator = service_manifest_map.find(id);
|
||||
auto manifest = iterator != service_manifest_map.end() ? iterator->second : nullptr;
|
||||
manifest_mutex.unlock();
|
||||
return manifest;
|
||||
const ::ServiceManifest* findManifestById(const std::string& id) {
|
||||
return service_manager_find_manifest(id.c_str());
|
||||
}
|
||||
|
||||
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) {
|
||||
LOG_I(TAG, "Starting %s", id.c_str());
|
||||
auto manifest = findManifestById(id);
|
||||
if (manifest == nullptr) {
|
||||
LOG_E(TAG, "manifest not found for service %s", id.c_str());
|
||||
const error_t error = service_manager_start(id.c_str());
|
||||
if (error != ERROR_NONE) {
|
||||
LOG_E(TAG, "Starting %s failed: %s", id.c_str(), error_to_string(error));
|
||||
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());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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) {
|
||||
auto instance = findServiceInstanceById(id);
|
||||
return instance != nullptr ? instance->getService() : nullptr;
|
||||
auto* instance = service_manager_find_instance(id.c_str());
|
||||
if (instance == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return *static_cast<std::shared_ptr<Service>*>(instance->data);
|
||||
}
|
||||
|
||||
bool stopService(const std::string& id) {
|
||||
LOG_I(TAG, "Stopping %s", id.c_str());
|
||||
auto service_instance = findServiceInstanceById(id);
|
||||
if (service_instance == nullptr) {
|
||||
error_t error = service_manager_stop(id.c_str());
|
||||
if (error != ERROR_NONE) {
|
||||
LOG_W(TAG, "Service not running: %s", id.c_str());
|
||||
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());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
State getState(const std::string& id) {
|
||||
auto service_instance = findServiceInstanceById(id);
|
||||
if (service_instance == nullptr) {
|
||||
return State::Stopped;
|
||||
}
|
||||
return service_instance->getState();
|
||||
return service_manager_get_state(id.c_str());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user