Compare commits

...

42 commits

Author SHA1 Message Date
173b9d41e8
Add weekday query parameter for macro execute 2024-04-16 23:08:15 +02:00
ea6b653121
Merge branch 'main' into testing 2024-04-16 16:32:14 +02:00
aa3253b190
Fix minor issues 2024-04-16 16:31:18 +02:00
8c5ad5a374 Add minor testing stuff 2020-12-31 03:18:08 +01:00
b2b71d6742 Bump version to 0.4.4 2020-12-31 01:06:05 +01:00
20f80a223f Fix fatal bug 2020-12-31 00:12:57 +01:00
3c04173fbf Bump version to 0.4.3 2020-11-22 01:52:14 +01:00
87b3f06092 Add configs for toml in controller 2020-11-22 01:49:20 +01:00
750002daf2 Bump version to 0.4.2 2020-11-21 15:41:39 +01:00
e2bb965cc0 Fix dropping system C_FLAGS 2020-11-21 15:38:04 +01:00
19a4a14cb9 Bump version to v0.4.1 2020-11-19 23:42:06 +01:00
1f315d2b92 Remove libbsd and strlcpy 2020-11-19 23:36:02 +01:00
3f11431df6 Cleanup logging, comments and files 2020-11-19 18:36:03 +01:00
2fe784dd78 Try fixing the gitea_release drone part by tile substitution 2020-11-19 16:56:34 +01:00
688e2e71ee Try fixing the gitea_release drone part 2020-11-19 16:44:00 +01:00
a3ce2bfac9 Bump version to 0.4.0 2020-11-19 16:26:29 +01:00
1bbaeacc84 Add simple test for conf.d 2020-11-19 13:37:26 +01:00
ce59bb364f Fix missing path concatination 2020-11-19 01:25:52 +01:00
0c13c03a73 Improve config behaviour 2020-11-19 00:59:26 +01:00
5796f88e05 Replace ini with toml 2020-11-17 23:13:01 +01:00
97a19135ce Fix leaks and remove some non-standard functions 2020-11-15 16:36:44 +01:00
c7cafa94c4 Fix more clang-tidy warnings 2020-11-14 00:34:20 +01:00
5747a65664 Fix wrong size in strlcpy 2020-11-14 00:04:14 +01:00
1d6e8ff037 Add text responses as json with msg key 2020-11-13 23:20:07 +01:00
f97b149376 Add clang-tidy target and fix problems 2020-11-13 13:41:47 +01:00
fca35ade9e Fix config handling 2020-11-12 22:49:38 +01:00
fad3d80f39 Fix the testing dir handling
Now the controller will be kept, and database and logs will be saved.
2020-11-12 00:24:12 +01:00
ead52a0f47 Fix config string handling 2020-11-04 23:03:33 +01:00
b7d3f2ad0b Rename config files and database 2020-11-04 23:03:05 +01:00
78f98010aa Upgrade Doxyfile and add more ignores 2020-10-21 10:02:43 +02:00
22b8ff4d6a Fix working dir to execute in source/root directory 2020-09-27 00:36:27 +02:00
011b9f4c14 Fix cmake to not break gdb 2020-09-27 00:05:48 +02:00
b38c3d61d8 fix: controller discovery now called by PUT 2020-09-06 13:19:10 +02:00
4b39631765 add: bump version 2020-09-06 13:14:19 +02:00
d3fd48b35a add: macro endpoint for execution 2020-09-06 13:12:33 +02:00
01ffb1d58d add: response macros
add: preparation for more macro endpoints
2020-09-05 13:29:46 +02:00
9d2c48d645 add: macro endpoints
add: basic macro support
fix: database locking with lock-pointer
fix: memory leaks
2020-09-04 00:28:49 +02:00
6a2b94ef1c fix: allow program to compile 2020-08-30 17:22:51 +02:00
0103b0b2ff add: more basic macro stuff
add: more efficient relay loading (only ids)
2020-08-29 23:51:45 +02:00
f67b7e9e0f add: basic macro functions
add: better migration handling (transactions)
2020-08-29 22:58:02 +02:00
7275d66c86 fix: tests had a dependency on emgauwa-webapp 2020-08-29 09:52:49 +02:00
4dd8329484 add: more tests 2020-08-29 09:10:50 +02:00
83 changed files with 5367 additions and 6683 deletions

2
.clang-tidy Normal file
View file

@ -0,0 +1,2 @@
---
Checks: 'clang-diagnostics-*,clang-analyzer-*,linuxkernel-*,modernize-*,readability-*,-readability-magic-numbers,portability-*'

View file

@ -53,9 +53,8 @@ steps:
api_key:
from_secret: gitea_token
base_url: https://git.serguzim.me
files:
- build/core
- build/core.ini
title: ${DRONE_TAG}
when:
event: tag

View file

@ -10,3 +10,7 @@ end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
[*.yml]
indent_style = space
indent_size = 2

1
.envrc Normal file
View file

@ -0,0 +1 @@
use nix

8
.gitignore vendored
View file

@ -1,7 +1,11 @@
build/
docs/
tests/testing_latest/
tests/testing_bak/
tests/testing/
include/sql/*.h
emgauwa-core.conf.d
emgauwa-core.sqlite
vgcore.*

View file

@ -1,6 +1,6 @@
cmake_minimum_required (VERSION 3.7)
project(core
VERSION 0.3.4
VERSION 0.4.5
LANGUAGES C)
add_executable(core src/main.c)
@ -8,11 +8,18 @@ add_executable(core src/main.c)
target_link_libraries(core -lsqlite3)
target_link_libraries(core -luuid)
set(CMAKE_C_FLAGS "$ENV{CFLAGS}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -std=gnu99 -Wpedantic -Werror -Wall -Wextra -ffile-prefix-map=${CMAKE_SOURCE_DIR}/src/=")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} $ENV{CFLAGS}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D'__FILENAME__=\"$(subst $(realpath ${CMAKE_SOURCE_DIR}/src/)/,,$(abspath $<))\"'")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -std=gnu99 -Wpedantic -Werror -Wall -Wextra")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -fprofile-arcs -ftest-coverage")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
file(GLOB_RECURSE ALL_SOURCE_FILES src/*.c)
add_definitions("-DMG_ENABLE_EXTRA_ERRORS_DESC -DMG_ENABLE_MQTT_BROKER")
aux_source_directory(src/ SRC_DIR)
@ -24,7 +31,6 @@ aux_source_directory(vendor VENDOR_SRC)
add_dependencies(core sql)
configure_file("core.ini" "core.ini" COPYONLY)
configure_file("version.h.in" "version.h" @ONLY)
@ -39,27 +45,8 @@ add_custom_target(sql
)
add_custom_target(run
COMMAND ./core start
COMMAND ${CMAKE_BINARY_DIR}/core
DEPENDS core
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_custom_target(debug
COMMAND valgrind -s ./core start
DEPENDS core
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_custom_target(debug-leak
COMMAND valgrind --leak-check=full --show-leak-kinds=all ./core start
DEPENDS core
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_custom_target(debug-callgrind
COMMAND valgrind --tool=callgrind ./core start
DEPENDS core
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_custom_target(docs
COMMAND doxygen
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
@ -68,13 +55,57 @@ add_custom_target(test
DEPENDS core
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
)
add_custom_target(test-callgrind
COMMAND ./run_tests.sh ${CMAKE_BINARY_DIR}/core "--tool=callgrind"
add_custom_target(docs
COMMAND doxygen
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
IF(CMAKE_BUILD_TYPE MATCHES Debug)
message(STATUS "loading debug targets")
add_custom_target(debug
COMMAND gdb ${CMAKE_BINARY_DIR}/core
DEPENDS core
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
add_custom_target(valgrind
COMMAND valgrind -s ${CMAKE_BINARY_DIR}/core
DEPENDS core
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
add_custom_target(valgrind-leak
COMMAND valgrind --leak-check=full --show-leak-kinds=all ${CMAKE_BINARY_DIR}/core
DEPENDS core
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
add_custom_target(valgrind-callgrind
COMMAND valgrind --tool=callgrind ${CMAKE_BINARY_DIR}/core
DEPENDS core
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
add_custom_target(coverage
COMMAND gcovr -s --root ${CMAKE_SOURCE_DIR} -e ${CMAKE_SOURCE_DIR}/vendor --html-details ${CMAKE_BINARY_DIR}/coverage.html --html-title "Emgauwa Core Coverage" ${CMAKE_BINARY_DIR}/CMakeFiles/core.dir
DEPENDS test
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
add_custom_target(test-callgrind
COMMAND ./run_tests.sh ${CMAKE_BINARY_DIR}/core "--tool=callgrind"
DEPENDS core
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
)
add_custom_target(
clang-tidy
COMMAND /usr/bin/clang-tidy
${ALL_SOURCE_FILES}
--header-filter=${CMAKE_SOURCE_DIR}/include/*
--
-std=gnu99
-I${CMAKE_SOURCE_DIR}/include
-I${CMAKE_SOURCE_DIR}/vendor
-I${CMAKE_BINARY_DIR}
-DMG_ENABLE_EXTRA_ERRORS_DESC
-DMG_ENABLE_MQTT_BROKER
)
ENDIF(CMAKE_BUILD_TYPE MATCHES Debug)

View file

@ -1,4 +1,4 @@
# Doxyfile 1.8.17
# Doxyfile 1.8.20
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project.
@ -227,6 +227,14 @@ QT_AUTOBRIEF = NO
MULTILINE_CPP_IS_BRIEF = NO
# By default Python docstrings are displayed as preformatted text and doxygen's
# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the
# doxygen's special commands can be used and the contents of the docstring
# documentation blocks is shown as doxygen documentation.
# The default value is: YES.
PYTHON_DOCSTRING = YES
# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
# documentation from any documented member that it re-implements.
# The default value is: YES.
@ -263,12 +271,6 @@ TAB_SIZE = 4
ALIASES =
# This tag can be used to specify a number of word-keyword mappings (TCL only).
# A mapping has the form "name=value". For example adding "class=itcl::class"
# will allow you to use the command class in the itcl::class meaning.
TCL_SUBST =
# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
# only. Doxygen will then generate output that is more tailored for C. For
# instance, some of the names that are used will be different. The list of all
@ -310,13 +312,13 @@ OPTIMIZE_OUTPUT_SLICE = NO
# extension. Doxygen has a built-in mapping, but you can override or extend it
# using this tag. The format is ext=language, where ext is a file extension, and
# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice,
# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL,
# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
# tries to guess whether the code is fixed or free formatted code, this is the
# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat
# .inc files as Fortran files (default is PHP), and .f files as C (default is
# Fortran), use: inc=Fortran f=C.
# default for Fortran type files). For instance to make doxygen treat .inc files
# as Fortran files (default is PHP), and .f files as C (default is Fortran),
# use: inc=Fortran f=C.
#
# Note: For files without extension you can use no_extension as a placeholder.
#
@ -455,6 +457,19 @@ TYPEDEF_HIDES_STRUCT = NO
LOOKUP_CACHE_SIZE = 0
# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use
# during processing. When set to 0 doxygen will based this on the number of
# cores available in the system. You can set it explicitly to a value larger
# than 0 to get more control over the balance between CPU load and processing
# speed. At this moment only the input processing can be done using multiple
# threads. Since this is still an experimental feature the default is set to 1,
# which efficively disables parallel processing. Please report any issues you
# encounter. Generating dot graphs in parallel is controlled by the
# DOT_NUM_THREADS setting.
# Minimum value: 0, maximum value: 32, default value: 1.
NUM_PROC_THREADS = 1
#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------
@ -559,7 +574,7 @@ INTERNAL_DOCS = NO
# names in lower-case letters. If set to YES, upper-case letters are also
# allowed. This is useful if you have classes or files whose names only differ
# in case and if your file system supports case sensitive file names. Windows
# (including Cygwin) ands Mac users are advised to set this option to NO.
# (including Cygwin) and Mac users are advised to set this option to NO.
# The default value is: system dependent.
CASE_SENSE_NAMES = NO
@ -829,7 +844,8 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
INPUT = .
INPUT = src/ \
include/
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@ -853,7 +869,7 @@ INPUT_ENCODING = UTF-8
# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen
# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f, *.for, *.tcl, *.vhd,
# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
# *.vhdl, *.ucf, *.qsf and *.ice.
FILE_PATTERNS = *.c \
@ -1378,7 +1394,7 @@ CHM_FILE =
HHC_LOCATION =
# The GENERATE_CHI flag controls if a separate .chi index file is generated
# (YES) or that it should be included in the master .chm file (NO).
# (YES) or that it should be included in the main .chm file (NO).
# The default value is: NO.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
@ -1514,7 +1530,7 @@ DISABLE_INDEX = NO
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
GENERATE_TREEVIEW = NO
GENERATE_TREEVIEW = YES
# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
# doxygen will group on one line in the generated HTML documentation.
@ -1540,6 +1556,17 @@ TREEVIEW_WIDTH = 250
EXT_LINKS_IN_WINDOW = NO
# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
# the HTML output. These images will generally look nicer at scaled resolutions.
# Possible values are: png (the default) and svg (looks nicer but requires the
# pdf2svg or inkscape tool).
# The default value is: png.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_FORMULA_FORMAT = png
# Use this tag to change the font size of LaTeX formulas included as images in
# the HTML documentation. When you change the font size after a successful
# doxygen run you need to manually remove any form_*.png images from the HTML
@ -1595,7 +1622,7 @@ MATHJAX_FORMAT = HTML-CSS
# Content Delivery Network so you can quickly see the result without installing
# MathJax. However, it is strongly recommended to install a local copy of
# MathJax from https://www.mathjax.org before deployment.
# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/.
# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2.
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/
@ -1834,9 +1861,11 @@ LATEX_EXTRA_FILES =
PDF_HYPERLINKS = YES
# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
# the PDF file directly from the LaTeX files. Set this option to YES, to get a
# higher quality PDF documentation.
# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as
# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX
# files. Set this option to YES, to get a higher quality PDF documentation.
#
# See also section LATEX_CMD_NAME for selecting the engine.
# The default value is: YES.
# This tag requires that the tag GENERATE_LATEX is set to YES.
@ -2075,6 +2104,10 @@ DOCBOOK_PROGRAMLISTING = NO
GENERATE_AUTOGEN_DEF = NO
#---------------------------------------------------------------------------
# Configuration options related to Sqlite3 output
#---------------------------------------------------------------------------
#---------------------------------------------------------------------------
# Configuration options related to the Perl module output
#---------------------------------------------------------------------------
@ -2248,7 +2281,7 @@ EXTERNAL_PAGES = YES
# powerful graphs.
# The default value is: YES.
CLASS_DIAGRAMS = YES
CLASS_DIAGRAMS = NO
# You can include diagrams made with dia in doxygen documentation. Doxygen will
# then run dia to produce the diagram and insert it in the documentation. The
@ -2270,7 +2303,7 @@ HIDE_UNDOC_RELATIONS = YES
# set to NO
# The default value is: NO.
HAVE_DOT = NO
HAVE_DOT = YES
# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
# to run in parallel. When set to 0 doxygen will base this on the number of
@ -2387,7 +2420,7 @@ INCLUDED_BY_GRAPH = YES
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
CALL_GRAPH = NO
CALL_GRAPH = YES
# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
# dependency graph for every global function or class method.
@ -2399,7 +2432,7 @@ CALL_GRAPH = NO
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
CALLER_GRAPH = NO
CALLER_GRAPH = YES
# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
# hierarchy of all classes instead of a textual one.

View file

@ -1,16 +0,0 @@
[core]
server-port = 5000
database = core.sqlite
content-dir = /usr/share/webapps/emgauwa
not-found-file = 404.html
not-found-file-mime = text/html
not-found-content = 404 - NOT FOUND
not-found-content-type = text/plain
: 4422 for testing; 4421 for dev-env; 4420 for testing-env; 4419 for prod-env
discovery-port = 4421
: 1886 for testing; 1885 for dev-env; 1884 for testing-env; 1883 for prod-env
mqtt-port = 1885
log-level = debug
log-file = stdout

19
emgauwa-core.conf Normal file
View file

@ -0,0 +1,19 @@
[core]
database = "emgauwa-core.sqlite"
content-dir = "/usr/share/webapps/emgauwa"
#include = "./emgauwa-core.conf.d/"
not-found-file = "404.html"
not-found-file-mime = "text/html"
not-found-content = "404 - NOT FOUND"
not-found-content-type = "text/plain"
[ports]
server = 4419
# 4422 for testing; 4421 for dev-env; 4420 for testing-env; 4419 for prod-env
discovery = 4421
# 1886 for testing; 1885 for dev-env; 1884 for testing-env; 1883 for prod-env
mqtt = 1885
[logging]
level = "debug"
file = "stdout"

View file

@ -36,7 +36,6 @@ void
cache_invalidate_relay(int relay_id, int status_relay);
void
cache_put_json_controller(int controller_id, char *controller_json);
@ -47,6 +46,16 @@ void
cache_invalidate_controller(int controller_id);
void
cache_put_json_macro(int macro_id, char *macro_json);
char*
cache_get_json_macro(int macro_id);
void
cache_invalidate_macro(int macro_id);
void
cache_invalidate_tagged(int tag_id);

12
include/cli.h Normal file
View file

@ -0,0 +1,12 @@
#ifndef CORE_CLI_H
#define CORE_CLI_H
typedef struct cli
{
const char *config_file;
} cli_t;
void
cli_parse(int argc, const char **argv, cli_t *cli);
#endif /* CORE_CLI_H */

View file

@ -4,37 +4,54 @@
#include <stdint.h>
#include <mongoose.h>
#include <confini.h>
typedef enum
{
RUN_TYPE_START,
RUN_TYPE_INVALID,
} run_type_t;
#include <toml.h>
typedef struct
{
char *file;
char database[256];
char user[256];
char group[256];
int log_level;
FILE *log_file;
run_type_t run_type;
uint16_t server_port;
uint16_t discovery_port;
uint16_t mqtt_port;
char content_dir[1024];
char not_found_file[256];
char not_found_file_type[256];
char not_found_content[256];
char not_found_content_type[256];
char *include;
char *database;
char *user;
char *group;
char *content_dir;
char *not_found_file;
char *not_found_file_type;
char *not_found_content;
char *not_found_content_type;
struct
{
int level;
FILE *file;
} logging;
struct
{
uint16_t server;
uint16_t discovery;
uint16_t mqtt;
} ports;
struct mg_serve_http_opts http_server_opts;
} config_t;
extern config_t global_config;
extern config_t *global_config;
int
config_load(IniDispatch *disp, void *config_void);
void
config_init();
void
config_free();
void
config_load_string(char **holder, const char *value);
void
config_load(config_t *config, const char *cli_config_file);
void
config_load_file(config_t *config, const char *file_name);
void
config_load_directory(config_t *config, const char *directory_name);
#endif /* CORE_CONFIG_H */

View file

@ -2,6 +2,7 @@
#define CORE_CONTANTS_H
#define SECONDS_PER_DAY 86400 // 60 * 60 * 24
#define DAYS_PER_WEEK 7
#define SECONDS_PER_MINUTE 60
@ -14,13 +15,6 @@
*/
#define MAX_NAME_LENGTH 128
/**
* @brief Maximum number of dbs for the databases for the MDB_env
*
* Used when calling mdb_env_set_maxdbs() in database_setup()
*/
#define MDB_MAXDBS 8
/**
* @brief How many milli seconds to wait until poll timeout in main loop
*/
@ -28,4 +22,11 @@
#define PIFACE_GPIO_BASE 200
#define DEFAULT_CONFIG_PATH "emgauwa-core.conf"
#define DEFAULT_GLOBAL_CONFIG_PATH "/etc/emgauwa/core.conf"
#define DEFAULT_DATABASE_PATH "emgauwa-core.sqlite"
#define DEFAULT_DISCOVERY_PORT 4421
#define DEFAULT_MQTT_PORT 1885
#define DEFAULT_SERVER_PORT 5000
#endif /* CORE_CONTANTS_H */

View file

@ -3,6 +3,8 @@
#include <sqlite3.h>
typedef int database_transaction_lock;
extern sqlite3 *global_database;
void
@ -15,14 +17,14 @@ void
database_migrate();
int
database_transaction_begin();
void
database_transaction_begin(database_transaction_lock *lock);
void
database_transaction_commit();
database_transaction_commit(const database_transaction_lock *lock);
void
database_transaction_rollback();
database_transaction_rollback(const database_transaction_lock *lock);
int

View file

