diff --git a/apps/0BUILD b/apps/0BUILD
index 3adc6a8..9a167cd 100644
--- a/apps/0BUILD
+++ b/apps/0BUILD
@@ -6,6 +6,7 @@
aqhomed
aqhome-tool
aqhome-mqttlog
+ aqhome-storage
diff --git a/apps/aqhome-storage/0BUILD b/apps/aqhome-storage/0BUILD
new file mode 100644
index 0000000..436a34c
--- /dev/null
+++ b/apps/aqhome-storage/0BUILD
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+ $(gwenhywfar_cflags)
+ -I$(topsrcdir)
+ -I$(topbuilddir)
+ -I$(builddir)
+ -I$(srcdir)
+
+
+
+ --include=$(builddir)
+ --include=$(srcdir)
+
+
+ $(visibility_cflags)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ aqhomestorage_p.h
+ aqhomestorage.h
+ init.h
+
+
+
+ $(local/typefiles)
+
+ aqhomestorage.c
+ init.c
+ main.c
+
+
+
+ aqhome
+
+
+
+ $(gwenhywfar_libs)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/aqhome-storage/aqhomestorage.c b/apps/aqhome-storage/aqhomestorage.c
new file mode 100644
index 0000000..e207056
--- /dev/null
+++ b/apps/aqhome-storage/aqhomestorage.c
@@ -0,0 +1,119 @@
+/****************************************************************************
+ * This file is part of the project AqHome.
+ * AqHome (c) by 2023 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include
+#endif
+
+
+#include "./aqhomestorage_p.h"
+
+#include
+#include
+
+
+
+
+
+
+AQHOME_STORAGE *AqHomeStorage_new()
+{
+ AQHOME_STORAGE *aqh;
+
+ GWEN_NEW_OBJECT(AQHOME_STORAGE, aqh);
+ aqh->rootEndpoint=GWEN_MsgEndpoint_new("root", 0);
+
+ return aqh;
+}
+
+
+
+void AqHomeStorage_free(AQHOME_STORAGE *aqh)
+{
+ if (aqh) {
+ AQH_Storage_free(aqh->storage);
+ GWEN_MsgEndpoint_free(aqh->rootEndpoint);
+ GWEN_MsgEndpoint_free(aqh->rootEndpoint);
+ GWEN_DB_Group_free(aqh->dbArgs);
+
+ aqh->storage=NULL;
+ aqh->rootEndpoint=NULL;
+ aqh->ipcdEndpoint=NULL;
+ aqh->mqttEndpoint=NULL;
+ aqh->httpdEndpoint=NULL;
+ aqh->dbArgs=NULL;
+ free(aqh->pidFile);
+
+ GWEN_FREE_OBJECT(aqh);
+ }
+}
+
+
+
+GWEN_MSG_ENDPOINT *AqHomeStorage_GetRootEndpoint(const AQHOME_STORAGE *aqh)
+{
+ return aqh?(aqh->rootEndpoint):NULL;
+}
+
+
+
+GWEN_MSG_ENDPOINT *AqHomeStorage_GetIpcdEndpoint(const AQHOME_STORAGE *aqh)
+{
+ return aqh?(aqh->ipcdEndpoint):NULL;
+}
+
+
+
+GWEN_MSG_ENDPOINT *AqHomeStorage_GetMqttEndpoint(const AQHOME_STORAGE *aqh)
+{
+ return aqh?(aqh->mqttEndpoint):NULL;
+}
+
+
+
+GWEN_MSG_ENDPOINT *AqHomeStorage_GetHttpdEndpoint(const AQHOME_STORAGE *aqh)
+{
+ return aqh?(aqh->httpdEndpoint):NULL;
+}
+
+
+
+GWEN_DB_NODE *AqHomeStorage_GetDbArgs(const AQHOME_STORAGE *aqh)
+{
+ return aqh?(aqh->dbArgs):NULL;
+}
+
+
+
+AQH_STORAGE *AqHomeStorage_GetStorage(const AQHOME_STORAGE *aqh)
+{
+ return aqh?(aqh->storage):NULL;
+}
+
+
+
+const char *AqHomeStorage_GetPidFile(const AQHOME_STORAGE *aqh)
+{
+ return aqh?aqh->pidFile:NULL;
+}
+
+
+
+void AqHomeStorage_SetPidFile(AQHOME_STORAGE *aqh, const char *s)
+{
+ if (aqh) {
+ free(aqh->pidFile);
+ aqh->pidFile=s?strdup(s):NULL;
+ }
+}
+
+
+
+
+
+
diff --git a/apps/aqhome-storage/aqhomestorage.h b/apps/aqhome-storage/aqhomestorage.h
new file mode 100644
index 0000000..b4584ff
--- /dev/null
+++ b/apps/aqhome-storage/aqhomestorage.h
@@ -0,0 +1,38 @@
+/****************************************************************************
+ * This file is part of the project AqHome.
+ * AqHome (c) by 2023 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifndef AQHOME_STORAGE_H
+#define AQHOME_STORAGE_H
+
+
+#include "aqhome/data/storage.h"
+
+#include
+
+
+typedef struct AQHOME_STORAGE AQHOME_STORAGE;
+
+
+AQHOME_STORAGE *AqHomeStorage_new();
+void AqHomeStorage_free(AQHOME_STORAGE *aqh);
+
+GWEN_MSG_ENDPOINT *AqHomeStorage_GetRootEndpoint(const AQHOME_STORAGE *aqh);
+GWEN_MSG_ENDPOINT *AqHomeStorage_GetIpcdEndpoint(const AQHOME_STORAGE *aqh);
+GWEN_MSG_ENDPOINT *AqHomeStorage_GetMqttEndpoint(const AQHOME_STORAGE *aqh);
+GWEN_MSG_ENDPOINT *AqHomeStorage_GetHttpdEndpoint(const AQHOME_STORAGE *aqh);
+
+GWEN_DB_NODE *AqHomeStorage_GetDbArgs(const AQHOME_STORAGE *aqh);
+
+AQH_STORAGE *AqHomeStorage_GetStorage(const AQHOME_STORAGE *aqh);
+
+const char *AqHomeStorage_GetPidFile(const AQHOME_STORAGE *aqh);
+void AqHomeStorage_SetPidFile(AQHOME_STORAGE *aqh, const char *s);
+
+
+#endif
+
diff --git a/apps/aqhome-storage/aqhomestorage_p.h b/apps/aqhome-storage/aqhomestorage_p.h
new file mode 100644
index 0000000..704ec0f
--- /dev/null
+++ b/apps/aqhome-storage/aqhomestorage_p.h
@@ -0,0 +1,43 @@
+/****************************************************************************
+ * This file is part of the project AqHome.
+ * AqHome (c) by 2023 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifndef AQHOME_STORAGE_P_H
+#define AQHOME_STORAGE_P_H
+
+
+#include "./aqhomestorage.h"
+
+
+
+/* default values */
+#define AQHOME_STORAGE_DEFAULT_PIDFILE "/var/run/aqhome-storage.pid"
+#define AQHOME_STORAGE_DEFAULT_IPC_PORT 45455
+#define AQHOME_STORAGE_DEFAULT_HTTP_PORT 45456
+#define AQHOME_STORAGE_DEFAULT_MQTT_CLIENTID "AQHOMESTORAGE"
+#define AQHOME_STORAGE_DEFAULT_MQTT_KEEPALIVE 600
+#define AQHOME_STORAGE_DEFAULT_MQTT_PORT 1883
+
+
+
+struct AQHOME_STORAGE {
+ GWEN_MSG_ENDPOINT *rootEndpoint;
+
+ GWEN_MSG_ENDPOINT *ipcdEndpoint;
+ GWEN_MSG_ENDPOINT *mqttEndpoint;
+ GWEN_MSG_ENDPOINT *httpdEndpoint;
+
+ GWEN_DB_NODE *dbArgs;
+
+ AQH_STORAGE *storage;
+
+ char *pidFile;
+
+};
+
+#endif
+
diff --git a/apps/aqhome-storage/init.c b/apps/aqhome-storage/init.c
new file mode 100644
index 0000000..4aef7fd
--- /dev/null
+++ b/apps/aqhome-storage/init.c
@@ -0,0 +1,452 @@
+/****************************************************************************
+ * This file is part of the project AqHome.
+ * AqHome (c) by 2023 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include
+#endif
+
+
+#include "./init.h"
+#include "./aqhomestorage_p.h"
+
+#include "aqhome/msg/endpoint_tty.h"
+#include "aqhome/ipc/endpoint_ipc.h"
+#include "aqhome/mqtt/endpoint_mqttc.h"
+#include "aqhome/http/endpoint_http.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#ifdef HAVE_SYS_TYPES_H
+# include
+#endif
+
+#ifdef HAVE_SYS_STAT_H
+# include
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * defines
+ * ------------------------------------------------------------------------------------------------
+ */
+
+//#define I18N(msg) msg
+#define I18S(msg) msg
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * forward declarations
+ * ------------------------------------------------------------------------------------------------
+ */
+
+static int _setupStorage(AQHOME_STORAGE *aqh, GWEN_DB_NODE *dbArgs);
+
+static void _setupIpc(AQHOME_STORAGE *aqh, GWEN_DB_NODE *dbArgs);
+static void _setupMqtt(AQHOME_STORAGE *aqh, GWEN_DB_NODE *dbArgs);
+static void _setupHttp(AQHOME_STORAGE *aqh, GWEN_DB_NODE *dbArgs);
+
+static GWEN_MSG_ENDPOINT *_acceptIpcFn(GWEN_MSG_ENDPOINT *ep, GWEN_SOCKET *sk, const GWEN_INETADDRESS *addr, void *data);
+static GWEN_MSG_ENDPOINT *_acceptHttpFn(GWEN_MSG_ENDPOINT *ep, GWEN_SOCKET *sk, const GWEN_INETADDRESS *addr, void *data);
+
+static int _readArgs(int argc, char **argv, GWEN_DB_NODE *dbArgs);
+static int _createPidFile(const char *pidFilename);
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * implementations
+ * ------------------------------------------------------------------------------------------------
+ */
+
+int AqHomeStorage_Init(AQHOME_STORAGE *aqh, int argc, char **argv)
+{
+ GWEN_DB_NODE *dbArgs;
+ int rv;
+ const char *s;
+
+ dbArgs=GWEN_DB_Group_new("args");
+ rv=_readArgs(argc, argv, dbArgs);
+ if (rv<0) {
+ DBG_ERROR(NULL, "Error reading args (%d)", rv);
+ return rv;
+ }
+ aqh->dbArgs=dbArgs;
+
+ s=GWEN_DB_GetCharValue(dbArgs, "pidfile", 0, AQHOME_STORAGE_DEFAULT_PIDFILE);
+ if (s && *s) {
+ AqHomeStorage_SetPidFile(aqh, s);
+ rv=_createPidFile(s);
+ if (rv<0) {
+ DBG_ERROR(NULL, "Error creating PID file (%d)", rv);
+ return rv;
+ }
+ }
+
+// _setupStorage(aqh, dbArgs);
+
+ _setupIpc(aqh, dbArgs);
+ _setupMqtt(aqh, dbArgs);
+ _setupHttp(aqh, dbArgs);
+
+ return 0;
+}
+
+
+
+int _setupStorage(AQHOME_STORAGE *aqh, GWEN_DB_NODE *dbArgs)
+{
+ AQH_STORAGE *sto;
+ const char *stateFile;
+
+ stateFile=GWEN_DB_GetCharValue(dbArgs, "stateFile", 0, NULL);
+
+ sto=AQH_Storage_new();
+
+ aqh->storage=sto;
+ return 0;
+}
+
+
+
+void _setupIpc(AQHOME_STORAGE *aqh, GWEN_DB_NODE *dbArgs)
+{
+ const char *tcpAddress;
+ int tcpPort;
+
+ tcpAddress=GWEN_DB_GetCharValue(dbArgs, "tcpAddress", 0, NULL);
+ tcpPort=GWEN_DB_GetIntValue(dbArgs, "tcpPort", 0, AQHOME_STORAGE_DEFAULT_IPC_PORT);
+
+ if (tcpAddress && *tcpAddress && tcpPort) {
+ GWEN_MSG_ENDPOINT *ep;
+
+ ep=GWEN_TcpdEndpoint_new(tcpAddress, tcpPort, NULL, 0);
+ GWEN_TcpdEndpoint_SetAcceptFn(ep, _acceptIpcFn, aqh);
+
+ GWEN_MsgEndpoint_Tree2_AddChild(aqh->rootEndpoint, ep);
+ aqh->ipcdEndpoint=ep;
+ }
+}
+
+
+
+void _setupMqtt(AQHOME_STORAGE *aqh, GWEN_DB_NODE *dbArgs)
+{
+ const char *mqttAddress;
+ int mqttPort;
+ const char *mqttClientId;
+ int mqttKeepAlive;
+
+ mqttAddress=GWEN_DB_GetCharValue(dbArgs, "mqttAddress", 0, NULL);
+ mqttPort=GWEN_DB_GetIntValue(dbArgs, "mqttPort", 0, AQHOME_STORAGE_DEFAULT_MQTT_PORT);
+ mqttClientId=GWEN_DB_GetCharValue(dbArgs, "mqttClientId", 0, AQHOME_STORAGE_DEFAULT_MQTT_CLIENTID);
+ mqttKeepAlive=GWEN_DB_GetIntValue(dbArgs, "mqttKeepAlive", 0, AQHOME_STORAGE_DEFAULT_MQTT_KEEPALIVE);
+
+ if (mqttAddress && *mqttAddress && mqttPort) {
+ GWEN_MSG_ENDPOINT *ep;
+ int rv;
+
+ ep=AQH_MqttClientEndpoint_new(mqttClientId, mqttAddress, mqttPort, NULL, 0);
+ AQH_MqttClientEndpoint_SetKeepAliveTime(ep, mqttKeepAlive);
+
+ GWEN_MsgEndpoint_Tree2_AddChild(aqh->rootEndpoint, ep);
+ aqh->mqttEndpoint=ep;
+
+ rv=AQH_MqttClientEndpoint_StartConnect(ep);
+ if (rv<0 && rv!=GWEN_ERROR_IN_PROGRESS) {
+ DBG_ERROR(NULL, "Error connecting to MQTT server %s:%d (%d), will retry later", mqttAddress, mqttPort, rv);
+ }
+ }
+}
+
+
+
+void _setupHttp(AQHOME_STORAGE *aqh, GWEN_DB_NODE *dbArgs)
+{
+ const char *tcpAddress;
+ int tcpPort;
+
+ tcpAddress=GWEN_DB_GetCharValue(dbArgs, "httpAddress", 0, NULL);
+ tcpPort=GWEN_DB_GetIntValue(dbArgs, "httpPort", 0, AQHOME_STORAGE_DEFAULT_HTTP_PORT);
+
+ if (tcpAddress && *tcpAddress && tcpPort) {
+ GWEN_MSG_ENDPOINT *ep;
+
+ ep=GWEN_TcpdEndpoint_new(tcpAddress, tcpPort, NULL, 0);
+ GWEN_TcpdEndpoint_SetAcceptFn(ep, _acceptHttpFn, aqh);
+
+ GWEN_MsgEndpoint_Tree2_AddChild(aqh->rootEndpoint, ep);
+ aqh->httpdEndpoint=ep;
+ }
+}
+
+
+
+GWEN_MSG_ENDPOINT *_acceptIpcFn(GWEN_MSG_ENDPOINT *ep,
+ GWEN_SOCKET *sk,
+ const GWEN_INETADDRESS *addr,
+ GWEN_UNUSED void *data)
+{
+/* AQHOME_STORAGE *aqh;
+ *
+ * aqh=(AQHOME_STORAGE*) data;
+ */
+ DBG_INFO(NULL, "Incoming IPC connection");
+ return AQH_IpcEndpoint_CreateIpcTcpServiceForSocket(sk, NULL, 0);
+}
+
+
+
+GWEN_MSG_ENDPOINT *_acceptHttpFn(GWEN_MSG_ENDPOINT *ep,
+ GWEN_SOCKET *sk,
+ const GWEN_INETADDRESS *addr,
+ GWEN_UNUSED void *data)
+{
+ GWEN_MSG_ENDPOINT *epIncoming;
+
+ /* AQHOME_STORAGE *aqh;
+ *
+ * aqh=(AQHOME_STORAGE*) data;
+ */
+
+ DBG_INFO(NULL, "Incoming HTTP connection");
+ epIncoming=GWEN_MsgEndpoint_new("http", 0);
+ GWEN_MsgEndpoint_SetSocket(epIncoming, sk);
+ GWEN_MsgIoEndpoint_Extend(epIncoming);
+ AQH_HttpEndpoint_Extend(epIncoming, AQH_ENDPOINT_HTTP_FLAGS_PASSIVE);
+ return epIncoming;
+}
+
+
+
+int _createPidFile(const char *pidFilename)
+{
+ FILE *f;
+ int pidfd;
+
+ if (remove(pidFilename)==0) {
+ DBG_ERROR(0, "Old PID file existed, removed. (Unclean shutdown?)");
+ }
+
+#ifdef HAVE_SYS_STAT_H
+ pidfd = open(pidFilename, O_EXCL|O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
+ if (pidfd < 0) {
+ DBG_ERROR(NULL, "Could not create PID file \"%s\" (%s), aborting.", pidFilename, strerror(errno));
+ return GWEN_ERROR_IO;
+ }
+
+ f = fdopen(pidfd, "w");
+#else /* HAVE_STAT_H */
+ f=fopen(pidFilename,"w+");
+#endif /* HAVE_STAT_H */
+
+ /* write pid */
+#ifdef HAVE_GETPID
+ fprintf(f,"%d\n",getpid());
+#else
+ fprintf(f,"-1\n");
+#endif
+ if (fclose(f)) {
+ DBG_ERROR(0, "Could not close PID file \"%s\" (%s), aborting.", pidFilename, strerror(errno));
+ return GWEN_ERROR_IO;
+ }
+ return 0;
+}
+
+
+
+
+int _readArgs(int argc, char **argv, GWEN_DB_NODE *dbArgs)
+{
+ int rv;
+ const GWEN_ARGS args[]= {
+ {
+ GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
+ GWEN_ArgsType_Char, /* type */
+ "cfgdir", /* name */
+ 0, /* minnum */
+ 1, /* maxnum */
+ "D", /* short option */
+ "cfgdir", /* long option */
+ I18S("Specify the configuration folder"),
+ I18S("Specify the configuration folder")
+ },
+ {
+ GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
+ GWEN_ArgsType_Char, /* type */
+ "tcpAddress", /* name */
+ 0, /* minnum */
+ 1, /* maxnum */
+ "t", /* short option */
+ "tcpaddress", /* long option */
+ I18S("Specify the TCP address to listen on (disabled if missing)"),
+ I18S("Specify the TCP address to listen on (disabled if missing)")
+ },
+ {
+ GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
+ GWEN_ArgsType_Int, /* type */
+ "tcpPort", /* name */
+ 0, /* minnum */
+ 1, /* maxnum */
+ "P", /* short option */
+ "tcpport", /* long option */
+ I18S("Specify the TCP port to listen on"),
+ I18S("Specify the TCP port to listen on")
+ },
+ {
+ GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
+ GWEN_ArgsType_Char, /* type */
+ "mqttAddress", /* name */
+ 0, /* minnum */
+ 1, /* maxnum */
+ "ma", /* short option */
+ "mqttaddress", /* long option */
+ I18S("Specify the address of the MQTT server to connect to (disabled if missing)"),
+ I18S("Specify the address of the MQTT server to connect to (disabled if missing)")
+ },
+ {
+ GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
+ GWEN_ArgsType_Int, /* type */
+ "mqttPort", /* name */
+ 0, /* minnum */
+ 1, /* maxnum */
+ "mp", /* short option */
+ "mqttport", /* long option */
+ I18S("Specify the port of the MQTT server (default: 1883)"),
+ I18S("Specify the port of the MQTT server (default: 1883)")
+ },
+ {
+ GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
+ GWEN_ArgsType_Char, /* type */
+ "mqttClientId", /* name */
+ 0, /* minnum */
+ 1, /* maxnum */
+ NULL, /* short option */
+ "mqttclientid", /* long option */
+ I18S("Specify client id for the MQTT server (default: \"AQHOMESTORAGE\")"),
+ I18S("Specify client id for the MQTT server (default: \"AQHOMESTORAGE\")")
+ },
+ {
+ GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
+ GWEN_ArgsType_Int, /* type */
+ "mqttKeepAlive", /* name */
+ 0, /* minnum */
+ 1, /* maxnum */
+ "mk", /* short option */
+ "mqttkeepalive", /* long option */
+ I18S("Specify keepalive time in seconds (defaults: 600)"),
+ I18S("Specify keepalive time in seconds (defaults: 600)")
+ },
+ {
+ GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
+ GWEN_ArgsType_Char, /* type */
+ "httpAddress", /* name */
+ 0, /* minnum */
+ 1, /* maxnum */
+ "ma", /* short option */
+ "httpaddress", /* long option */
+ I18S("Specify the address to bind the http service to (disabled if missing)"),
+ I18S("Specify the address to bind the http service to (disabled if missing)")
+ },
+ {
+ GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
+ GWEN_ArgsType_Int, /* type */
+ "httpPort", /* name */
+ 0, /* minnum */
+ 1, /* maxnum */
+ "mp", /* short option */
+ "httpport", /* long option */
+ I18S("Specify the port to listen on for HTTP connections"),
+ I18S("Specify the port to listen on for HTTP connections")
+ },
+ {
+ GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
+ GWEN_ArgsType_Char, /* type */
+ "statefile", /* name */
+ 0, /* minnum */
+ 1, /* maxnum */
+ "S", /* short option */
+ "statefile", /* long option */
+ I18S("File where rooms, devices and values etc. are stored"),
+ I18S("File where rooms, devices and values etc. are stored")
+ },
+ {
+ GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
+ GWEN_ArgsType_Char, /* type */
+ "pidfile", /* name */
+ 0, /* minnum */
+ 1, /* maxnum */
+ "p", /* short option */
+ "pidfile", /* long option */
+ I18S("Specify the PID file"),
+ I18S("Specify the PID file")
+ },
+ {
+ GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */
+ GWEN_ArgsType_Int, /* type */
+ "timeout", /* name */
+ 0, /* minnum */
+ 1, /* maxnum */
+ "T", /* short option */
+ "timeout", /* long option */
+ I18S("Specify timeout in second (default: no timeout)"),
+ I18S("Specify timeout in second (default: no timeout)")
+ },
+ {
+ GWEN_ARGS_FLAGS_HELP | GWEN_ARGS_FLAGS_LAST, /* flags */
+ GWEN_ArgsType_Int, /* type */
+ "help", /* name */
+ 0, /* minnum */
+ 0, /* maxnum */
+ "h", /* short option */
+ "help",
+ I18S("Show this help screen."),
+ I18S("Show this help screen.")
+ }
+ };
+
+ rv=GWEN_Args_Check(argc, argv, 1, 0, args, dbArgs);
+ if (rv==GWEN_ARGS_RESULT_ERROR) {
+ fprintf(stderr, "ERROR: Could not parse arguments main\n");
+ return GWEN_ERROR_INVALID;
+ }
+ else if (rv==GWEN_ARGS_RESULT_HELP) {
+ GWEN_BUFFER *ubuf;
+
+ ubuf=GWEN_Buffer_new(0, 1024, 0, 1);
+ GWEN_Buffer_AppendArgs(ubuf,
+ I18N("This is version %s.\nUsage: %s [OPTIONS]\n\nOptions:\n"),
+ AQHOME_VERSION_STRING,
+ argv[0]);
+ if (GWEN_Args_Usage(args, ubuf, GWEN_ArgsOutType_Txt)) {
+ fprintf(stderr, "ERROR: Could not create help string\n");
+ return 1;
+ }
+ GWEN_Buffer_AppendString(ubuf, "\n");
+
+ fprintf(stdout, "%s\n", GWEN_Buffer_GetStart(ubuf));
+ GWEN_Buffer_free(ubuf);
+ return GWEN_ERROR_CLOSE;
+ }
+ return 0;
+}
+
+
+
diff --git a/apps/aqhome-storage/init.h b/apps/aqhome-storage/init.h
new file mode 100644
index 0000000..6627a7f
--- /dev/null
+++ b/apps/aqhome-storage/init.h
@@ -0,0 +1,23 @@
+/****************************************************************************
+ * This file is part of the project AqHome.
+ * AqHome (c) by 2023 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifndef AQHOME_STORAGE_INIT_H
+#define AQHOME_STORAGE_INIT_H
+
+
+#include "./aqhomestorage.h"
+
+
+
+int AqHomeStorage_Init(AQHOME_STORAGE *aqh, int argc, char **argv);
+
+
+
+#endif
+
+
diff --git a/apps/aqhome-storage/main.c b/apps/aqhome-storage/main.c
new file mode 100644
index 0000000..b20f0fb
--- /dev/null
+++ b/apps/aqhome-storage/main.c
@@ -0,0 +1,12 @@
+
+
+
+
+
+int main(int argc, char **argv)
+{
+ return 1;
+}
+
+
+
diff --git a/aqhome/data/0BUILD b/aqhome/data/0BUILD
index 4c7218f..ed915d8 100644
--- a/aqhome/data/0BUILD
+++ b/aqhome/data/0BUILD
@@ -73,6 +73,8 @@
storage_p.h
+ storage_readxml.h
+ storage_writexml.h
@@ -80,6 +82,8 @@
$(local/typefiles)
storage.c
+ storage_readxml.c
+ storage_writexml.c
diff --git a/aqhome/data/storage.c b/aqhome/data/storage.c
index 8fb712c..c7d12f7 100644
--- a/aqhome/data/storage.c
+++ b/aqhome/data/storage.c
@@ -11,8 +11,12 @@
#endif
#include "aqhome/data/storage_p.h"
+#include "aqhome/data/storage_readxml.h"
+#include "aqhome/data/storage_writexml.h"
#include
+#include
+#include
@@ -25,7 +29,6 @@
-
/* ------------------------------------------------------------------------------------------------
* implementations
* ------------------------------------------------------------------------------------------------
@@ -37,6 +40,7 @@ AQH_STORAGE *AQH_Storage_new(void)
AQH_STORAGE *sto;
GWEN_NEW_OBJECT(AQH_STORAGE, sto);
+ sto->roomList=AQH_Room_List_new();
sto->deviceList=AQH_Device_List_new();
sto->mqttTopicList=AQH_MqttTopic_List_new();
sto->valueList=AQH_Value_List_new();
@@ -52,6 +56,8 @@ void AQH_Storage_free(AQH_STORAGE *sto)
AQH_Value_List_free(sto->valueList);
AQH_MqttTopic_List_free(sto->mqttTopicList);
AQH_Device_List_free(sto->deviceList);
+ AQH_Room_List_free(sto->roomList);
+ free(sto->stateFile);
GWEN_FREE_OBJECT(sto);
}
@@ -59,6 +65,23 @@ void AQH_Storage_free(AQH_STORAGE *sto)
+const char *AQH_Storage_GetStateFile(const AQH_STORAGE *sto)
+{
+ return sto?(sto->stateFile):NULL;
+}
+
+
+
+void AQH_Storage_SetStateFile(AQH_STORAGE *sto, const char *s)
+{
+ if (sto) {
+ free(sto->stateFile);
+ sto->stateFile=s?strdup(s):NULL;
+ }
+}
+
+
+
void AQH_Storage_AddDevice(AQH_STORAGE *sto, AQH_DEVICE *dev)
{
if (sto && dev) {
@@ -154,5 +177,43 @@ void AQH_Storage_HandleMqttPublish(AQH_STORAGE *sto, const char *topic, const ch
+int AQH_Storage_Init(AQH_STORAGE *sto)
+{
+ int rv;
+
+ rv=GWEN_Directory_GetPath(sto->stateFile,
+ GWEN_PATH_FLAGS_CHECKROOT | GWEN_PATH_FLAGS_PATHMUSTEXIST | GWEN_PATH_FLAGS_NAMEMUSTEXIST);
+ if (rv==0) {
+ rv=AQH_Storage_ReadStateFile(sto, sto->stateFile);
+ if (rv<0) {
+ DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv);
+ return rv;
+ }
+ }
+ else {
+ DBG_WARN(AQH_LOGDOMAIN, "State file \"%s\" not available, will try to create it later", sto->stateFile);
+ }
+
+ return 0;
+}
+
+
+
+int AQH_Storage_WriteState(AQH_STORAGE *sto)
+{
+ int rv;
+
+ rv=AQH_Storage_WriteStateFile(sto, sto->stateFile);
+ if (rv<0) {
+ DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv);
+ return rv;
+ }
+
+ return 0;
+}
+
+
+
+
diff --git a/aqhome/data/storage.h b/aqhome/data/storage.h
index b4e461b..b2a0cdb 100644
--- a/aqhome/data/storage.h
+++ b/aqhome/data/storage.h
@@ -59,6 +59,13 @@ AQHOME_API void AQH_Storage_AddValue(AQH_STORAGE *sto, AQH_VALUE *value);
AQHOME_API AQH_VALUE_LIST *AQH_Storage_GetValueList(const AQH_STORAGE *sto);
AQHOME_API AQH_VALUE *AQH_Storage_GetValueById(const AQH_STORAGE *sto, uint64_t id);
+AQHOME_API const char *AQH_Storage_GetStateFile(const AQH_STORAGE *sto);
+AQHOME_API void AQH_Storage_SetStateFile(AQH_STORAGE *sto, const char *s);
+
+
+AQHOME_API int AQH_Storage_Init(AQH_STORAGE *sto);
+AQHOME_API int AQH_Storage_WriteState(AQH_STORAGE *sto);
+
AQHOME_API void AQH_Storage_HandleMqttPublish(AQH_STORAGE *sto, const char *topic, const char *value);
diff --git a/aqhome/data/storage_p.h b/aqhome/data/storage_p.h
index c10e234..082262a 100644
--- a/aqhome/data/storage_p.h
+++ b/aqhome/data/storage_p.h
@@ -13,15 +13,34 @@
#include "aqhome/data/storage.h"
+#define AQH_STORAGE_XML_ELEMENTNAME_LASTIDS "lastIds"
+
+#define AQH_STORAGE_XML_ELEMENTNAME_ROOMS "rooms"
+#define AQH_STORAGE_XML_ELEMENTNAME_ROOM "room"
+
+#define AQH_STORAGE_XML_ELEMENTNAME_DEVICES "devices"
+#define AQH_STORAGE_XML_ELEMENTNAME_DEVICE "device"
+
+#define AQH_STORAGE_XML_ELEMENTNAME_TOPICS "topics"
+#define AQH_STORAGE_XML_ELEMENTNAME_TOPIC "topic"
+
+#define AQH_STORAGE_XML_ELEMENTNAME_VALUES "values"
+#define AQH_STORAGE_XML_ELEMENTNAME_VALUE "value"
+
+
struct AQH_STORAGE {
+ AQH_ROOM_LIST *roomList;
AQH_DEVICE_LIST *deviceList;
AQH_MQTT_TOPIC_LIST *mqttTopicList;
AQH_VALUE_LIST *valueList;
+ uint64_t lastRoomId;
uint64_t lastDeviceId;
uint64_t lastTopicId;
uint64_t lastValueId;
+
+ char *stateFile;
};
diff --git a/aqhome/data/storage_readxml.c b/aqhome/data/storage_readxml.c
new file mode 100644
index 0000000..0767351
--- /dev/null
+++ b/aqhome/data/storage_readxml.c
@@ -0,0 +1,184 @@
+/****************************************************************************
+ * This file is part of the project Gwenhywfar.
+ * Gwenhywfar (c) by 2023 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include
+#endif
+
+#include "aqhome/data/storage_readxml.h"
+#include "aqhome/data/storage_p.h"
+
+#include
+#include
+#include
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * forward declarations
+ * ------------------------------------------------------------------------------------------------
+ */
+
+
+static void _readLastIdsFromXml(AQH_STORAGE *sto, GWEN_XMLNODE *rootNode);
+static void _readRoomsFromXml(AQH_STORAGE *sto, GWEN_XMLNODE *rootNode);
+static void _readDevicesFromXml(AQH_STORAGE *sto, GWEN_XMLNODE *rootNode);
+static void _readTopicsFromXml(AQH_STORAGE *sto, GWEN_XMLNODE *rootNode);
+static void _readValuesFromXml(AQH_STORAGE *sto, GWEN_XMLNODE *rootNode);
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * implementations
+ * ------------------------------------------------------------------------------------------------
+ */
+
+int AQH_Storage_ReadStateFile(AQH_STORAGE *sto, const char *sFilename)
+{
+ GWEN_XMLNODE *rootNode;
+ int rv;
+
+ rootNode=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, NULL);
+ rv=GWEN_XML_ReadFile(rootNode, sFilename, GWEN_XML_FLAGS_DEFAULT);
+ if (rv<0) {
+ DBG_ERROR(AQH_LOGDOMAIN, "Error reading XML file \"%s\": %d", sFilename, rv);
+ GWEN_XMLNode_free(rootNode);
+ return rv;
+ }
+
+ _readLastIdsFromXml(sto, rootNode);
+ _readRoomsFromXml(sto, rootNode);
+ _readDevicesFromXml(sto, rootNode);
+ _readTopicsFromXml(sto, rootNode);
+ _readValuesFromXml(sto, rootNode);
+
+ return 0;
+}
+
+
+
+void _readLastIdsFromXml(AQH_STORAGE *sto, GWEN_XMLNODE *rootNode)
+{
+ GWEN_XMLNODE *nLastIds;
+
+ nLastIds=GWEN_XMLNode_FindFirstTag(rootNode, AQH_STORAGE_XML_ELEMENTNAME_LASTIDS, NULL, NULL);
+ if (nLastIds) {
+ sto->lastRoomId=GWEN_XMLNode_GetIntValue(nLastIds, "lastRoomId", 0);
+ sto->lastDeviceId=GWEN_XMLNode_GetIntValue(nLastIds, "lastDeviceId", 0);
+ sto->lastTopicId=GWEN_XMLNode_GetIntValue(nLastIds, "lastTopicId", 0);
+ sto->lastValueId=GWEN_XMLNode_GetIntValue(nLastIds, "lastValueId", 0);
+ }
+ else {
+ sto->lastRoomId=0;
+ sto->lastDeviceId=0;
+ sto->lastTopicId=0;
+ sto->lastValueId=0;
+ }
+}
+
+
+
+void _readRoomsFromXml(AQH_STORAGE *sto, GWEN_XMLNODE *rootNode)
+{
+ GWEN_XMLNODE *nRooms;
+
+ nRooms=GWEN_XMLNode_FindFirstTag(rootNode, AQH_STORAGE_XML_ELEMENTNAME_ROOMS, NULL, NULL);
+ if (nRooms) {
+ GWEN_XMLNODE *nRoom;
+
+ nRoom=GWEN_XMLNode_FindFirstTag(nRooms, AQH_STORAGE_XML_ELEMENTNAME_ROOM, NULL, NULL);
+ while(nRoom) {
+ AQH_ROOM *r;
+
+ r=AQH_Room_fromXml(nRoom);
+ if (r) {
+ AQH_Room_List_Add(r, sto->roomList);
+ }
+ nRoom=GWEN_XMLNode_FindNextTag(nRoom, AQH_STORAGE_XML_ELEMENTNAME_ROOM, NULL, NULL);
+ }
+ }
+}
+
+
+
+void _readDevicesFromXml(AQH_STORAGE *sto, GWEN_XMLNODE *rootNode)
+{
+ GWEN_XMLNODE *nDevices;
+
+ nDevices=GWEN_XMLNode_FindFirstTag(rootNode, AQH_STORAGE_XML_ELEMENTNAME_DEVICES, NULL, NULL);
+ if (nDevices) {
+ GWEN_XMLNODE *nDevice;
+
+ nDevice=GWEN_XMLNode_FindFirstTag(nDevices, AQH_STORAGE_XML_ELEMENTNAME_DEVICE, NULL, NULL);
+ while(nDevice) {
+ AQH_DEVICE *device;
+
+ device=AQH_Device_fromXml(nDevice);
+ if (device) {
+ AQH_Device_List_Add(device, sto->deviceList);
+ }
+ nDevice=GWEN_XMLNode_FindNextTag(nDevice, AQH_STORAGE_XML_ELEMENTNAME_DEVICE, NULL, NULL);
+ }
+ }
+}
+
+
+
+void _readTopicsFromXml(AQH_STORAGE *sto, GWEN_XMLNODE *rootNode)
+{
+ GWEN_XMLNODE *nTopics;
+
+ nTopics=GWEN_XMLNode_FindFirstTag(rootNode, AQH_STORAGE_XML_ELEMENTNAME_TOPICS, NULL, NULL);
+ if (nTopics) {
+ GWEN_XMLNODE *nTopic;
+
+ nTopic=GWEN_XMLNode_FindFirstTag(nTopics, AQH_STORAGE_XML_ELEMENTNAME_TOPIC, NULL, NULL);
+ while(nTopic) {
+ AQH_MQTT_TOPIC *topic;
+
+ topic=AQH_MqttTopic_fromXml(nTopic);
+ if (topic) {
+ AQH_MqttTopic_List_Add(topic, sto->mqttTopicList);
+ }
+ nTopic=GWEN_XMLNode_FindNextTag(nTopic, AQH_STORAGE_XML_ELEMENTNAME_TOPIC, NULL, NULL);
+ }
+ }
+}
+
+
+
+void _readValuesFromXml(AQH_STORAGE *sto, GWEN_XMLNODE *rootNode)
+{
+ GWEN_XMLNODE *nValues;
+
+ nValues=GWEN_XMLNode_FindFirstTag(rootNode, AQH_STORAGE_XML_ELEMENTNAME_VALUES, NULL, NULL);
+ if (nValues) {
+ GWEN_XMLNODE *nValue;
+
+ nValue=GWEN_XMLNode_FindFirstTag(nValues, AQH_STORAGE_XML_ELEMENTNAME_VALUE, NULL, NULL);
+ while(nValue) {
+ AQH_VALUE *value;
+
+ value=AQH_Value_fromXml(nValue);
+ if (value) {
+ AQH_Value_List_Add(value, sto->roomList);
+ }
+ nValue=GWEN_XMLNode_FindNextTag(nValue, AQH_STORAGE_XML_ELEMENTNAME_VALUE, NULL, NULL);
+ }
+ }
+}
+
+
+
+
+
+
+
+
+
+
diff --git a/aqhome/data/storage_readxml.h b/aqhome/data/storage_readxml.h
new file mode 100644
index 0000000..62f0fa4
--- /dev/null
+++ b/aqhome/data/storage_readxml.h
@@ -0,0 +1,24 @@
+/****************************************************************************
+ * This file is part of the project Gwenhywfar.
+ * Gwenhywfar (c) by 2023 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifndef AQH_STORAGE_READXML_H
+#define AQH_STORAGE_READXML_H
+
+
+#include "aqhome/data/storage.h"
+
+
+
+int AQH_Storage_ReadStateFile(AQH_STORAGE *sto, const char *sFilename);
+
+
+
+
+#endif
+
+
diff --git a/aqhome/data/storage_writexml.c b/aqhome/data/storage_writexml.c
new file mode 100644
index 0000000..dac9aed
--- /dev/null
+++ b/aqhome/data/storage_writexml.c
@@ -0,0 +1,190 @@
+/****************************************************************************
+ * This file is part of the project Gwenhywfar.
+ * Gwenhywfar (c) by 2023 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include
+#endif
+
+#include "aqhome/data/storage_writexml.h"
+#include "aqhome/data/storage_p.h"
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * forward declarations
+ * ------------------------------------------------------------------------------------------------
+ */
+
+
+static void _writeLastIdsToXml(const AQH_STORAGE *sto, GWEN_XMLNODE *rootNode);
+static void _writeRoomsToXml(const AQH_STORAGE *sto, GWEN_XMLNODE *rootNode);
+static void _writeDevicesToXml(const AQH_STORAGE *sto, GWEN_XMLNODE *rootNode);
+static void _writeTopicsToXml(const AQH_STORAGE *sto, GWEN_XMLNODE *rootNode);
+static void _writeValuesToXml(const AQH_STORAGE *sto, GWEN_XMLNODE *rootNode);
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * implementations
+ * ------------------------------------------------------------------------------------------------
+ */
+
+int AQH_Storage_WriteStateFile(const AQH_STORAGE *sto, const char *sFilename)
+{
+ GWEN_XMLNODE *rootNode;
+ int rv;
+ GWEN_BUFFER *nbuf;
+
+ rootNode=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, "root");
+ _writeLastIdsToXml(sto, rootNode);
+ _writeRoomsToXml(sto, rootNode);
+ _writeDevicesToXml(sto, rootNode);
+ _writeTopicsToXml(sto, rootNode);
+ _writeValuesToXml(sto, rootNode);
+
+ nbuf=GWEN_Buffer_new(0, 256, 0, 1);
+ GWEN_Buffer_AppendString(nbuf, sFilename);
+ GWEN_Buffer_AppendString(nbuf, ".tmp");
+ unlink(GWEN_Buffer_GetStart(nbuf));
+ rv=GWEN_XMLNode_WriteFile(rootNode,
+ GWEN_Buffer_GetStart(nbuf),
+ GWEN_XML_FLAGS_SIMPLE | GWEN_XML_FLAGS_HANDLE_HEADERS | GWEN_XML_FLAGS_INDENT);
+ if (rv<0) {
+ DBG_ERROR(AQH_LOGDOMAIN, "Error writing XML file \"%s\": %d", GWEN_Buffer_GetStart(nbuf), rv);
+ GWEN_Buffer_free(nbuf);
+ GWEN_XMLNode_free(rootNode);
+ return rv;
+ }
+ if (rename(GWEN_Buffer_GetStart(nbuf), sFilename)<0) {
+ DBG_ERROR(AQH_LOGDOMAIN, "Error renaming \"%s\"->\"%s\": %d (%s)",
+ GWEN_Buffer_GetStart(nbuf), sFilename, errno, strerror(errno));
+ GWEN_Buffer_free(nbuf);
+ GWEN_XMLNode_free(rootNode);
+ return rv;
+ }
+ GWEN_XMLNode_free(rootNode);
+ return 0;
+}
+
+
+
+void _writeLastIdsToXml(const AQH_STORAGE *sto, GWEN_XMLNODE *rootNode)
+{
+ GWEN_XMLNODE *nLastIds;
+
+ nLastIds=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, AQH_STORAGE_XML_ELEMENTNAME_LASTIDS);
+ GWEN_XMLNode_SetIntValue(nLastIds, "lastRoomId", sto->lastRoomId);
+ GWEN_XMLNode_SetIntValue(nLastIds, "lastDeviceId", sto->lastDeviceId);
+ GWEN_XMLNode_SetIntValue(nLastIds, "lastTopicId", sto->lastTopicId);
+ GWEN_XMLNode_SetIntValue(nLastIds, "lastValueId", sto->lastValueId);
+
+ GWEN_XMLNode_AddChild(rootNode, nLastIds);
+}
+
+
+
+void _writeRoomsToXml(const AQH_STORAGE *sto, GWEN_XMLNODE *rootNode)
+{
+ GWEN_XMLNODE *nElems;
+ AQH_ROOM *elem;
+
+ nElems=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, AQH_STORAGE_XML_ELEMENTNAME_ROOMS);
+
+ elem=AQH_Room_List_First(sto->roomList);
+ while(elem) {
+ GWEN_XMLNODE *nElem;
+
+ nElem=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, AQH_STORAGE_XML_ELEMENTNAME_ROOM);
+ AQH_Room_toXml(elem, nElem);
+ GWEN_XMLNode_AddChild(nElems, nElem);
+ elem=AQH_Room_List_Next(elem);
+ }
+ GWEN_XMLNode_AddChild(rootNode, nElems);
+}
+
+
+
+void _writeDevicesToXml(const AQH_STORAGE *sto, GWEN_XMLNODE *rootNode)
+{
+ GWEN_XMLNODE *nElems;
+ AQH_DEVICE *elem;
+
+ nElems=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, AQH_STORAGE_XML_ELEMENTNAME_DEVICES);
+
+ elem=AQH_Device_List_First(sto->roomList);
+ while(elem) {
+ GWEN_XMLNODE *nElem;
+
+ nElem=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, AQH_STORAGE_XML_ELEMENTNAME_DEVICE);
+ AQH_Device_toXml(elem, nElem);
+ GWEN_XMLNode_AddChild(nElems, nElem);
+ elem=AQH_Device_List_Next(elem);
+ }
+ GWEN_XMLNode_AddChild(rootNode, nElems);
+}
+
+
+
+void _writeTopicsToXml(const AQH_STORAGE *sto, GWEN_XMLNODE *rootNode)
+{
+ GWEN_XMLNODE *nElems;
+ AQH_MQTT_TOPIC *elem;
+
+ nElems=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, AQH_STORAGE_XML_ELEMENTNAME_TOPICS);
+
+ elem=AQH_MqttTopic_List_First(sto->mqttTopicList);
+ while(elem) {
+ GWEN_XMLNODE *nElem;
+
+ nElem=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, AQH_STORAGE_XML_ELEMENTNAME_TOPIC);
+ AQH_MqttTopic_toXml(elem, nElem);
+ GWEN_XMLNode_AddChild(nElems, nElem);
+ elem=AQH_MqttTopic_List_Next(elem);
+ }
+ GWEN_XMLNode_AddChild(rootNode, nElems);
+}
+
+
+
+void _writeValuesToXml(const AQH_STORAGE *sto, GWEN_XMLNODE *rootNode)
+{
+ GWEN_XMLNODE *nElems;
+ AQH_VALUE *elem;
+
+ nElems=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, AQH_STORAGE_XML_ELEMENTNAME_VALUES);
+
+ elem=AQH_Value_List_First(sto->valueList);
+ while(elem) {
+ GWEN_XMLNODE *nElem;
+
+ nElem=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, AQH_STORAGE_XML_ELEMENTNAME_VALUE);
+ AQH_Value_toXml(elem, nElem);
+ GWEN_XMLNode_AddChild(nElems, nElem);
+ elem=AQH_Value_List_Next(elem);
+ }
+ GWEN_XMLNode_AddChild(rootNode, nElems);
+}
+
+
+
+
+
+
+
+
+
+
diff --git a/aqhome/data/storage_writexml.h b/aqhome/data/storage_writexml.h
new file mode 100644
index 0000000..246f865
--- /dev/null
+++ b/aqhome/data/storage_writexml.h
@@ -0,0 +1,24 @@
+/****************************************************************************
+ * This file is part of the project Gwenhywfar.
+ * Gwenhywfar (c) by 2023 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifndef AQH_STORAGE_WRITEXML_H
+#define AQH_STORAGE_WRITEXML_H
+
+
+#include "aqhome/data/storage.h"
+
+
+
+int AQH_Storage_WriteStateFile(const AQH_STORAGE *sto, const char *sFilename);
+
+
+
+
+#endif
+
+
diff --git a/aqhome/http/0BUILD b/aqhome/http/0BUILD
index a12c16e..f8acc58 100644
--- a/aqhome/http/0BUILD
+++ b/aqhome/http/0BUILD
@@ -46,11 +46,13 @@
endpoint_http.h
+ urlhandler.h
endpoint_http_p.h
+ urlhandler_p.h
@@ -58,6 +60,7 @@
$(local/typefiles)
endpoint_http.c
+ urlhandler.c
diff --git a/aqhome/http/urlhandler.c b/aqhome/http/urlhandler.c
new file mode 100644
index 0000000..0e226d1
--- /dev/null
+++ b/aqhome/http/urlhandler.c
@@ -0,0 +1,96 @@
+/****************************************************************************
+ * This file is part of the project AqHome.
+ * AqHome (c) by 2023 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include
+#endif
+
+
+#include "./urlhandler_p.h"
+
+#include
+#include
+#include
+
+
+
+
+GWEN_INHERIT_FUNCTIONS(AQH_URLHANDLER)
+GWEN_LIST_FUNCTIONS(AQH_URLHANDLER, AQH_UrlHandler)
+
+
+
+
+
+AQH_URLHANDLER *AQH_UrlHandler_new(void)
+{
+ AQH_URLHANDLER *uh;
+
+ GWEN_NEW_OBJECT(AQH_URLHANDLER, uh);
+ GWEN_INHERIT_INIT(AQH_URLHANDLER, uh);
+ uh->urlPatternList=GWEN_StringList_new();
+
+ return uh;
+}
+
+
+
+void AQH_UrlHandler_free(AQH_URLHANDLER *uh)
+{
+ if (uh) {
+ GWEN_INHERIT_FINI(AQH_URLHANDLER, uh);
+ GWEN_StringList_free(uh->urlPatternList);
+ GWEN_FREE_OBJECT(uh);
+ }
+}
+
+
+
+void AQH_UrlHandler_AddUrlPattern(AQH_URLHANDLER *uh, const char *s)
+{
+ if (uh && s && *s)
+ GWEN_StringList_AppendString(uh->urlPatternList, s, 0, 1);
+}
+
+
+
+int AQH_UrlHandler_UrlMatches(const AQH_URLHANDLER *uh, const char *s)
+{
+ if (uh && s && *s) {
+ GWEN_STRINGLISTENTRY *se;
+
+ se=GWEN_StringList_FirstEntry(uh->urlPatternList);
+ while(se) {
+ const char *pattern;
+
+ pattern=GWEN_StringListEntry_Data(se);
+ if (GWEN_Text_ComparePattern(s, pattern, 0)!=-1)
+ return 1;
+ se=GWEN_StringListEntry_Next(se);
+ }
+ }
+ return 0;
+}
+
+
+
+GWEN_MSG *AQH_UrlHandler_HandleMessage(AQH_URLHANDLER *uh, GWEN_MSG_ENDPOINT *ep, const GWEN_MSG *msgReceived)
+{
+ if (uh && uh->handleFn)
+ return uh->handleFn(uh, ep, msgReceived);
+ return NULL;
+}
+
+
+
+
+
+
+
+
+
diff --git a/aqhome/http/urlhandler.h b/aqhome/http/urlhandler.h
new file mode 100644
index 0000000..4386ca4
--- /dev/null
+++ b/aqhome/http/urlhandler.h
@@ -0,0 +1,43 @@
+/****************************************************************************
+ * This file is part of the project AqHome.
+ * AqHome (c) by 2023 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifndef AQHOME_URLHANDLER_H
+#define AQHOME_URLHANDLER_H
+
+#include
+
+#include
+#include
+#include
+#include
+
+
+
+typedef struct AQH_URLHANDLER AQH_URLHANDLER;
+GWEN_INHERIT_FUNCTION_LIB_DEFS(AQH_URLHANDLER, AQHOME_API)
+GWEN_LIST_FUNCTION_LIB_DEFS(AQH_URLHANDLER, AQH_UrlHandler, AQHOME_API)
+
+
+
+typedef GWEN_MSG*(*AQH_URLHANDLER_HANDLE_FN)(AQH_URLHANDLER *uh, GWEN_MSG_ENDPOINT *ep, const GWEN_MSG *msgReceived);
+
+
+AQHOME_API AQH_URLHANDLER *AQH_UrlHandler_new(void);
+AQHOME_API void AQH_UrlHandler_free(AQH_URLHANDLER *uh);
+
+AQHOME_API void AQH_UrlHandler_AddUrlPattern(AQH_URLHANDLER *uh, const char *s);
+AQHOME_API int AQH_UrlHandler_UrlMatches(const AQH_URLHANDLER *uh, const char *s);
+
+AQHOME_API GWEN_MSG *AQH_UrlHandler_HandleMessage(AQH_URLHANDLER *uh, GWEN_MSG_ENDPOINT *ep, const GWEN_MSG *msgReceived);
+
+
+
+
+
+
+#endif
diff --git a/aqhome/http/urlhandler_p.h b/aqhome/http/urlhandler_p.h
new file mode 100644
index 0000000..6177e7d
--- /dev/null
+++ b/aqhome/http/urlhandler_p.h
@@ -0,0 +1,27 @@
+/****************************************************************************
+ * This file is part of the project AqHome.
+ * AqHome (c) by 2023 Martin Preuss, all rights reserved.
+ *
+ * The license for this file can be found in the file COPYING which you
+ * should have received along with this file.
+ ****************************************************************************/
+
+#ifndef AQHOME_URLHANDLER_P_H
+#define AQHOME_URLHANDLER_P_H
+
+
+#include "aqhome/http/urlhandler.h"
+
+
+struct AQH_URLHANDLER {
+ GWEN_INHERIT_ELEMENT(AQH_URLHANDLER);
+ GWEN_LIST_ELEMENT(AQH_URLHANDLER);
+
+ GWEN_STRINGLIST *urlPatternList;
+ AQH_URLHANDLER_HANDLE_FN handleFn;
+};
+
+
+
+
+#endif