Files
aqhomecontrol/apps/aqhome-cgi/modules/devices/mdevices_page2.c
2026-08-31 12:18:46 +02:00

572 lines
16 KiB
C

/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2026 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 <config.h>
#endif
#include "./mdevices_page2.h"
#include "./mdevices_page2_graph.h"
#include "./mdevices_page2_actor.h"
#include "aqhome-cgi/modules/mdataclient.h"
#include "aqhome-cgi/modules/devices/mdevices_pagectx.h"
#include <aqcgi/service/module.h>
#include <aqdiagram/graph/timegraph.h>
#include <aqdiagram/graph/w_graph.h>
#include <aqdiagram/draw/context_cairo.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/timestamp.h>
#include <gwenhywfar/text.h>
#include <gwenhywfar/directory.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
/* ------------------------------------------------------------------------------------------------
* defs and enums
* ------------------------------------------------------------------------------------------------
*/
#define GBAS GWEN_Buffer_AppendString
#define GBAA GWEN_Buffer_AppendArgs
#define DOC_PAGE2 "page.html"
#define FOLDER_PAGES "pages2"
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static void _sendPageList(AQCGI_MODULE *m, GWEN_BUFFER *dbuf);
static GWEN_STRINGLIST *_listPageFiles(AQCGI_MODULE *m);
static GWEN_XMLNODE *_readPage(AQCGI_MODULE *m, const char *sPageName);
static GWEN_XMLNODE *_readPageFile(const char *sFilename);
static void _writePage(AQCGI_MODULE *m, AQH_DATACLIENT *dc, GWEN_XMLNODE *nPage, GWEN_BUFFER *dbuf);
static void _writePageChildElements(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *n, GWEN_BUFFER *dbuf);
static void _writeHtmlElement(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *nElement, GWEN_BUFFER *dbuf);
static void _writePageElement(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *n, GWEN_BUFFER *dbuf);
static void _writePageForEveryValue(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *nElement, GWEN_BUFFER *dbuf);
static void _writePageDataSet(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *nElement, GWEN_BUFFER *dbuf);
static void _writePageItem(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *nElement, GWEN_BUFFER *dbuf);
static AQH_MD_PAGECTX *_createInitialCtx(AQCGI_MODULE *m, AQH_DATACLIENT *dc);
/* ------------------------------------------------------------------------------------------------
* code
* ------------------------------------------------------------------------------------------------
*/
void AQH_ModDevices_RunPage2Get(AQCGI_MODULE *m, AQCGI_REQUEST *rq,
GWEN_UNUSED AQCGI_SESSION *session,
AQH_DATACLIENT *dc, GWEN_BUFFER *dbuf)
{
GWEN_DB_NODE *dbQuery;
const char *sPageId;
DBG_INFO(NULL, "RunPage2Get");
dbQuery=AQCGI_Request_GetDbQuery(rq);
sPageId=dbQuery?GWEN_DB_GetCharValue(dbQuery, "page", 0, NULL):NULL;
if (sPageId && *sPageId) {
GWEN_XMLNODE *fileNode;
DBG_INFO(NULL, "Reading page \"%s\"", sPageId);
fileNode=_readPage(m, sPageId);
if (fileNode) {
GWEN_XMLNODE *nPage;
nPage=GWEN_XMLNode_FindFirstTag(fileNode, "page", NULL, NULL);
if (nPage) {
_writePage(m, dc, nPage, dbuf);
//AQCGI_Request_AddResponseHeaderData(rq, "Refresh: 120");
}
else {
DBG_ERROR(NULL, "No page element in file for \"%s\"", sPageId);
}
GWEN_XMLNode_free(fileNode);
}
else {
DBG_ERROR(NULL, "here");
}
}
else {
DBG_INFO(NULL, "Reading page list");
_sendPageList(m, dbuf);
}
}
void AQH_ModDevices_RunPage2Graph(AQCGI_MODULE *m, AQCGI_REQUEST *rq,
GWEN_UNUSED AQCGI_SESSION *session,
AQH_DATACLIENT *dc, GWEN_BUFFER *dbuf)
{
GWEN_DB_NODE *dbQuery;
const char *sPageId;
const char *sGraphId;
DBG_INFO(NULL, "RunPageGraphGet");
dbQuery=AQCGI_Request_GetDbQuery(rq);
sPageId=GWEN_DB_GetCharValue(dbQuery, "page", 0, NULL);
sGraphId=GWEN_DB_GetCharValue(dbQuery, "graph", 0, NULL);
if (sPageId && *sPageId && sGraphId && *sGraphId)
AQH_ModDevices_HandleGraph(m, rq, dc, sPageId, sGraphId, dbuf);
}
void AQH_ModDevices_RunPage2Post(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, AQH_DATACLIENT *dc, GWEN_BUFFER *dbuf)
{
GWEN_DB_NODE *dbPost;
const char *sPageId;
const char *sActorId;
DBG_INFO(NULL, "RunPagePost");
dbPost=AQCGI_Request_GetDbPostBody(rq);
sPageId=GWEN_DB_GetCharValue(dbPost, "page", 0, NULL);
sActorId=GWEN_DB_GetCharValue(dbPost, "actor", 0, NULL);
if (sPageId && *sPageId && sActorId && *sActorId) {
GWEN_XMLNODE *fileNode;
fileNode=_readPage(m, sPageId);
if (fileNode) {
GWEN_XMLNODE *nPage;
GWEN_XMLNODE *nActor;
nPage=GWEN_XMLNode_FindFirstTag(fileNode, "page", "id", sPageId);
nActor=AQH_ModDevices_FindXmlElement(nPage, "actor", "id", sActorId);
if (nPage && nActor) {
AQH_MD_PAGECTX *ctx;
ctx=_createInitialCtx(m, dc);
AQH_ModDevices_HandlePageActor(ctx, rq, sActorId, nActor);
AQH_MDPageCtx_free(ctx);
}
else {
DBG_ERROR(NULL, "Actor %s/%s not found", sPageId, sActorId);
}
GWEN_XMLNode_free(fileNode);
}
else {
DBG_INFO(NULL, "here");
}
}
AQH_ModDevices_RedirectToPage(rq, DOC_PAGE2, sPageId);
}
void AQH_ModDevices_SetCharValue(GWEN_DB_NODE *dbDest, uint32_t flags, const char *sVar, const char *sValue, GWEN_DB_NODE *dbContext)
{
if (sValue && *sValue) {
GWEN_BUFFER *tbuf;
int rv;
tbuf=GWEN_Buffer_new(0, 256, 0, 1);
rv=GWEN_DB_ReplaceVars(dbContext, sValue, tbuf);
if (rv<0) {
DBG_ERROR(NULL, "Error replacing vars for %s=[%s]", sVar, sValue);
}
else {
/*DBG_ERROR(NULL, "Setting value [%s]=[%s] (%s)", sVar, GWEN_Buffer_GetStart(tbuf), sValue);*/
GWEN_DB_SetCharValue(dbDest, flags, sVar, GWEN_Buffer_GetStart(tbuf));
}
GWEN_Buffer_free(tbuf);
}
else
GWEN_DB_DeleteVar(dbDest, sVar);
}
void AQH_ModDevices_SetupDbForDeviceAndValue(const AQH_VALUE *v, GWEN_DB_NODE *dbCtx)
{
GWEN_DB_NODE *db;
AQH_DEVICE *device;
db=GWEN_DB_GetGroup(dbCtx, GWEN_DB_FLAGS_OVERWRITE_GROUPS, "value");
AQH_Value_toDb(v, db);
device=AQH_Value_GetDevicePtr(v);
if (device) {
const char *sNameForSystem;
const char *sLocation;
const char *sDescription;
GWEN_BUFFER *tbuf;
db=GWEN_DB_GetGroup(dbCtx, GWEN_DB_FLAGS_OVERWRITE_GROUPS, "device");
AQH_Device_toDb(device, db);
/* generate descriptive name */
sNameForSystem=AQH_Device_GetNameForSystem(device);
sLocation=AQH_Device_GetLocation(device);
sDescription=AQH_Device_GetDescription(device);
tbuf=GWEN_Buffer_new(0, 256, 0, 1);
if (sLocation && sDescription)
GBAA(tbuf, "%s - %s", sDescription, sLocation);
else if (sLocation)
GBAA(tbuf, "%s", sLocation);
else if (sDescription)
GBAA(tbuf, "%s", sDescription);
else
GBAA(tbuf, "%s", sNameForSystem);
GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "descriptiveName", GWEN_Buffer_GetStart(tbuf));
GWEN_Buffer_free(tbuf);
}
else {
DBG_ERROR(NULL, "No device pointer in device %s", AQH_Value_GetNameForSystem(v));
}
}
// TODO: sort
void _sendPageList(AQCGI_MODULE *m, GWEN_BUFFER *dbuf)
{
GWEN_STRINGLIST *sl;
GBAS(dbuf, "<h1>Page List</h1>\n");
sl=_listPageFiles(m);
if (sl) {
GWEN_STRINGLISTENTRY *se;
GWEN_StringList_Sort(sl, 1, GWEN_StringList_SortModeNoCase);
GBAS(dbuf,
"<table class=\"datatable\">\n"
"<thead><tr><th>Page</th><th>Title</th></tr></thead>\n"
"<tbody>\n");
se=GWEN_StringList_FirstEntry(sl);
while(se) {
const char *filename;
filename=GWEN_StringListEntry_Data(se);
if (filename && *filename) {
GWEN_XMLNODE *node;
DBG_INFO(NULL, "Reading file \"%s\"", filename);
node=_readPageFile(filename);
if (node) {
GWEN_XMLNODE *nPage;
nPage=GWEN_XMLNode_FindFirstTag(node, "page", NULL, NULL);
if (nPage) {
const char *sId;
const char *sTitle;
sId=GWEN_XMLNode_GetProperty(nPage, "id", NULL);
sTitle=GWEN_XMLNode_GetProperty(nPage, "title", sId);
if (sId && *sId)
GBAA(dbuf, "<tr><td><a href=\""DOC_PAGE2"?page=%s\">%s</a></td><td>%s</td></tr>\n", sId, sId, sTitle);
}
GWEN_XMLNode_free(node);
}
}
se=GWEN_StringListEntry_Next(se);
}
GBAS(dbuf, "</tbody></table>");
GWEN_StringList_free(sl);
}
else {
GBAS(dbuf, "No pages.");
}
}
GWEN_STRINGLIST *_listPageFiles(AQCGI_MODULE *m)
{
GWEN_BUFFER *fbuf;
AQCGI_SERVICE *sv;
GWEN_STRINGLIST *sl;
int rv;
sv=AQH_ModService_GetService(m);
fbuf=GWEN_Buffer_new(0, 256, 0, 1);
GBAA(fbuf, "%s%s"FOLDER_PAGES, AQCGI_Service_GetRuntimeFolder(sv), GWEN_DIR_SEPARATOR_S);
sl=GWEN_StringList_new();
rv=GWEN_Directory_GetMatchingFilesRecursively(GWEN_Buffer_GetStart(fbuf), sl, "*.xml");
if (rv<0) {
DBG_INFO(NULL, "Error reading pages (%d)", rv);
GWEN_StringList_free(sl);
GWEN_Buffer_free(fbuf);
return NULL;
}
if (GWEN_StringList_Count(sl)<1) {
GWEN_StringList_free(sl);
GWEN_Buffer_free(fbuf);
return NULL;
}
GWEN_Buffer_free(fbuf);
return sl;
}
GWEN_XMLNODE *_readPage(AQCGI_MODULE *m, const char *sPageName)
{
GWEN_BUFFER *fbuf;
AQCGI_SERVICE *sv;
GWEN_XMLNODE *fileNode;
sv=AQH_ModService_GetService(m);
fbuf=GWEN_Buffer_new(0, 256, 0, 1);
GBAA(fbuf, "%s%s"FOLDER_PAGES"%s", AQCGI_Service_GetRuntimeFolder(sv), GWEN_DIR_SEPARATOR_S, GWEN_DIR_SEPARATOR_S);
AQH_ModService_EscapeToBuffer(sPageName, fbuf);
GBAS(fbuf, ".xml");
fileNode=_readPageFile(GWEN_Buffer_GetStart(fbuf));
if (fileNode==NULL) {
DBG_INFO(NULL, "here");
GWEN_Buffer_free(fbuf);
return NULL;
}
GWEN_Buffer_free(fbuf);
return fileNode;
}
GWEN_XMLNODE *_readPageFile(const char *sFilename)
{
GWEN_XMLNODE *fileNode;
int rv;
fileNode=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, sFilename);
rv=GWEN_XML_ReadFile(fileNode, sFilename, GWEN_XML_FLAGS_DEFAULT);
if (rv<0) {
DBG_ERROR(NULL, "Error reading \"%s\": %s (%d)", sFilename?sFilename:"<no name>", strerror(errno), errno);
GWEN_XMLNode_free(fileNode);
return NULL;
}
return fileNode;
}
void _writePage(AQCGI_MODULE *m, AQH_DATACLIENT *dc, GWEN_XMLNODE *nPage, GWEN_BUFFER *dbuf)
{
const char *sPageId;
const char *s;
AQH_MD_PAGECTX *ctx;
ctx=_createInitialCtx(m, dc);
sPageId=GWEN_XMLNode_GetProperty(nPage, "id", NULL);
/* title */
s=GWEN_XMLNode_GetProperty(nPage, "title", NULL);
if (s && *s)
GBAA(dbuf, "<h1>%s</h1>\n", s);
_writePageChildElements(ctx, sPageId, nPage, dbuf);
AQH_MDPageCtx_free(ctx);
}
void _writePageChildElements(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *nElement, GWEN_BUFFER *dbuf)
{
GWEN_XMLNODE *n;
n=GWEN_XMLNode_GetChild(nElement);
while(n) {
_writePageElement(ctx, sPageId, n, dbuf);
n=GWEN_XMLNode_Next(n);
}
}
void _writePageElement(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *nElement, GWEN_BUFFER *dbuf)
{
if (GWEN_XMLNode_GetType(nElement)==GWEN_XMLNodeTypeTag) {
const char *sElementName;
sElementName=GWEN_XMLNode_GetData(nElement);
if (sElementName && *sElementName) {
/* DBG_INFO(NULL, "Reading element \"%s\"", sElementName); */
if (strcasecmp(sElementName, "dataset")==0)
_writePageDataSet(ctx, sPageId, nElement, dbuf);
else if (strcasecmp(sElementName, "item")==0)
_writePageItem(ctx, sPageId, nElement, dbuf);
else if (strcasecmp(sElementName, "graph")==0)
AQH_ModDevices_PageWriteGraph(ctx, sPageId, nElement, dbuf);
else if (strcasecmp(sElementName, "actor")==0)
AQH_ModDevices_PageWriteActor(ctx, sPageId, nElement, dbuf);
// else if (strcasecmp(sName, "valueTable")==0)
// _writePageValueTable(ctx, sPageId, n, dbuf);
else if (strcasecmp(sElementName, "forEveryValue")==0)
_writePageForEveryValue(ctx, sPageId, nElement, dbuf);
else if (strcasecmp(sElementName, "table")==0 ||
strcasecmp(sElementName, "tr")==0 ||
strcasecmp(sElementName, "td")==0 ||
strcasecmp(sElementName, "p")==0)
_writeHtmlElement(ctx, sPageId, nElement, dbuf);
/* DBG_INFO(NULL, "Done with element \"%s\"", sElementName); */
}
}
else if (GWEN_XMLNode_GetType(nElement)==GWEN_XMLNodeTypeData) {
const char *s;
/* copy data after stripping unnecessary blanks */
s=GWEN_XMLNode_GetData(nElement);
if (s && *s) {
GWEN_BUFFER *tbuf;
tbuf=GWEN_Buffer_new(0, 256, 0, 1);
GWEN_DB_ReplaceVars(AQH_MDPageCtx_GetDbVars(ctx), s, tbuf);
GWEN_Text_CondenseBuffer(tbuf);
GBAS(dbuf, GWEN_Buffer_GetStart(tbuf));
GWEN_Buffer_free(tbuf);
}
}
}
void _writeHtmlElement(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *nElement, GWEN_BUFFER *dbuf)
{
const char *sElementName;
sElementName=GWEN_XMLNode_GetData(nElement);
if (sElementName) { /* ugly hack because GWEN_XML does not expose raw attributes */
GWEN_XMLNODE *newRoot;
GWEN_XMLNODE *newElement;
GWEN_BUFFER *tbuf;
char *p;
newRoot=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, "root");
newElement=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, sElementName);
GWEN_XMLNode_AddChild(newRoot, newElement);
GWEN_XMLNode_CopyProperties(newElement, nElement, 0);
GWEN_XMLNode_ExpandProperties(newElement, AQH_MDPageCtx_GetDbVars(ctx));
tbuf=GWEN_Buffer_new(0, 256, 0, 1);
GWEN_XMLNode_toBuffer(newRoot, tbuf, GWEN_XML_FLAGS_SIMPLE);
p=strchr(GWEN_Buffer_GetStart(tbuf), '>');
if (p && *p) {
p[1]=0;
GBAS(dbuf, GWEN_Buffer_GetStart(tbuf));
}
GWEN_Buffer_free(tbuf);
}
_writePageChildElements(ctx, sPageId, nElement, dbuf);
GBAA(dbuf, "</%s>", sElementName);
}
void _writePageForEveryValue(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *nElement, GWEN_BUFFER *dbuf)
{
AQH_VALUE_LIST *valueList;
valueList=AQH_MDPageCtx_GetValues(ctx);
if (valueList) {
const AQH_VALUE *v;
GWEN_DB_NODE *dbCtx;
dbCtx=AQH_MDPageCtx_GetDbVars(ctx);
v=AQH_Value_List_First(valueList);
while(v) {
DBG_INFO(NULL, "Writing for value %s", AQH_Value_GetNameForSystem(v));
AQH_ModDevices_SetupDbForDeviceAndValue(v, dbCtx);
_writePageChildElements(ctx, sPageId, nElement, dbuf);
v=AQH_Value_List_Next(v);
}
}
else {
DBG_ERROR(NULL, "No values");
}
}
void _writePageDataSet(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *nElement, GWEN_BUFFER *dbuf)
{
const char *sRoom;
const char *sVarType;
const char *sModality;
const char *sVarName;
const char *sLastChange;
int modality;
int varType;
uint64_t lastChange;
AQH_MD_PAGECTX *newCtx;
/* prepare new context */
sVarType=GWEN_XMLNode_GetProperty(nElement, "vartype", NULL);
sRoom=GWEN_XMLNode_GetProperty(nElement, "room", NULL);
sModality=GWEN_XMLNode_GetProperty(nElement, "modality", NULL);
sVarName=GWEN_XMLNode_GetProperty(nElement, "varname", NULL);
sLastChange=GWEN_XMLNode_GetProperty(nElement, "lastChange", NULL);
modality=sModality?AQH_ValueModality_fromString(sModality):AQH_ValueModality_Unknown;
varType=sVarType?AQH_ValueType_fromString(sVarType):AQH_ValueType_Sensor;
lastChange=sLastChange?AQH_ParseTime(sLastChange):0;
newCtx=AQH_MDPageCtx_CopyWithMatchingVars(ctx, varType, sRoom, modality, sVarName, lastChange);
/* enter context */
_writePageChildElements(newCtx, sPageId, nElement, dbuf);
/* free new context */
AQH_MDPageCtx_free(newCtx);
}
void _writePageItem(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *nElement, GWEN_BUFFER *dbuf)
{
const char *sTitle;
/* write title */
sTitle=GWEN_XMLNode_GetProperty(nElement, "title", NULL);
if (sTitle && *sTitle)
GBAA(dbuf, "<h2>%s</h2>\n", sTitle);
_writePageChildElements(ctx, sPageId, nElement, dbuf);
}
AQH_MD_PAGECTX *_createInitialCtx(AQCGI_MODULE *m, AQH_DATACLIENT *dc)
{
AQH_DEVICE_LIST *deviceList;
AQH_VALUE_LIST *valueList;
AQH_MD_PAGECTX *ctx;
AQH_ModDevices_ReadDevicesAndValues(m, dc);
deviceList=AQH_ModDevices_GetDeviceList(m);
valueList=AQH_ModDevices_GetValueList(m);
ctx=AQH_MDPageCtx_new(m, dc);
AQH_MDPageCtx_SetDevices(ctx, deviceList);
if (valueList)
AQH_MDPageCtx_SetValues(ctx, AQH_Value_List_dup(valueList));
return ctx;
}