@ -24,7 +24,7 @@ typedef struct
int status_code;
const char *content_type;
size_t content_length;
const char *content;
char *content;
int alloced_content;
} endpoint_response_t;
@ -53,10 +53,16 @@ endpoint_func_index(struct mg_connection *nc, struct http_message *hm, endpoint_
void
endpoint_func_not_found(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
endpoint_response_msg(endpoint_response_t *response, int status_code, const char *content, int content_length);
void
endpoint_response_text(endpoint_response_t *response, int status_code, const char *content, int content_length);
void
endpoint_response_json(endpoint_response_t *response, int status_code, const cJSON *json_root);
void
endpoint_response_free_content(endpoint_response_t *response);
#endif /* CORE_ENDPOINT_H */

View file

@ -4,7 +4,7 @@
#include <router.h>
void
api_v1_controllers_discover_POST(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
api_v1_controllers_discover_PUT(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_controllers_GET(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);

View file

@ -0,0 +1,24 @@
#ifndef CORE_ENDPOINTS_API_V1_MACROS_H
#define CORE_ENDPOINTS_API_V1_MACROS_H
#include <router.h>
void
api_v1_macros_GET(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_macros_POST(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_macros_STR_GET(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_macros_STR_PUT(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_macros_STR_DELETE(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_macros_STR_execute_PUT(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
#endif /* CORE_ENDPOINTS_API_V1_MACROS_H */

View file

@ -2,19 +2,19 @@
#define CORE_HELPERS_H
#include <time.h>
#include <mongoose.h>
#include <config.h>
#include <confini.h>
int
helper_connect_tcp_server(char* host, uint16_t port);
void
helper_parse_cli(int argc, const char **argv, config_t *config);
int
helper_get_weekday(const struct tm *time_struct);
int
helper_drop_privileges();
char*
find_query_param(struct mg_str query_mg_str, char* search_key);
#endif /* CORE_HELPERS_H */

View file

@ -8,15 +8,22 @@
#include <colors.h>
#include <config.h>
#define LOG_NONE INT_MAX
#ifndef __FILENAME__
#define __FILENAME__ __FILE__
#endif
void
logger_log(int level, const char *filename, int line, const char *func, const char *msg, ...);
#define LOGGER_DEBUG(...) logger_log(LOG_DEBUG , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_INFO(...) logger_log(LOG_INFO , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_NOTICE(...) logger_log(LOG_NOTICE , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_WARNING(...) logger_log(LOG_WARNING, __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_ERR(...) logger_log(LOG_ERR , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_CRIT(...) logger_log(LOG_CRIT , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_EMERG(...) logger_log(LOG_EMERG , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_NONE(...) logger_log(LOG_NONE , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_DEBUG(...) logger_log(LOG_DEBUG , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_INFO(...) logger_log(LOG_INFO , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_NOTICE(...) logger_log(LOG_NOTICE , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_WARNING(...) logger_log(LOG_WARNING, __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_ERR(...) logger_log(LOG_ERR , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_CRIT(...) logger_log(LOG_CRIT , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_EMERG(...) logger_log(LOG_EMERG , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#endif //EMGAUWA_LOGGER_H

View file

@ -1,6 +1,30 @@
#ifndef CORE_MACROS_H
#define CORE_MACROS_H
#define STRLEN(s) ((sizeof(s)/sizeof(s[0])) - sizeof(s[0]))
#include <logger.h>
#define M_STRLEN(s) ((sizeof(s)/sizeof(s[0])) - sizeof(s[0]))
#define M_RESPONSE_MSG(logger_func, response, code, content) do { logger_func("%s\n", content); endpoint_response_msg(response, code, content, M_STRLEN(content)); } while(0)
#define M_RESPONSE_TEXT(logger_func, response, code, content) do { logger_func("%s\n", content); endpoint_response_text(response, code, content, M_STRLEN(content)); } while(0)
#define M_RESPONSE_400_NO_VALID_JSON(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request is not valid json")
#define M_RESPONSE_400_NO_VALID_ID(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain a valid id (uuid)")
#define M_RESPONSE_400_NO_NAME(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain a name")
#define M_RESPONSE_400_NO_VALID_NAME(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain a valid name")
#define M_RESPONSE_400_NO_IP(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain an ip address")
#define M_RESPONSE_400_NO_VALID_IP(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain a valid ip address")
#define M_RESPONSE_403_PROTECTED_SCHEDULE(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 403, "the target schedule is protected")
#define M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "no controller was found for the requested id")
#define M_RESPONSE_404_NO_RELAY_FOUND_FOR_ID_AND_NUMBER(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "no relay was found for the requested controller id and relay number")
#define M_RESPONSE_404_NO_SCHEDULE_FOUND_FOR_ID(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "no schedule was found for the requested id")
#define M_RESPONSE_404_NO_MACRO_FOUND_FOR_ID(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "no schedule was found for the requested id")
#define M_RESPONSE_404_NO_TAG_FOUND(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "the requested tag was not found")
#define M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response) M_RESPONSE_MSG(LOGGER_ERR, response, 500, "the server failed to save to the database")
#define M_RESPONSE_500_FAILED_TO_READ_FROM_DATABASE(response) M_RESPONSE_MSG(LOGGER_ERR, response, 500, "the server failed to read from the database")
#define M_RESPONSE_500_FAILED_TO_DELETE_FROM_DATABASE(response) M_RESPONSE_MSG(LOGGER_ERR, response, 500, "the server failed to delete from the database")
#endif /* CORE_MACROS_H */

View file

@ -25,16 +25,16 @@ typedef struct
} controller_t;
void
controller_free(controller_t* contoller);
controller_free(controller_t *controller);
int
controller_save(controller_t* contoller);
controller_save(controller_t *controller);
int
controller_remove(controller_t* contoller);
controller_remove(controller_t *controller);
cJSON*
controller_to_json(controller_t* contoller);
controller_to_json(controller_t *controller);
controller_t*
controller_get_by_id(int id);

View file

@ -10,4 +10,7 @@ junction_relay_schedule_remove_for_relay(int relay_id);
int
junction_relay_schedule_insert_weekdays(int relay_id, int *schedule_ids);
int*
junction_relay_schedule_get_relay_ids_with_schedule(int schedule_id);
#endif /* CORE_MODELS_JUNCTION_RELAY_SCHEDULE_H */

45
include/models/macro.h Normal file
View file

@ -0,0 +1,45 @@
#ifndef CORE_MACRO_H
#define CORE_MACRO_H
#include <uuid/uuid.h>
#include <cJSON.h>
#include <constants.h>
#include <models/macro_action.h>
typedef struct
{
int id;
uuid_t uid;
char name[MAX_NAME_LENGTH + 1];
} macro_t;
int
macro_save(macro_t *macro);
int
macro_remove(macro_t *macro);
void
macro_free(macro_t *macro);
void
macro_free_list(macro_t **macro);
cJSON*
macro_to_json(macro_t *macro);
macro_t*
macro_get_by_id(int id);
macro_t*
macro_get_by_uid(uuid_t uid);
macro_t**
macro_get_all();
int*
macro_get_target_relay_ids(int macro_id);
#endif /* CORE_MACRO_H */

View file

@ -0,0 +1,36 @@
#ifndef CORE_MODELS_MACRO_ACTION_H
#define CORE_MODELS_MACRO_ACTION_H
typedef struct
{
int macro_id;
int relay_id;
int schedule_id;
uint8_t weekday;
} macro_action_t;
int
macro_action_insert(macro_action_t *macro_action);
int
macro_action_delete_for_macro(int macro_id);
macro_action_t**
macro_action_get_for_macro(int macro_id);
macro_action_t**
macro_action_get_for_macro_and_weekday(int macro_id, int weekday);
int
macro_action_execute(macro_action_t *macro_action);
void
macro_action_free_list(macro_action_t **macro_actions);
int*
macro_action_get_macro_ids_with_schedule(int schedule_id);
int*
macro_action_get_macro_ids_with_relay(int relay_id);
#endif /* CORE_MODELS_MACRO_ACTION_H */

View file

@ -35,9 +35,6 @@ schedule_free_list(schedule_t **schedule);
cJSON*
schedule_to_json(schedule_t *schedule);
void
schedule_free_list(schedule_t **schedules_list);
uint16_t*
schedule_periods_to_blob(schedule_t *schedule);
@ -65,4 +62,10 @@ schedule_uid_parse(const char *uid_str, uuid_t result);
void
schedule_uid_unparse(const uuid_t uid, char *result);
void
schedule_get_uid_off(uuid_t target);
void
schedule_get_uid_on(uuid_t target);
#endif /* CORE_SCHEDULE_H */

8
shell.nix Normal file
View file

@ -0,0 +1,8 @@
with import <nixpkgs> {};
mkShell {
nativeBuildInputs = [
cmake
sqlite
util-linux
];
}

View file

@ -1,3 +1,5 @@
-- a key-value table used for the json-cache
CREATE TABLE cache (
key STRING
PRIMARY KEY,

View file

@ -1,4 +1,6 @@
create table controllers
-- base migration
CREATE TABLE controllers
(
id INTEGER
PRIMARY KEY
@ -14,7 +16,7 @@ create table controllers
NOT NULL
);
create table relays
CREATE TABLE relays
(
id INTEGER
PRIMARY KEY
@ -28,7 +30,7 @@ create table relays
ON DELETE CASCADE
);
create table schedules
CREATE TABLE schedules
(
id INTEGER
PRIMARY KEY
@ -40,7 +42,7 @@ create table schedules
periods BLOB
);
create table tags
CREATE TABLE tags
(
id INTEGER
PRIMARY KEY
@ -50,7 +52,7 @@ create table tags
UNIQUE
);
create table junction_tag
CREATE TABLE junction_tag
(
tag_id INTEGER
NOT NULL
@ -64,7 +66,7 @@ create table junction_tag
ON DELETE CASCADE
);
create table junction_relay_schedule
CREATE TABLE junction_relay_schedule
(
weekday SMALLINT
NOT NULL,
@ -78,4 +80,4 @@ create table junction_relay_schedule
);
INSERT INTO schedules (uid, name, periods) VALUES (x'6f666600000000000000000000000000', 'off', x'00');
INSERT INTO schedules (uid, name, periods) VALUES (x'6f6e0000000000000000000000000000', 'on', x'010000009F05');
INSERT INTO schedules (uid, name, periods) VALUES (x'6f6e0000000000000000000000000000', 'on', x'010000000000');

28
sql/migration_1.sql Normal file
View file

@ -0,0 +1,28 @@
-- migration to add macros
CREATE TABLE macros
(
id INTEGER
PRIMARY KEY
AUTOINCREMENT,
uid BLOB
NOT NULL
UNIQUE,
name VARCHAR(128)
);
CREATE TABLE macro_actions
(
macro_id INTEGER
NOT NULL
REFERENCES macros (id)
ON DELETE CASCADE,
relay_id INTEGER
REFERENCES relays (id)
ON DELETE CASCADE,
schedule_id INTEGER
REFERENCES schedules (id)
ON DELETE CASCADE,
weekday SMALLINT
NOT NULL
);

View file

@ -1,7 +1,9 @@
#include <cache.h>
#include <logger.h>
#include <sql/cache.h>
#include <models/junction_relay_schedule.h>
#include <models/junction_tag.h>
#include <models/macro_action.h>
#include <sql/cache.h>
sqlite3 *cache_database;
@ -23,8 +25,15 @@ cache_get_value(char *key)
if (s == SQLITE_ROW)
{
const char *found_value = (const char *)sqlite3_column_text(stmt, 0);
result = (char*)malloc(sizeof(char) * (strlen(found_value) + 1));
strcpy(result, found_value);
size_t found_value_len = sqlite3_column_bytes(stmt, 0);
if(result)
{
free(result);
}
result = (char*)malloc(sizeof(char) * (found_value_len + 1));
strncpy(result, found_value, found_value_len);
result[found_value_len] = '\0';
}
else
{
@ -32,13 +41,11 @@ cache_get_value(char *key)
{
break;
}
else
{
LOGGER_WARNING("failed selecting %s from cache: %s\n", key, sqlite3_errstr(s));
break;
}
}
}
sqlite3_finalize(stmt);
@ -134,14 +141,19 @@ cache_invalidate_schedule(int schedule_id)
sprintf(key, "schedule_json:%d", schedule_id);
cache_invalidate(key);
relay_t **relays = relay_get_with_schedule(schedule_id);
for(int i = 0; relays[i] != NULL; ++i)
int *relay_ids = junction_relay_schedule_get_relay_ids_with_schedule(schedule_id);
for(int i = 0; relay_ids[i] != 0; ++i)
{
cache_invalidate_relay(relays[i]->id, -1);
cache_invalidate_relay(relay_ids[i], -1);
}
free(relay_ids);
relay_free_list(relays);
int *macro_ids = macro_action_get_macro_ids_with_schedule(schedule_id);
for(int i = 0; macro_ids[i] != 0; ++i)
{
cache_invalidate_macro(macro_ids[i]);
}
free(macro_ids);
}
@ -180,6 +192,13 @@ cache_invalidate_relay(int relay_id, int status_relay)
{
cache_invalidate_controller(controller_id);
}
int *macro_ids = macro_action_get_macro_ids_with_relay(relay_id);
for(int i = 0; macro_ids[i] != 0; ++i)
{
cache_invalidate_macro(macro_ids[i]);
}
free(macro_ids);
}
@ -208,6 +227,34 @@ cache_invalidate_controller(int controller_id)
cache_invalidate(key);
}
void
cache_put_json_macro(int macro_id, char *macro_json)
{
char key[32];
sprintf(key, "macro_json:%d", macro_id);
cache_insert_value(key, macro_json);
}
char*
cache_get_json_macro(int macro_id)
{
char key[32];
sprintf(key, "macro_json:%d", macro_id);
return cache_get_value(key);
}
void
cache_invalidate_macro(int macro_id)
{
char key[32];
sprintf(key, "macro_json:%d", macro_id);
cache_invalidate(key);
}
void
cache_invalidate_tagged(int tag_id)
{

View file

@ -5,25 +5,29 @@
#include <argparse.h>
#include <config.h>
#include <cli.h>
#include <logger.h>
#include <helpers.h>
#include <version.h>
cli_t *global_cli;
static const char *const usage[] = {
"core [options] [[--] args]",
"core [options]",
NULL,
};
void
helper_parse_cli(int argc, const char **argv, config_t *config)
cli_parse(int argc, const char **argv, cli_t *cli)
{
cli->config_file = NULL;
int version = 0;
struct argparse_option options[] =
{
OPT_HELP(),
OPT_GROUP("Basic options"),
OPT_STRING('c', "config", &config->file, "path to config file", NULL, 0, OPT_NONEG),
OPT_STRING('c', "config", &cli->config_file, "path to config file", NULL, 0, OPT_NONEG),
OPT_BOOLEAN('v', "version", &version, "print version", NULL, 0, OPT_NONEG),
OPT_END(),
};
@ -35,29 +39,11 @@ helper_parse_cli(int argc, const char **argv, config_t *config)
"\nA brief description of what the program does and how it works.",
"\nAdditional description of the program after the description of the arguments."
);
argc = argparse_parse(&argparse, argc, argv);
argparse_parse(&argparse, argc, argv);
if(version)
{
printf("%s\n", EMGAUWA_CORE_VERSION);
exit(0);
}
if(argc == 1)
{
config->run_type = RUN_TYPE_INVALID;
if(strcmp(argv[0], "start") == 0)
{
config->run_type = RUN_TYPE_START;
return;
}
LOGGER_CRIT("bad action '%s' given ('start')\n", argv[0]);
exit(1);
}
else
{
LOGGER_CRIT("no action given ('start')\n");
exit(1);
}
return;
}

View file

@ -90,13 +90,17 @@ command_schedule_update(schedule_t *schedule)
LOGGER_ERR("couldn't find controller for relay %d\n", relays[i]->id);
continue;
}
controller_free(controller);
LOGGER_DEBUG("sending command to controller %s\n", controller->name);
result |= command_send(controller, payload, payload_size);
controller_free(controller);
}
relay_free_list(relays);
free(payload);
return result;
}
@ -207,8 +211,6 @@ command_controller_name_set(controller_t *controller)
int
command_send(controller_t *controller, char *payload, uint32_t payload_size)
{
int bytes_transferred;
int fd_controller = helper_connect_tcp_server(controller->ip, controller->port);
if(fd_controller == -1)
@ -217,12 +219,12 @@ command_send(controller_t *controller, char *payload, uint32_t payload_size)
return 1;
}
if((bytes_transferred = send(fd_controller, &payload_size, sizeof(payload_size), 0)) <= 0)
if(send(fd_controller, &payload_size, sizeof(payload_size), 0) <= 0)
{
LOGGER_ERR("error during sending size\n");
return 1;
}
if((bytes_transferred = send(fd_controller, payload, payload_size, 0)) <= 0)
if(send(fd_controller, payload, payload_size, 0) <= 0)
{
LOGGER_ERR("error during sending\n");
return 1;

View file

@ -1,151 +1,380 @@
#include <dirent.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <logger.h>
#include <config.h>
#include <constants.h>
#include <logger.h>
config_t global_config;
#define CONFINI_IS_KEY(SECTION, KEY) \
(ini_array_match(SECTION, disp->append_to, '.', disp->format) && \
ini_string_match_ii(KEY, disp->data, disp->format))
config_t *global_config;
static int
config_load_log_level(IniDispatch *disp, config_t *config)
config_load_log_level(config_t *config, char *value)
{
if(strcasecmp(disp->value, "debug") == 0)
if(strcmp(value, "debug") == 0)
{
setlogmask(LOG_UPTO(LOG_DEBUG));
config->log_level = LOG_DEBUG;
config->logging.level = LOG_DEBUG;
return 0;
}
if(strcasecmp(disp->value, "info") == 0)
if(strcmp(value, "info") == 0)
{
setlogmask(LOG_UPTO(LOG_INFO));
config->log_level = LOG_INFO;
config->logging.level = LOG_INFO;
return 0;
}
if(strcasecmp(disp->value, "notice") == 0)
if(strcmp(value, "notice") == 0)
{
setlogmask(LOG_UPTO(LOG_NOTICE));
config->log_level = LOG_NOTICE;
config->logging.level = LOG_NOTICE;
return 0;
}
if(strcasecmp(disp->value, "warning") == 0)
if(strcmp(value, "warning") == 0)
{
setlogmask(LOG_UPTO(LOG_WARNING));
config->log_level = LOG_WARNING;
config->logging.level = LOG_WARNING;
return 0;
}
if(strcasecmp(disp->value, "err") == 0)
if(strcmp(value, "err") == 0)
{
setlogmask(LOG_UPTO(LOG_ERR));
config->log_level = LOG_ERR;
config->logging.level = LOG_ERR;
return 0;
}
if(strcasecmp(disp->value, "crit") == 0)
if(strcmp(value, "crit") == 0)
{
setlogmask(LOG_UPTO(LOG_CRIT));
config->log_level = LOG_CRIT;
config->logging.level = LOG_CRIT;
return 0;
}
if(strcasecmp(disp->value, "emerg") == 0)
if(strcmp(value, "emerg") == 0)
{
setlogmask(LOG_UPTO(LOG_EMERG));
config->log_level = LOG_EMERG;
config->logging.level = LOG_EMERG;
return 0;
}
LOGGER_WARNING("invalid log-level '%s'\n", disp->value);
LOGGER_WARNING("invalid log-level '%s'\n", value);
return 0;
}
static int
config_load_log_file(IniDispatch *disp, config_t *config)
config_load_log_file(config_t *config, char *value)
{
if(strcasecmp(disp->value, "stdout") == 0)
if(strcmp(value, "stdout") == 0)
{
config->log_file = stdout;
config->logging.file = stdout;
return 0;
}
if(strcasecmp(disp->value, "stderr") == 0)
if(strcmp(value, "stderr") == 0)
{
config->log_file = stderr;
config->logging.file = stderr;
return 0;
}
config->log_file = fopen(disp->value, "a+");
config->logging.file = fopen(value, "a+");
return 0;
}
int
config_load(IniDispatch *disp, void *config_void)
static void
config_load_section_core(config_t *config, toml_table_t* core)
{
config_t *config = (config_t*)config_void;
toml_datum_t config_entry;
if(disp->type == INI_KEY)
config_entry = toml_string_in(core, "database");
if(config_entry.ok)
{
if(CONFINI_IS_KEY("core", "database"))
{
strcpy(config->database, disp->value);
return 0;
config_load_string(&config->database, config_entry.u.s);
free(config_entry.u.s);
}
if(CONFINI_IS_KEY("core", "user"))
config_entry = toml_string_in(core, "user");
if(config_entry.ok)
{
strcpy(config->user, disp->value);
return 0;
config_load_string(&config->user, config_entry.u.s);
free(config_entry.u.s);
}
if(CONFINI_IS_KEY("core", "group"))
config_entry = toml_string_in(core, "group");
if(config_entry.ok)
{
strcpy(config->group, disp->value);
return 0;
config_load_string(&config->group, config_entry.u.s);
free(config_entry.u.s);
}
if(CONFINI_IS_KEY("core", "content-dir"))
config_entry = toml_string_in(core, "content-dir");
if(config_entry.ok)
{
strcpy(config->content_dir, disp->value);
return 0;
config_load_string(&config->content_dir, config_entry.u.s);
free(config_entry.u.s);
}
if(CONFINI_IS_KEY("core", "not-found-file"))
config_entry = toml_string_in(core, "not-found-file");
if(config_entry.ok)
{
strcpy(config->not_found_file, disp->value);
return 0;
config_load_string(&config->not_found_file, config_entry.u.s);
free(config_entry.u.s);
}
if(CONFINI_IS_KEY("core", "not-found-file-type"))
config_entry = toml_string_in(core, "not-found-file-type");
if(config_entry.ok)
{
strcpy(config->not_found_file_type, disp->value);
return 0;
config_load_string(&config->not_found_file_type, config_entry.u.s);
free(config_entry.u.s);
}
if(CONFINI_IS_KEY("core", "not-found-content"))
config_entry = toml_string_in(core, "not-found-content");
if(config_entry.ok)
{
strcpy(config->not_found_content, disp->value);
return 0;
config_load_string(&config->not_found_content, config_entry.u.s);
free(config_entry.u.s);
}
if(CONFINI_IS_KEY("core", "not-found-content-type"))
config_entry = toml_string_in(core, "not-found-content-type");
if(config_entry.ok)
{
strcpy(config->not_found_content_type, disp->value);
return 0;
config_load_string(&config->not_found_content_type, config_entry.u.s);
free(config_entry.u.s);
}
if(CONFINI_IS_KEY("core", "log-level"))
config_entry = toml_string_in(core, "include");
if(config_entry.ok)
{
return config_load_log_level(disp, config);
config_load_string(&config->include, config_entry.u.s);
free(config_entry.u.s);
}
if(CONFINI_IS_KEY("core", "log-file"))
}
static void
config_load_section_logging(config_t *config, toml_table_t* logging)
{
return config_load_log_file(disp, config);
}
if(CONFINI_IS_KEY("core", "server-port"))
toml_datum_t config_entry;
config_entry = toml_string_in(logging, "level");
if(config_entry.ok)
{
config->server_port = atoi(disp->value);
return 0;
config_load_log_level(config, config_entry.u.s);
free(config_entry.u.s);
}
if(CONFINI_IS_KEY("core", "discovery-port"))
config_entry = toml_string_in(logging, "file");
if(config_entry.ok)
{
config->discovery_port = atoi(disp->value);
return 0;
config_load_log_file(config, config_entry.u.s);
free(config_entry.u.s);
}
if(CONFINI_IS_KEY("core", "mqtt-port"))
}
static void
config_load_section_ports(config_t *config, toml_table_t* ports)
{
config->mqtt_port = atoi(disp->value);
return 0;
toml_datum_t config_entry;
config_entry = toml_int_in(ports, "server");
if(config_entry.ok)
{
config->ports.server = config_entry.u.i;
}
config_entry = toml_int_in(ports, "discovery");
if(config_entry.ok)
{
config->ports.discovery = config_entry.u.i;
}
config_entry = toml_int_in(ports, "mqtt");
if(config_entry.ok)
{
config->ports.mqtt = config_entry.u.i;
}
}
void
config_init()
{
global_config = calloc(1, sizeof(config_t));
config_load_string(&global_config->database, DEFAULT_DATABASE_PATH);
global_config->ports.discovery = DEFAULT_DISCOVERY_PORT;
global_config->ports.mqtt = DEFAULT_MQTT_PORT;
global_config->ports.server = DEFAULT_SERVER_PORT;
global_config->logging.level = LOG_DEBUG;
global_config->logging.file = stdout;
global_config->user = NULL;
global_config->group = NULL;
global_config->include = NULL;
config_load_string(&global_config->content_dir, ".");
config_load_string(&global_config->not_found_file, "404.html");
config_load_string(&global_config->not_found_file_type, "text/html");
config_load_string(&global_config->not_found_content, "404 - NOT FOUND");
config_load_string(&global_config->not_found_content_type, "text/plain");
}
void
config_free()
{
free(global_config->include);
free(global_config->database);
free(global_config->user);
free(global_config->group);
free(global_config->content_dir);
free(global_config->not_found_file);
free(global_config->not_found_file_type);
free(global_config->not_found_content);
free(global_config->not_found_content_type);
free(global_config);
}
void
config_load_string(char **holder, const char *value)
{
if(*holder)
{
free(*holder);
}
size_t value_len = strlen(value);
char *new_holder = malloc(sizeof(char) * (value_len + 1));
strcpy(new_holder, value);
new_holder[value_len] = '\0';
*holder = new_holder;
}
static int
config_try_file(const char *path)
{
if(access(path, F_OK) != 0)
{
return 1;
}
if(access(path, R_OK) != 0)
{
return 1;
}
return 0;
}
void
config_load(config_t *config, const char *cli_config_file)
{
if(cli_config_file)
{
if(config_try_file(cli_config_file) == 0)
{
config_load_file(config, cli_config_file);
return;
}
LOGGER_CRIT("unable to open the passed config file '%s'\n", cli_config_file);
exit(1);
}
if(config_try_file(DEFAULT_CONFIG_PATH) == 0)
{
config_load_file(config, DEFAULT_CONFIG_PATH);
return;
}
if(config_try_file(DEFAULT_GLOBAL_CONFIG_PATH) == 0)
{
config_load_file(config, DEFAULT_GLOBAL_CONFIG_PATH);
return;
}
}
void
config_load_file(config_t *config, const char *file_name)
{
FILE *fp;
toml_table_t* config_toml;
char errbuf[256];
/* Open the file and parse content */
fp = fopen(file_name, "r");
if(fp == NULL) {
LOGGER_CRIT("unable to open config file '%s'\n", file_name);
exit(1);
}
config_toml = toml_parse_file(fp, errbuf, sizeof(errbuf));
fclose(fp);
if(config_toml == NULL) {
LOGGER_CRIT("unable to parse config file '%s': %s\n", file_name, errbuf);
exit(1);
}
toml_table_t* core = toml_table_in(config_toml, "core");
if(core)
{
config_load_section_core(config, core);
}
toml_table_t* logging = toml_table_in(config_toml, "logging");
if(logging)
{
config_load_section_logging(config, logging);
}
toml_table_t* ports = toml_table_in(config_toml, "ports");
if(ports)
{
config_load_section_ports(config, ports);
}
toml_free(config_toml);
LOGGER_DEBUG("Loaded config from %s\n", file_name);
}
void
config_load_directory(config_t *config, const char *directory_name)
{
struct dirent *directory_entry;
DIR *directory;
(void)config;
directory = opendir(directory_name);
if(directory == NULL)
{
LOGGER_CRIT("cannot open directory '%s': %s\n", directory_name, strerror(errno));
exit(1);
}
while((directory_entry = readdir(directory)) != NULL)
{
struct stat sb;
const char *entry_name = directory_entry->d_name;
size_t copied = 0;
// Add 2 for '/' and '\0'.
size_t path_len = strlen(directory_name) + strlen(entry_name) + 1;
char *path = malloc(sizeof(char) * (path_len + 1));
path[0] = '\0';
strncat(path + copied, directory_name, path_len - copied);
copied = strlen(path);
if(path[copied - 1] != '/')
{
strncat(path + copied, "/", path_len - copied);
copied = strlen(path);
}
strncat(path + copied, entry_name, path_len - copied);
if(stat(path, &sb))
{
LOGGER_WARNING("failed to get info for '%s': %s\n", path, strerror(errno));
}
if(S_ISREG(sb.st_mode))
{
config_load_file(config, path);
}
free(path);
}
closedir(directory);
}

View file

@ -5,14 +5,15 @@
#include <database.h>
#include <sql/migration_0.h>
#include <sql/migration_1.h>
sqlite3 *global_database;
static int in_transaction;
static database_transaction_lock *transaction_lock;
void
database_init()
{
int rc = sqlite3_open(global_config.database, &global_database);
int rc = sqlite3_open(global_config->database, &global_database);
if(rc)
{
@ -20,10 +21,12 @@ database_init()
exit(1);
}
LOGGER_DEBUG("Opened database %s\n", global_config->database);
database_migrate();
sqlite3_exec(global_database, "PRAGMA foreign_keys = ON", 0, 0, 0);
in_transaction = 0;
transaction_lock = NULL;
}
void
@ -32,11 +35,35 @@ database_free()
sqlite3_close(global_database);
}
static void
database_migrate_step_simple(int level, const char* sql_migration)
{
LOGGER_INFO("migrating LEVEL %d\n", level);
char* err_msg;
sqlite3_exec(global_database, "BEGIN TRANSACTION;", NULL, NULL, NULL);
int rc = sqlite3_exec(global_database, sql_migration, NULL, NULL, &err_msg);
if(rc)
{
LOGGER_CRIT("couldn't migrate LEVEL %d (%s)\n", level, err_msg);
sqlite3_exec(global_database, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
exit(1);
}
LOGGER_DEBUG("storing new user_version %d\n", level + 1);
char pragma_query[32];
sprintf(pragma_query, "PRAGMA user_version=%d;", level + 1);
sqlite3_exec(global_database, pragma_query, 0, 0, 0);
sqlite3_exec(global_database, "COMMIT TRANSACTION;", NULL, NULL, NULL);
}
void
database_migrate()
{
uint16_t version_num = 0;
int s, rc;
int s;
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "PRAGMA user_version;", -1, &stmt, NULL);
s = sqlite3_step(stmt);
@ -49,61 +76,52 @@ database_migrate()
version_num = 0;
}
uint16_t new_version_num = version_num;
char* err_msg;
sqlite3_finalize(stmt);
switch(version_num)
{
case 0:
LOGGER_INFO("migrating LEVEL 0\n");
rc = sqlite3_exec(global_database, (const char *)sql_migration_0_sql, NULL, NULL, &err_msg);
if(rc)
{
LOGGER_CRIT("couldn't migrate LEVEL 0 (%s)\n", err_msg);
exit(1);
}
new_version_num = 1;
database_migrate_step_simple(0, (const char*)sql_migration_0_sql);
__attribute__ ((fallthrough));
case 1:
database_migrate_step_simple(1, (const char*)sql_migration_1_sql);
__attribute__ ((fallthrough));
default:
break;
}
char pragma_query[32];
sprintf(pragma_query, "PRAGMA user_version=%d;", new_version_num);
sqlite3_exec(global_database, pragma_query, 0, 0, 0);
LOGGER_DEBUG("storing new user_version %d\n", new_version_num);
return;
}
int
database_transaction_begin()
void
database_transaction_begin(database_transaction_lock *lock)
{
if(!in_transaction)
if(transaction_lock == NULL)
{
LOGGER_DEBUG("beginning transaction\n");
sqlite3_exec(global_database, "BEGIN TRANSACTION;", NULL, NULL, NULL);
in_transaction = 1;
return 1;
transaction_lock = lock;
}
return 0;
}
void
database_transaction_commit()
database_transaction_commit(const database_transaction_lock *lock)
{
if(transaction_lock == lock)
{
LOGGER_DEBUG("commiting transaction\n");
sqlite3_exec(global_database, "COMMIT TRANSACTION;", NULL, NULL, NULL);
in_transaction = 0;
transaction_lock = NULL;
}
}
void
database_transaction_rollback()
database_transaction_rollback(const database_transaction_lock *lock)
{
if(transaction_lock == lock)
{
LOGGER_DEBUG("rolling back transaction\n");
sqlite3_exec(global_database, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
in_transaction = 0;
transaction_lock = NULL;
}
}
int
@ -126,14 +144,12 @@ database_helper_get_id(sqlite3_stmt *stmt)
{
break;
}
else
{
LOGGER_ERR("error selecting id from database: %s\n", sqlite3_errstr(s));
sqlite3_finalize(stmt);
return 0;
}
}
}
sqlite3_finalize(stmt);
@ -170,14 +186,16 @@ database_helper_get_ids(sqlite3_stmt *stmt)
{
break;
}
else
if(result)
{
free(result);
}
LOGGER_ERR("error selecting ids from database: %s\n", sqlite3_errstr(s));
sqlite3_finalize(stmt);
return NULL;
}
}
}
sqlite3_finalize(stmt);
result[row] = 0;
@ -198,8 +216,15 @@ database_helper_get_string(sqlite3_stmt *stmt)
if (s == SQLITE_ROW)
{
const char *found_string = (const char *)sqlite3_column_text(stmt, 0);
result = (char*)malloc(sizeof(char) * (strlen(found_string) + 1));
strcpy(result, found_string);
size_t found_string_len = sqlite3_column_bytes(stmt, 0);
if(result)
{
free(result);
}
result = (char*)malloc(sizeof(char) * (found_string_len + 1));
strncpy(result, found_string, found_string_len);
result[found_string_len] = '\0';
}
else
{
@ -207,14 +232,16 @@ database_helper_get_string(sqlite3_stmt *stmt)
{
break;
}
else
if(result)
{
free(result);
}
LOGGER_ERR("error selecting string from database: %s\n", sqlite3_errstr(s));
sqlite3_finalize(stmt);
return NULL;
}
}
}
sqlite3_finalize(stmt);
@ -236,12 +263,13 @@ database_helper_get_strings(sqlite3_stmt *stmt)
if (s == SQLITE_ROW)
{
const char *new_string = (const char *)sqlite3_column_text(stmt, 0);
int new_string_len = strlen(new_string);
size_t new_string_len = sqlite3_column_bytes(stmt, 0);
row++;
result = (char**)realloc(result, sizeof(char*) * (row + 1));
result[row - 1] = malloc(sizeof(char) * (new_string_len + 1));
strcpy(result[row - 1], new_string);
strncpy(result[row - 1], new_string, new_string_len);
result[row - 1][new_string_len] = '\0';
}
else
{
@ -249,13 +277,11 @@ database_helper_get_strings(sqlite3_stmt *stmt)
{
break;
}
else
{
LOGGER_ERR("error selecting strings from database: %s\n", sqlite3_errstr(s));
break;
}
}
}
sqlite3_finalize(stmt);
result[row] = NULL;
return result;

View file

@ -14,7 +14,7 @@ endpoint_func_index(struct mg_connection *nc, struct http_message *hm, endpoint_
response->status_code = 0;
mg_serve_http(nc, hm, global_config.http_server_opts);
mg_serve_http(nc, hm, global_config->http_server_opts);
}
void
@ -24,27 +24,61 @@ endpoint_func_not_found(struct mg_connection *nc, struct http_message *hm, endpo
(void)hm;
(void)nc;
if(access(global_config.not_found_file, R_OK) != -1)
if(access(global_config->not_found_file, R_OK) != -1)
{
struct mg_str mime_type = mg_mk_str(global_config.not_found_file_type);
struct mg_str mime_type = mg_mk_str(global_config->not_found_file_type);
response->status_code = 0;
mg_http_serve_file(nc, hm, global_config.not_found_file, mime_type, mg_mk_str(""));
mg_http_serve_file(nc, hm, global_config->not_found_file, mime_type, mg_mk_str(""));
}
else
{
LOGGER_DEBUG("404 file not found\n");
endpoint_response_free_content(response);
response->status_code = 404;
response->content_type = global_config.not_found_content_type;
response->content_length = strlen(global_config.not_found_content);
response->content = global_config.not_found_content;
response->content_type = global_config->not_found_content_type;
response->content_length = strlen(global_config->not_found_content);
response->content = global_config->not_found_content;
response->alloced_content = false;
}
}
void
endpoint_response_msg(endpoint_response_t *response, int status_code, const char *content, int content_length)
{
endpoint_response_free_content(response);
cJSON *json;
json = cJSON_CreateObject();
cJSON *json_msg;
if(content_length)
{
json_msg = cJSON_CreateStringReference(content);
}
else
{
json_msg = cJSON_CreateString(content);
}
if(json_msg == NULL)
{
endpoint_response_text(response, status_code, content, content_length);
return;
}
cJSON_AddItemToObject(json, "msg", json_msg);
endpoint_response_json(response, status_code, json);
cJSON_Delete(json);
}
void
endpoint_response_text(endpoint_response_t *response, int status_code, const char *content, int content_length)
{
endpoint_response_free_content(response);
if(content == NULL)
{
content = "";
@ -53,22 +87,27 @@ endpoint_response_text(endpoint_response_t *response, int status_code, const cha
response->status_code = status_code;
response->content_type = "text/plain";
if(content_length >= 0)
if(content_length)
{
response->content_length = content_length;
response->alloced_content = false;
response->content = (char*)content;
}
else
{
response->content_length = strlen(content);
response->alloced_content = true;
response->content = malloc(sizeof(char) * (response->content_length + 1));
strcpy(response->content, content);
response->content[response->content_length] = '\0';
}
response->content = content;
}
void
endpoint_response_json(endpoint_response_t *response, int status_code, const cJSON *json_root)
{
endpoint_response_free_content(response);
if(json_root != NULL)
{
char *json_str = cJSON_Print(json_root);
@ -84,8 +123,16 @@ endpoint_response_json(endpoint_response_t *response, int status_code, const cJS
}
}
LOGGER_ERR("failed to print schedule json\n");
static const char content[] = "failed to print json";
endpoint_response_text(response, status_code, content, STRLEN(content));
M_RESPONSE_MSG(LOGGER_ERR, response, 500, "failed to print json");
}
void
endpoint_response_free_content(endpoint_response_t *response)
{
if(response->alloced_content)
{
free(response->content);
response->content = NULL;
response->alloced_content = false;
}
}

View file

@ -18,10 +18,7 @@ api_v1_controllers_STR_GET(struct mg_connection *nc, struct http_message *hm, en
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
LOGGER_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_400_NO_VALID_ID(response);
return;
}
@ -29,10 +26,7 @@ api_v1_controllers_STR_GET(struct mg_connection *nc, struct http_message *hm, en
if(!controller)
{
LOGGER_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no controller for id found";
endpoint_response_text(response, 404, content, STRLEN(content));
M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response);
return;
}
LOGGER_DEBUG("returning controller for uid '%s'\n", args[0].value.v_str);
@ -53,10 +47,7 @@ api_v1_controllers_STR_PUT(struct mg_connection *nc, struct http_message *hm, en
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
LOGGER_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_400_NO_VALID_ID(response);
return;
}
@ -64,10 +55,7 @@ api_v1_controllers_STR_PUT(struct mg_connection *nc, struct http_message *hm, en
if(!controller)
{
LOGGER_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no controller for id found";
endpoint_response_text(response, 404, content, STRLEN(content));
M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response);
return;
}
LOGGER_DEBUG("starting overwrite for controller %s\n", args[0].value.v_str);
@ -76,36 +64,41 @@ api_v1_controllers_STR_PUT(struct mg_connection *nc, struct http_message *hm, en
if(json == NULL)
{
static const char content[] = "no valid json was supplied";
endpoint_response_text(response, 400, content, STRLEN(content));
controller_free(controller);
M_RESPONSE_400_NO_VALID_JSON(response);
return;
}
cJSON *json_name = cJSON_GetObjectItemCaseSensitive(json, "name");
if(json_name)
{
if(cJSON_IsString(json_name) && json_name->valuestring)
if(!cJSON_IsString(json_name) || json_name->valuestring == NULL)
{
cJSON_Delete(json);
controller_free(controller);
M_RESPONSE_400_NO_VALID_NAME(response);
return;
}
strncpy(controller->name, json_name->valuestring, MAX_NAME_LENGTH);
controller->name[MAX_NAME_LENGTH] = '\0';
LOGGER_DEBUG("new controller name: %s\n", controller->name);
}
else
{
static const char content[] = "the given name is no valid string";
endpoint_response_text(response, 400, content, STRLEN(content));
cJSON_Delete(json);
controller_free(controller);
return;
}
}
cJSON *json_ip = cJSON_GetObjectItemCaseSensitive(json, "ip");
if(json_ip)
{
if(cJSON_IsString(json_ip) && json_ip->valuestring)
if(!cJSON_IsString(json_ip) || json_ip->valuestring == NULL)
{
cJSON_Delete(json);
controller_free(controller);
M_RESPONSE_400_NO_IP(response);
return;
}
unsigned char buf[sizeof(struct in_addr)];
if(inet_pton(AF_INET, json_ip->valuestring, buf))
{
@ -115,31 +108,20 @@ api_v1_controllers_STR_PUT(struct mg_connection *nc, struct http_message *hm, en
}
else
{
static const char content[] = "the given ip address is no valid IPv4 address";
endpoint_response_text(response, 400, content, STRLEN(content));
cJSON_Delete(json);
controller_free(controller);
return;
}
}
else
{
static const char content[] = "the given ip address is no valid string";
endpoint_response_text(response, 400, content, STRLEN(content));
cJSON_Delete(json);
controller_free(controller);
M_RESPONSE_400_NO_VALID_IP(response);
return;
}
}
if(controller_save(controller))
{
LOGGER_ERR("failed to save controller\n");
controller_free(controller);
cJSON_Delete(json);
static const char content[] = "failed to save controller to database";
endpoint_response_text(response, 500, content, STRLEN(content));
M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response);
return;
}
LOGGER_DEBUG("saved controller %s\n", args[0].value.v_str);
@ -165,10 +147,7 @@ api_v1_controllers_STR_DELETE(struct mg_connection *nc, struct http_message *hm,
uuid_t target_uid;
if(uuid_parse(target_uid_str, target_uid))
{
LOGGER_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_400_NO_VALID_ID(response);
return;
}
@ -176,25 +155,17 @@ api_v1_controllers_STR_DELETE(struct mg_connection *nc, struct http_message *hm,
if(!controller)
{
LOGGER_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no controller for id found";
endpoint_response_text(response, 404, content, STRLEN(content));
M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response);
return;
}
if(controller_remove(controller))
{
LOGGER_ERR("failed to remove controller from database\n");
static const char content[] = "failed to remove controller from database";
endpoint_response_text(response, 500, content, STRLEN(content));
M_RESPONSE_500_FAILED_TO_DELETE_FROM_DATABASE(response);
}
else
{
LOGGER_DEBUG("deleted controller %s\n", args[0].value.v_str);
endpoint_response_text(response, 200, "", 0);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 200, "deleted controller");
}
controller_free(controller);
return;
}

View file

@ -15,10 +15,7 @@ api_v1_controllers_STR_relays_GET(struct mg_connection *nc, struct http_message
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
LOGGER_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_400_NO_VALID_ID(response);
return;
}
@ -26,10 +23,7 @@ api_v1_controllers_STR_relays_GET(struct mg_connection *nc, struct http_message
if(!controller)
{
LOGGER_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no controller for id found";
endpoint_response_text(response, 404, content, STRLEN(content));
M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response);
return;
}

View file

@ -18,10 +18,7 @@ api_v1_controllers_STR_relays_INT_GET(struct mg_connection *nc, struct http_mess
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
LOGGER_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_400_NO_VALID_ID(response);
return;
}
@ -29,21 +26,18 @@ api_v1_controllers_STR_relays_INT_GET(struct mg_connection *nc, struct http_mess
if(!controller)
{
LOGGER_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no controller for id found";
endpoint_response_text(response, 404, content, STRLEN(content));
M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response);
return;
}
relay_t* relay = relay_get_for_controller(controller->id, args[1].value.v_int);
int controller_id = controller->id;
controller_free(controller);
relay_t* relay = relay_get_for_controller(controller_id, args[1].value.v_int);
if(!relay)
{
LOGGER_DEBUG("could not find a relay with num %d for controller '%s'\n", args[1].value.v_int, args[0].value.v_str);
static const char content[] = "no relay for this controller found";
endpoint_response_text(response, 404, content, STRLEN(content));
M_RESPONSE_404_NO_RELAY_FOUND_FOR_ID_AND_NUMBER(response);
return;
}
@ -53,7 +47,6 @@ api_v1_controllers_STR_relays_INT_GET(struct mg_connection *nc, struct http_mess
endpoint_response_json(response, 200, json);
cJSON_Delete(json);
relay_free(relay);
controller_free(controller);
}
void
@ -65,10 +58,7 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
LOGGER_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_400_NO_VALID_ID(response);
return;
}
@ -76,23 +66,21 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess
if(!controller)
{
LOGGER_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no controller for id found";
endpoint_response_text(response, 404, content, STRLEN(content));
M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response);
return;
}
if(args[1].value.v_int > controller->relay_count)
int controller_id = controller->id;
int controller_relay_count = controller->relay_count;
controller_free(controller);
if(args[1].value.v_int > controller_relay_count)
{
LOGGER_DEBUG("relay num %d is too high for %s\n", args[1].value.v_int, args[0].value.v_str);
static const char content[] = "relay number is too high for this controller";
endpoint_response_text(response, 404, content, STRLEN(content));
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "relay number is too high for this controller");
return;
}
relay_t* relay = relay_get_for_controller(controller->id, args[1].value.v_int);
relay_t* relay = relay_get_for_controller(controller_id, args[1].value.v_int);
if(!relay)
{
@ -102,7 +90,7 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess
relay->number = args[1].value.v_int;
snprintf(relay->name, MAX_NAME_LENGTH, "Relay %d", relay->number);
relay->name[MAX_NAME_LENGTH] = '\0';
relay->controller_id = controller->id;
relay->controller_id = controller_id;
uuid_t tmp_uuid;
memset(tmp_uuid, 0, sizeof(uuid_t));
@ -117,15 +105,13 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess
relay->active_schedule = relay->schedules[helper_get_weekday(time_struct)];
}
controller_free(controller);
LOGGER_DEBUG("overwriting relay %d for controller %s\n", args[1].value.v_int, args[0].value.v_str);
cJSON *json = cJSON_ParseWithLength(hm->body.p, hm->body.len);
if(json == NULL)
{
static const char content[] = "no valid json was supplied";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_400_NO_VALID_JSON(response);
return;
}
@ -149,21 +135,17 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess
cJSON *json_schedule_uid = cJSON_GetObjectItemCaseSensitive(json_schedule, "id");
if(!cJSON_IsString(json_schedule_uid) || (json_schedule_uid->valuestring == NULL))
{
LOGGER_DEBUG("schedules[%d] is missing uid\n", schedule_position);
cJSON_Delete(json);
static const char content[] = "at least one schedule is missing an id";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one schedule is missing an id");
return;
}
uuid_t target_uid;
if(schedule_uid_parse(json_schedule_uid->valuestring, target_uid))
{
LOGGER_DEBUG("schedules[%d] has bad uid\n", schedule_position);
cJSON_Delete(json);
static const char content[] = "at least one schedule has a bad id";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "at least one schedule has a bad id");
return;
}
@ -190,11 +172,9 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess
uuid_t target_uid;
if(schedule_uid_parse(json_active_schedule_uid->valuestring, target_uid))
{
LOGGER_DEBUG("active_schedule has bad uid\n");
cJSON_Delete(json);
static const char content[] = "active_schedule has a bad id";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "active schedule has a bad uid");
return;
}
relay->schedules[day_of_week] = schedule_get_by_uid_or_off(target_uid);
@ -202,21 +182,15 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess
}
}
int opened_transaction = database_transaction_begin();
database_transaction_lock lock;
database_transaction_begin(&lock);
if(relay_save(relay))
{
LOGGER_ERR("failed to save relay\n");
if(opened_transaction)
{
database_transaction_rollback();
}
database_transaction_rollback(&lock);
cJSON_Delete(json);
static const char content[] = "failed to save relay to database";
endpoint_response_text(response, 500, content, STRLEN(content));
M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response);
return;
}
@ -235,19 +209,12 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess
{
if(!cJSON_IsString(json_tag) || (json_tag->valuestring == NULL))
{
LOGGER_DEBUG("invalid tag in tags\n");
if(opened_transaction)
{
database_transaction_rollback();
}
database_transaction_rollback(&lock);
relay_free(relay);
cJSON_Delete(json);
free(tag_ids);
static const char content[] = "invalid tag in tags";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "invalid tag in tags");
return;
}
const char *tag = json_tag->valuestring;
@ -259,13 +226,12 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess
}
tag_ids[i++] = tag_id;
}
junction_tag_insert_list(tag_ids, relay->id, 0, json_tags_count);
free(tag_ids);
}
if(opened_transaction)
{
database_transaction_commit();
}
database_transaction_commit(&lock);
cJSON_Delete(json);
json = relay_to_json(relay, 0);

View file

@ -18,10 +18,7 @@ api_v1_controllers_STR_relays_INT_pulse_POST(struct mg_connection *nc, struct ht
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
LOGGER_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_400_NO_VALID_ID(response);
return;
}
@ -29,21 +26,18 @@ api_v1_controllers_STR_relays_INT_pulse_POST(struct mg_connection *nc, struct ht
if(!controller)
{
LOGGER_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no controller for id found";
endpoint_response_text(response, 404, content, STRLEN(content));
M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response);
return;
}
relay_t* relay = relay_get_for_controller(controller->id, args[1].value.v_int);
int controller_id = controller->id;
controller_free(controller);
relay_t* relay = relay_get_for_controller(controller_id, args[1].value.v_int);
if(!relay)
{
LOGGER_DEBUG("could not find a relay with num %d for controller '%s'\n", args[1].value.v_int, args[0].value.v_str);
static const char content[] = "no relay for this controller found";
endpoint_response_text(response, 404, content, STRLEN(content));
M_RESPONSE_404_NO_RELAY_FOUND_FOR_ID_AND_NUMBER(response);
return;
}
@ -64,7 +58,6 @@ api_v1_controllers_STR_relays_INT_pulse_POST(struct mg_connection *nc, struct ht
LOGGER_DEBUG("commanding pulse to relay %d for controller %s\n", args[1].value.v_int, args[0].value.v_str);
command_relay_pulse(relay, duration);
endpoint_response_text(response, 200, "", 0);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 200, "sent pulse");
relay_free(relay);
controller_free(controller);
}

View file

@ -19,7 +19,8 @@ typedef enum
static int
bind_tcp_server(const char *addr, const char *port, int max_client_backlog)
{
struct addrinfo hints, *res;
struct addrinfo hints;
struct addrinfo *res;
int fd;
int status;
@ -105,7 +106,7 @@ send_udp_broadcast(const char *addr, uint16_t port, void *message, size_t length
}
void
api_v1_controllers_discover_POST(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
api_v1_controllers_discover_PUT(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)nc;
(void)hm;
@ -114,13 +115,7 @@ api_v1_controllers_discover_POST(struct mg_connection *nc, struct http_message *
if(discover_server_port == -1)
{
LOGGER_ERR("failed to get server port for discovery\n");
static const char content[] = "";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
M_RESPONSE_MSG(LOGGER_ERR, response, 500, "the server failed to prepare discovery");
return;
}
@ -128,21 +123,16 @@ api_v1_controllers_discover_POST(struct mg_connection *nc, struct http_message *
payload[0] = discover_server_port;
LOGGER_DEBUG("sending udp broadcast\n");
if(send_udp_broadcast("255.255.255.255", global_config.discovery_port, payload, sizeof(payload)) < 0)
if(send_udp_broadcast("255.255.255.255", global_config->ports.discovery, payload, sizeof(payload)) < 0)
{
LOGGER_ERR("failed to send UDP broadcast\n");
static const char content[] = "";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
M_RESPONSE_MSG(LOGGER_ERR, response, 500, "the server failed to send discovery broadcast");
return;
}
struct sockaddr_storage their_addr;
socklen_t addr_size;
int client_fd, s_ret;
int s_ret;
int client_fd;
fd_set accept_fds;
struct timeval timeout;
@ -165,8 +155,7 @@ api_v1_controllers_discover_POST(struct mg_connection *nc, struct http_message *
{
break;
}
else
{
if((client_fd = accept(discover_server_socket, (struct sockaddr *) &their_addr, &addr_size)) < 0)
{
LOGGER_ERR("error accepting client %s\n", strerror(errno));
@ -181,10 +170,8 @@ api_v1_controllers_discover_POST(struct mg_connection *nc, struct http_message *
continue;
}
char *answer_payload = (char*)malloc((payload_length));
ssize_t bytes_transferred;
if((bytes_transferred = recv(client_fd, answer_payload, payload_length, 0)) <= 0)
char *answer_payload = (char*)malloc((payload_length + 1));
if(recv(client_fd, answer_payload, payload_length, 0) <= 0)
{
LOGGER_ERR("error receiving payload from client\n");
continue;
@ -232,8 +219,11 @@ api_v1_controllers_discover_POST(struct mg_connection *nc, struct http_message *
known_controllers[i]->active = 1;
strncpy(known_controllers[i]->name, discovered_name, discovered_name_len);
strcpy(known_controllers[i]->ip, inet_ntoa(addr.sin_addr));
known_controllers[i]->name[discovered_name_len] = '\0';
strncpy(known_controllers[i]->ip, inet_ntoa(addr.sin_addr), IP_LENGTH);
known_controllers[i]->ip[IP_LENGTH] = '\0';
known_controllers[i]->port = discovered_command_port;
known_controllers[i]->relay_count = discovered_relay_count;
@ -256,8 +246,11 @@ api_v1_controllers_discover_POST(struct mg_connection *nc, struct http_message *
controller_t *discovered_controller = malloc(sizeof(controller_t));
discovered_controller->id = 0;
strcpy(discovered_controller->ip, inet_ntoa(addr.sin_addr));
memcpy(discovered_controller->uid, discovered_id, sizeof(uuid_t));
strncpy(discovered_controller->ip, inet_ntoa(addr.sin_addr), IP_LENGTH + 1);
discovered_controller->ip[IP_LENGTH] = '\0';
uuid_copy(discovered_controller->uid, discovered_id);
strncpy(discovered_controller->name, discovered_name, discovered_name_len);
discovered_controller->name[discovered_name_len] = '\0';
discovered_controller->relay_count = discovered_relay_count;
@ -304,7 +297,6 @@ api_v1_controllers_discover_POST(struct mg_connection *nc, struct http_message *
send(client_fd, discover_answer_buf, sizeof(uint8_t), 0);
close(client_fd);
}
}
for(int i = 0; known_controllers[i] != NULL; i++)
{
LOGGER_DEBUG("lost controller %s at %s\n", known_controllers[i]->name, known_controllers[i]->ip);

View file

@ -0,0 +1,240 @@
#include <cJSON.h>
#include <macros.h>
#include <constants.h>
#include <database.h>
#include <endpoints/api_v1_macros.h>
#include <logger.h>
#include <models/macro.h>
#include <models/macro_action.h>
#include <models/schedule.h>
#include <models/relay.h>
#include <models/controller.h>
void
api_v1_macros_GET(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)args;
(void)hm;
(void)nc;
macro_t** all_macros = macro_get_all();
cJSON *json = cJSON_CreateArray();
for(int i = 0; all_macros[i] != NULL; ++i)
{
cJSON_AddItemToArray(json, macro_to_json(all_macros[i]));
free(all_macros[i]);
}
endpoint_response_json(response, 200, json);
cJSON_Delete(json);
free(all_macros);
}
void
api_v1_macros_POST(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)args;
(void)nc;
cJSON *json = cJSON_ParseWithLength(hm->body.p, hm->body.len);
if(json == NULL)
{
M_RESPONSE_400_NO_VALID_JSON(response);
return;
}
cJSON *json_name = cJSON_GetObjectItemCaseSensitive(json, "name");
if(!cJSON_IsString(json_name) || (json_name->valuestring == NULL))
{
M_RESPONSE_400_NO_NAME(response);
return;
}
cJSON *json_actions = cJSON_GetObjectItemCaseSensitive(json, "actions");
if(!cJSON_IsArray(json_actions))
{
cJSON_Delete(json);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contains actions for the macro");
return;
}
database_transaction_lock lock;
database_transaction_begin(&lock);
macro_t *new_macro = malloc(sizeof(macro_t));
new_macro->id = 0;
uuid_generate(new_macro->uid);
strncpy(new_macro->name, json_name->valuestring, MAX_NAME_LENGTH);
new_macro->name[MAX_NAME_LENGTH] = '\0';
if(macro_save(new_macro))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response);
return;
}
cJSON *json_action;
cJSON_ArrayForEach(json_action, json_actions)
{
cJSON *json_action_weekday = cJSON_GetObjectItemCaseSensitive(json_action, "weekday");
if(!cJSON_IsNumber(json_action_weekday) || (json_action_weekday->valueint < 0) || (json_action_weekday->valueint > 6))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action without weekday");
return;
}
cJSON *json_action_schedule = cJSON_GetObjectItemCaseSensitive(json_action, "schedule");
if(!cJSON_IsObject(json_action_schedule))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action without schedule");
return;
}
cJSON *json_action_schedule_uid = cJSON_GetObjectItemCaseSensitive(json_action_schedule, "id");
if(!cJSON_IsString(json_action_schedule_uid) || (json_action_schedule_uid->valuestring == NULL))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action without schedule id");
return;
}
uuid_t action_schedule_uid;
if(schedule_uid_parse(json_action_schedule_uid->valuestring, action_schedule_uid))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action with an invalid schedule id");
return;
}
schedule_t *action_schedule = schedule_get_by_uid(action_schedule_uid);
if(action_schedule == NULL)
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "the schedule for at least one action was not found");
return;
}
int action_schedule_id = action_schedule->id;
schedule_free(action_schedule);
cJSON *json_action_relay = cJSON_GetObjectItemCaseSensitive(json_action, "relay");
if(!cJSON_IsObject(json_action_relay))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action without relay");
return;
}
cJSON *json_action_relay_number = cJSON_GetObjectItemCaseSensitive(json_action_relay, "number");
if(!cJSON_IsNumber(json_action_relay_number))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action without relay number");
return;
}
cJSON *json_action_relay_controller_uid = cJSON_GetObjectItemCaseSensitive(json_action_relay, "controller_id");
if(!cJSON_IsString(json_action_relay_controller_uid) || (json_action_relay_controller_uid->valuestring == NULL))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action without relay controller id");
return;
}
uuid_t action_controller_uid;
if(uuid_parse(json_action_relay_controller_uid->valuestring, action_controller_uid))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action with an invalid relay controller id");
return;
}
controller_t *action_controller = controller_get_by_uid(action_controller_uid);
if(action_controller == NULL)
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "the controller for at least one action relay was not found");
return;
}
int controller_id = action_controller->id;
int relay_num = json_action_relay_number->valueint;
controller_free(action_controller);
relay_t *action_relay = relay_get_for_controller(controller_id, relay_num);
if(action_relay == NULL)
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "the relay for at least one action was not found");
return;
}
int action_relay_id = action_relay->id;
relay_free(action_relay);
macro_action_t *new_action = malloc(sizeof(macro_action_t));
new_action->macro_id = new_macro->id;
new_action->relay_id = action_relay_id;
new_action->schedule_id = action_schedule_id;
new_action->weekday = json_action_weekday->valueint;
macro_action_insert(new_action);
free(new_action);
}
database_transaction_commit(&lock);
cJSON_Delete(json);
json = macro_to_json(new_macro);
endpoint_response_json(response, 201, json);
cJSON_Delete(json);
macro_free(new_macro);
}

View file

@ -0,0 +1,280 @@
#include <cJSON.h>
#include <macros.h>
#include <constants.h>
#include <endpoints/api_v1_macros.h>
#include <logger.h>
#include <command.h>
#include <models/macro_action.h>
#include <models/macro.h>
#include <models/relay.h>
#include <models/tag.h>
void
api_v1_macros_STR_GET(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)hm;
(void)nc;
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
M_RESPONSE_400_NO_VALID_ID(response);
return;
}
macro_t* macro = macro_get_by_uid(target_uid);
if(!macro)
{
M_RESPONSE_404_NO_MACRO_FOUND_FOR_ID(response);
return;
}
cJSON *json = macro_to_json(macro);
endpoint_response_json(response, 200, json);
cJSON_Delete(json);
macro_free(macro);
}
void
api_v1_macros_STR_PUT(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)hm;
(void)nc;
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
M_RESPONSE_400_NO_VALID_ID(response);
return;
}
macro_t* macro = macro_get_by_uid(target_uid);
if(!macro)
{
M_RESPONSE_404_NO_MACRO_FOUND_FOR_ID(response);
return;
}
cJSON *json = cJSON_ParseWithLength(hm->body.p, hm->body.len);
if(json == NULL)
{
M_RESPONSE_400_NO_VALID_JSON(response);
return;
}
database_transaction_lock lock;
database_transaction_begin(&lock);
cJSON *json_name = cJSON_GetObjectItemCaseSensitive(json, "name");
if(cJSON_IsString(json_name) && (json_name->valuestring != NULL))
{
strncpy(macro->name, json_name->valuestring, MAX_NAME_LENGTH);
macro->name[MAX_NAME_LENGTH] = '\0';
if(macro_save(macro))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response);
return;
}
}
macro_action_delete_for_macro(macro->id);
cJSON *json_actions = cJSON_GetObjectItemCaseSensitive(json, "actions");
if(cJSON_IsArray(json_actions))
{
cJSON *json_action;
cJSON_ArrayForEach(json_action, json_actions)
{
cJSON *json_action_weekday = cJSON_GetObjectItemCaseSensitive(json_action, "weekday");
if(!cJSON_IsNumber(json_action_weekday) || (json_action_weekday->valueint < 0) || (json_action_weekday->valueint > 6))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action is missing a weekday");
return;
}
cJSON *json_action_schedule = cJSON_GetObjectItemCaseSensitive(json_action, "schedule");
if(!cJSON_IsObject(json_action_schedule))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action is missing a schedule");
return;
}
cJSON *json_action_schedule_uid = cJSON_GetObjectItemCaseSensitive(json_action_schedule, "id");
if(!cJSON_IsString(json_action_schedule_uid) || (json_action_schedule_uid->valuestring == NULL))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action is missing a schedule id");
return;
}
uuid_t action_schedule_uid;
if(schedule_uid_parse(json_action_schedule_uid->valuestring, action_schedule_uid))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action has a bad schedule id");
return;
}
schedule_t *action_schedule = schedule_get_by_uid(action_schedule_uid);
if(action_schedule == NULL)
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action schedule was not found");
return;
}
int action_schedule_id = action_schedule->id;
schedule_free(action_schedule);
cJSON *json_action_relay = cJSON_GetObjectItemCaseSensitive(json_action, "relay");
if(!cJSON_IsObject(json_action_relay))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action is missing a relay");
return;
}
cJSON *json_action_relay_number = cJSON_GetObjectItemCaseSensitive(json_action_relay, "number");
if(!cJSON_IsNumber(json_action_relay_number))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action is missing a relay number");
return;
}
cJSON *json_action_relay_controller_uid = cJSON_GetObjectItemCaseSensitive(json_action_relay, "controller_id");
if(!cJSON_IsString(json_action_relay_controller_uid) || (json_action_relay_controller_uid->valuestring == NULL))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action is missing a relay controller id");
return;
}
uuid_t action_controller_uid;
if(uuid_parse(json_action_relay_controller_uid->valuestring, action_controller_uid))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action has a bad relay controller id");
return;
}
controller_t *action_controller = controller_get_by_uid(action_controller_uid);
if(action_controller == NULL)
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action relay controller was not found");
return;
}
int controller_id = action_controller->id;
int relay_num = json_action_relay_number->valueint;
controller_free(action_controller);
relay_t *action_relay = relay_get_for_controller(controller_id, relay_num);
if(action_relay == NULL)
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "action relay was not found");
return;
}
int action_relay_id = action_relay->id;
relay_free(action_relay);
macro_action_t *action = malloc(sizeof(macro_action_t));
action->macro_id = macro->id;
action->relay_id = action_relay_id;
action->schedule_id = action_schedule_id;
action->weekday = json_action_weekday->valueint;
macro_action_insert(action);
free(action);
}
}
database_transaction_commit(&lock);
cJSON_Delete(json);
json = macro_to_json(macro);
endpoint_response_json(response, 201, json);
cJSON_Delete(json);
macro_free(macro);
}
void
api_v1_macros_STR_DELETE(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)hm;
(void)nc;
const char *target_uid_str = args[0].value.v_str;
uuid_t target_uid;
if(uuid_parse(target_uid_str, target_uid))
{
M_RESPONSE_400_NO_VALID_ID(response);
return;
}
macro_t* macro = macro_get_by_uid(target_uid);
if(!macro)
{
M_RESPONSE_404_NO_MACRO_FOUND_FOR_ID(response);
return;
}
if(macro_remove(macro))
{
M_RESPONSE_500_FAILED_TO_DELETE_FROM_DATABASE(response);
}
else
{
M_RESPONSE_MSG(LOGGER_DEBUG, response, 200, "deleted macro");
}
macro_free(macro);
}

View file

@ -0,0 +1,85 @@
#include <cJSON.h>
#include <macros.h>
#include <constants.h>
#include <endpoints/api_v1_macros.h>
#include <logger.h>
#include <command.h>
#include <models/macro_action.h>
#include <models/macro.h>
#include <models/relay.h>
#include <models/tag.h>
void
api_v1_macros_STR_execute_PUT(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)nc;
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
M_RESPONSE_400_NO_VALID_ID(response);
return;
}
macro_t* macro = macro_get_by_uid(target_uid);
if(!macro)
{
M_RESPONSE_404_NO_MACRO_FOUND_FOR_ID(response);
return;
}
char *weekday_str = find_query_param(hm->query_string, "weekday");
macro_action_t** macro_actions;
if (weekday_str != NULL) {
errno = 0;
char *end;
long weekday = strtol(weekday_str, &end, 10);
bool weekday_str_is_end = weekday_str == end;
free(weekday_str);
if (errno != 0 || weekday_str_is_end || weekday < 0 || weekday >= 7) {
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the query contains an invalid weekday");
return;
}
macro_actions = macro_action_get_for_macro_and_weekday(macro->id, weekday);
}
else {
macro_actions = macro_action_get_for_macro(macro->id);
}
database_transaction_lock lock;
database_transaction_begin(&lock);
for(int i = 0; macro_actions[i] != NULL; ++i)
{
macro_action_execute(macro_actions[i]);
}
database_transaction_commit(&lock);
int *target_relay_ids = macro_get_target_relay_ids(macro->id);
for(int i = 0; target_relay_ids[i] != 0; ++i)
{
relay_t *target_relay = relay_get_by_id(target_relay_ids[i]);
if(!target_relay)
{
LOGGER_ERR("failed to load target relay from database\n");
continue;
}
command_relay_schedules_set(target_relay);
relay_free(target_relay);
}
free(target_relay_ids);
macro_action_free_list(macro_actions);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 200, "macro got executed");
}

View file

@ -16,17 +16,13 @@ api_v1_relays_tag_STR_GET(struct mg_connection *nc, struct http_message *hm, end
int tag_id = tag_get_id(args[0].value.v_str);
if(tag_id == 0)
{
static const char content[] = "tag was not found";
endpoint_response_text(response, 404, content, STRLEN(content));
M_RESPONSE_404_NO_TAG_FOUND(response);
return;
}
int *relays_ids = junction_tag_get_relays_for_tag_id(tag_id);
if(relays_ids == NULL)
{
LOGGER_ERR("failed to load relays for tag from database\n");
static const char content[] = "failed to load relays for tag from database";
endpoint_response_text(response, 500, content, STRLEN(content));
M_RESPONSE_500_FAILED_TO_READ_FROM_DATABASE(response);
return;
}

View file

@ -16,29 +16,25 @@ api_v1_schedules_POST(struct mg_connection *nc, struct http_message *hm, endpoin
if(json == NULL)
{
static const char content[] = "no valid json was supplied";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_400_NO_VALID_JSON(response);
return;
}
cJSON *json_name = cJSON_GetObjectItemCaseSensitive(json, "name");
if(!cJSON_IsString(json_name) || (json_name->valuestring == NULL))
{
LOGGER_DEBUG("no name for schedule provided\n");
cJSON_Delete(json);
static const char content[] = "no name for schedule provided";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_400_NO_NAME(response);
return;
}
cJSON *json_periods = cJSON_GetObjectItemCaseSensitive(json, "periods");
if(!cJSON_IsArray(json_periods))
{
LOGGER_DEBUG("no periods for schedule provided\n");
cJSON_Delete(json);
static const char content[] = "no periods for schedule provided";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain periods");
return;
}
@ -48,11 +44,9 @@ api_v1_schedules_POST(struct mg_connection *nc, struct http_message *hm, endpoin
{
if(!cJSON_IsString(json_tag) || (json_tag->valuestring == NULL))
{
LOGGER_DEBUG("invalid tag in tags\n");
cJSON_Delete(json);
static const char content[] = "invalid tag in tags";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one invalid tag");
return;
}
}
@ -78,22 +72,18 @@ api_v1_schedules_POST(struct mg_connection *nc, struct http_message *hm, endpoin
if(!cJSON_IsString(json_period_start) || (json_period_start->valuestring == NULL))
{
LOGGER_DEBUG("period is missing start\n");
cJSON_Delete(json);
schedule_free(new_schedule);
static const char content[] = "one period is missing a start";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period without a start");
return;
}
if(!cJSON_IsString(json_period_end) || (json_period_end->valuestring == NULL))
{
LOGGER_DEBUG("period is missing end\n");
cJSON_Delete(json);
schedule_free(new_schedule);
static const char content[] = "one period is missing an end";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period without an end");
return;
}
@ -101,22 +91,18 @@ api_v1_schedules_POST(struct mg_connection *nc, struct http_message *hm, endpoin
uint16_t end;
if(period_helper_parse_hhmm(json_period_start->valuestring, &start))
{
LOGGER_DEBUG("couldn't parse start '%s'\n", json_period_start->valuestring);
cJSON_Delete(json);
schedule_free(new_schedule);
static const char content[] = "the start for one period is invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period with an invalid start");
return;
}
if(period_helper_parse_hhmm(json_period_end->valuestring, &end))
{
LOGGER_DEBUG("couldn't parse end '%s'\n", json_period_end->valuestring);
cJSON_Delete(json);
schedule_free(new_schedule);
static const char content[] = "the end for one period is invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period with an invalid end");
return;
}

View file

@ -19,10 +19,7 @@ api_v1_schedules_STR_GET(struct mg_connection *nc, struct http_message *hm, endp
uuid_t target_uid;
if(schedule_uid_parse(args[0].value.v_str, target_uid))
{
LOGGER_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_400_NO_VALID_ID(response);
return;
}
@ -30,10 +27,7 @@ api_v1_schedules_STR_GET(struct mg_connection *nc, struct http_message *hm, endp
if(!schedule)
{
LOGGER_DEBUG("could not find a schedule for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no schedule for id found";
endpoint_response_text(response, 404, content, STRLEN(content));
M_RESPONSE_404_NO_SCHEDULE_FOUND_FOR_ID(response);
return;
}
@ -53,10 +47,7 @@ api_v1_schedules_STR_PUT(struct mg_connection *nc, struct http_message *hm, endp
uuid_t target_uid;
if(schedule_uid_parse(args[0].value.v_str, target_uid))
{
LOGGER_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_400_NO_VALID_ID(response);
return;
}
@ -64,10 +55,7 @@ api_v1_schedules_STR_PUT(struct mg_connection *nc, struct http_message *hm, endp
if(!schedule)
{
LOGGER_DEBUG("could not find a schedule for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no schedule for id found";
endpoint_response_text(response, 404, content, STRLEN(content));
M_RESPONSE_404_NO_SCHEDULE_FOUND_FOR_ID(response);
return;
}
@ -75,8 +63,7 @@ api_v1_schedules_STR_PUT(struct mg_connection *nc, struct http_message *hm, endp
if(json == NULL)
{
static const char content[] = "no valid json was supplied";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_400_NO_VALID_JSON(response);
return;
}
@ -137,12 +124,10 @@ api_v1_schedules_STR_PUT(struct mg_connection *nc, struct http_message *hm, endp
if(schedule_save(schedule))
{
LOGGER_ERR("failed to save schedule\n");
free(schedule);
cJSON_Delete(json);
static const char content[] = "failed to save schedule to database";
endpoint_response_text(response, 500, content, STRLEN(content));
M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response);
return;
}
@ -190,10 +175,7 @@ api_v1_schedules_STR_DELETE(struct mg_connection *nc, struct http_message *hm, e
uuid_t target_uid;
if(schedule_uid_parse(target_uid_str, target_uid))
{
LOGGER_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_400_NO_VALID_ID(response);
return;
}
@ -201,33 +183,25 @@ api_v1_schedules_STR_DELETE(struct mg_connection *nc, struct http_message *hm, e
if(!schedule)
{
LOGGER_DEBUG("could not find a schedule for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no schedule for id found";
endpoint_response_text(response, 404, content, STRLEN(content));
M_RESPONSE_404_NO_SCHEDULE_FOUND_FOR_ID(response);
return;
}
if(schedule_is_protected(schedule))
{
static const char content[] = "target schedule is protected";
endpoint_response_text(response, 403, content, STRLEN(content));
schedule_free(schedule);
M_RESPONSE_403_PROTECTED_SCHEDULE(response);
return;
}
if(schedule_remove(schedule))
{
LOGGER_ERR("failed to remove schedule from database\n");
static const char content[] = "failed to remove schedule from database";
endpoint_response_text(response, 500, content, STRLEN(content));
M_RESPONSE_500_FAILED_TO_DELETE_FROM_DATABASE(response);
}
else
{
endpoint_response_text(response, 200, "", 0);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 200, "the target schedule got deleted");
}
schedule_free(schedule);
return;
}

View file

@ -20,29 +20,23 @@ api_v1_schedules_list_POST(struct mg_connection *nc, struct http_message *hm, en
{
if(json == NULL)
{
static const char content[] = "no valid json was supplied";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_400_NO_VALID_JSON(response);
return;
}
cJSON *json_name = cJSON_GetObjectItemCaseSensitive(json, "name");
if(!cJSON_IsString(json_name) || (json_name->valuestring == NULL))
{
LOGGER_DEBUG("no name for schedule provided\n");
cJSON_Delete(json_list);
static const char content[] = "no name for schedule provided";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain a name for at least one schedule");
return;
}
cJSON *json_periods = cJSON_GetObjectItemCaseSensitive(json, "periods");
if(!cJSON_IsArray(json_periods))
{
LOGGER_DEBUG("no periods for schedule provided\n");
cJSON_Delete(json_list);
static const char content[] = "no periods for schedule provided";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain periods for at least one schedule");
return;
}
@ -52,11 +46,9 @@ api_v1_schedules_list_POST(struct mg_connection *nc, struct http_message *hm, en
{
if(!cJSON_IsString(json_tag) || (json_tag->valuestring == NULL))
{
LOGGER_DEBUG("invalid tag in tags\n");
cJSON_Delete(json_list);
static const char content[] = "invalid tag in tags";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one invalid tag");
return;
}
}
@ -82,22 +74,18 @@ api_v1_schedules_list_POST(struct mg_connection *nc, struct http_message *hm, en
if(!cJSON_IsString(json_period_start) || (json_period_start->valuestring == NULL))
{
LOGGER_DEBUG("period is missing start\n");
cJSON_Delete(json_list);
schedule_free(new_schedule);
static const char content[] = "one period is missing a start";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period without a start");
return;
}
if(!cJSON_IsString(json_period_end) || (json_period_end->valuestring == NULL))
{
LOGGER_DEBUG("period is missing end\n");
cJSON_Delete(json_list);
schedule_free(new_schedule);
static const char content[] = "one period is missing an end";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period without an end");
return;
}
@ -105,22 +93,18 @@ api_v1_schedules_list_POST(struct mg_connection *nc, struct http_message *hm, en
uint16_t end;
if(period_helper_parse_hhmm(json_period_start->valuestring, &start))
{
LOGGER_DEBUG("couldn't parse start '%s'\n", json_period_start->valuestring);
cJSON_Delete(json_list);
schedule_free(new_schedule);
static const char content[] = "the start for one period is invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period with an invalid start");
return;
}
if(period_helper_parse_hhmm(json_period_end->valuestring, &end))
{
LOGGER_DEBUG("couldn't parse end '%s'\n", json_period_end->valuestring);
cJSON_Delete(json_list);
schedule_free(new_schedule);
static const char content[] = "the end for one period is invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period with an invalid end");
return;
}

View file

@ -16,17 +16,13 @@ api_v1_schedules_tag_STR_GET(struct mg_connection *nc, struct http_message *hm,
int tag_id = tag_get_id(args[0].value.v_str);
if(tag_id == 0)
{
static const char content[] = "tag was not found";
endpoint_response_text(response, 404, content, STRLEN(content));
M_RESPONSE_404_NO_TAG_FOUND(response);
return;
}
int *schedules_ids = junction_tag_get_schedules_for_tag_id(tag_id);
if(schedules_ids == NULL)
{
LOGGER_ERR("failed to load schedules for tag from database\n");
static const char content[] = "failed to load schedules for tag from database";
endpoint_response_text(response, 500, content, STRLEN(content));
M_RESPONSE_500_FAILED_TO_READ_FROM_DATABASE(response);
return;
}

View file

@ -44,56 +44,54 @@ api_v1_tags_POST(struct mg_connection *nc, struct http_message *hm, endpoint_arg
if(json == NULL)
{
static const char content[] = "no valid json was supplied";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_400_NO_VALID_JSON(response);
return;
}
cJSON *json_tag = cJSON_GetObjectItemCaseSensitive(json, "tag");
if(!cJSON_IsString(json_tag) || (json_tag->valuestring == NULL))
{
LOGGER_DEBUG("no tag provided\n");
cJSON_Delete(json);
static const char content[] = "no tag provided";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
if(tag_get_id(json_tag->valuestring))
{
LOGGER_DEBUG("tag existed already\n");
cJSON_Delete(json);
static const char content[] = "tag existed already";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain a tag");
return;
}
if(strlen(json_tag->valuestring) == 0)
{
LOGGER_DEBUG("tag is empty\n");
cJSON_Delete(json);
static const char content[] = "tag is empty";
endpoint_response_text(response, 400, content, STRLEN(content));
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an empty tag");
return;
}
if(tag_get_id(json_tag->valuestring))
{
cJSON_Delete(json);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the tag does already exist");
return;
}
if(tag_save(0, json_tag->valuestring))
{
LOGGER_DEBUG("tag could not be saved\n");
static const char content[] = "tag could not be saved";
endpoint_response_text(response, 500, content, STRLEN(content));
M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response);
}
else
{
LOGGER_DEBUG("new tag saved\n");
endpoint_response_text(response, 201, json_tag->valuestring, 0);
size_t tag_len = strlen(json_tag->valuestring);
// NOLINT(clang-analyzer-unix.Malloc): The endpoint response will be freed later.
char *tag = malloc(sizeof(char) * (tag_len + 1));
strcpy(tag, json_tag->valuestring);
tag[tag_len] = '\0';
endpoint_response_text(response, 201, tag, 0);
free(tag);
}
cJSON_Delete(json);
return;
}

View file

@ -16,26 +16,19 @@ api_v1_tags_STR_GET(struct mg_connection *nc, struct http_message *hm, endpoint_
int tag_id = tag_get_id(args[0].value.v_str);
if(tag_id == 0)
{
static const char content[] = "tag was not found";
endpoint_response_text(response, 404, content, STRLEN(content));
M_RESPONSE_404_NO_TAG_FOUND(response);
return;
}
int *relays_ids = junction_tag_get_relays_for_tag_id(tag_id);
if(relays_ids == NULL)
{
LOGGER_ERR("failed to load relays for tag from database\n");
static const char content[] = "failed to load relays for tag from database";
endpoint_response_text(response, 500, content, STRLEN(content));
M_RESPONSE_500_FAILED_TO_READ_FROM_DATABASE(response);
return;
}
int *schedules_ids = junction_tag_get_schedules_for_tag_id(tag_id);
if(schedules_ids == NULL)
{
LOGGER_ERR("failed to load schedules for tag from database\n");
static const char content[] = "failed to load schedules for tag from database";
endpoint_response_text(response, 500, content, STRLEN(content));
M_RESPONSE_500_FAILED_TO_READ_FROM_DATABASE(response);
return;
}
@ -91,20 +84,16 @@ api_v1_tags_STR_DELETE(struct mg_connection *nc, struct http_message *hm, endpoi
int tag_id = tag_get_id(args[0].value.v_str);
if(tag_id == 0)
{
static const char content[] = "tag was not found";
endpoint_response_text(response, 404, content, STRLEN(content));
M_RESPONSE_404_NO_TAG_FOUND(response);
return;
}
if(tag_remove(tag_id))
{
LOGGER_ERR("failed to remove tag from database\n");
static const char content[] = "failed to remove tag from database";
endpoint_response_text(response, 500, content, STRLEN(content));
M_RESPONSE_500_FAILED_TO_DELETE_FROM_DATABASE(response);
}
else
{
endpoint_response_text(response, 200, "", 0);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 200, "the tag got deleted");
}
}

View file

@ -17,16 +17,17 @@ static char*
add_extra_headers(char *extra_headers)
{
char *result;
size_t std_headers_len = strlen(global_config.http_server_opts.extra_headers);
size_t std_headers_len = strlen(global_config->http_server_opts.extra_headers);
if(extra_headers == NULL)
{
result = malloc(sizeof(char) * (std_headers_len + 1));
strcpy(result, global_config.http_server_opts.extra_headers);
strcpy(result, global_config->http_server_opts.extra_headers);
result[std_headers_len] = '\0';
return result;
}
result = malloc(sizeof(char) * (std_headers_len + strlen(extra_headers) + 3));
sprintf(result, "%s\r\n%s", global_config.http_server_opts.extra_headers, extra_headers);
sprintf(result, "%s\r\n%s", global_config->http_server_opts.extra_headers, extra_headers);
return result;
}
@ -44,11 +45,6 @@ send_response(struct mg_connection *nc, endpoint_response_t *response)
free(response_headers);
free(extra_headers);
if(response->alloced_content)
{
free((char*)response->content);
}
}
}
@ -82,15 +78,18 @@ handle_http_request(struct mg_connection *nc, struct http_message *hm)
endpoint_t *endpoint = router_find_endpoint(hm->uri.p, hm->uri.len, &hm->method);
endpoint_response_t response;
static const char content[] = "the server did not create a response";
endpoint_response_text(&response, 500, content, STRLEN(content));
response.content = NULL;
response.alloced_content = false;
M_RESPONSE_MSG(LOGGER_NONE, &response, 500, "server did not create a response");
if(!endpoint)
{
/* Normalize path - resolve "." and ".." (in-place). */
if (!mg_normalize_uri_path(&hm->uri, &hm->uri)) {
mg_http_send_error(nc, 400, global_config.http_server_opts.extra_headers);
mg_http_send_error(nc, 400, global_config->http_server_opts.extra_headers);
LOGGER_DEBUG("failed to normalize uri %.*s\n", hm->uri.len, hm->uri.p);
endpoint_response_free_content(&response);
return;
}
LOGGER_DEBUG("no endpoint found - serving file\n");
@ -105,24 +104,23 @@ handle_http_request(struct mg_connection *nc, struct http_message *hm)
++request_file;
}
char *request_file_path = malloc(sizeof(char) * (strlen(request_file) + strlen(global_config.content_dir) + 2));
sprintf(request_file_path, "%s/%s", global_config.content_dir, request_file);
char *request_file_path = malloc(sizeof(char) * (strlen(request_file) + strlen(global_config->content_dir) + 2));
sprintf(request_file_path, "%s/%s", global_config->content_dir, request_file);
int access_result = access(request_file_path, R_OK);
free(request_file_path);
free(request_file_org);
if(access_result != -1)
{
response.status_code = 0;
mg_serve_http(nc, hm, global_config.http_server_opts);
mg_serve_http(nc, hm, global_config->http_server_opts);
LOGGER_DEBUG("serving %.*s\n", hm->uri.len, hm->uri.p);
endpoint_response_free_content(&response);
return;
}
else
{
LOGGER_DEBUG("serving 'not found'\n");
endpoint = router_get_not_found_endpoint();
}
}
if(endpoint->method == HTTP_METHOD_OPTIONS)
{
@ -148,6 +146,7 @@ handle_http_request(struct mg_connection *nc, struct http_message *hm)
send_response(nc, &response);
}
endpoint_response_free_content(&response);
LOGGER_DEBUG("freeing endpoint args\n");
for(int i = 0; i < endpoint->args_count; ++i)
{

View file

@ -9,7 +9,7 @@
#include <models/relay.h>
static void
handle_mqtt_publish_controller(char **topic_save, int controller_id, char *payload)
handle_mqtt_publish_controller(char **topic_save, int controller_id, const char *payload)
{
(void)controller_id;
(void)payload;

View file

@ -13,8 +13,11 @@ helper_connect_tcp_server(char* host, uint16_t port)
char port_str[6];
sprintf(port_str, "%d", port);
int s, status;
struct addrinfo hints, *res;
int s;
int status;
struct addrinfo *res;
struct addrinfo hints;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET; //set IP Protocol flag (IPv4 or IPv6 - we don't care)
hints.ai_socktype = SOCK_STREAM; //set socket flag
@ -27,7 +30,7 @@ helper_connect_tcp_server(char* host, uint16_t port)
//res got filled out by getaddrinfo() for us
s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); //creating Socket
if ((status = connect(s, res->ai_addr, res->ai_addrlen)) != 0) {
if (connect(s, res->ai_addr, res->ai_addrlen) != 0) {
LOGGER_ERR("connect() failed\n");
freeaddrinfo(res);
return -1;

View file

@ -15,21 +15,20 @@ get_uid_for_user(char *user)
{
return getuid();
}
struct passwd *pwd = calloc(1, sizeof(struct passwd));
struct passwd pwd;
struct passwd *result = NULL;
size_t buffer_len = sysconf(_SC_GETPW_R_SIZE_MAX) * sizeof(char);
char *buffer = malloc(buffer_len);
getpwnam_r(user, pwd, buffer, buffer_len, &pwd);
getpwnam_r(user, &pwd, buffer, buffer_len, &result);
if(pwd == NULL)
if(result == NULL)
{
LOGGER_CRIT("couldn't find user to drop privileges\n");
exit(1);
}
uid_t result = pwd->pw_uid;
free(buffer);
free(pwd);
return result;
return result->pw_uid;
}
static gid_t
@ -39,28 +38,27 @@ get_gid_for_group(char *group)
{
return getgid();
}
struct group *grp = calloc(1, sizeof(struct group));
struct group grp;
struct group *result = NULL;
size_t buffer_len = sysconf(_SC_GETPW_R_SIZE_MAX) * sizeof(char);
char *buffer = malloc(buffer_len);
getgrnam_r(group, grp, buffer, buffer_len, &grp);
getgrnam_r(group, &grp, buffer, buffer_len, &result);
if(grp == NULL)
if(result == NULL)
{
LOGGER_CRIT("couldn't find group to drop privileges\n");
exit(1);
}
gid_t result = grp->gr_gid;
free(buffer);
free(grp);
return result;
return result->gr_gid;
}
int
helper_drop_privileges()
{
uid_t uid = get_uid_for_user(global_config.user);
gid_t gid = get_gid_for_group(global_config.group);
uid_t uid = get_uid_for_user(global_config->user);
gid_t gid = get_gid_for_group(global_config->group);
LOGGER_DEBUG("drop privileges to %lu:%lu\n", uid, gid);

View file

@ -0,0 +1,37 @@
#include <string.h>
#include <mongoose.h>
char*
find_query_param(struct mg_str query_mg_str, char* search_key)
{
if (query_mg_str.len == 0) {
return NULL;
}
// Convert query string to null-terminated string
char* query = malloc(sizeof(char) * (query_mg_str.len + 1));
strncpy(query, query_mg_str.p, query_mg_str.len);
query[query_mg_str.len] = '\0';
char* result = NULL;
// Tokenize query string
char *token = strtok(query, "&");
while (token != NULL) {
char *key = strtok(token, "=");
char *val = strtok(NULL, "=");
if (key != NULL && val != NULL) {
if (strcmp(key, search_key) == 0) {
size_t val_len = strlen(val);
result = malloc(sizeof(char) * (val_len + 1));
strncpy(result, val, val_len + 1);
result[val_len] = '\0'; // Ensure null termination
break;
}
}
token = strtok(NULL, "&");
}
free(query);
return result;
}

View file

@ -1,11 +1,12 @@
#include <time.h>
#include <constants.h>
#include <helpers.h>
int
helper_get_weekday(const struct tm *time_struct)
{
int wday_sun_sat = time_struct->tm_wday;
int wday_mon_sun = (wday_sun_sat + 6) % 7;
int wday_mon_sun = (wday_sun_sat + (DAYS_PER_WEEK - 1)) % DAYS_PER_WEEK;
return wday_mon_sun;
}

View file

@ -17,10 +17,11 @@ const char *COLOR_EMERG = COLOR_MAGENTA;
void
logger_log(int level, const char *filename, int line, const char *func, const char *msg, ...)
{
if(global_config.log_level < level)
if(global_config->logging.level < level || level == LOG_NONE )
{
return;
}
va_list args;
const char *level_str;
const char *color;
@ -62,24 +63,34 @@ logger_log(int level, const char *filename, int line, const char *func, const ch
time_t rawtime;
time(&rawtime);
strftime(timestamp_str, 32, "%Y-%m-%d %H:%M:%S", localtime(&rawtime));
size_t timestamp_len = strlen(timestamp_str);
char *buffer = malloc(sizeof(char) * (128 + strlen(msg)));
sprintf(buffer, "%s[%5s] %s:%d:%s " COLOR_NONE "%s", color, level_str, filename, line, func, msg);
size_t buffer_size = 128;
buffer_size += timestamp_len;
buffer_size += strlen(filename);
buffer_size += strlen(func);
buffer_size += strlen(msg);
//fprintf(stream, "%s %s:%d:%s " COLOR_NONE, timestamp_str, filename, line, func);
char *buffer = malloc(sizeof(char) * (buffer_size));
sprintf(buffer, "%s %s[%5s] %s:%d:%s " COLOR_NONE "%s", timestamp_str, color, level_str, filename, line, func, msg);
va_list args;
// start arg va_list and find log_len
va_start(args, msg);
vsyslog(level, buffer, args);
size_t log_len = vsnprintf(NULL, 0, buffer, args); // NOLINT(clang-analyzer-valist.Uninitialized): clang-tidy bug
va_end(args);
char *buffer_timed = malloc(sizeof(char) * (strlen(timestamp_str) + strlen(buffer) + 2));
sprintf(buffer_timed, "%s %s", timestamp_str, buffer);
char *log_line = malloc(sizeof(char) * (log_len + 1));
// start arg va_list again and write log_line
va_start(args, msg);
vfprintf(global_config.log_file, buffer_timed, args);
fflush(global_config.log_file);
vsprintf(log_line, buffer, args); // NOLINT(clang-analyzer-valist.Uninitialized): clang-tidy bug
va_end(args);
syslog(level, "%s", log_line + timestamp_len + 1);
fprintf(global_config->logging.file, "%s", log_line);
fflush(global_config->logging.file);
free(buffer);
free(buffer_timed);
free(log_line);
}

View file

@ -1,28 +1,28 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <syslog.h>
#include <mongoose.h>
#include <confini.h>
#include <cache.h>
#include <router.h>
#include <logger.h>
#include <cli.h>
#include <config.h>
#include <database.h>
#include <handlers.h>
#include <enums.h>
#include <handlers.h>
#include <helpers.h>
#include <status.h>
#include <logger.h>
#include <models/controller.h>
#include <router.h>
#include <status.h>
static struct mg_mgr mgr;
static void
terminate(int signum)
{
LOGGER_INFO("terminating controller (%d)\n", signum);
LOGGER_INFO("terminating core (%d)\n", signum);
mg_mgr_free(&mgr);
@ -30,6 +30,7 @@ terminate(int signum)
router_free();
status_free();
cache_free();
config_free();
closelog();
@ -51,54 +52,27 @@ main(int argc, const char** argv)
signal(SIGABRT, terminate);
signal(SIGTERM, terminate);
setlogmask(LOG_UPTO(LOG_INFO));
openlog("emgauwa-core", 0, LOG_USER);
setlogmask(LOG_UPTO(LOG_DEBUG));
/******************** LOAD CONFIG ********************/
global_config.file = "core.ini";
global_config.discovery_port = 4421;
global_config.mqtt_port = 1885;
global_config.server_port = 5000;
config_init();
global_config.log_level = LOG_INFO;
global_config.log_file = stdout;
cli_t cli;
cli_parse(argc, argv, &cli);
strcpy(global_config.user, "");
strcpy(global_config.group, "");
config_load(global_config, cli.config_file);
strcpy(global_config.content_dir, ".");
strcpy(global_config.not_found_file, "404.html");
strcpy(global_config.not_found_file_type, "text/html");
strcpy(global_config.not_found_content, "404 - NOT FOUND");
strcpy(global_config.not_found_content_type, "text/plain");
helper_parse_cli(argc, argv, &global_config);
FILE * const ini_file = fopen(global_config.file, "rb");
if(ini_file == NULL)
if(global_config->logging.file == NULL)
{
LOGGER_CRIT("config file '%s' was not found\n", global_config.file);
exit(1);
}
if(load_ini_file( ini_file, INI_DEFAULT_FORMAT, NULL, config_load, &global_config))
{
LOGGER_CRIT("unable to parse ini file\n");
exit(1);
global_config->logging.file = stdout;
}
fclose(ini_file);
memset(&global_config.http_server_opts, 0, sizeof(global_config.http_server_opts));
global_config.http_server_opts.document_root = global_config.content_dir;
global_config.http_server_opts.enable_directory_listing = "no";
global_config.http_server_opts.extra_headers = "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Headers: *\r\nAccess-Control-Allow-Methods: *";
if(global_config.log_file == NULL)
if(global_config->include)
{
global_config.log_file = stdout;
config_load_directory(global_config, global_config->include);
}
openlog("emgauwa-core", 0, LOG_USER);
/******************** SETUP CONNECTION ********************/
@ -108,20 +82,20 @@ main(int argc, const char** argv)
mg_mgr_init(&mgr, NULL);
char address[100];
sprintf(address, "tcp://0.0.0.0:%u", global_config.server_port);
sprintf(address, "tcp://0.0.0.0:%u", global_config->ports.server);
struct mg_connection *c_http = mg_bind(&mgr, address, handler_http);
if(c_http == NULL)
{
LOGGER_CRIT("failed to bind http server to port %u\n", global_config.server_port);
LOGGER_CRIT("failed to bind http server to port %u\n", global_config->ports.server);
exit(1);
}
mg_set_protocol_http_websocket(c_http);
sprintf(address, "tcp://0.0.0.0:%u", global_config.mqtt_port);
sprintf(address, "tcp://0.0.0.0:%u", global_config->ports.mqtt);
struct mg_connection *c_mqtt = mg_bind(&mgr, address, handler_mqtt);
if(c_mqtt == NULL)
{
LOGGER_CRIT("failed to bind mqtt server to port %u\n", global_config.mqtt_port);
LOGGER_CRIT("failed to bind mqtt server to port %u\n", global_config->ports.mqtt);
exit(1);
}
mg_mqtt_broker_init(&brk, NULL);
@ -130,6 +104,11 @@ main(int argc, const char** argv)
helper_drop_privileges();
memset(&global_config->http_server_opts, 0, sizeof(global_config->http_server_opts));
global_config->http_server_opts.document_root = global_config->content_dir;
global_config->http_server_opts.enable_directory_listing = "no";
global_config->http_server_opts.extra_headers = "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Headers: *\r\nAccess-Control-Allow-Methods: *";
/******************** INIT COMPONENTS ********************/

View file

@ -35,6 +35,8 @@ static controller_t*
controller_db_select_mapper(sqlite3_stmt *stmt)
{
controller_t *new_controller = malloc(sizeof(controller_t));
new_controller->id = 0;
for(int i = 0; i < sqlite3_column_count(stmt); i++)
{
const char *name = sqlite3_column_name(stmt, i);
@ -50,14 +52,16 @@ controller_db_select_mapper(sqlite3_stmt *stmt)
new_controller->id = sqlite3_column_int(stmt, i);
break;
case 'p': // ip
strncpy(new_controller->ip, (const char*)sqlite3_column_text(stmt, i), 16);
strncpy(new_controller->ip, (const char*)sqlite3_column_text(stmt, i), IP_LENGTH);
new_controller->ip[IP_LENGTH] = '\0';
break;
default: // ignore columns not implemented
break;
}
break;
case 'n': // name
strncpy(new_controller->name, (const char*)sqlite3_column_text(stmt, i), 127);
strncpy(new_controller->name, (const char*)sqlite3_column_text(stmt, i), MAX_NAME_LENGTH);
new_controller->name[MAX_NAME_LENGTH] = '\0';
break;
case 'p': // port
new_controller->port = sqlite3_column_int(stmt, i);
@ -102,13 +106,11 @@ controller_db_select(sqlite3_stmt *stmt)
{
break;
}
else
{
LOGGER_ERR("error selecting controllers from database: %s\n", sqlite3_errstr(s));
break;
}
}
}
sqlite3_finalize(stmt);
all_controllers[row] = NULL;
return all_controllers;
@ -117,7 +119,8 @@ controller_db_select(sqlite3_stmt *stmt)
int
controller_save(controller_t *controller)
{
int opened_transaction = database_transaction_begin();
database_transaction_lock lock;
database_transaction_begin(&lock);
sqlite3_stmt *stmt;
if(controller->id)
@ -142,10 +145,7 @@ controller_save(controller_t *controller)
LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database));
}
if(opened_transaction)
{
database_transaction_rollback();
}
database_transaction_rollback(&lock);
}
else
{
@ -154,10 +154,7 @@ controller_save(controller_t *controller)
controller->id = sqlite3_last_insert_rowid(global_database);
}
if(opened_transaction)
{
database_transaction_commit();
}
database_transaction_commit(&lock);
}
cache_invalidate_controller(controller->id);

View file

@ -40,14 +40,16 @@ junction_relay_schedule_insert_weekdays(int relay_id, int *schedule_ids)
static const char query_base[] = "INSERT INTO junction_relay_schedule (weekday, schedule_id, relay_id) VALUES";
static const char query_extender[] = " (?, ?, ?)";
size_t query_len = STRLEN(query_base) + (7 * (STRLEN(query_extender) + 1)) + 1;
size_t query_len = M_STRLEN(query_base) + (7 * (M_STRLEN(query_extender) + 1)) + 1;
char *query = malloc(sizeof(char) * query_len + 1);
strncpy(query, query_base, query_len);
query_len -= STRLEN(query_base);
query[query_len] = '\0';
query_len -= M_STRLEN(query_base);
for(int i = 0; i < 7; ++i)
{
strncat(query, query_extender, query_len);
query_len -= STRLEN(query_extender);
query_len -= M_STRLEN(query_extender);
char *query_divider = (i < 7 - 1) ? "," : ";";
strncat(query, query_divider, query_len);
query_len -= 1;
@ -62,16 +64,18 @@ junction_relay_schedule_insert_weekdays(int relay_id, int *schedule_ids)
sqlite3_bind_int(stmt, i * 3 + 3, relay_id);
}
free(query);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
LOGGER_ERR("error inserting data: %s", sqlite3_errmsg(global_database));
return false;
return 1;
}
sqlite3_finalize(stmt);
return true;
return 0;
}
int
@ -87,3 +91,14 @@ junction_relay_schedule_remove_for_relay(int relay_id)
return rc == SQLITE_DONE;
}
int*
junction_relay_schedule_get_relay_ids_with_schedule(int schedule_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT DISTINCT relay_id FROM junction_relay_schedule WHERE schedule_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, schedule_id);
return database_helper_get_ids(stmt);
}

View file

@ -67,14 +67,16 @@ junction_tag_insert_list(int *tag_ids, int relay_id, int schedule_id, int count)
static const char query_base[] = "INSERT INTO junction_tag(tag_id, schedule_id, relay_id) VALUES";
static const char query_extender[] = " (?, ?, ?)";
size_t query_len = STRLEN(query_base) + (count * (STRLEN(query_extender) + 1)) + 1;
char *query = malloc(sizeof(char) * query_len + 1);
size_t query_len = M_STRLEN(query_base) + (count * (M_STRLEN(query_extender) + 1)) + 1;
char *query = malloc(sizeof(char) * (query_len + 1));
strncpy(query, query_base, query_len);
query_len -= STRLEN(query_base);
query[query_len] = '\0';
query_len -= M_STRLEN(query_base);
for(int i = 0; i < count; ++i)
{
strncat(query, query_extender, query_len);
query_len -= STRLEN(query_extender);
query_len -= M_STRLEN(query_extender);
char *query_divider = (i < count - 1) ? "," : ";";
strncat(query, query_divider, query_len);
query_len -= 1;
@ -105,6 +107,8 @@ junction_tag_insert_list(int *tag_ids, int relay_id, int schedule_id, int count)
}
}
free(query);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{

328
src/models/macro.c Normal file
View file

@ -0,0 +1,328 @@
#include <stdlib.h>
#include <string.h>
#include <sqlite3.h>
#include <cache.h>
#include <cJSON.h>
#include <logger.h>
#include <database.h>
#include <models/macro.h>
#include <models/macro_action.h>
#include <models/schedule.h>
#include <models/tag.h>
static int
db_update_insert(macro_t *macro, sqlite3_stmt *stmt)
{
LOGGER_DEBUG("saving macro '%s' into database (id: %d)\n", macro->name, macro->id);
int rc;
sqlite3_bind_int(stmt, 1, macro->id);
sqlite3_bind_blob(stmt, 2, macro->uid, sizeof(uuid_t), SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, macro->name, -1, SQLITE_STATIC);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc != SQLITE_DONE;
}
static macro_t*
macro_db_select_mapper(sqlite3_stmt *stmt)
{
macro_t *new_macro = malloc(sizeof(macro_t));
for(int i = 0; i < sqlite3_column_count(stmt); i++)
{
const char *name = sqlite3_column_name(stmt, i);
switch(name[0])
{
case 'i': // id
new_macro->id = sqlite3_column_int(stmt, i);
break;
case 'n': // name
strncpy(new_macro->name, (const char*)sqlite3_column_text(stmt, i), MAX_NAME_LENGTH);
new_macro->name[MAX_NAME_LENGTH] = '\0';
break;
case 'u': // uid
uuid_copy(new_macro->uid, (const unsigned char*)sqlite3_column_blob(stmt, i));
break;
default: // ignore columns not implemented
break;
}
}
return new_macro;
}
static macro_t**
macro_db_select(sqlite3_stmt *stmt)
{
macro_t **all_macros = malloc(sizeof(macro_t*));
int row = 0;
for(;;)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
macro_t *new_macro = macro_db_select_mapper(stmt);
row++;
all_macros = (macro_t**)realloc(all_macros, sizeof(macro_t*) * (row + 1));
all_macros[row - 1] = new_macro;
}
else
{
if(s == SQLITE_DONE)
{
break;
}
LOGGER_ERR("error selecting macros from database: %s\n", sqlite3_errstr(s));
break;
}
}
sqlite3_finalize(stmt);
all_macros[row] = NULL;
return all_macros;
}
int
macro_save(macro_t *macro)
{
database_transaction_lock lock;
database_transaction_begin(&lock);
sqlite3_stmt *stmt;
if(macro->id)
{
sqlite3_prepare_v2(global_database, "UPDATE macros SET uid = ?2, name = ?3 WHERE id=?1;", -1, &stmt, NULL);
}
else
{
sqlite3_prepare_v2(global_database, "INSERT INTO macros(uid, name) values (?2, ?3);", -1, &stmt, NULL);
}
int result = db_update_insert(macro, stmt);
if(result)
{
if(macro->id)
{
LOGGER_ERR("error inserting data: %s\n", sqlite3_errmsg(global_database));
}
else
{
LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database));
}
database_transaction_rollback(&lock);
}
else
{
if(!macro->id)
{
macro->id = sqlite3_last_insert_rowid(global_database);
}
database_transaction_commit(&lock);
}
cache_invalidate_macro(macro->id);
return result;
}
int
macro_remove(macro_t *macro)
{
sqlite3_stmt *stmt;
if(macro->id == 0)
{
return 0;
}
sqlite3_prepare_v2(global_database, "DELETE FROM macros WHERE id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, macro->id);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
cache_invalidate_macro(macro->id);
return rc != SQLITE_DONE;
}
void
macro_free(macro_t *macro)
{
free(macro);
}
void
macro_free_list(macro_t **macros)
{
for(int i = 0; macros[i] != NULL; ++i)
{
macro_free(macros[i]);
}
free(macros);
}
cJSON*
macro_to_json(macro_t *macro)
{
cJSON *json;
char *cached = cache_get_json_macro(macro->id);
if(cached)
{
json = cJSON_CreateRaw(cached);
free(cached);
return json;
}
char uuid_str[UUID_STR_LEN];
uuid_unparse(macro->uid, uuid_str);
LOGGER_DEBUG("JSONifying macro %s\n", uuid_str);
json = cJSON_CreateObject();
cJSON *json_name = cJSON_CreateString(macro->name);
if(json_name == NULL)
{
cJSON_Delete(json);
return NULL;
}
cJSON_AddItemToObject(json, "name", json_name);
cJSON *json_id = cJSON_CreateString(uuid_str);
if(json_name == NULL)
{
cJSON_Delete(json);
return NULL;
}
cJSON_AddItemToObject(json, "id", json_id);
cJSON *json_actions = cJSON_CreateArray();
macro_action_t **macro_actions = macro_action_get_for_macro(macro->id);
for(int i = 0; macro_actions[i] != NULL; ++i)
{
cJSON *json_action = cJSON_CreateObject();
cJSON *json_action_weekday = cJSON_CreateNumber(macro_actions[i]->weekday);
if(json_action_weekday == NULL)
{
LOGGER_DEBUG("failed to create weekday from number %d\n", macro_actions[i]->weekday);
continue;
}
relay_t *relay = relay_get_by_id(macro_actions[i]->relay_id);
if(!relay)
{
LOGGER_DEBUG("failed to get relay\n");
continue;
}
schedule_t *schedule = schedule_get_by_id(macro_actions[i]->schedule_id);
if(!schedule)
{
LOGGER_DEBUG("failed to get schedule\n");
relay_free(relay);
continue;
}
cJSON_AddItemToObject(json_action, "weekday", json_action_weekday);
cJSON_AddItemToObject(json_action, "relay", relay_to_json(relay, 0));
cJSON_AddItemToObject(json_action, "schedule", schedule_to_json(schedule));
cJSON_AddItemToArray(json_actions, json_action);
}
cJSON_AddItemToObject(json, "actions", json_actions);
macro_action_free_list(macro_actions);
char *json_str = cJSON_Print(json);
cache_put_json_macro(macro->id, json_str);
cJSON_Delete(json);
json = cJSON_CreateRaw(json_str);
free(json_str);
return json;
}
macro_t*
macro_get_by_id(int id)
{
LOGGER_DEBUG("getting macro [id=%d] from database\n", id);
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM macros WHERE id = ?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, id);
macro_t **sql_result = macro_db_select(stmt);
macro_t *result = sql_result[0];
free(sql_result);
return result;
}
macro_t*
macro_get_by_uid(uuid_t uid)
{
char uuid_str[UUID_STR_LEN];
uuid_unparse(uid, uuid_str);
LOGGER_DEBUG("getting macro [uid=%s] from database\n", uuid_str);
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM macros WHERE uid = ?1;", -1, &stmt, NULL);
sqlite3_bind_blob(stmt, 1, uid, sizeof(uuid_t), SQLITE_STATIC);
macro_t **sql_result = macro_db_select(stmt);
macro_t *result = sql_result[0];
free(sql_result);
return result;
}
macro_t**
macro_get_relay_weekdays(int relay_id)
{
LOGGER_DEBUG("getting macros [relay_id=%d] from database\n", relay_id);
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT macros.* FROM macros INNER JOIN junction_relay_macro ON macros.id == junction_relay_macro.macro_id WHERE junction_relay_macro.relay_id = ?1 ORDER BY junction_relay_macro.weekday ASC", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, relay_id);
return macro_db_select(stmt);
}
macro_t**
macro_get_all()
{
LOGGER_DEBUG("getting all macros from database\n");
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM macros;", -1, &stmt, NULL);
return macro_db_select(stmt);
}
int*
macro_get_target_relay_ids(int macro_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT DISTINCT relay_id FROM macro_actions WHERE macro_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, macro_id);
return database_helper_get_ids(stmt);
}

189
src/models/macro_action.c Normal file
View file

@ -0,0 +1,189 @@
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <models/macro_action.h>
#include <logger.h>
#include <cache.h>
#include <database.h>
static macro_action_t*
macro_action_db_select_mapper(sqlite3_stmt *stmt)
{
macro_action_t *new_macro_action = malloc(sizeof(macro_action_t));
for(int i = 0; i < sqlite3_column_count(stmt); i++)
{
const char *name = sqlite3_column_name(stmt, i);
switch(name[0])
{
case 'm': // macro_id
new_macro_action->macro_id = sqlite3_column_int(stmt, i);
break;
case 'r': // relay_id
new_macro_action->relay_id = sqlite3_column_int(stmt, i);
break;
case 's': // schedule_id
new_macro_action->schedule_id = sqlite3_column_int(stmt, i);
break;
case 'w': // weekday
new_macro_action->weekday = (uint8_t)sqlite3_column_int(stmt, i);
break;
default: // ignore columns not implemented
break;
}
}
return new_macro_action;
}
static macro_action_t**
macro_action_db_select(sqlite3_stmt *stmt)
{
macro_action_t **all_macro_actions = malloc(sizeof(macro_action_t*));
int row = 0;
for(;;)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
macro_action_t *new_macro_action = macro_action_db_select_mapper(stmt);
row++;
all_macro_actions = (macro_action_t**)realloc(all_macro_actions, sizeof(macro_action_t*) * (row + 1));
all_macro_actions[row - 1] = new_macro_action;
}
else
{
if(s == SQLITE_DONE)
{
break;
}
LOGGER_ERR("error selecting macro_actions from database: %s\n", sqlite3_errstr(s));
break;
}
}
sqlite3_finalize(stmt);
all_macro_actions[row] = NULL;
return all_macro_actions;
}
int
macro_action_insert(macro_action_t *macro_action)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "INSERT INTO macro_actions(macro_id, relay_id, schedule_id, weekday) values (?1, ?2, ?3, ?4);", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, macro_action->macro_id);
sqlite3_bind_int(stmt, 2, macro_action->relay_id);
sqlite3_bind_int(stmt, 3, macro_action->schedule_id);
sqlite3_bind_int(stmt, 4, macro_action->weekday);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
cache_invalidate_macro(macro_action->macro_id);
return rc != SQLITE_DONE;
}
int
macro_action_delete_for_macro(int macro_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "DELETE FROM macro_actions WHERE macro_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, macro_id);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
cache_invalidate_macro(macro_id);
return rc != SQLITE_DONE;
}
macro_action_t**
macro_action_get_for_macro(int macro_id)
{
LOGGER_DEBUG("getting macro_actions [macro_id=%d] from database\n", macro_id);
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM macro_actions WHERE macro_id=?", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, macro_id);
return macro_action_db_select(stmt);
}
macro_action_t**
macro_action_get_for_macro_and_weekday(int macro_id, int weekday)
{
LOGGER_DEBUG("getting macro_actions [macro_id=%d weekday=%d] from database\n", macro_id, weekday);
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM macro_actions WHERE macro_id=? AND weekday=?", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, macro_id);
sqlite3_bind_int(stmt, 2, weekday);
return macro_action_db_select(stmt);
}
int
macro_action_execute(macro_action_t *macro_action)
{
schedule_t *schedule = schedule_get_by_id(macro_action->schedule_id);
if(!schedule)
{
return 1;
}
relay_t *relay = relay_get_by_id(macro_action->relay_id);
if(!relay)
{
free(schedule);
return 1;
}
schedule_free(relay->schedules[macro_action->weekday]);
relay->schedules[macro_action->weekday] = schedule;
return relay_save(relay);
}
void
macro_action_free_list(macro_action_t **macro_actions)
{
for(int i = 0; macro_actions[i] != NULL; ++i)
{
free(macro_actions[i]);
}
free(macro_actions);
}
int*
macro_action_get_macro_ids_with_schedule(int schedule_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT macro_id FROM macro_actions WHERE schedule_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, schedule_id);
return database_helper_get_ids(stmt);
}
int*
macro_action_get_macro_ids_with_relay(int relay_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT macro_id FROM macro_actions WHERE relay_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, relay_id);
return database_helper_get_ids(stmt);
}

View file

@ -27,9 +27,8 @@ period_helper_parse_hhmm(const char *hhmm_str, uint16_t *hhmm)
}
}
uint16_t tmp_h, tmp_m;
tmp_h = (uint16_t)strtol(&hhmm_str[0], NULL, 10);
tmp_m = (uint16_t)strtol(&hhmm_str[3], NULL, 10);
uint16_t tmp_h = (uint16_t)strtol(&hhmm_str[0], NULL, 10);
uint16_t tmp_m = (uint16_t)strtol(&hhmm_str[3], NULL, 10);
if(tmp_h > 24 || tmp_m >= 60)
{

View file

@ -1,18 +1,19 @@
#include <sqlite3.h>
#include <stdlib.h>
#include <string.h>
#include <sqlite3.h>
#include <cache.h>
#include <cJSON.h>
#include <logger.h>
#include <cache.h>
#include <constants.h>
#include <database.h>
#include <status.h>
#include <models/relay.h>
#include <logger.h>
#include <models/controller.h>
#include <models/schedule.h>
#include <models/junction_tag.h>
#include <models/junction_relay_schedule.h>
#include <models/junction_tag.h>
#include <models/relay.h>
#include <models/schedule.h>
#include <models/tag.h>
#include <status.h>
static int
db_update_insert(relay_t *relay, sqlite3_stmt *stmt)
@ -35,6 +36,7 @@ static relay_t*
relay_db_select_mapper(sqlite3_stmt *stmt)
{
relay_t *new_relay = malloc(sizeof(relay_t));
new_relay->id = 0;
new_relay->is_on = 0;
for(int i = 0; i < sqlite3_column_count(stmt); i++)
@ -52,7 +54,8 @@ relay_db_select_mapper(sqlite3_stmt *stmt)
switch(name[1])
{
case 'a': // name
strncpy(new_relay->name, (const char*)sqlite3_column_text(stmt, i), 127);
strncpy(new_relay->name, (const char*)sqlite3_column_text(stmt, i), MAX_NAME_LENGTH);
new_relay->name[MAX_NAME_LENGTH] = '\0';
break;
case 'u': // number
new_relay->number = sqlite3_column_int(stmt, i);
@ -66,7 +69,7 @@ relay_db_select_mapper(sqlite3_stmt *stmt)
}
}
memset(new_relay->schedules, 0, sizeof(schedule_t*) * 7);
memset(new_relay->schedules, 0, sizeof(schedule_t*) * DAYS_PER_WEEK);
relay_reload_schedules(new_relay);
relay_reload_active_schedule(new_relay);
@ -100,13 +103,11 @@ relay_db_select(sqlite3_stmt *stmt)
{
break;
}
else
{
LOGGER_ERR("error selecting relays from database: %s\n", sqlite3_errstr(s));
break;
}
}
}
sqlite3_finalize(stmt);
all_relays[row] = NULL;
return all_relays;
@ -115,7 +116,8 @@ relay_db_select(sqlite3_stmt *stmt)
int
relay_save(relay_t *relay)
{
int opened_transaction = database_transaction_begin();
database_transaction_lock lock;
database_transaction_begin(&lock);
sqlite3_stmt *stmt;
if(relay->id)
@ -140,10 +142,7 @@ relay_save(relay_t *relay)
LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database));
}
if(opened_transaction)
{
database_transaction_rollback();
}
database_transaction_rollback(&lock);
}
else
{
@ -156,17 +155,14 @@ relay_save(relay_t *relay)
junction_relay_schedule_remove_for_relay(relay->id);
LOGGER_DEBUG("rebuilding relay_schedule junction\n");
int schedule_ids[7];
for(int i = 0; i < 7; ++i)
int schedule_ids[DAYS_PER_WEEK];
for(int i = 0; i < DAYS_PER_WEEK; ++i)
{
schedule_ids[i] = relay->schedules[i]->id;
}
junction_relay_schedule_insert_weekdays(relay->id, schedule_ids);
if(opened_transaction)
{
database_transaction_commit();
}
database_transaction_commit(&lock);
}
cache_invalidate_relay(relay->id, -1);
@ -188,17 +184,16 @@ relay_reload_schedules(relay_t *relay)
{
schedule_t **schedules = schedule_get_relay_weekdays(relay->id);
uuid_t off_id;
memset(off_id, 0, sizeof(uuid_t));
memcpy(off_id, "off", 3);
uuid_t off_uid;
schedule_get_uid_off(off_uid);
int fill_with_off = 0;
for(int i = 0; i < 7; ++i)
for(int i = 0; i < DAYS_PER_WEEK; ++i)
{
if(schedules[i] == NULL || fill_with_off)
{
LOGGER_WARNING("got only %d/7 schedules for relay_id %d\n", i, relay->id);
relay->schedules[i] = schedule_get_by_uid(off_id);
relay->schedules[i] = schedule_get_by_uid(off_uid);
fill_with_off = 1;
}
@ -218,7 +213,7 @@ relay_reload_schedules(relay_t *relay)
void
relay_free(relay_t *relay)
{
for(int i = 0; i < 7; ++i)
for(int i = 0; i < DAYS_PER_WEEK; ++i)
{
schedule_free(relay->schedules[i]);
}
@ -311,7 +306,7 @@ relay_to_json(relay_t *relay, int status_relay)
cJSON_AddItemToObject(json, "active_schedule", json_active_schedule);
cJSON *json_schedules = cJSON_CreateArray();
for(int i = 0; i < 7; ++i)
for(int i = 0; i < DAYS_PER_WEEK; ++i)
{
cJSON *json_schedule = schedule_to_json(relay->schedules[i]);
cJSON_AddItemToArray(json_schedules, json_schedule);

View file

@ -37,6 +37,8 @@ schedule_db_select_mapper(sqlite3_stmt *stmt)
{
const uint16_t *periods_blob;
schedule_t *new_schedule = malloc(sizeof(schedule_t));
new_schedule->id = 0;
for(int i = 0; i < sqlite3_column_count(stmt); i++)
{
const char *name = sqlite3_column_name(stmt, i);
@ -46,8 +48,8 @@ schedule_db_select_mapper(sqlite3_stmt *stmt)
new_schedule->id = sqlite3_column_int(stmt, i);
break;
case 'n': // name
strncpy(new_schedule->name, (const char*)sqlite3_column_text(stmt, i), 127);
new_schedule->name[127] = '\0';
strncpy(new_schedule->name, (const char*)sqlite3_column_text(stmt, i), MAX_NAME_LENGTH);
new_schedule->name[MAX_NAME_LENGTH] = '\0';
break;
case 'p': // periods
periods_blob = sqlite3_column_blob(stmt, i);
@ -97,13 +99,11 @@ schedule_db_select(sqlite3_stmt *stmt)
{
break;
}
else
{
LOGGER_ERR("error selecting schedules from database: %s\n", sqlite3_errstr(s));
break;
}
}
}
sqlite3_finalize(stmt);
all_schedules[row] = NULL;
return all_schedules;
@ -112,7 +112,8 @@ schedule_db_select(sqlite3_stmt *stmt)
int
schedule_save(schedule_t *schedule)
{
int opened_transaction = database_transaction_begin();
database_transaction_lock lock;
database_transaction_begin(&lock);
sqlite3_stmt *stmt;
if(schedule->id)
@ -137,10 +138,7 @@ schedule_save(schedule_t *schedule)
LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database));
}
if(opened_transaction)
{
database_transaction_rollback();
}
database_transaction_rollback(&lock);
}
else
{
@ -149,10 +147,7 @@ schedule_save(schedule_t *schedule)
schedule->id = sqlite3_last_insert_rowid(global_database);
}
if(opened_transaction)
{
database_transaction_commit();
}
database_transaction_commit(&lock);
}
cache_invalidate_schedule(schedule->id);
@ -182,18 +177,16 @@ schedule_remove(schedule_t *schedule)
int
schedule_is_protected(schedule_t *schedule)
{
uuid_t tmp_uuid;
uuid_t tmp_uid;
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "off", 3);
if(uuid_compare(schedule->uid, tmp_uuid) == 0)
schedule_get_uid_off(tmp_uid);
if(uuid_compare(schedule->uid, tmp_uid) == 0)
{
return 1;
}
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "on", 2);
if(uuid_compare(schedule->uid, tmp_uuid) == 0)
schedule_get_uid_on(tmp_uid);
if(uuid_compare(schedule->uid, tmp_uid) == 0)
{
return 1;
}
@ -285,7 +278,8 @@ schedule_to_json(schedule_t *schedule)
continue;
}
char start_str[8], end_str[8];
char start_str[8];
char end_str[8];
period_t *period = &schedule->periods[i];
sprintf(start_str, "%02d:%02d", period->start / 60 , period->start % 60);
sprintf(end_str, "%02d:%02d", period->end / 60, period->end % 60);
@ -364,11 +358,10 @@ schedule_get_by_id_or_off(int id)
return result;
}
uuid_t tmp_uuid;
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "off", 3);
uuid_t tmp_uid;
schedule_get_uid_off(tmp_uid);
return schedule_get_by_uid(tmp_uuid);
return schedule_get_by_uid(tmp_uid);
}
schedule_t*
@ -409,11 +402,10 @@ schedule_get_by_uid_or_off(uuid_t uid)
return result;
}
uuid_t tmp_uuid;
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "off", 3);
uuid_t tmp_uid;
schedule_get_uid_off(tmp_uid);
return schedule_get_by_uid(tmp_uuid);
return schedule_get_by_uid(tmp_uid);
}
schedule_t*
@ -464,14 +456,12 @@ schedule_uid_parse(const char *uid_str, uuid_t result)
{
if(strcmp("off", uid_str) == 0)
{
memset(result, 0, sizeof(uuid_t));
memcpy(result, "off", 3);
schedule_get_uid_off(result);
return 0;
}
if(strcmp("on", uid_str) == 0)
{
memset(result, 0, sizeof(uuid_t));
memcpy(result, "on", 2);
schedule_get_uid_on(result);
return 0;
}
@ -485,23 +475,37 @@ schedule_uid_parse(const char *uid_str, uuid_t result)
void
schedule_uid_unparse(const uuid_t uid, char *result)
{
uuid_t tmp_uuid;
uuid_t tmp_uid;
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "off", 3);
if(uuid_compare(uid, tmp_uuid) == 0)
schedule_get_uid_off(tmp_uid);
if(uuid_compare(uid, tmp_uid) == 0)
{
strcpy(result, "off");
strncpy(result, "off", 4);
result[3] = '\0';
return;
}
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "on", 2);
if(uuid_compare(uid, tmp_uuid) == 0)
schedule_get_uid_on(tmp_uid);
if(uuid_compare(uid, tmp_uid) == 0)
{
strcpy(result, "on");
strncpy(result, "on", 3);
result[2] = '\0';
return;
}
uuid_unparse(uid, result);
}
void
schedule_get_uid_off(uuid_t target)
{
uuid_clear(target);
strncpy((char*)target, "off", 4);
}
void
schedule_get_uid_on(uuid_t target)
{
uuid_clear(target);
strncpy((char*)target, "on", 3);
}

View file

@ -10,6 +10,7 @@
#include <endpoints/api_v1_controllers.h>
#include <endpoints/api_v1_relays.h>
#include <endpoints/api_v1_tags.h>
#include <endpoints/api_v1_macros.h>
#include <endpoints/api_v1_ws.h>
static endpoint_t endpoints[ROUTER_ENDPOINTS_MAX_COUNT];
@ -71,7 +72,7 @@ router_init()
router_register_endpoint("/api/v1/schedules/{str}", HTTP_METHOD_DELETE, api_v1_schedules_STR_DELETE);
router_register_endpoint("/api/v1/schedules/tag/{str}", HTTP_METHOD_GET, api_v1_schedules_tag_STR_GET);
router_register_endpoint("/api/v1/controllers/discover/", HTTP_METHOD_POST, api_v1_controllers_discover_POST);
router_register_endpoint("/api/v1/controllers/discover/", HTTP_METHOD_PUT, api_v1_controllers_discover_PUT);
router_register_endpoint("/api/v1/controllers/", HTTP_METHOD_GET, api_v1_controllers_GET);
router_register_endpoint("/api/v1/controllers/{str}", HTTP_METHOD_GET, api_v1_controllers_STR_GET);
router_register_endpoint("/api/v1/controllers/{str}", HTTP_METHOD_PUT, api_v1_controllers_STR_PUT);
@ -90,6 +91,13 @@ router_init()
router_register_endpoint("/api/v1/tags/{str}", HTTP_METHOD_GET, api_v1_tags_STR_GET);
router_register_endpoint("/api/v1/tags/{str}", HTTP_METHOD_DELETE, api_v1_tags_STR_DELETE);
router_register_endpoint("/api/v1/macros/", HTTP_METHOD_GET, api_v1_macros_GET);
router_register_endpoint("/api/v1/macros/", HTTP_METHOD_POST, api_v1_macros_POST);
router_register_endpoint("/api/v1/macros/{str}", HTTP_METHOD_GET, api_v1_macros_STR_GET);
router_register_endpoint("/api/v1/macros/{str}", HTTP_METHOD_PUT, api_v1_macros_STR_PUT);
router_register_endpoint("/api/v1/macros/{str}", HTTP_METHOD_DELETE, api_v1_macros_STR_DELETE);
router_register_endpoint("/api/v1/macros/{str}/execute", HTTP_METHOD_PUT, api_v1_macros_STR_execute_PUT);
router_register_endpoint("/api/v1/ws/relays", HTTP_METHOD_WEBSOCKET, api_v1_ws_relays);
}
@ -146,7 +154,7 @@ router_register_endpoint(const char *route, int method, endpoint_func_f func)
// +1 for NULL terminator
endpoint->route = malloc(sizeof(char*) * (route_parts_count + 1));
endpoint->route_keeper = malloc(sizeof(char) * (route_len + 1));
strncpy(endpoint->route_keeper, route, route_len);
strncpy(endpoint->route_keeper, route, route_len + 1);
endpoint->route_keeper[route_len] = '\0';
int route_part = 0;
@ -305,7 +313,6 @@ router_find_endpoint(const char *uri_str, size_t uri_len, struct mg_str *method_
continue;
}
endpoints[i].possible_route = 0;
continue;
}
uri_token = strtok_r(NULL, delimiter, &uri_token_save);
++route_part;
@ -333,8 +340,10 @@ router_find_endpoint(const char *uri_str, size_t uri_len, struct mg_str *method_
{
if(best_endpoint->args[i].type == ENDPOINT_ARG_TYPE_STR)
{
char *arg_value_str = malloc(sizeof(char) * (strlen(best_endpoint->args[i].value.v_str) + 1));
size_t arg_value_str_len = strlen(best_endpoint->args[i].value.v_str);
char *arg_value_str = malloc(sizeof(char) * (arg_value_str_len + 1));
strcpy(arg_value_str, best_endpoint->args[i].value.v_str);
arg_value_str[arg_value_str_len] = '\0';
best_endpoint->args[i].value.v_str = arg_value_str;
}
}

View file

@ -1,65 +0,0 @@
[controller]
name = new emgauwa device
: 4422 for testing; 4421 for dev-env; 4420 for testing-env; 4419 for prod-env
discovery-port = 4422
: 1886 for testing; 1885 for dev-env; 1884 for testing-env; 1883 for prod-env
mqtt-port = 1886
mqtt-host = localhost
relay-count = 10
database = controller.sqlite
log-level = debug
log-file = stdout
[relay-0]
driver = piface
pin = 0
inverted = 0
[relay-1]
driver = piface
pin = 1
inverted = 0
[relay-2]
driver = gpio
pin = 5
inverted = 1
[relay-3]
driver = gpio
pin = 4
inverted = 1
[relay-4]
driver = gpio
pin = 3
inverted = 1
[relay-5]
driver = gpio
pin = 2
inverted = 1
[relay-6]
driver = gpio
pin = 1
inverted = 1
pulse-duration = 3
[relay-7]
driver = gpio
pin = 0
inverted = 1
pulse-duration = 3
[relay-8]
driver = gpio
pin = 16
inverted = 1
[relay-9]
driver = gpio
pin = 15
inverted = 1

View file

@ -1,16 +0,0 @@
[core]
server-port = 5000
database = core.sqlite
content-dir = /usr/share/webapps/emgauwa
not-found-file = 404.html
not-found-file-mime = text/html
not-found-content = 404 - NOT FOUND
not-found-content-type = text/plain
: 4422 for testing; 4421 for dev-env; 4420 for testing-env; 4419 for prod-env
discovery-port = 4422
: 1886 for testing; 1885 for dev-env; 1884 for testing-env; 1883 for prod-env
mqtt-port = 1886
log-level = debug
log-file = stdout

View file

@ -0,0 +1,66 @@
[controller]
database = "emgauwa-controller.sqlite"
mqtt-host = "127.0.0.1"
[ports]
# 4422 for testing; 4421 for dev-env; 4420 for testing-env; 4419 for prod-env
discovery = 4422
# 1886 for testing; 1885 for dev-env; 1884 for testing-env; 1883 for prod-env
mqtt = 1886
[logging]
level = "debug"
file = "stdout"
[[relays]]
driver = "piface"
pin = 0
inverted = 0
[[relays]]
driver = "piface"
pin = 1
inverted = 0
[[relays]]
driver = "gpio"
pin = 5
inverted = 1
[[relays]]
driver = "gpio"
pin = 4
inverted = 1
[[relays]]
driver = "gpio"
pin = 3
inverted = 1
[[relays]]
driver = "gpio"
pin = 2
inverted = 1
[[relays]]
driver = "gpio"
pin = 1
inverted = 1
pulse-duration = 3
[[relays]]
driver = "gpio"
pin = 0
inverted = 1
pulse-duration = 3
[[relays]]
driver = "gpio"
pin = 16
inverted = 1
[[relays]]
driver = "gpio"
pin = 15
inverted = 1

View file

@ -0,0 +1,17 @@
[core]
database = "emgauwa-core.sqlite"
include = "../emgauwa-core-testing.conf.d"
content-dir = "."
not-found-file = "404.html"
not-found-file-mime = "text/html"
[ports]
server = 5000
# 4422 for testing; 4421 for dev-env; 4420 for testing-env; 4419 for prod-env
discovery = 4422
# 1886 for testing; 1885 for dev-env; 1884 for testing-env; 1883 for prod-env
mqtt = 1886
[logging]
level = "debug"
file = "stdout"

View file

@ -0,0 +1,3 @@
[core]
not-found-content = '{"msg": "conf.d working"}'
not-found-content-type = "application/json"

View file

@ -1,14 +1,9 @@
#!/usr/bin/env sh
source_dir=$PWD
working_dir=$PWD/testing_latest
working_bak=$PWD/testing_bak
working_dir=$PWD/testing
alias valgrind_emgauwa="valgrind $2 --log-file=$working_dir/valgrind.log"
rm -rf $working_bak
[ -d $working_dir ] && mv $working_dir $working_bak
alias valgrind_emgauwa="valgrind -s $2 --log-file=$working_dir/valgrind.log"
mkdir -p $working_dir
cd $working_dir
@ -17,15 +12,18 @@ target_branch=$(git rev-parse --abbrev-ref HEAD)
if [ -z "$EMGAUWA_CONTROLLER_EXE" ]
then
git clone --quiet ssh://git@git.serguzim.me:3022/emgauwa/controller.git controller || exit
cd ./controller
mkdir -p controller
cd controller
echo "Trying to pull or clone controller"
git clone --quiet ssh://git@git.serguzim.me:3022/emgauwa/controller.git . >/dev/null 2>&1 || git pull >/dev/null || exit
git checkout dev >/dev/null 2>&1
git checkout $target_branch >/dev/null 2>&1
git checkout $2 >/dev/null 2>&1
echo "Building controller on branch $(git rev-parse --abbrev-ref HEAD)"
mkdir build
mkdir -p build
cd build
cmake -DWIRING_PI_DEBUG=on .. >/dev/null
@ -35,20 +33,16 @@ fi
echo "Emgauwa controller: $($EMGAUWA_CONTROLLER_EXE --version)"
$EMGAUWA_CONTROLLER_EXE start -c $source_dir/controller.testing.ini >$working_dir/controller.log 2>&1 &
$EMGAUWA_CONTROLLER_EXE -c $source_dir/emgauwa-controller-testing.conf >$working_dir/controller.log 2>&1 &
controller_id=$!
cd $working_dir
touch $working_dir/index.html
cp $1 $working_dir/core
cp $source_dir/core.testing.ini $working_dir/core.ini
echo "=== invalids start (must exit) ===" >$working_dir/core.log
$working_dir/core >>$working_dir/core.log 2>&1
$working_dir/core INVALID_ACTION >>$working_dir/core.log 2>&1
echo "=== valid start ===" >>$working_dir/core.log
valgrind_emgauwa $working_dir/core start >>$working_dir/core.log 2>&1 &
valgrind_emgauwa $working_dir/core -c $source_dir/emgauwa-core-testing.conf >>$working_dir/core.log 2>&1 &
core_id=$!
@ -68,4 +62,12 @@ test_result=$?
kill $core_id
kill $controller_id
timestamp=$(date -Iseconds)
for backup_file in core.log controller.log valgrind.log emgauwa-core.sqlite; do
mv $backup_file $timestamp.$backup_file
ln -sf $timestamp.$backup_file latest.$backup_file
done
cat latest.valgrind.log
exit $test_result

View file

@ -0,0 +1,25 @@
test_name: "[test_basics] Test basic calls"
stages:
- name: "[test_basics] get index"
request:
url: "http://localhost:5000/"
method: GET
response:
status_code: 200
- name: "[test_basics] get 404"
request:
url: "http://localhost:5000/invalid_url_for_testing_do_not_use"
method: GET
response:
status_code: 404
- name: "[test_basics] verify conf.d by 404"
request:
url: "http://localhost:5000/invalid_url_for_testing_do_not_use"
method: GET
response:
status_code: 404
json:
msg: "conf.d working"

View file

@ -3,7 +3,7 @@ test_name: Test basic controller functions
stages:
- name: "[controllers_basic] discover controllers"
request:
method: POST
method: PUT
url: "http://localhost:5000/api/v1/controllers/discover/"
response:
status_code: 200
@ -86,7 +86,7 @@ stages:
- name: "[controllers_basic] discover controllers again"
request:
method: POST
method: PUT
url: "http://localhost:5000/api/v1/controllers/discover/"
response:
status_code: 200

View file

@ -26,7 +26,7 @@ stages:
extra_kwargs:
relay_count: !int "{returned_relay_count:d}"
- name: "[controller_relays_basic] get controller relays, check length"
- name: "[controller_relays_basic] get controller relay"
request:
method: GET
url: "http://localhost:5000/api/v1/controllers/{returned_id}/relays/5"
@ -40,3 +40,69 @@ stages:
function: validate_relay:check_number
extra_kwargs:
number: 5
- name: "[controller_relays_basic] get controller relay with invalid uid"
request:
method: GET
url: "http://localhost:5000/api/v1/controllers/INVALID-UUID/relays/5"
response:
status_code: 400
- name: "[controller_relays_basic] get controller relay with unavailable uid"
request:
method: GET
url: "http://localhost:5000/api/v1/controllers/00000000-0000-0000-0000-000000000000/relays/5"
response:
status_code: 404
- name: "[controller_relays_basic] get controller relay with invalid number"
request:
method: GET
url: "http://localhost:5000/api/v1/controllers/{returned_id}/relays/not_a_number"
response:
status_code: 404
- name: "[controller_relays_basic] get controller relay with unavailable number"
request:
method: GET
url: "http://localhost:5000/api/v1/controllers/{returned_id}/relays/9001"
response:
status_code: 404
- name: "[controller_relays_basic] pulse relay"
request:
method: POST
url: "http://localhost:5000/api/v1/controllers/{returned_id}/relays/6/pulse"
response:
status_code: 200
- name: "[controller_relays_basic] pulse relay with invalid uid"
request:
method: POST
url: "http://localhost:5000/api/v1/controllers/INVALID-UUID/relays/6/pulse"
response:
status_code: 400
- name: "[controller_relays_basic] pulse relay with unavailable uid"
request:
method: POST
url: "http://localhost:5000/api/v1/controllers/00000000-0000-0000-0000-000000000000/relays/6/pulse"
response:
status_code: 404
- name: "[controller_relays_basic] pulse relay with invalid number"
request:
method: POST
url: "http://localhost:5000/api/v1/controllers/{returned_id}/relays/not_a_number/pulse"
response:
status_code: 404
- name: "[controller_relays_basic] pulse relay with unavailable number"
request:
method: POST
url: "http://localhost:5000/api/v1/controllers/{returned_id}/relays/9001/pulse"
response:
status_code: 404

View file

@ -98,6 +98,13 @@ stages:
controller_id: "{returned_id}"
tag: "{returned_tag}"
- name: "[tags] get returned tag with relays and schedules"
request:
method: GET
url: "http://localhost:5000/api/v1/tags/{returned_tag}"
response:
status_code: 200
- name: "[tags] get tags"
request:
method: GET
@ -106,3 +113,56 @@ stages:
status_code: 200
verify_response_with:
function: validate_tag:multiple
- name: "[tags] get unavailable tag"
request:
method: GET
url: "http://localhost:5000/api/v1/tags/invalid_unavailable_tag"
response:
status_code: 404
- name: "[tags] post tag"
request:
method: POST
url: "http://localhost:5000/api/v1/tags/"
json:
tag: "unused_tag_1"
response:
status_code: 201
- name: "[tags] get posted tag"
request:
method: GET
url: "http://localhost:5000/api/v1/tags/unused_tag_1"
response:
status_code: 200
- name: "[tags] delete posted tag"
request:
method: DELETE
url: "http://localhost:5000/api/v1/tags/unused_tag_1"
response:
status_code: 200
- name: "[tags] get deleted tag"
request:
method: GET
url: "http://localhost:5000/api/v1/tags/unused_tag_1"
response:
status_code: 404
- name: "[tags] delete deleted tag again"
request:
method: DELETE
url: "http://localhost:5000/api/v1/tags/unused_tag_1"
response:
status_code: 404
- name: "[tags] get tags again"
request:
method: GET
url: "http://localhost:5000/api/v1/tags/"
response:
status_code: 200
verify_response_with:
function: validate_tag:multiple

View file

@ -11,24 +11,9 @@ def multiple(response):
for tag in response.json():
_verify_single(tag)
#def find(response, name=None, number=None, controller_id=None, tag=None):
# print(response.json())
# for tag in response.json():
# if number != None and number != tag.get("number"):
# continue
#
# if name != None and name != tag.get("name"):
# continue
#
# if controller_id != None and controller_id != tag.get("controller_id"):
# continue
#
# if tag != None:
# found_in_response = False
# for response_tag in tag.get("tags"):
# if response_tag == tag:
# found_in_response = True
# if not found_in_response:
# continue
# return
# assert False, "tag not found in list"
def find(response, tag):
print(response.json())
for response_tag in response.json():
if response_tag == tag:
return
assert False, "tag not found in list"

5016
vendor/confini.c vendored

File diff suppressed because it is too large Load diff

547
vendor/confini.h vendored
View file

@ -1,547 +0,0 @@
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */
/**
@file confini.h
@brief libconfini header
@author Stefano Gioffr&eacute;
@copyright GNU General Public License, version 3 or any later version
@version 1.14.0
@date 2016-2020
@see https://madmurphy.github.io/libconfini
**/
#ifndef _LIBCONFINI_HEADER_
#define _LIBCONFINI_HEADER_
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* PRIVATE (HEADER-SCOPED) MACROS */
#define __INIFORMAT_TABLE_CB_FIELDS__(NAME, OFFSET, SIZE, DEFVAL) \
unsigned char NAME:SIZE;
#define __INIFORMAT_TABLE_CB_DEFAULT__(NAME, OFFSET, SIZE, DEFVAL) DEFVAL,
#define __INIFORMAT_TABLE_CB_ZERO__(NAME, OFFSET, SIZE, DEFVAL) 0,
#define _LIBCONFINI_INIFORMAT_TYPE_ \
struct IniFormat { INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_FIELDS__) }
#define _LIBCONFINI_DEFAULT_FORMAT_ \
{ INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_DEFAULT__) }
#define _LIBCONFINI_UNIXLIKE_FORMAT_ \
{ INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_ZERO__) }
/* PUBLIC MACROS */
/**
@brief Calls a user-given macro (that accepts four arguments) for each row
of the table
**/
/*
NOTE: The following table and the order of its rows **define** (and link
together) both the #IniFormat and #IniFormatNum data types declared in this
header
*/
#define INIFORMAT_TABLE_AS(_____) /* IniFormat table *\
NAME BIT SIZE DEFAULT
*/\
_____( delimiter_symbol, 0, 7, INI_EQUALS ) \
_____( case_sensitive, 7, 1, false )/*
*/\
_____( semicolon_marker, 8, 2, INI_DISABLED_OR_COMMENT ) \
_____( hash_marker, 10, 2, INI_DISABLED_OR_COMMENT ) \
_____( section_paths, 12, 2, INI_ABSOLUTE_AND_RELATIVE ) \
_____( multiline_nodes, 14, 2, INI_MULTILINE_EVERYWHERE )/*
*/\
_____( no_single_quotes, 16, 1, false ) \
_____( no_double_quotes, 17, 1, false ) \
_____( no_spaces_in_names, 18, 1, false ) \
_____( implicit_is_not_empty, 19, 1, false ) \
_____( do_not_collapse_values, 20, 1, false ) \
_____( preserve_empty_quotes, 21, 1, false ) \
_____( disabled_after_space, 22, 1, false ) \
_____( disabled_can_be_implicit, 23, 1, false )
/**
@brief Checks whether a format does **not** support escape sequences
**/
#define INIFORMAT_HAS_NO_ESC(FORMAT) \
(FORMAT.multiline_nodes == INI_NO_MULTILINE && \
FORMAT.no_double_quotes && FORMAT.no_single_quotes)
/* PUBLIC TYPEDEFS */
/**
@brief 24-bit bitfield representing the format of an INI file (INI
dialect)
**/
typedef _LIBCONFINI_INIFORMAT_TYPE_ IniFormat;
/**
@brief Global statistics about an INI file
**/
typedef struct IniStatistics {
const IniFormat format;
const size_t bytes;
const size_t members;
} IniStatistics;
/**
@brief Dispatch of a single INI node
**/
typedef struct IniDispatch {
const IniFormat format;
uint8_t type;
char * data;
char * value;
const char * append_to;
size_t d_len;
size_t v_len;
size_t at_len;
size_t dispatch_id;
} IniDispatch;
/**
@brief The unique ID of an INI format (24-bit maximum)
**/
typedef uint32_t IniFormatNum;
/**
@brief Callback function for handling an #IniStatistics structure
**/
typedef int (* IniStatsHandler) (
IniStatistics * statistics,
void * user_data
);
/**
@brief Callback function for handling an #IniDispatch structure
**/
typedef int (* IniDispHandler) (
IniDispatch * dispatch,
void * user_data
);
/**
@brief Callback function for handling an INI string belonging to a
sequence of INI strings
**/
typedef int (* IniStrHandler) (
char * ini_string,
size_t string_length,
size_t string_num,
IniFormat format,
void * user_data
);
/**
@brief Callback function for handling a selected fragment of an INI string
**/
typedef int (* IniSubstrHandler) (
const char * ini_string,
size_t fragm_offset,
size_t fragm_length,
size_t fragm_num,
IniFormat format,
void * user_data
);
/* PUBLIC FUNCTIONS */
extern int strip_ini_cache (
register char * const ini_source,
const size_t ini_length,
const IniFormat format,
const IniStatsHandler f_init,
const IniDispHandler f_foreach,
void * const user_data
);
extern int load_ini_file (
FILE * const ini_file,
const IniFormat format,
const IniStatsHandler f_init,
const IniDispHandler f_foreach,
void * const user_data
);
extern int load_ini_path (
const char * const path,
const IniFormat format,
const IniStatsHandler f_init,
const IniDispHandler f_foreach,
void * const user_data
);
extern bool ini_string_match_ss (
const char * const simple_string_a,
const char * const simple_string_b,
const IniFormat format
);
extern bool ini_string_match_si (
const char * const simple_string,
const char * const ini_string,
const IniFormat format
);
extern bool ini_string_match_ii (
const char * const ini_string_a,
const char * const ini_string_b,
const IniFormat format
);
extern bool ini_array_match (
const char * const ini_string_a,
const char * const ini_string_b,
const char delimiter,
const IniFormat format
);
extern size_t ini_unquote (
char * const ini_string,
const IniFormat format
);
extern size_t ini_string_parse (
char * const ini_string,
const IniFormat format
);
extern size_t ini_array_get_length (
const char * const ini_string,
const char delimiter,
const IniFormat format
);
extern int ini_array_foreach (
const char * const ini_string,
const char delimiter,
const IniFormat format,
const IniSubstrHandler f_foreach,
void * const user_data
);
extern size_t ini_array_shift (
const char ** const ini_strptr,
const char delimiter,
const IniFormat format
);
extern size_t ini_array_collapse (
char * const ini_string,
const char delimiter,
const IniFormat format
);
extern char * ini_array_break (
char * const ini_string,
const char delimiter,
const IniFormat format
);
extern char * ini_array_release (
char ** const ini_strptr,
const char delimiter,
const IniFormat format
);
extern int ini_array_split (
char * const ini_string,
const char delimiter,
const IniFormat format,
const IniStrHandler f_foreach,
void * const user_data
);
extern void ini_global_set_lowercase_mode (
const bool lowercase
);
extern void ini_global_set_implicit_value (
char * const implicit_value,
const size_t implicit_v_len
);
extern IniFormatNum ini_fton (
const IniFormat format
);
extern IniFormat ini_ntof (
const IniFormatNum format_id
);
extern int ini_get_bool (
const char * const ini_string,
const int when_fail
);
/* PUBLIC LINKS */
extern int (* const ini_get_int) (
const char * ini_string
);
extern long int (* const ini_get_lint) (
const char * ini_string
);
extern long long int (* const ini_get_llint) (
const char * ini_string
);
extern double (* const ini_get_double) (
const char * ini_string
);
/**
@brief Legacy support, soon to be replaced with a `float` data type --
please **do not use `ini_get_float()`!**
**/
#define ini_get_float \
_Pragma("GCC warning \"function `ini_get_float()` is deprecated for parsing a `double` data type; use `ini_get_double()` instead\"") \
ini_get_double
/* PUBLIC CONSTANTS AND VARIABLES */
/**
@brief Error mask (flags not present in user-generated interruptions)
**/
#define CONFINI_ERROR 252
/**
@brief Error codes
**/
enum ConfiniInterruptNo {
CONFINI_SUCCESS = 0, /**< There have been no interruptions, everything
went well [value=0] **/
CONFINI_IINTR = 1, /**< Interrupted by the user during `f_init()`
[value=1] **/
CONFINI_FEINTR = 2, /**< Interrupted by the user during `f_foreach()`
[value=2] **/
CONFINI_ENOENT = 4, /**< File inaccessible [value=4] **/
CONFINI_ENOMEM = 5, /**< Error allocating virtual memory [value=5] **/
CONFINI_EIO = 6, /**< Error reading the file [value=6] **/
CONFINI_EOOR = 7, /**< Out-of-range error: callbacks are more than
expected [value=7] **/
CONFINI_EBADF = 8, /**< The stream specified is not a seekable stream
[value=8] **/
CONFINI_EFBIG = 9, /**< File too large [value=9] **/
CONFINI_EROADDR = 10 /**< Address is read-only [value=10] **/
};
/**
@brief INI node types
**/
enum IniNodeType {
INI_UNKNOWN = 0, /**< This is a node impossible to categorize
[value=0] **/
INI_VALUE = 1, /**< Not used by **libconfini** (values are
dispatched together with keys) -- but
available for user's implementations
[value=1] **/
INI_KEY = 2, /**< This is a key [value=2] **/
INI_SECTION = 3, /**< This is a section or a section path
[value=3] **/
INI_COMMENT = 4, /**< This is a comment [value=4] **/
INI_INLINE_COMMENT = 5, /**< This is an inline comment [value=5] **/
INI_DISABLED_KEY = 6, /**< This is a disabled key [value=6] **/
INI_DISABLED_SECTION = 7 /**< This is a disabled section path
[value=7] **/
};
/**
@brief Common array and key-value delimiters (but a delimiter may also be
any other ASCII character not present in this list)
**/
enum IniDelimiters {
INI_ANY_SPACE = 0, /**< In multi-line INIs:
`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`, in
non-multi-line INIs: `/[\t \v\f])+/` **/
INI_EQUALS = '=', /**< Equals character (`=`) **/
INI_COLON = ':', /**< Colon character (`:`) **/
INI_DOT = '.', /**< Dot character (`.`) **/
INI_COMMA = ',' /**< Comma character (`,`) **/
};
/**
@brief Possible values of #IniFormat::semicolon_marker and
#IniFormat::hash_marker (i.e., meaning of `/\s+;/` and `/\s+#/` in
respect to a format)
**/
enum IniCommentMarker {
INI_DISABLED_OR_COMMENT = 0, /**< This marker opens a comment or a
disabled entry **/
INI_ONLY_COMMENT = 1, /**< This marker opens a comment **/
INI_IGNORE = 2, /**< This marker opens a comment that has
been marked for deletion and must not
be dispatched or counted **/
INI_IS_NOT_A_MARKER = 3 /**< This is not a marker at all, but a
normal character instead **/
};
/**
@brief Possible values of #IniFormat::section_paths
**/
enum IniSectionPaths {
INI_ABSOLUTE_AND_RELATIVE = 0, /**< Section paths starting with a dot
express nesting to the current parent,
to root otherwise **/
INI_ABSOLUTE_ONLY = 1, /**< Section paths starting with a dot will
be cleaned of their leading dot and
appended to root **/
INI_ONE_LEVEL_ONLY = 2, /**< Format supports sections, but the dot
does not express nesting and is not a
meta-character **/
INI_NO_SECTIONS = 3 /**< Format does *not* support sections --
`/\[[^\]]*\]/g`, if any, will be
treated as keys! **/
};
/**
@brief Possible values of #IniFormat::multiline_nodes
**/
enum IniMultiline {
INI_MULTILINE_EVERYWHERE = 0, /**< Comments, section paths and keys
-- disabled or not -- are allowed
to be multi-line **/
INI_BUT_COMMENTS = 1, /**< Only section paths and keys --
disabled or not -- are allowed to
be multi-line **/
INI_BUT_DISABLED_AND_COMMENTS = 2, /**< Only active section paths and
active keys are allowed to be
multi-line **/
INI_NO_MULTILINE = 3 /**< Multi-line escape sequences are
disabled **/
};
/**
@brief A model format for standard INI files
**/
static const IniFormat INI_DEFAULT_FORMAT = _LIBCONFINI_DEFAULT_FORMAT_;
/**
@brief A model format for Unix-like .conf files (where space characters
are delimiters between keys and values)
**/
/* All fields are set to `0` here. */
static const IniFormat INI_UNIXLIKE_FORMAT = _LIBCONFINI_UNIXLIKE_FORMAT_;
/**
@brief If set to `true`, key and section names in case-insensitive INI
formats will be dispatched lowercase, verbatim otherwise (default
value: `false`)
**/
extern bool INI_GLOBAL_LOWERCASE_MODE;
/**
@brief Value to be assigned to implicit keys (default value: `NULL`)
**/
extern char * INI_GLOBAL_IMPLICIT_VALUE;
/**
@brief Length of the value assigned to implicit keys (default value: `0`)
**/
extern size_t INI_GLOBAL_IMPLICIT_V_LEN;
/* CLEAN THE PRIVATE ENVIRONMENT */
#undef _LIBCONFINI_UNIXLIKE_FORMAT_
#undef _LIBCONFINI_DEFAULT_FORMAT_
#undef _LIBCONFINI_INIFORMAT_TYPE_
#undef __INIFORMAT_TABLE_CB_ZERO__
#undef __INIFORMAT_TABLE_CB_DEFAULT__
#undef __INIFORMAT_TABLE_CB_FIELDS__
/* END OF `_LIBCONFINI_HEADER_` */
#ifdef __cplusplus
}
#endif
#endif
/* EOF */

2
vendor/mongoose.h vendored
View file

@ -1867,7 +1867,7 @@ typedef struct {
void cs_md5_init(cs_md5_ctx *c);
void cs_md5_update(cs_md5_ctx *c, const unsigned char *data, size_t len);
void cs_md5_final(unsigned char *md, cs_md5_ctx *c);
void cs_md5_final(unsigned char digest[16], cs_md5_ctx *c);
#ifdef __cplusplus
}

2274
vendor/toml.c vendored Normal file

File diff suppressed because it is too large Load diff

175
vendor/toml.h vendored Normal file
View file

@ -0,0 +1,175 @@
/*
MIT License
Copyright (c) 2017 - 2019 CK Tan
https://github.com/cktan/tomlc99
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef TOML_H
#define TOML_H
#include <stdio.h>
#include <stdint.h>
#ifdef __cplusplus
#define TOML_EXTERN extern "C"
#else
#define TOML_EXTERN extern
#endif
typedef struct toml_timestamp_t toml_timestamp_t;
typedef struct toml_table_t toml_table_t;
typedef struct toml_array_t toml_array_t;
typedef struct toml_datum_t toml_datum_t;
/* Parse a file. Return a table on success, or 0 otherwise.
* Caller must toml_free(the-return-value) after use.
*/
TOML_EXTERN toml_table_t* toml_parse_file(FILE* fp,
char* errbuf,
int errbufsz);
/* Parse a string containing the full config.
* Return a table on success, or 0 otherwise.
* Caller must toml_free(the-return-value) after use.
*/
TOML_EXTERN toml_table_t* toml_parse(char* conf, /* NUL terminated, please. */
char* errbuf,
int errbufsz);
/* Free the table returned by toml_parse() or toml_parse_file(). Once
* this function is called, any handles accessed through this tab
* directly or indirectly are no longer valid.
*/
TOML_EXTERN void toml_free(toml_table_t* tab);
/* Timestamp types. The year, month, day, hour, minute, second, z
* fields may be NULL if they are not relevant. e.g. In a DATE
* type, the hour, minute, second and z fields will be NULLs.
*/
struct toml_timestamp_t {
struct { /* internal. do not use. */
int year, month, day;
int hour, minute, second, millisec;
char z[10];
} __buffer;
int *year, *month, *day;
int *hour, *minute, *second, *millisec;
char* z;
};
/*-----------------------------------------------------------------
* Enhanced access methods
*/
struct toml_datum_t {
int ok;
union {
toml_timestamp_t* ts; /* ts must be freed after use */
char* s; /* string value. s must be freed after use */
int b; /* bool value */
int64_t i; /* int value */
double d; /* double value */
} u;
};
/* on arrays: */
/* ... retrieve size of array. */
TOML_EXTERN int toml_array_nelem(const toml_array_t* arr);
/* ... retrieve values using index. */
TOML_EXTERN toml_datum_t toml_string_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_datum_t toml_bool_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_datum_t toml_int_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_datum_t toml_double_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_datum_t toml_timestamp_at(const toml_array_t* arr, int idx);
/* ... retrieve array or table using index. */
TOML_EXTERN toml_array_t* toml_array_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_table_t* toml_table_at(const toml_array_t* arr, int idx);
/* on tables: */
/* ... retrieve the key in table at keyidx. Return 0 if out of range. */
TOML_EXTERN const char* toml_key_in(const toml_table_t* tab, int keyidx);
/* ... retrieve values using key. */
TOML_EXTERN toml_datum_t toml_string_in(const toml_table_t* arr, const char* key);
TOML_EXTERN toml_datum_t toml_bool_in(const toml_table_t* arr, const char* key);
TOML_EXTERN toml_datum_t toml_int_in(const toml_table_t* arr, const char* key);
TOML_EXTERN toml_datum_t toml_double_in(const toml_table_t* arr, const char* key);
TOML_EXTERN toml_datum_t toml_timestamp_in(const toml_table_t* arr, const char* key);
/* .. retrieve array or table using key. */
TOML_EXTERN toml_array_t* toml_array_in(const toml_table_t* tab,
const char* key);
TOML_EXTERN toml_table_t* toml_table_in(const toml_table_t* tab,
const char* key);
/*-----------------------------------------------------------------
* lesser used
*/
/* Return the array kind: 't'able, 'a'rray, 'v'alue */
TOML_EXTERN char toml_array_kind(const toml_array_t* arr);
/* For array kind 'v'alue, return the type of values
i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp
0 if unknown
*/
TOML_EXTERN char toml_array_type(const toml_array_t* arr);
/* Return the key of an array */
TOML_EXTERN const char* toml_array_key(const toml_array_t* arr);
/* Return the number of key-values in a table */
TOML_EXTERN int toml_table_nkval(const toml_table_t* tab);
/* Return the number of arrays in a table */
TOML_EXTERN int toml_table_narr(const toml_table_t* tab);
/* Return the number of sub-tables in a table */
TOML_EXTERN int toml_table_ntab(const toml_table_t* tab);
/* Return the key of a table*/
TOML_EXTERN const char* toml_table_key(const toml_table_t* tab);
/*--------------------------------------------------------------
* misc
*/
TOML_EXTERN int toml_utf8_to_ucs(const char* orig, int len, int64_t* ret);
TOML_EXTERN int toml_ucs_to_utf8(int64_t code, char buf[6]);
TOML_EXTERN void toml_set_memutil(void* (*xxmalloc)(size_t),
void (*xxfree)(void*));
/*--------------------------------------------------------------
* deprecated
*/
/* A raw value, must be processed by toml_rto* before using. */
typedef const char* toml_raw_t;
TOML_EXTERN toml_raw_t toml_raw_in(const toml_table_t* tab, const char* key);
TOML_EXTERN toml_raw_t toml_raw_at(const toml_array_t* arr, int idx);
TOML_EXTERN int toml_rtos(toml_raw_t s, char** ret);
TOML_EXTERN int toml_rtob(toml_raw_t s, int* ret);
TOML_EXTERN int toml_rtoi(toml_raw_t s, int64_t* ret);
TOML_EXTERN int toml_rtod(toml_raw_t s, double* ret);
TOML_EXTERN int toml_rtod_ex(toml_raw_t s, double* ret, char* buf, int buflen);
TOML_EXTERN int toml_rtots(toml_raw_t s, toml_timestamp_t* ret);
#endif /* TOML_H */