Merge branch 'rewrite' into dev

This commit is contained in:
Tobias Reisinger 2020-05-28 13:59:54 +02:00
commit b68cdd224e
129 changed files with 40046 additions and 3787 deletions

10
.gitignore vendored
View file

@ -1,5 +1,7 @@
build
cmake-build-debug
.idea
build/
docs/
sql/*.h
tests/testing_tmp/
tests/testing_bak/
include/migrations/*.sql.h

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "drogon"]
path = drogon
url = https://github.com/an-tao/drogon.git

View file

@ -1,71 +1,28 @@
cmake_minimum_required (VERSION 3.2)
cmake_minimum_required (VERSION 3.7)
project(core)
include(CheckIncludeFileCXX)
add_executable(core main.c)
check_include_file_cxx(any HAS_ANY)
check_include_file_cxx(string_view HAS_STRING_VIEW)
if(HAS_ANY AND HAS_STRING_VIEW)
set(CMAKE_CXX_STANDARD 17)
else()
set(CMAKE_CXX_STANDARD 14)
endif()
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Wpedantic -Werror -Wall -Wextra -luuid -lsqlite3 -g -fprofile-arcs -ftest-coverage")
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
string(LENGTH "${CMAKE_SOURCE_DIR}/" SOURCE_PATH_SIZE)
add_definitions("-DSOURCE_PATH_SIZE=${SOURCE_PATH_SIZE}")
set(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} -Wall -Wextra -Werror -g -latomic")
set(CMAKE_CXX_FLAGS_DEBUG "-g")
add_definitions("-DMG_ENABLE_EXTRA_ERRORS_DESC")
##########
# If you include the drogon source code locally in your project, use this method to add drogon
# add_subdirectory(drogon)
# set(Drogon_DIR ${PROJECT_BINARY_DIR}/drogon)
# find_package(Drogon CONFIG REQUIRED NO_DEFAULT_PATH)
# include_directories(${DROGON_INCLUDE_DIRS})
# link_libraries(${DROGON_LIBRARIES})
##########
aux_source_directory(vendor VENDOR_SRC) # vendor first to put their warnings on top
aux_source_directory(. SRC_DIR)
aux_source_directory(models MODELS_SRC)
aux_source_directory(helpers HELPERS_SRC)
aux_source_directory(handlers HANDLERS_SRC)
aux_source_directory(endpoints ENDPOINTS_SRC)
# find_package(Drogon CONFIG REQUIRED)
# include_directories(${DROGON_INCLUDE_DIRS})
# link_libraries(${DROGON_LIBRARIES})
configure_file("core.ini" "core.ini" COPYONLY)
if(CMAKE_CXX_STANDARD LESS 17)
#With C++14, use boost to support any and string_view
message(STATUS "use c++14")
find_package(Boost 1.61.0 REQUIRED)
include_directories(${Boost_INCLUDE_DIRS})
else()
message(STATUS "use c++17")
endif()
target_sources(core PRIVATE ${VENDOR_SRC} ${SRC_DIR} ${HANDLERS_SRC} ${HELPERS_SRC} ${MODELS_SRC} ${ENDPOINTS_SRC})
target_include_directories(core PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(core PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vendor)
aux_source_directory(./ SRC_DIR)
aux_source_directory(controllers CTL_SRC)
aux_source_directory(filters FILTER_SRC)
aux_source_directory(plugins PLUGIN_SRC)
aux_source_directory(models MODEL_SRC)
aux_source_directory(helpers HELPER_SRC)
file(GLOB SCP_LIST ${CMAKE_CURRENT_SOURCE_DIR}/views/*.csp)
foreach(cspFile ${SCP_LIST})
message(STATUS "cspFile:" ${cspFile})
EXEC_PROGRAM(basename ARGS "${cspFile} .csp" OUTPUT_VARIABLE classname)
message(STATUS "view classname:" ${classname})
ADD_CUSTOM_COMMAND(OUTPUT ${classname}.h ${classname}.cc
COMMAND drogon_ctl
ARGS create view ${cspFile}
DEPENDS ${cspFile}
VERBATIM )
set(VIEWSRC ${VIEWSRC} ${classname}.cc)
endforeach()
configure_file("config.json" "config.json" COPYONLY)
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
add_executable(core ${SRC_DIR} ${CTL_SRC} ${FILTER_SRC} ${VIEWSRC} ${PLUGIN_SRC} ${MODEL_SRC} ${HELPER_SRC})
add_subdirectory(drogon)
target_link_libraries(${PROJECT_NAME} PRIVATE drogon)
add_custom_target(migrations
COMMAND ./compile_migrations.sh
@ -73,23 +30,32 @@ add_custom_target(migrations
)
add_custom_target(run
COMMAND core
COMMAND ./core start
DEPENDS core
WORKING_DIRECTORY ${CMAKE_PROJECT_DIR}
)
add_custom_target(debug
COMMAND valgrind ./core
COMMAND valgrind ./core start
DEPENDS core
WORKING_DIRECTORY ${CMAKE_PROJECT_DIR}
)
add_custom_target(debug-full
COMMAND valgrind --leak-check=full --show-leak-kinds=all ./core
COMMAND valgrind --leak-check=full --show-leak-kinds=all ./core start
DEPENDS core
WORKING_DIRECTORY ${CMAKE_PROJECT_DIR}
)
add_custom_target(docs
COMMAND doxygen
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
add_custom_target(test
COMMAND ./run_tests.sh ${CMAKE_BINARY_DIR}/core ${CMAKE_SOURCE_DIR}/config.testing.json
COMMAND ./run_tests.sh ${CMAKE_BINARY_DIR}/core "dev"
DEPENDS core
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tavern_tests
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
)
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}
)

2548
Doxyfile Normal file

File diff suppressed because it is too large Load diff

141
command.c Normal file
View file

@ -0,0 +1,141 @@
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <command.h>
#include <mpack.h>
#include <logger.h>
#include <helpers.h>
#include <enums.h>
#include <models/controller.h>
int
command_set_relay_schedule(relay_t *relay)
{
controller_t *controller = controller_get_by_id(relay->controller_id);
if(!controller)
{
LOG_ERROR("couldn't find controller\n");
return 1;
}
char* payload;
size_t payload_size;
mpack_writer_t writer;
mpack_writer_init_growable(&writer, &payload, &payload_size);
// 3 = code, relay num, relay name, schedules(array)
mpack_start_map(&writer, 3);
mpack_write_uint(&writer, COMMAND_MAPPING_CODE);
mpack_write_u8(&writer, COMMAND_CODE_SET_SCHEDULE);
mpack_write_uint(&writer, COMMAND_MAPPING_RELAY_NUM);
mpack_write_u8(&writer, relay->number);
mpack_write_uint(&writer, COMMAND_MAPPING_SCHEDULES_ARRAY);
// 7 = days of week
mpack_start_array(&writer, 7);
for(int i = 0; i < 7; ++i)
{
uint16_t *periods_blob = schedule_periods_to_blob(relay->schedules[i]);
uint16_t periods_count = periods_blob[0];
// 3 = code, relaynum, schedules(array)
mpack_start_map(&writer, 3);
mpack_write_uint(&writer, COMMAND_MAPPING_PERIODS_COUNT);
mpack_write_u16(&writer, periods_count);
mpack_write_uint(&writer, COMMAND_MAPPING_SCHEDULE_ID);
mpack_write_bin(&writer, (char*)relay->schedules[0]->uid, sizeof(uuid_t));
mpack_write_uint(&writer, COMMAND_MAPPING_PERIODS_BLOB);
// periods + 1 to skip length in periods[0]
// periods_count * 2 because each uint16_t is a timestamp. 2 are start and end
mpack_write_bin(&writer, (char*)(periods_blob + 1), sizeof(uint16_t) * periods_count * 2);
mpack_finish_map(&writer);
free(periods_blob);
}
mpack_finish_array(&writer);
mpack_finish_map(&writer);
// finish writing
if (mpack_writer_destroy(&writer) != mpack_ok)
{
LOG_ERROR("an error occurred encoding the data");
controller_free(controller);
return 1;
}
int result = command_send(controller, COMMAND_CODE_SET_SCHEDULE, payload, payload_size);
controller_free(controller);
free(payload);
return result;
}
int
command_set_controller_name(controller_t *controller)
{
char* payload;
size_t payload_size;
mpack_writer_t writer;
mpack_writer_init_growable(&writer, &payload, &payload_size);
// write the example on the msgpack homepage
mpack_start_map(&writer, 2);
mpack_write_uint(&writer, COMMAND_MAPPING_CODE);
mpack_write_u8(&writer, COMMAND_CODE_SET_NAME);
mpack_write_uint(&writer, COMMAND_MAPPING_NAME);
mpack_write_cstr(&writer, controller->name);
mpack_finish_map(&writer);
// finish writing
if (mpack_writer_destroy(&writer) != mpack_ok)
{
LOG_ERROR("an error occurred encoding the data");
return 1;
}
int result = command_send(controller, COMMAND_CODE_SET_NAME, payload, payload_size);
free(payload);
return result;
}
int
command_send(controller_t *controller, int command_code, char *payload, uint32_t payload_size)
{
LOG_DEBUG("commanding %d\n", command_code);
int bytes_transferred;
int fd_controller = helper_connect_tcp_server(controller->ip, controller->port);
if(fd_controller == -1)
{
LOG_ERROR("can't open command socket %s:%d\n", controller->ip, controller->port);
return 1;
}
if((bytes_transferred = send(fd_controller, &payload_size, sizeof(payload_size), 0)) <= 0)
{
LOG_ERROR("error during sending size\n");
return 1;
}
if((bytes_transferred = send(fd_controller, payload, payload_size, 0)) <= 0)
{
LOG_ERROR("error during sending\n");
return 1;
}
close(fd_controller);
return 0;
}

View file

@ -6,7 +6,7 @@ migration_num=0;
while [ -f sql/migration_$migration_num.sql ]
do
xxd -i sql/migration_$migration_num.sql | sed 's/\([0-9a-f]\)$/\0, 0x00/' > sql/migration_$migration_num.h
xxd -i sql/migration_$migration_num.sql | sed 's/\([0-9a-f]\)$/\0, 0x00/' > include/migrations/$migration_num.sql.h
((migration_num++));
done;

74
config.c Normal file
View file

@ -0,0 +1,74 @@
#include <stdlib.h>
#include <string.h>
#include <logger.h>
#include <config.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))
int
config_load(IniDispatch *disp, void *config_void)
{
config_t *config = (config_t*)config_void;
if(disp->type == INI_KEY)
{
if(CONFINI_IS_KEY("core", "database"))
{
config->database = malloc(sizeof(char) * (strlen(disp->value) + 1));
strcpy(config->database, disp->value);
return 0;
}
if(CONFINI_IS_KEY("core", "log-level"))
{
if(strcasecmp(disp->value, "trace") == 0)
{
config->log_level = LOG_LEVEL_TRACE;
return 0;
}
if(strcasecmp(disp->value, "debug") == 0)
{
config->log_level = LOG_LEVEL_DEBUG;
return 0;
}
if(strcasecmp(disp->value, "info") == 0)
{
config->log_level = LOG_LEVEL_INFO;
return 0;
}
if(strcasecmp(disp->value, "warn") == 0)
{
config->log_level = LOG_LEVEL_WARN;
return 0;
}
if(strcasecmp(disp->value, "error") == 0)
{
config->log_level = LOG_LEVEL_ERROR;
return 0;
}
if(strcasecmp(disp->value, "fatal") == 0)
{
config->log_level = LOG_LEVEL_FATAL;
return 0;
}
LOG_WARN("invalid log-level '%s'\n", disp->value);
return 0;
}
if(CONFINI_IS_KEY("core", "discovery-port"))
{
config->discovery_port = atoi(disp->value);
return 0;
}
if(CONFINI_IS_KEY("core", "server-port"))
{
strcpy(config->server_port, disp->value);
return 0;
}
}
return 0;
}

View file

@ -1,23 +0,0 @@
#include "config.h"
namespace config
{
char version[] = "0.0.1";
uint16_t discover_max_client_backlog = 20;
uint16_t discover_port_dev = 4421;
uint16_t discover_port_test = 4420;
uint16_t discover_port = 4419;
uint16_t discover_timeout_ms = 2000;
uint8_t discover_code_accept = 0;
uint8_t discover_code_reject = 100;
uint8_t command_code_get_time = 1;
uint8_t command_code_get_id = 2;
uint8_t command_code_set_name = 100;
uint8_t command_code_get_name = 101;
uint8_t command_code_set_schedule = 102;
uint8_t command_code_get_schedule = 103;
uint8_t command_code_set_relay_name = 104;
uint8_t command_code_get_relay_name = 105;
}

View file

@ -1,27 +0,0 @@
#ifndef EMGAUWA_CORE_CONFIG_H
#define EMGAUWA_CORE_CONFIG_H
#include <stdint-gcc.h>
namespace config
{
extern char version[];
extern uint16_t discover_max_client_backlog;
extern uint16_t discover_port_dev;
extern uint16_t discover_port;
extern uint16_t discover_timeout_ms;
extern uint8_t discover_code_accept;
extern uint8_t discover_code_reject;
extern uint8_t command_code_get_time;
extern uint8_t command_code_get_id;
extern uint8_t command_code_set_name;
extern uint8_t command_code_get_name;
extern uint8_t command_code_set_schedule;
extern uint8_t command_code_get_schedule;
extern uint8_t command_code_set_relay_name;
extern uint8_t command_code_get_relay_name;
}
#endif //EMGAUWA_CORE_CONFIG_H

View file

@ -1,12 +0,0 @@
{
"listeners": [
{
"address": "0.0.0.0",
"port": 5000,
"https": false
}
],
"custom_config": {
"db_name": "core.sqlite"
}
}

View file

@ -1,185 +0,0 @@
/* This is a JSON format configuration file
*/
{
/*
//ssl:The global ssl files setting
"ssl": {
"cert": "../../trantor/trantor/tests/server.pem",
"key": "../../trantor/trantor/tests/server.pem"
},
"listeners": [
{
//address: Ip address,0.0.0.0 by default
"address": "0.0.0.0",
//port: Port number
"port": 80,
//https: If true, use https for security,false by default
"https": false
},
{
"address": "0.0.0.0",
"port": 443,
"https": true,
//cert,key: Cert file path and key file path, empty by default,
//if empty, use the global setting
"cert": "",
"key": ""
}
],
"db_clients": [
{
//name: Name of the client,'default' by default
//"name":"",
//rdbms: Server type, postgresql,mysql or sqlite3, "postgresql" by default
"rdbms": "postgresql",
//filename: Sqlite3 db file name
//"filename":"",
//host: Server address,localhost by default
"host": "127.0.0.1",
//port: Server port, 5432 by default
"port": 5432,
//dbname: Database name
"dbname": "test",
//user: 'postgres' by default
"user": "",
//passwd: '' by default
"passwd": "",
//is_fast: false by default, if it is true, the client is faster but user can't call
//any synchronous interface of it.
"is_fast": false,
//connection_number: 1 by default, valid only if is_fast is false.
"connection_number": 1
}
],*/
"app": {
//threads_num: The number of IO threads, 1 by default, if the value is set to 0, the number of threads
//is the number of CPU cores
"threads_num": 1,
//enable_session: False by default
"enable_session": false,
"session_timeout": 0,
//document_root: Root path of HTTP document, defaut path is ./
"document_root": "./",
//home_page: Set the HTML file of the home page, the default value is "index.html"
//If there isn't any handler registered to the path "/", the home page file in the "document_root" is send to clients as a response
//to the request for "/".
"home_page": "index.html",
//upload_path: The path to save the uploaded file. "uploads" by default.
//If the path isn't prefixed with /, ./ or ../,
//it is relative path of document_root path
"upload_path": "uploads",
/* file_types:
* HTTP download file types,The file types supported by drogon
* by default are "html", "js", "css", "xml", "xsl", "txt", "svg",
* "ttf", "otf", "woff2", "woff" , "eot", "png", "jpg", "jpeg",
* "gif", "bmp", "ico", "icns", etc. */
"file_types": [
"gif",
"png",
"jpg",
"js",
"css",
"html",
"ico",
"swf",
"xap",
"apk",
"cur",
"xml"
],
//max_connections: maximum connections number,100000 by default
"max_connections": 100000,
//max_connections_per_ip: maximum connections number per clinet,0 by default which means no limit
"max_connections_per_ip": 0,
//Load_dynamic_views: False by default, when set to true, drogon
//compiles and loads dynamically "CSP View Files" in directories defined
//by "dynamic_views_path"
"load_dynamic_views": false,
//dynamic_views_path: If the path isn't prefixed with /, ./ or ../,
//it is relative path of document_root path
"dynamic_views_path": [
"./views"
],
//log: Set log output, drogon output logs to stdout by default
"log": {
//log_path: Log file path,empty by default,in which case,logs are output to the stdout
//"log_path": "./",
//logfile_base_name: Log file base name,empty by default which means drogon names logfile as
//drogon.log ...
"logfile_base_name": "",
//log_size_limit: 100000000 bytes by default,
//When the log file size reaches "log_size_limit", the log file is switched.
"log_size_limit": 100000000,
//log_level: "DEBUG" by default,options:"TRACE","DEBUG","INFO","WARN"
//The TRACE level is only valid when built in DEBUG mode.
"log_level": "DEBUG"
},
//run_as_daemon: False by default
"run_as_daemon": false,
//relaunch_on_error: False by default, if true, the program will be restart by the parent after exiting;
"relaunch_on_error": false,
//use_sendfile: True by default, if ture, the program
//uses sendfile() system-call to send static files to clients;
"use_sendfile": true,
//use_gzip: True by default, use gzip to compress the response body's content;
"use_gzip": true,
//static_files_cache_time: 5 (seconds) by default, the time in which the static file response is cached,
//0 means cache forever, the negative value means no cache
"static_files_cache_time": 5,
//simple_controllers_map: Used to configure mapping from path to simple controller
"simple_controllers_map": [{
"path": "/path/name",
"controller": "controllerClassName",
"http_methods": [
"get",
"post"
],
"filters": [
"FilterClassName"
]
}],
//idle_connection_timeout: Defaults to 60 seconds, the lifetime
//of the connection without read or write
"idle_connection_timeout": 60,
//server_header_field: Set the 'server' header field in each response sent by drogon,
//empty string by default with which the 'server' header field is set to "Server: drogon/version string\r\n"
"server_header_field": "",
//keepalive_requests: Set the maximum number of requests that can be served through one keep-alive connection.
//After the maximum number of requests are made, the connection is closed.
//The default value of 0 means no limit.
"keepalive_requests": 0,
//pipelining_requests: Set the maximum number of unhandled requests that can be cached in pipelining buffer.
//After the maximum number of requests are made, the connection is closed.
//The default value of 0 means no limit.
"pipelining_requests": 0,
//gzip_static: If it is set to true, when the client requests a static file, drogon first finds the compressed
//file with the extension ".gz" in the same path and send the compressed file to the client.
//The default value of gzip_static is true.
"gzip_static": true,
//client_max_body_size: Set the maximum body size of HTTP requests received by drogon. The default value is "1M".
//One can set it to "1024", "1k", "10M", "1G", etc. Setting it to "" means no limit.
"client_max_body_size": "1M",
//max_memory_body_size: Set the maximum body size in memory of HTTP requests received by drogon. The default value is "64K" bytes.
//If the body size of a HTTP request exceeds this limit, the body is stored to a temporary file for processing.
//Setting it to "" means no limit.
"client_max_memory_body_size": "64K",
//client_max_websocket_message_size: Set the maximum size of messages sent by WebSocket client. The default value is "128K".
//One can set it to "1024", "1k", "10M", "1G", etc. Setting it to "" means no limit.
"client_max_websocket_message_size": "128K"
},
//plugins: Define all plugins running in the application
"plugins": [{
//name: The class name of the plugin
//"name": "TestPlugin",
//dependencies: Plugins that the plugin depends on. It can be commented out
"dependencies": [],
//config: The configuration of the plugin. This json object is the parameter to initialize the plugin.
//It can be commented out
"config": {
"heartbeat_interval": 2
}
}],
//custom_config: custom configuration for users. This object can be get by the app().getCustomConfig() method.
"custom_config": {}
}

View file

@ -1,12 +0,0 @@
{
"listeners": [
{
"address": "0.0.0.0",
"port": 5000,
"https": false
}
],
"custom_config": {
"db_name": "core.testing.sqlite"
}
}

View file

@ -1,180 +0,0 @@
#include <models/controller_dbo.h>
#include <config.h>
#include <enums.h>
#include <mpack.h>
#include "api_v1_controllers.h"
using namespace api::v1;
void
controllers::get_all(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback)
{
controller_dbo **all_controllers = controller_dbo::get_all();
Json::Value all_controllers_json(Json::arrayValue);
for(int i = 0; all_controllers[i] != nullptr; i++)
{
all_controllers_json.append(all_controllers[i]->to_json());
}
auto resp = HttpResponse::newHttpJsonResponse(all_controllers_json);
callback(resp);
controller_dbo::free_list(all_controllers);
}
void
controllers::get_one_by_id(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback,
const std::string& controller_id_str)
{
uuid_t controller_id;
if(uuid_parse(controller_id_str.c_str(), controller_id))
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
controller_dbo **controllers = controller_dbo::get_by_simple("id", controller_id, (intptr_t) &sqlite3_bind_blob, sizeof(uuid_t));
if(controllers[0])
{
auto resp = HttpResponse::newHttpJsonResponse(controllers[0]->to_json());
callback(resp);
}
else
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k404NotFound);
callback(resp);
}
controller_dbo::free_list(controllers);
}
void
controllers::get_one_by_tag(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback,
const std::string &controller_tag)
{
controller_dbo **controllers = controller_dbo::get_by_simple("tag", controller_tag.c_str(), (intptr_t) &sqlite3_bind_text, -1);
if(controllers[0])
{
auto resp = HttpResponse::newHttpJsonResponse(controllers[0]->to_json());
callback(resp);
}
else
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k404NotFound);
callback(resp);
}
controller_dbo::free_list(controllers);
}
void
controllers::delete_one_by_id(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, const std::string& controller_id_str)
{
uuid_t controller_id;
if(uuid_parse(controller_id_str.c_str(), controller_id))
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
controller_dbo **controllers = controller_dbo::get_by_simple("id", controller_id, (intptr_t) &sqlite3_bind_blob, sizeof(uuid_t));
if(controllers[0])
{
auto resp = HttpResponse::newHttpResponse();
if(!controllers[0]->remove())
{
resp->setStatusCode(k500InternalServerError);
}
callback(resp);
}
else
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k404NotFound);
callback(resp);
}
controller_dbo::free_list(controllers);
}
void
controllers::put_one_by_id(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback,
const std::string &controller_id_str)
{
Json::Value body = *req->getJsonObject();
uuid_t controller_id;
if(uuid_parse(controller_id_str.c_str(), controller_id))
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
controller_dbo **controllers = controller_dbo::get_by_simple("id", controller_id, (intptr_t) &sqlite3_bind_blob, sizeof(uuid_t));
if(controllers[0])
{
strncpy(controllers[0]->name, body["name"].asCString(), 127);
strncpy(controllers[0]->ip, body["ip"].asCString(), 16);
controllers[0]->name[127] = '\0';
controllers[0]->ip[16] = '\0';
auto resp = HttpResponse::newHttpResponse();
if(controllers[0]->update())
{
char* data;
size_t size;
mpack_writer_t writer;
mpack_writer_init_growable(&writer, &data, &size);
// write the example on the msgpack homepage
mpack_start_map(&writer, 2);
mpack_write_uint(&writer, COMMAND_MAPPING_CODE);
mpack_write_u8(&writer, config::command_code_set_name);
mpack_write_uint(&writer, COMMAND_MAPPING_NAME);
mpack_write_cstr(&writer, controllers[0]->name);
mpack_finish_map(&writer);
// finish writing
if (mpack_writer_destroy(&writer) != mpack_ok) {
LOG_ERROR << "an error occurred encoding the data";
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k500InternalServerError);
callback(resp);
return;
}
controllers[0]->command(config::command_code_set_name, data, size);
resp = HttpResponse::newHttpJsonResponse(controllers[0]->to_json());
}
else
{
resp->setStatusCode(k500InternalServerError);
}
callback(resp);
}
else
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k404NotFound);
callback(resp);
}
controller_dbo::free_list(controllers);
}

View file

@ -1,33 +0,0 @@
#pragma once
#include <drogon/HttpController.h>
using namespace drogon;
namespace api::v1
{
class controllers:public drogon::HttpController<controllers>
{
public:
METHOD_LIST_BEGIN
METHOD_ADD(controllers::post_discover, "/discover", Post, Options);
METHOD_ADD(controllers::get_all, "/", Get, Options);
METHOD_ADD(controllers::get_one_by_id, "/{1}", Get, Options);
METHOD_ADD(controllers::get_one_by_tag, "/tag/{1}", Get, Options);
METHOD_ADD(controllers::delete_one_by_id, "/{1}", Delete, Options);
METHOD_ADD(controllers::put_one_by_id, "/{1}", Put, Options, "filters::json_required", "filters::controllers::valid_json");
METHOD_ADD(controllers::get_relays_all, "/{1}/relays/", Get, Options);
METHOD_ADD(controllers::get_relays_one_by_id_and_num, "/{1}/relays/{2}", Get, Options);
METHOD_ADD(controllers::put_relays_one_by_id_and_num, "/{1}/relays/{2}", Put, Options, "filters::json_required", "filters::relays::valid_json");
METHOD_LIST_END
static void post_discover(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback);
static void get_all(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback);
static void get_one_by_id(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback, const std::string& controller_id_str);
static void get_one_by_tag(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback, const std::string& controller_tag);
static void delete_one_by_id(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback, const std::string& controller_id_str);
static void put_one_by_id(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback, const std::string& controller_id);
static void get_relays_all(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback, const std::string& controller_id_str);
static void get_relays_one_by_id_and_num(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback, const std::string& controller_id_str, int relay_num);
static void put_relays_one_by_id_and_num(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback, const std::string& controller_id_str, int relay_num);
};
}

View file

@ -1,187 +0,0 @@
#include <netdb.h>
#include <unistd.h>
#include <cmath>
#include <uuid/uuid.h>
#include <mpack.h>
#include <config.h>
#include <helpers.h>
#include <models/controller_dbo.h>
#include "api_v1_controllers.h"
using namespace api::v1;
enum DISCOVERY_MAPPING
{
DISCOVERY_MAPPING_ID = 0,
DISCOVERY_MAPPING_NAME = 1,
DISCOVERY_MAPPING_COMMAND_PORT = 2,
DISCOVERY_MAPPING_RELAY_COUNT = 3,
};
void controllers::post_discover(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback)
{
int discover_server_socket = helpers::bind_tcp_server("0.0.0.0", "0", config::discover_max_client_backlog);
int discover_server_port = helpers::get_server_port(discover_server_socket);
if(discover_server_port == -1)
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k500InternalServerError);
callback(resp);
return;
}
int16_t payload[1];
payload[0] = discover_server_port;
if(helpers::send_udp_broadcast("255.255.255.255", config::discover_port_dev, payload, sizeof(payload)) < 0)
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k500InternalServerError);
callback(resp);
return;
}
struct sockaddr_storage their_addr{};
socklen_t addr_size;
int client_fd, s_ret;
fd_set accept_fds;
struct timeval timeout{};
uint8_t discover_answer_buf[1];
controller_dbo **known_controllers = controller_dbo::get_all();
while(true)
{
addr_size = sizeof(their_addr);
FD_ZERO(&accept_fds);
FD_SET(discover_server_socket, &accept_fds); // NOLINT(hicpp-signed-bitwise)
timeout.tv_sec = config::discover_timeout_ms / 1000;
timeout.tv_usec = (config::discover_timeout_ms % 1000) * 1000;
s_ret = select(discover_server_socket + 1, &accept_fds, nullptr, nullptr, &timeout);
if(s_ret == 0)
{
break;
}
else
{
if((client_fd = accept(discover_server_socket, (struct sockaddr *) &their_addr, &addr_size)) < 0)
{
LOG_ERROR << "Error Accepting client " << strerror(errno);
continue;
}
size_t payload_length;
if(recv(client_fd, &payload_length, sizeof(payload_length), 0) <= 0)
{
LOG_ERROR << "Error Receiving header from client";
continue;
}
char *answer_payload = (char*)malloc((payload_length));
ssize_t bytes_transferred;
if((bytes_transferred = recv(client_fd, answer_payload, payload_length, 0)) <= 0)
{
LOG_ERROR << "Error Receiving payload from client";
continue;
}
struct sockaddr_in addr{};
socklen_t client_addr_size = sizeof(struct sockaddr_in);
if(getpeername(client_fd, (struct sockaddr *)&addr, &client_addr_size) != 0)
{
LOG_ERROR << "Error Receiving payload from client";
continue;
}
uuid_t discovered_id;
mpack_tree_t tree;
mpack_tree_init_data(&tree, answer_payload, payload_length);
mpack_tree_parse(&tree);
mpack_node_t root = mpack_tree_root(&tree);
memcpy(discovered_id, mpack_node_data(mpack_node_map_uint(root, DISCOVERY_MAPPING_ID)), sizeof(uuid_t));
uint16_t discovered_command_port = mpack_node_u16(mpack_node_map_uint(root, DISCOVERY_MAPPING_COMMAND_PORT));
uint8_t discovered_relay_count = mpack_node_u8(mpack_node_map_uint(root, DISCOVERY_MAPPING_RELAY_COUNT));
const char *discovered_name = mpack_node_str(mpack_node_map_uint(root, DISCOVERY_MAPPING_NAME));
bool found_discovered_in_list = false;
for(int i = 0; known_controllers[i] != nullptr; i++)
{
if(!found_discovered_in_list)
{
if(uuid_compare(known_controllers[i]->id, discovered_id) == 0)
{
known_controllers[i]->active = true;
strncpy(known_controllers[i]->name, discovered_name, 128);
known_controllers[i]->name[127] = '\0';
known_controllers[i]->port = discovered_command_port;
known_controllers[i]->relay_count = discovered_relay_count;
known_controllers[i]->update();
delete known_controllers[i];
found_discovered_in_list = true;
known_controllers[i] = known_controllers[i + 1];
}
}
else
{
known_controllers[i] = known_controllers[i + 1];
}
}
if(!found_discovered_in_list)
{
controller_dbo discovered_controller{};
strcpy(discovered_controller.ip, inet_ntoa(addr.sin_addr));
memcpy(discovered_controller.id, discovered_id, sizeof(uuid_t));
strcpy(discovered_controller.name, discovered_name);
discovered_controller.relay_count = discovered_relay_count;
discovered_controller.port = discovered_command_port;
discovered_controller.active = true;
discovered_controller.insert();
}
mpack_tree_destroy(&tree);
free(answer_payload);
discover_answer_buf[0] = config::discover_code_accept;
send(client_fd, discover_answer_buf, sizeof(uint8_t), 0);
close(client_fd);
}
}
for(int i = 0; known_controllers[i] != nullptr; i++)
{
known_controllers[i]->active = false;
known_controllers[i]->update();
LOG_DEBUG << "Lost: " << known_controllers[i]->name;
}
controller_dbo::free_list(known_controllers);
controller_dbo **all_controllers = controller_dbo::get_all();
Json::Value all_controllers_json(Json::arrayValue);
for(int i = 0; all_controllers[i] != nullptr; i++)
{
all_controllers_json.append(all_controllers[i]->to_json());
}
auto resp = HttpResponse::newHttpJsonResponse(all_controllers_json);
callback(resp);
controller_dbo::free_list(all_controllers);
}

View file

@ -1,268 +0,0 @@
#include <netdb.h>
#include <models/relay_dbo.h>
#include <helpers.h>
#include <mpack.h>
#include <models/controller_dbo.h>
#include <models/schedule_dbo.h>
#include <models/junction_tag_dbo.h>
#include <models/tag_dbo.h>
#include <config.h>
#include <enums.h>
#include "api_v1_controllers.h"
using namespace api::v1;
void
controllers::get_relays_all(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback,
const std::string& controller_id_str)
{
uuid_t controller_id;
if(uuid_parse(controller_id_str.c_str(), controller_id))
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
relay_dbo **all_controller_relays = relay_dbo::get_by_simple("controller_id", (void *) controller_id, (intptr_t) sqlite3_bind_blob, sizeof(uuid_t));
Json::Value all_relays_json(Json::arrayValue);
for(int i = 0; all_controller_relays[i] != nullptr; i++)
{
all_relays_json.append(all_controller_relays[i]->to_json());
}
auto resp = HttpResponse::newHttpJsonResponse(all_relays_json);
callback(resp);
relay_dbo::free_list(all_controller_relays);
}
void
controllers::get_relays_one_by_id_and_num(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback, const std::string& controller_id_str,
int relay_num)
{
uuid_t controller_id;
if(uuid_parse(controller_id_str.c_str(), controller_id))
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
relay_dbo *relay = relay_dbo::get_relay_for_controller(controller_id, relay_num);
if(relay)
{
auto resp = HttpResponse::newHttpJsonResponse(relay->to_json());
callback(resp);
delete relay;
}
else
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k404NotFound);
callback(resp);
}
}
void
controllers::put_relays_one_by_id_and_num(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback, const std::string& controller_id_str,
int relay_num)
{
uuid_t controller_id;
if(uuid_parse(controller_id_str.c_str(), controller_id))
{
LOG_DEBUG << "bad uuid";
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
if(!relay_dbo::valid_num_for_controller(controller_id, relay_num))
{
LOG_DEBUG << "invalid num for controller";
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
Json::Value body = *req->getJsonObject();
bool set_name = body["name"].type() == Json::ValueType::stringValue;
bool set_tags = body["tags"].type() == Json::ValueType::arrayValue;
bool set_schedules = body["schedules"].type() == Json::ValueType::arrayValue;
bool set_active_schedule = body["active_schedule"].type() == Json::ValueType::objectValue;
relay_dbo *relay = relay_dbo::get_relay_for_controller(controller_id, relay_num);
schedule_dbo **schedule_list;
schedule_dbo *active_schedule;
schedule_dbo *schedules[7];
if(set_schedules)
{
uuid_t schedules_ids[7];
for(int i = 0; i < 7; ++i)
{
if(schedule_dbo::parse_uid(body["schedules"][i]["id"].asCString(), schedules_ids[i]))
{
LOG_DEBUG << "parse_uid failed for schedule " << i;
auto res = drogon::HttpResponse::newHttpResponse();
res->setStatusCode(k400BadRequest);
callback(res);
return;
}
}
for(int i = 0; i < 7; ++i)
{
schedule_list = schedule_dbo::get_by_simple("uid", schedules_ids[i], (intptr_t)&sqlite3_bind_blob, sizeof(uuid_t));
schedules[i] = schedule_list[0];
free(schedule_list);
}
}
if(set_active_schedule)
{
uuid_t active_schedule_id;
if(schedule_dbo::parse_uid(body["active_schedule"]["id"].asCString(), active_schedule_id))
{
LOG_DEBUG << "bad active_schedule uuid";
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
schedule_list = schedule_dbo::get_by_simple("uid", active_schedule_id, (intptr_t)&sqlite3_bind_blob, sizeof(uuid_t));
active_schedule = schedule_list[0];
free(schedule_list);
}
if(!relay)
{
relay = new relay_dbo();
relay->number = relay_num;
uuid_copy(relay->controller_id, controller_id);
for(int i = 0; i < 7; ++i)
{
relay->schedules[i] = schedule_dbo::get_by_id_or_off(0);
}
}
if(set_name)
{
strncpy(relay->name, body["name"].asCString(), 127);
}
if(set_tags)
{
junction_tag_dbo::remove_for_relay(relay->id);
for(int i = 0; i < body["tags"].size(); ++i)
{
const char *tag = body["tags"][i].asCString();
int tag_id = tag_dbo::get_id(tag);
if(tag_id == 0)
{
tag_dbo::save(tag_id, tag);
tag_id = tag_dbo::get_id(tag);
}
junction_tag_dbo::insert(tag_id, relay->id, 0);
}
}
if(set_schedules)
{
for(int i = 0; i < 7; ++i)
{
relay->schedules[i] = schedules[i];
}
relay->active_schedule = schedules[helpers::get_day_of_week()];
}
if(set_active_schedule)
{
relay->schedules[helpers::get_day_of_week()] = active_schedule;
relay->active_schedule = active_schedule;
}
if(!relay->save())
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k500InternalServerError);
callback(resp);
}
else
{
auto controllers = controller_dbo::get_by_simple("id", controller_id, (intptr_t)&sqlite3_bind_blob, sizeof(uuid_t));
char* data;
size_t size;
mpack_writer_t writer;
mpack_writer_init_growable(&writer, &data, &size);
// 3 = code, relay num, relay name, schedules(array)
mpack_start_map(&writer, 3);
mpack_write_uint(&writer, COMMAND_MAPPING_CODE);
mpack_write_u8(&writer, config::command_code_set_schedule);
mpack_write_uint(&writer, COMMAND_MAPPING_RELAY_NUM);
mpack_write_u8(&writer, relay->number);
mpack_write_uint(&writer, COMMAND_MAPPING_SCHEDULES_ARRAY);
// 7 = days of week
mpack_start_array(&writer, 7);
for(int i = 0; i < 7; ++i)
{
uint16_t *periods = relay->schedules[i]->periods->to_blob();
uint16_t periods_count = periods[0];
// 3 = code, relaynum, schedules(array)
mpack_start_map(&writer, 3);
mpack_write_uint(&writer, COMMAND_MAPPING_PERIODS_COUNT);
mpack_write_u16(&writer, periods_count);
mpack_write_uint(&writer, COMMAND_MAPPING_SCHEDULE_ID);
mpack_write_bin(&writer, (char*)relay->schedules[0]->uid, sizeof(uuid_t));
mpack_write_uint(&writer, COMMAND_MAPPING_PERIODS_BLOB);
// periods + 1 to skip length in periods[0]
// periods_count * 2 because each uint16_t is a timestamp. 2 are start and end
mpack_write_bin(&writer, (char*)(periods + 1), sizeof(uint16_t) * periods_count * 2);
mpack_finish_map(&writer);
free(periods);
}
mpack_finish_array(&writer);
mpack_finish_map(&writer);
// finish writing
if (mpack_writer_destroy(&writer) != mpack_ok) {
LOG_ERROR << "an error occurred encoding the data";
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k500InternalServerError);
callback(resp);
return;
}
controllers[0]->command(config::command_code_set_name, data, size);
auto resp = HttpResponse::newHttpJsonResponse(relay->to_json());
callback(resp);
controller_dbo::free_list(controllers);
}
delete relay;
}

View file

@ -1,54 +0,0 @@
#include <netdb.h>
#include <models/relay_dbo.h>
#include <models/tag_dbo.h>
#include <models/junction_tag_dbo.h>
#include <helpers.h>
#include "api_v1_relays.h"
using namespace api::v1;
void
relays::get_all(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback)
{
relay_dbo **all_relays = relay_dbo::get_all();
Json::Value all_relays_json(Json::arrayValue);
for(int i = 0; all_relays[i] != nullptr; i++)
{
all_relays_json.append(all_relays[i]->to_json());
}
auto resp = HttpResponse::newHttpJsonResponse(all_relays_json);
callback(resp);
relay_dbo::free_list(all_relays);
}
void
relays::get_by_tag(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback,
const std::string &tag)
{
int tag_id = tag_dbo::get_id(tag.c_str());
int *relays_ids = junction_tag_dbo::get_relays_for_tag_id(tag_id);
if(relays_ids == nullptr)
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k500InternalServerError);
callback(resp);
}
Json::Value relays_json(Json::arrayValue);
for(int i = 0; relays_ids[i] != 0; ++i)
{
relay_dbo *relay = relay_dbo::get_by_id(relays_ids[i]);
if(relay)
{
relays_json.append(relay->to_json());
}
}
auto resp = HttpResponse::newHttpJsonResponse(relays_json);
callback(resp);
free(relays_ids);
}

View file

@ -1,17 +0,0 @@
#pragma once
#include <drogon/HttpController.h>
using namespace drogon;
namespace api::v1
{
class relays:public drogon::HttpController<relays>
{
public:
METHOD_LIST_BEGIN
METHOD_ADD(relays::get_all, "/", Get, Options);
METHOD_ADD(relays::get_by_tag, "/tag/{1}", Get, Options);
METHOD_LIST_END
static void get_all(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback);
static void get_by_tag(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback, const std::string& tag);
};
}

View file

@ -1,277 +0,0 @@
#include <netdb.h>
#include <models/schedule_dbo.h>
#include <models/tag_dbo.h>
#include <models/junction_tag_dbo.h>
#include <helpers.h>
#include "api_v1_schedules.h"
using namespace api::v1;
void
schedules::get_all(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback)
{
schedule_dbo **all_schedules = schedule_dbo::get_all();
Json::Value all_schedules_json(Json::arrayValue);
for(int i = 0; all_schedules[i] != nullptr; i++)
{
all_schedules_json.append(all_schedules[i]->to_json());
}
Json::StreamWriterBuilder jw;
auto resp = HttpResponse::newHttpJsonResponse(all_schedules_json);
callback(resp);
schedule_dbo::free_list(all_schedules);
}
void
schedules::get_one_by_id(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, const std::string& schedule_id_str)
{
uuid_t schedule_id;
if(schedule_dbo::parse_uid(schedule_id_str.c_str(), schedule_id))
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
schedule_dbo **schedules = schedule_dbo::get_by_simple("uid", schedule_id, (intptr_t) &sqlite3_bind_blob, sizeof(uuid_t));
if(schedules[0])
{
auto resp = HttpResponse::newHttpJsonResponse(schedules[0]->to_json());
callback(resp);
}
else
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k404NotFound);
callback(resp);
}
schedule_dbo::free_list(schedules);
}
void
schedules::delete_one_by_id(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, const std::string& schedule_id_str)
{
if(strcmp(schedule_id_str.c_str(), "off") == 0 || strcmp(schedule_id_str.c_str(), "on") == 0)
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k403Forbidden);
callback(resp);
return;
}
uuid_t schedule_id;
if(schedule_dbo::parse_uid(schedule_id_str.c_str(), schedule_id))
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
schedule_dbo **schedules = schedule_dbo::get_by_simple("uid", schedule_id, (intptr_t) &sqlite3_bind_blob, sizeof(uuid_t));
if(schedules[0])
{
auto resp = HttpResponse::newHttpResponse();
if(!schedules[0]->remove())
{
resp->setStatusCode(k500InternalServerError);
}
callback(resp);
}
else
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k404NotFound);
callback(resp);
}
schedule_dbo::free_list(schedules);
}
void
schedules::post_new(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback)
{
Json::Value body = *req->jsonObject();
bool set_name = body["name"].type() == Json::ValueType::stringValue;
bool set_tags = body["tags"].type() == Json::ValueType::arrayValue;
//bool set_periods = body["periods"].type() == Json::ValueType::arrayValue;
if(!set_name || !set_name)
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
callback(resp);
}
schedule_dbo new_schedule{};
strncpy(new_schedule.name, body["name"].asCString(), 127);
new_schedule.name[127] = '\0';
uuid_generate(new_schedule.uid);
new_schedule.periods = helpers::parse_periods(body["periods"]);
if(!new_schedule.insert())
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k500InternalServerError);
callback(resp);
}
else
{
auto resp = HttpResponse::newHttpJsonResponse(new_schedule.to_json());
callback(resp);
}
}
void
schedules::post_list(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback)
{
Json::Value body = *req->jsonObject();
Json::Value schedules_json(Json::arrayValue);
for(int i = 0; i < body.size(); ++i)
{
bool set_name = body[i]["name"].type() == Json::ValueType::stringValue;
bool set_tags = body[i]["tags"].type() == Json::ValueType::arrayValue;
//bool set_periods = body[i]["periods"].type() == Json::ValueType::arrayValue;
if(!set_name || !set_name)
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
callback(resp);
}
schedule_dbo new_schedule{};
strncpy(new_schedule.name, body[i]["name"].asCString(), 127);
new_schedule.name[127] = '\0';
uuid_generate(new_schedule.uid);
new_schedule.periods = helpers::parse_periods(body[i]["periods"]);
if(new_schedule.insert())
{
schedules_json.append(new_schedule.to_json());
}
}
auto resp = HttpResponse::newHttpJsonResponse(schedules_json);
callback(resp);
}
void
schedules::put_one_by_id(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback,
const std::string &schedule_id_str)
{
uuid_t schedule_id;
if(schedule_dbo::parse_uid(schedule_id_str.c_str(), schedule_id))
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
schedule_dbo **schedules = schedule_dbo::get_by_simple("uid", schedule_id, (intptr_t) &sqlite3_bind_blob, sizeof(uuid_t));
if(schedules[0] == nullptr)
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k404NotFound);
callback(resp);
}
Json::Value body = *req->jsonObject();
bool set_name = body["name"].type() == Json::ValueType::stringValue;
bool set_tags = body["tags"].type() == Json::ValueType::arrayValue;
bool set_periods = body["periods"].type() == Json::ValueType::arrayValue;
if(set_name)
{
strncpy(schedules[0]->name, body["name"].asCString(), 127);
schedules[0]->name[127] = '\0';
}
if(set_periods)
{
// if neither "off" nor "on" allow overwrite of periods
if(strcmp(schedule_id_str.c_str(), "off") && strcmp(schedule_id_str.c_str(), "on"))
{
delete schedules[0]->periods;
schedules[0]->periods = helpers::parse_periods(body["periods"]);
}
}
if(set_tags)
{
junction_tag_dbo::remove_for_schedule(schedules[0]->id);
for(int i = 0; i < body["tags"].size(); ++i)
{
const char *tag = body["tags"][i].asCString();
int tag_id = tag_dbo::get_id(tag);
if(tag_id == 0)
{
tag_dbo::save(tag_id, tag);
tag_id = tag_dbo::get_id(tag);
}
junction_tag_dbo::insert(tag_id, 0, schedules[0]->id);
}
}
if(!schedules[0]->update())
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k500InternalServerError);
callback(resp);
}
else
{
auto resp = HttpResponse::newHttpJsonResponse(schedules[0]->to_json());
callback(resp);
}
schedule_dbo::free_list(schedules);
}
void
schedules::get_by_tag(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback,
const std::string &tag)
{
int tag_id = tag_dbo::get_id(tag.c_str());
int *schedules_ids = junction_tag_dbo::get_schedules_for_tag_id(tag_id);
if(schedules_ids == nullptr)
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k500InternalServerError);
callback(resp);
}
Json::Value schedules_json(Json::arrayValue);
for(int i = 0; schedules_ids[i] != 0; ++i)
{
schedule_dbo *schedule = schedule_dbo::get_by_id(schedules_ids[i]);
if(schedule)
{
schedules_json.append(schedule->to_json());
}
}
auto resp = HttpResponse::newHttpJsonResponse(schedules_json);
callback(resp);
free(schedules_ids);
}

View file

@ -1,31 +0,0 @@
#pragma once
#include <drogon/HttpController.h>
using namespace drogon;
namespace api::v1
{
class schedules:public drogon::HttpController<schedules>
{
public:
METHOD_LIST_BEGIN
METHOD_ADD(schedules::post_new, "/", Post, Options, "filters::json_required", "filters::schedules::valid_json");
METHOD_ADD(schedules::post_list, "/list", Post, Options, "filters::json_required");
METHOD_ADD(schedules::get_all, "/", Get, Options);
METHOD_ADD(schedules::get_one_by_id, "/{1}", Get, Options);
METHOD_ADD(schedules::delete_one_by_id, "/{1}", Delete, Options);
METHOD_ADD(schedules::put_one_by_id, "/{1}", Put, Options, "filters::json_required", "filters::schedules::valid_json");
METHOD_ADD(schedules::get_by_tag, "/tag/{1}", Get, Options);
//METHOD_ADD(controllers::get_relays_all,"/{1}/relays",Get);
//METHOD_ADD(controllers::get_relays_one,"/{1}/relays/{2}",Get);
METHOD_LIST_END
static void post_new(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback);
static void post_list(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback);
static void get_all(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback);
static void get_one_by_id(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback, const std::string& schedule_id);
static void delete_one_by_id(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback, const std::string& schedule_id);
static void put_one_by_id(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback, const std::string& schedule_id);
static void get_by_tag(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback, const std::string& tag);
//void get_relays_all(const HttpRequestPtr& req,std::function<void (const HttpResponsePtr &)> &&callback,std::string schedule_id);
//void get_relays_one(const HttpRequestPtr& req,std::function<void (const HttpResponsePtr &)> &&callback,std::string schedule_id,std::string relay_id);
};
}

7
core.ini Normal file
View file

@ -0,0 +1,7 @@
[core]
server-port = 5000
: 4421 for dev-env; 4420 for testing-env; 4419 for prod-env; 4422 for testing
discovery-port = 4421
database = core.sqlite
log-level = debug

67
database.c Normal file
View file

@ -0,0 +1,67 @@
#include <stdint.h>
#include <stddef.h>
#include <logger.h>
#include <database.h>
#include <migrations/0.sql.h>
sqlite3 *global_database;
int
database_migrate()
{
uint16_t version_num = 0;
int s, rc;
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT version_num FROM meta LIMIT 1;", -1, &stmt, NULL);
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
version_num = sqlite3_column_int(stmt, 0);
}
else
{
version_num = 0;
}
uint16_t new_version_num = version_num;
char* err_msg;
sqlite3_finalize(stmt);
switch(version_num)
{
case 0:
LOG_INFO("migrating LEVEL 0\n");
rc = sqlite3_exec(global_database, (const char *)sql_migration_0_sql, NULL, NULL, &err_msg);
if(rc != 0)
{
LOG_FATAL("couldn't migrate LEVEL 0 (%s)\n", err_msg);
break;
}
new_version_num = 1;
default:
break;
}
if(version_num == 0)
{
sqlite3_prepare_v2(global_database, "INSERT INTO meta (version_num) VALUES (?1);", -1, &stmt, NULL);
}
else
{
sqlite3_prepare_v2(global_database, "UPDATE meta SET version_num=?1;", -1, &stmt, NULL);
}
sqlite3_bind_int(stmt, 1, new_version_num);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
LOG_FATAL("couldn't write new schema version");
}
sqlite3_finalize(stmt);
return rc != SQLITE_DONE;
}

1
drogon

@ -1 +0,0 @@
Subproject commit 543d1a8c8062b3873ef89c64ffd7394c6dd7c7e8

View file

@ -0,0 +1,48 @@
#include <cJSON.h>
#include <macros.h>
#include <constants.h>
#include <endpoints/api_v1_controllers.h>
#include <logger.h>
#include <models/junction_tag.h>
#include <models/controller.h>
#include <models/tag.h>
void
api_v1_controllers_GET(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)args;
(void)hm;
controller_t** all_controllers = controller_get_all();
cJSON *json = cJSON_CreateArray();
for(int i = 0; all_controllers[i] != NULL; ++i)
{
cJSON *json_controller = controller_to_json(all_controllers[i]);
cJSON_AddItemToArray(json, json_controller);
}
char *json_str = cJSON_Print(json);
if (json_str == NULL)
{
LOG_ERROR("failed to print controllers json\n");
static const char content[] = "failed to print json for controllers";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
}
else
{
response->status_code = 200;
response->content_type = "application/json";
response->content_length = strlen(json_str);
response->content = json_str;
response->alloced_content = true;
}
cJSON_Delete(json);
controller_free_list(all_controllers);
}

View file

@ -0,0 +1,240 @@
#include <cJSON.h>
#include <macros.h>
#include <command.h>
#include <constants.h>
#include <endpoints/api_v1_controllers.h>
#include <logger.h>
#include <models/controller.h>
#include <models/tag.h>
void
api_v1_controllers_STR_GET(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)hm;
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
LOG_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
response->status_code = 400;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
controller_t* controller = controller_get_by_uid(target_uid);
if(!controller)
{
LOG_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no controller for id found";
response->status_code = 404;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
cJSON *json = controller_to_json(controller);
char *json_str = cJSON_Print(json);
if (json_str == NULL)
{
LOG_ERROR("failed to print controller json\n");
static const char content[] = "failed to print json for controller";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
else
{
response->status_code = 200;
response->content_type = "application/json";
response->content_length = strlen(json_str);
response->content = json_str;
response->alloced_content = true;
}
cJSON_Delete(json);
controller_free(controller);
}
void
api_v1_controllers_STR_PUT(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)hm;
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
LOG_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
response->status_code = 400;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
controller_t* controller = controller_get_by_uid(target_uid);
if(!controller)
{
LOG_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no controller for id found";
response->status_code = 404;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
cJSON *json = cJSON_ParseWithLength(hm->body.p, hm->body.len);
if(json == NULL)
{
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr != NULL)
{
LOG_DEBUG("error before: %s\n", error_ptr);
}
cJSON_Delete(json);
static const char content[] = "no valid json was supplied";
response->status_code = 400;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
cJSON *json_name = cJSON_GetObjectItemCaseSensitive(json, "name");
if(cJSON_IsString(json_name) && json_name->valuestring)
{
strncpy(controller->name, json_name->valuestring, MAX_NAME_LENGTH);
controller->name[MAX_NAME_LENGTH] = '\0';
}
cJSON *json_ip = cJSON_GetObjectItemCaseSensitive(json, "ip");
if(cJSON_IsString(json_ip) && json_ip->valuestring)
{
strncpy(controller->ip, json_ip->valuestring, IP_LENGTH);
controller->ip[IP_LENGTH] = '\0';
}
if(controller_save(controller))
{
LOG_ERROR("failed to save controller\n");
free(controller);
cJSON_Delete(json);
static const char content[] = "failed to save controller to database";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
cJSON_Delete(json);
json = controller_to_json(controller);
command_set_controller_name(controller);
char *json_str = cJSON_Print(json);
if (json_str == NULL)
{
LOG_ERROR("failed to print controller json\n");
static const char content[] = "failed to print json for controller";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
}
else
{
response->status_code = 200;
response->content_type = "application/json";
response->content_length = strlen(json_str);
response->content = json_str;
response->alloced_content = true;
}
cJSON_Delete(json);
controller_free(controller);
}
void
api_v1_controllers_STR_DELETE(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)hm;
const char *target_uid_str = args[0].value.v_str;
uuid_t target_uid;
if(uuid_parse(target_uid_str, target_uid))
{
LOG_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
response->status_code = 400;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
controller_t* controller = controller_get_by_uid(target_uid);
if(!controller)
{
LOG_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no controller for id found";
response->status_code = 404;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
if(controller_remove(controller))
{
LOG_ERROR("failed to remove controller from database\n");
static const char content[] = "failed to remove controller from database";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
}
else
{
response->status_code = 200;
response->content_type = "application/json";
response->content_length = 0;
response->content = "";
response->alloced_content = false;
}
controller_free(controller);
return;
}

View file

@ -0,0 +1,77 @@
#include <cJSON.h>
#include <macros.h>
#include <constants.h>
#include <endpoints/api_v1_controllers.h>
#include <logger.h>
#include <models/controller.h>
#include <models/tag.h>
void
api_v1_controllers_STR_relays_GET(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)hm;
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
LOG_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
response->status_code = 400;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
controller_t* controller = controller_get_by_uid(target_uid);
if(!controller)
{
LOG_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no controller for id found";
response->status_code = 404;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
relay_t** all_relays = relay_get_by_controller_id(controller->id);
cJSON *json = cJSON_CreateArray();
for(int i = 0; all_relays[i] != NULL; ++i)
{
cJSON *json_relay = relay_to_json(all_relays[i]);
cJSON_AddItemToArray(json, json_relay);
}
char *json_str = cJSON_Print(json);
if (json_str == NULL)
{
LOG_ERROR("failed to print relays json\n");
static const char content[] = "failed to print json for relays";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
}
else
{
response->status_code = 200;
response->content_type = "application/json";
response->content_length = strlen(json_str);
response->content = json_str;
response->alloced_content = true;
}
cJSON_Delete(json);
relay_free_list(all_relays);
controller_free(controller);
}

View file

@ -0,0 +1,315 @@
#include <cJSON.h>
#include <macros.h>
#include <command.h>
#include <constants.h>
#include <endpoints/api_v1_controllers.h>
#include <logger.h>
#include <models/junction_tag.h>
#include <models/controller.h>
#include <models/relay.h>
#include <models/tag.h>
void
api_v1_controllers_STR_relays_INT_GET(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)hm;
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
LOG_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
response->status_code = 400;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
controller_t* controller = controller_get_by_uid(target_uid);
if(!controller)
{
LOG_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no controller for id found";
response->status_code = 404;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
relay_t* relay = relay_get_for_controller(controller->id, args[1].value.v_int);
if(!relay)
{
LOG_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";
response->status_code = 404;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
cJSON *json = relay_to_json(relay);
char *json_str = cJSON_Print(json);
if (json_str == NULL)
{
LOG_ERROR("failed to print relay json\n");
static const char content[] = "failed to print json for relay";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
}
else
{
response->status_code = 200;
response->content_type = "application/json";
response->content_length = strlen(json_str);
response->content = json_str;
response->alloced_content = true;
}
cJSON_Delete(json);
relay_free(relay);
controller_free(controller);
}
void
api_v1_controllers_STR_relays_INT_PUT(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)hm;
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
LOG_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
response->status_code = 400;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
controller_t* controller = controller_get_by_uid(target_uid);
if(!controller)
{
LOG_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no controller for id found";
response->status_code = 404;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
relay_t* relay = relay_get_for_controller(controller->id, args[1].value.v_int);
if(!relay)
{
relay = malloc(sizeof(relay_t));
relay->id = 0;
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;
uuid_t tmp_uuid;
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "off", 3);
for(int i = 0; i < 7; ++i)
{
relay->schedules[i] = schedule_get_by_uid(tmp_uuid);
}
time_t timestamp = time(NULL);
struct tm *time_struct = localtime(&timestamp);
relay->active_schedule = relay->schedules[helper_get_weekday(time_struct)];
}
cJSON *json = cJSON_ParseWithLength(hm->body.p, hm->body.len);
if(json == NULL)
{
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr != NULL)
{
LOG_ERROR("error before: %s\n", error_ptr);
}
cJSON_Delete(json);
static const char content[] = "no valid json was supplied";
response->status_code = 400;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
cJSON *json_name = cJSON_GetObjectItemCaseSensitive(json, "name");
if(cJSON_IsString(json_name) && json_name->valuestring)
{
strncpy(relay->name, json_name->valuestring, MAX_NAME_LENGTH);
relay->name[MAX_NAME_LENGTH] = '\0';
}
cJSON *json_schedule;
cJSON *json_schedules = cJSON_GetObjectItemCaseSensitive(json, "schedules");
if(cJSON_GetArraySize(json_schedules) == 7)
{
int schedule_position = 0;
cJSON_ArrayForEach(json_schedule, json_schedules)
{
cJSON *json_schedule_uid = cJSON_GetObjectItemCaseSensitive(json_schedule, "id");
if(!cJSON_IsString(json_schedule_uid) || (json_schedule_uid->valuestring == NULL))
{
LOG_DEBUG("schedules[%d] is missing uid\n", schedule_position);
cJSON_Delete(json);
static const char content[] = "at least one schedule is missing an id";
response->status_code = 400;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
uuid_t target_uid;
if(schedule_uid_parse(json_schedule_uid->valuestring, target_uid))
{
LOG_DEBUG("schedules[%d] has bad uid\n", schedule_position);
cJSON_Delete(json);
static const char content[] = "at least one schedule has a bad id";
response->status_code = 400;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
schedule_free(relay->schedules[schedule_position]);
relay->schedules[schedule_position] = schedule_get_by_uid_or_off(target_uid);
++schedule_position;
}
}
cJSON *json_active_schedule = cJSON_GetObjectItemCaseSensitive(json, "active_schedule");
if(cJSON_IsObject(json_active_schedule))
{
cJSON *json_active_schedule_uid = cJSON_GetObjectItemCaseSensitive(json_active_schedule, "id");
if(cJSON_IsString(json_active_schedule_uid) && json_active_schedule_uid->valuestring)
{
time_t timestamp = time(NULL);
struct tm *time_struct = localtime(&timestamp);
int day_of_week = helper_get_weekday(time_struct);
schedule_free(relay->schedules[day_of_week]);
uuid_t target_uid;
if(schedule_uid_parse(json_active_schedule_uid->valuestring, target_uid))
{
LOG_DEBUG("active_schedule has bad uid\n");
cJSON_Delete(json);
static const char content[] = "active_schedule has a bad id";
response->status_code = 400;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
relay->schedules[day_of_week] = schedule_get_by_uid_or_off(target_uid);
}
}
if(relay_save(relay))
{
LOG_ERROR("failed to save relay\n");
free(controller);
cJSON_Delete(json);
static const char content[] = "failed to save relay to database";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
return;
}
cJSON *json_tag;
cJSON *json_tags = cJSON_GetObjectItemCaseSensitive(json, "tags");
if(cJSON_IsArray(json_tags))
{
junction_tag_remove_for_relay(relay->id);
}
cJSON_ArrayForEach(json_tag, json_tags)
{
if(!cJSON_IsString(json_tag) || (json_tag->valuestring == NULL))
{
LOG_DEBUG("invalid tag in tags\n");
continue;
}
const char *tag = json_tag->valuestring;
int tag_id = tag_get_id(tag);
if(tag_id == 0)
{
tag_save(tag_id, tag);
tag_id = tag_get_id(tag);
}
junction_tag_insert(tag_id, relay->id, 0);
}
cJSON_Delete(json);
json = relay_to_json(relay);
command_set_relay_schedule(relay);
char *json_str = cJSON_Print(json);
if (json_str == NULL)
{
LOG_ERROR("failed to print relay json\n");
static const char content[] = "failed to print json for relay";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
}
else
{
response->status_code = 200;
response->content_type = "application/json";
response->content_length = strlen(json_str);
response->content = json_str;
response->alloced_content = true;
}
cJSON_Delete(json);
relay_free(relay);
controller_free(controller);
}

View file

@ -0,0 +1,307 @@
#include <cJSON.h>
#include <macros.h>
#include <constants.h>
#include <endpoints/api_v1_controllers.h>
#include <models/controller.h>
#include <logger.h>
#include <mpack.h>
#define DISCOVERY_TIMEOUT_MS 2000
typedef enum
{
DISCOVERY_MAPPING_ID = 0,
DISCOVERY_MAPPING_NAME = 1,
DISCOVERY_MAPPING_COMMAND_PORT = 2,
DISCOVERY_MAPPING_RELAY_COUNT = 3,
} discovery_mapping_t;
static int
bind_tcp_server(const char *addr, const char *port, int max_client_backlog)
{
struct addrinfo hints, *res;
int fd;
int status;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if ((status = getaddrinfo(addr, port, &hints, &res)) != 0)
{
LOG_ERROR("error getting address info: %s\n", gai_strerror(status));
}
fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if ((status = bind(fd, res->ai_addr, res->ai_addrlen)) == -1)
{
LOG_ERROR("error binding socket: %s\n", status);
freeaddrinfo(res);
return -1;
}
if ((status = listen(fd, max_client_backlog)) == -1)
{
LOG_ERROR("error setting up listener: %s\n", status);
freeaddrinfo(res);
return -1;
}
freeaddrinfo(res);
return fd;
}
static int
get_server_port(int fd)
{
if(fd == -1)
{
return -1;
}
struct sockaddr_in sin;
socklen_t addr_len = sizeof(sin);
if(getsockname(fd, (struct sockaddr *)&sin, &addr_len) == 0)
{
return ntohs(sin.sin_port);
}
return -1;
}
static int
send_udp_broadcast(const char *addr, uint16_t port, void *message, size_t length)
{
struct sockaddr_in their_addr;
int fd;
if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
LOG_ERROR("error creating socket\n");
return -1;
}
int broadcast = 1;
if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof broadcast) < 0)
{
LOG_ERROR("error setting broadcast\n");
return -1;
}
memset(&their_addr, 0, sizeof(their_addr));
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(port);
their_addr.sin_addr.s_addr = inet_addr(addr);
if(sendto(fd, message, length, 0, (struct sockaddr *)&their_addr, sizeof(their_addr)) < 0)
{
LOG_ERROR("error sending broadcast (%d): '%s'\n", errno, strerror(errno));
return -1;
}
close(fd);
return 0;
}
void
api_v1_controllers_discover_POST(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
int discover_server_socket = bind_tcp_server("0.0.0.0", "0", 20);
int discover_server_port = get_server_port(discover_server_socket);
if(discover_server_port == -1)
{
LOG_ERROR("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;
return;
}
int16_t payload[1];
payload[0] = discover_server_port;
if(send_udp_broadcast("255.255.255.255", global_config.discovery_port, payload, sizeof(payload)) < 0)
{
LOG_ERROR("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;
return;
}
struct sockaddr_storage their_addr;
socklen_t addr_size;
int client_fd, s_ret;
fd_set accept_fds;
struct timeval timeout;
uint8_t discover_answer_buf[1];
controller_t **known_controllers = controller_get_all();
while(true)
{
addr_size = sizeof(their_addr);
FD_ZERO(&accept_fds);
FD_SET(discover_server_socket, &accept_fds); // NOLINT(hicpp-signed-bitwise)
timeout.tv_sec = DISCOVERY_TIMEOUT_MS / 1000;
timeout.tv_usec = (DISCOVERY_TIMEOUT_MS % 1000) * 1000;
s_ret = select(discover_server_socket + 1, &accept_fds, NULL, NULL, &timeout);
if(s_ret == 0)
{
break;
}
else
{
if((client_fd = accept(discover_server_socket, (struct sockaddr *) &their_addr, &addr_size)) < 0)
{
LOG_ERROR("error accepting client %s\n", strerror(errno));
continue;
}
size_t payload_length;
if(recv(client_fd, &payload_length, sizeof(payload_length), 0) <= 0)
{
LOG_ERROR("error receiving header from client\n");
continue;
}
char *answer_payload = (char*)malloc((payload_length));
ssize_t bytes_transferred;
if((bytes_transferred = recv(client_fd, answer_payload, payload_length, 0)) <= 0)
{
LOG_ERROR("error receiving payload from client\n");
continue;
}
struct sockaddr_in addr;
socklen_t client_addr_size = sizeof(struct sockaddr_in);
if(getpeername(client_fd, (struct sockaddr *)&addr, &client_addr_size) != 0)
{
LOG_ERROR("error receiving payload from client\n");
continue;
}
uuid_t discovered_id;
mpack_tree_t tree;
mpack_tree_init_data(&tree, answer_payload, payload_length);
mpack_tree_parse(&tree);
mpack_node_t root = mpack_tree_root(&tree);
memcpy(discovered_id, mpack_node_data(mpack_node_map_uint(root, DISCOVERY_MAPPING_ID)), sizeof(uuid_t));
uint16_t discovered_command_port = mpack_node_u16(mpack_node_map_uint(root, DISCOVERY_MAPPING_COMMAND_PORT));
uint8_t discovered_relay_count = mpack_node_u8(mpack_node_map_uint(root, DISCOVERY_MAPPING_RELAY_COUNT));
const char *discovered_name = mpack_node_str(mpack_node_map_uint(root, DISCOVERY_MAPPING_NAME));
size_t discovered_name_len = mpack_node_strlen(mpack_node_map_uint(root, DISCOVERY_MAPPING_NAME));
if(discovered_name_len > MAX_NAME_LENGTH)
{
discovered_name_len = MAX_NAME_LENGTH;
}
bool found_discovered_in_list = 0;
for(int i = 0; known_controllers[i] != NULL; i++)
{
if(!found_discovered_in_list)
{
if(uuid_compare(known_controllers[i]->uid, discovered_id) == 0)
{
known_controllers[i]->active = 1;
strncpy(known_controllers[i]->name, discovered_name, discovered_name_len);
known_controllers[i]->name[discovered_name_len] = '\0';
known_controllers[i]->port = discovered_command_port;
known_controllers[i]->relay_count = discovered_relay_count;
controller_save(known_controllers[i]);
controller_free(known_controllers[i]);
found_discovered_in_list = 1;
known_controllers[i] = known_controllers[i + 1];
}
}
else
{
known_controllers[i] = known_controllers[i + 1];
}
}
if(!found_discovered_in_list)
{
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->name, discovered_name, discovered_name_len);
discovered_controller->name[discovered_name_len] = '\0';
discovered_controller->relay_count = discovered_relay_count;
discovered_controller->port = discovered_command_port;
discovered_controller->active = 1;
controller_save(discovered_controller);
// TODO get relays during discovery and don't create empty ones
relay_t **discovered_relays = malloc(sizeof(relay_t*) * (discovered_controller->relay_count + 1));
for(int i = 0; i < discovered_controller->relay_count; ++i)
{
relay_t *new_relay = malloc(sizeof(relay_t));
new_relay->id = 0;
sprintf(new_relay->name, "Relay %d", i + 1);
new_relay->number = i;
new_relay->controller_id = discovered_controller->id;
uuid_t tmp_uuid;
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "off", 3);
for(int i = 0; i < 7; ++i)
{
new_relay->schedules[i] = schedule_get_by_uid(tmp_uuid);
}
time_t timestamp = time(NULL);
struct tm *time_struct = localtime(&timestamp);
new_relay->active_schedule = new_relay->schedules[helper_get_weekday(time_struct)];
relay_save(new_relay);
discovered_relays[i] = new_relay;
}
discovered_relays[discovered_controller->relay_count] = NULL;
discovered_controller->relays = discovered_relays;
controller_free(discovered_controller);
}
mpack_tree_destroy(&tree);
free(answer_payload);
discover_answer_buf[0] = 0; // TODO add discovery return codes
send(client_fd, discover_answer_buf, sizeof(uint8_t), 0);
close(client_fd);
}
}
for(int i = 0; known_controllers[i] != NULL; i++)
{
known_controllers[i]->active = false;
controller_save(known_controllers[i]);
LOG_DEBUG("lost: %s\n", known_controllers[i]->name);
}
controller_free_list(known_controllers);
api_v1_controllers_GET(hm, args, response);
}

48
endpoints/api_v1_relays.c Normal file
View file

@ -0,0 +1,48 @@
#include <cJSON.h>
#include <macros.h>
#include <constants.h>
#include <endpoints/api_v1_relays.h>
#include <logger.h>
#include <models/junction_tag.h>
#include <models/relay.h>
#include <models/tag.h>
void
api_v1_relays_GET(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)args;
(void)hm;
relay_t** all_relays = relay_get_all();
cJSON *json = cJSON_CreateArray();
for(int i = 0; all_relays[i] != NULL; ++i)
{
cJSON *json_relay = relay_to_json(all_relays[i]);
cJSON_AddItemToArray(json, json_relay);
}
char *json_str = cJSON_Print(json);
if (json_str == NULL)
{
LOG_ERROR("failed to print relays json\n");
static const char content[] = "failed to print json for relays";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
}
else
{
response->status_code = 200;
response->content_type = "application/json";
response->content_length = strlen(json_str);
response->content = json_str;
response->alloced_content = true;
}
cJSON_Delete(json);
relay_free_list(all_relays);
}

View file

@ -0,0 +1,70 @@
#include <cJSON.h>
#include <macros.h>
#include <constants.h>
#include <endpoints/api_v1_relays.h>
#include <logger.h>
#include <models/junction_tag.h>
#include <models/relay.h>
#include <models/tag.h>
void
api_v1_relays_tag_STR_GET(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)hm;
int tag_id = tag_get_id(args[0].value.v_str);
int *relays_ids = junction_tag_get_relays_for_tag_id(tag_id);
if(relays_ids == NULL)
{
LOG_ERROR("failed to load relays for tag from database\n");
static const char content[] = "failed to load relays for tag from database";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
cJSON *json = cJSON_CreateArray();
for(int i = 0; relays_ids[i] != 0; ++i)
{
relay_t* relay = relay_get_by_id(relays_ids[i]);
if(!relay)
{
continue;
}
cJSON *json_relay = relay_to_json(relay);
cJSON_AddItemToArray(json, json_relay);
relay_free(relay);
}
char *json_str = cJSON_Print(json);
if (json_str == NULL)
{
LOG_ERROR("failed to print relays json\n");
static const char content[] = "failed to print json for relays";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
}
else
{
response->status_code = 200;
response->content_type = "application/json";
response->content_length = strlen(json_str);
response->content = json_str;
response->alloced_content = true;
}
cJSON_Delete(json);
free(relays_ids);
}

View file

@ -0,0 +1,187 @@
#include <cJSON.h>
#include <macros.h>
#include <constants.h>
#include <endpoints/api_v1_schedules.h>
#include <logger.h>
#include <models/junction_tag.h>
#include <models/schedule.h>
#include <models/tag.h>
void
api_v1_schedules_POST(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)args;
cJSON *json = cJSON_ParseWithLength(hm->body.p, hm->body.len);
if(json == NULL)
{
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr != NULL)
{
LOG_ERROR("error before: %s\n", error_ptr);
}
cJSON_Delete(json);
static const char content[] = "no valid json was supplied";
response->status_code = 400;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
cJSON *json_name = cJSON_GetObjectItemCaseSensitive(json, "name");
if(!cJSON_IsString(json_name) || (json_name->valuestring == NULL))
{
LOG_DEBUG("no name for schedule provided\n");
cJSON_Delete(json);
static const char content[] = "no name for schedule provided";
response->status_code = 400;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
cJSON *json_period;
cJSON *json_periods = cJSON_GetObjectItemCaseSensitive(json, "periods");
schedule_t *new_schedule = malloc(sizeof(schedule_t));
new_schedule->id = 0;
uuid_generate(new_schedule->uid);
strncpy(new_schedule->name, json_name->valuestring, MAX_NAME_LENGTH);
new_schedule->name[MAX_NAME_LENGTH] = '\0';
int periods_count = cJSON_GetArraySize(json_periods);
new_schedule->periods = malloc(sizeof(period_t) * periods_count);
int periods_valid = 0;
cJSON_ArrayForEach(json_period, json_periods)
{
cJSON *json_period_start = cJSON_GetObjectItemCaseSensitive(json_period, "start");
cJSON *json_period_end = cJSON_GetObjectItemCaseSensitive(json_period, "end");
if(!cJSON_IsString(json_period_start) || (json_period_start->valuestring == NULL))
{
LOG_DEBUG("period is missing start\n");
continue;
}
if(!cJSON_IsString(json_period_end) || (json_period_end->valuestring == NULL))
{
LOG_DEBUG("period is missing end\n");
continue;
}
uint16_t start;
uint16_t end;
if(period_helper_parse_hhmm(json_period_start->valuestring, &start))
{
LOG_DEBUG("couldn't parse start '%s'\n", json_period_start->valuestring);
continue;
}
if(period_helper_parse_hhmm(json_period_end->valuestring, &end))
{
LOG_DEBUG("couldn't parse end '%s'\n", json_period_end->valuestring);
continue;
}
new_schedule->periods[periods_valid].start = start;
new_schedule->periods[periods_valid].end = end;
++periods_valid;
}
new_schedule->periods_count = periods_valid;
schedule_save(new_schedule);
junction_tag_remove_for_schedule(new_schedule->id);
cJSON *json_tag;
cJSON *json_tags = cJSON_GetObjectItemCaseSensitive(json, "tags");
cJSON_ArrayForEach(json_tag, json_tags)
{
if(!cJSON_IsString(json_tag) || (json_tag->valuestring == NULL))
{
LOG_DEBUG("invalid tag in tags\n");
continue;
}
const char *tag = json_tag->valuestring;
int tag_id = tag_get_id(tag);
if(tag_id == 0)
{
tag_save(tag_id, tag);
tag_id = tag_get_id(tag);
}
junction_tag_insert(tag_id, 0, new_schedule->id);
}
cJSON_Delete(json);
json = schedule_to_json(new_schedule);
char *json_str = cJSON_Print(json);
if (json_str == NULL)
{
LOG_ERROR("failed to print schedule json\n");
static const char content[] = "failed to print json for schedule";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
}
else
{
response->status_code = 201;
response->content_type = "application/json";
response->content_length = strlen(json_str);
response->content = json_str;
response->alloced_content = true;
}
cJSON_Delete(json);
schedule_free(new_schedule);
}
void
api_v1_schedules_GET(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)args;
(void)hm;
schedule_t** all_schedules = schedule_get_all();
cJSON *json = cJSON_CreateArray();
for(int i = 0; all_schedules[i] != NULL; ++i)
{
cJSON *json_schedule = schedule_to_json(all_schedules[i]);
cJSON_AddItemToArray(json, json_schedule);
}
char *json_str = cJSON_Print(json);
if (json_str == NULL)
{
LOG_ERROR("failed to print schedules json\n");
static const char content[] = "failed to print json for schedules";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
}
else
{
response->status_code = 200;
response->content_type = "application/json";
response->content_length = strlen(json_str);
response->content = json_str;
response->alloced_content = true;
}
cJSON_Delete(json);
schedule_free_list(all_schedules);
}

View file

@ -0,0 +1,324 @@
#include <cJSON.h>
#include <macros.h>
#include <constants.h>
#include <endpoints/api_v1_schedules.h>
#include <logger.h>
#include <command.h>
#include <models/junction_tag.h>
#include <models/junction_relay_schedule.h>
#include <models/schedule.h>
#include <models/relay.h>
#include <models/tag.h>
void
api_v1_schedules_STR_GET(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)hm;
uuid_t target_uid;
if(schedule_uid_parse(args[0].value.v_str, target_uid))
{
LOG_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
response->status_code = 400;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
schedule_t* schedule = schedule_get_by_uid(target_uid);
if(!schedule)
{
LOG_DEBUG("could not find a schedule for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no schedule for id found";
response->status_code = 404;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
cJSON *json = schedule_to_json(schedule);
char *json_str = cJSON_Print(json);
if (json_str == NULL)
{
LOG_ERROR("failed to print schedules json\n");
static const char content[] = "failed to print json for schedules";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
}
else
{
response->status_code = 200;
response->content_type = "application/json";
response->content_length = strlen(json_str);
response->content = json_str;
response->alloced_content = true;
}
cJSON_Delete(json);
schedule_free(schedule);
}
void
api_v1_schedules_STR_PUT(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)hm;
uuid_t target_uid;
if(schedule_uid_parse(args[0].value.v_str, target_uid))
{
LOG_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
response->status_code = 400;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
schedule_t* schedule = schedule_get_by_uid(target_uid);
if(!schedule)
{
LOG_DEBUG("could not find a schedule for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no schedule for id found";
response->status_code = 404;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
cJSON *json = cJSON_ParseWithLength(hm->body.p, hm->body.len);
if(json == NULL)
{
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr != NULL)
{
LOG_DEBUG("error before: %s\n", error_ptr);
}
cJSON_Delete(json);
static const char content[] = "no valid json was supplied";
response->status_code = 400;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
cJSON *json_name = cJSON_GetObjectItemCaseSensitive(json, "name");
if(cJSON_IsString(json_name) && json_name->valuestring)
{
strncpy(schedule->name, json_name->valuestring, MAX_NAME_LENGTH);
schedule->name[MAX_NAME_LENGTH] = '\0';
}
if(!schedule_is_protected(schedule))
{
cJSON *json_period;
cJSON *json_periods = cJSON_GetObjectItemCaseSensitive(json, "periods");
int periods_count = cJSON_GetArraySize(json_periods);
free(schedule->periods);
schedule->periods = malloc(sizeof(period_t) * periods_count);
int periods_valid = 0;
cJSON_ArrayForEach(json_period, json_periods)
{
cJSON *json_period_start = cJSON_GetObjectItemCaseSensitive(json_period, "start");
cJSON *json_period_end = cJSON_GetObjectItemCaseSensitive(json_period, "end");
if(!cJSON_IsString(json_period_start) || (json_period_start->valuestring == NULL))
{
LOG_DEBUG("period is missing start\n");
continue;
}
if(!cJSON_IsString(json_period_end) || (json_period_end->valuestring == NULL))
{
LOG_DEBUG("period is missing end\n");
continue;
}
uint16_t start;
uint16_t end;
if(period_helper_parse_hhmm(json_period_start->valuestring, &start))
{
LOG_DEBUG("couldn't parse start '%s'\n", json_period_start->valuestring);
continue;
}
if(period_helper_parse_hhmm(json_period_end->valuestring, &end))
{
LOG_DEBUG("couldn't parse end '%s'\n", json_period_end->valuestring);
continue;
}
schedule->periods[periods_valid].start = start;
schedule->periods[periods_valid].end = end;
++periods_valid;
}
schedule->periods_count = periods_valid;
}
if(schedule_save(schedule))
{
LOG_ERROR("failed to save schedule\n");
free(schedule);
cJSON_Delete(json);
static const char content[] = "failed to save schedule to database";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
relay_t **relays = relay_get_with_schedule(schedule->id);
for(int i = 0; relays[i] != NULL; ++i)
{
command_set_relay_schedule(relays[i]);
}
cJSON *json_tag;
cJSON *json_tags = cJSON_GetObjectItemCaseSensitive(json, "tags");
if(cJSON_IsArray(json_tags))
{
junction_tag_remove_for_schedule(schedule->id);
}
cJSON_ArrayForEach(json_tag, json_tags)
{
if(!cJSON_IsString(json_tag) || (json_tag->valuestring == NULL))
{
LOG_DEBUG("invalid tag in tags\n");
continue;
}
const char *tag = json_tag->valuestring;
int tag_id = tag_get_id(tag);
if(tag_id == 0)
{
tag_save(tag_id, tag);
tag_id = tag_get_id(tag);
}
junction_tag_insert(tag_id, 0, schedule->id);
}
cJSON_Delete(json);
json = schedule_to_json(schedule);
char *json_str = cJSON_Print(json);
if (json_str == NULL)
{
LOG_ERROR("failed to print schedule json\n");
static const char content[] = "failed to print json for schedule";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
}
else
{
response->status_code = 200;
response->content_type = "application/json";
response->content_length = strlen(json_str);
response->content = json_str;
response->alloced_content = true;
}
cJSON_Delete(json);
relay_free_list(relays);
schedule_free(schedule);
}
void
api_v1_schedules_STR_DELETE(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)hm;
const char *target_uid_str = args[0].value.v_str;
uuid_t target_uid;
if(schedule_uid_parse(target_uid_str, target_uid))
{
LOG_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
response->status_code = 400;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
schedule_t* schedule = schedule_get_by_uid(target_uid);
if(!schedule)
{
LOG_DEBUG("could not find a schedule for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no schedule for id found";
response->status_code = 404;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
if(schedule_is_protected(schedule))
{
static const char content[] = "target schedule is protected";
response->status_code = 403;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
schedule_free(schedule);
return;
}
if(schedule_remove(schedule))
{
LOG_ERROR("failed to remove schedule from database\n");
static const char content[] = "failed to remove schedule from database";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
}
else
{
response->status_code = 200;
response->content_type = "application/json";
response->content_length = 0;
response->content = "";
response->alloced_content = false;
}
schedule_free(schedule);
return;
}

View file

@ -0,0 +1,70 @@
#include <cJSON.h>
#include <macros.h>
#include <constants.h>
#include <endpoints/api_v1_schedules.h>
#include <logger.h>
#include <models/junction_tag.h>
#include <models/schedule.h>
#include <models/tag.h>
void
api_v1_schedules_tag_STR_GET(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)hm;
int tag_id = tag_get_id(args[0].value.v_str);
int *schedules_ids = junction_tag_get_schedules_for_tag_id(tag_id);
if(schedules_ids == NULL)
{
LOG_ERROR("failed to load schedules for tag from database\n");
static const char content[] = "failed to load schedules for tag from database";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
cJSON *json = cJSON_CreateArray();
for(int i = 0; schedules_ids[i] != 0; ++i)
{
schedule_t* schedule = schedule_get_by_id(schedules_ids[i]);
if(!schedule)
{
continue;
}
cJSON *json_schedule = schedule_to_json(schedule);
cJSON_AddItemToArray(json, json_schedule);
schedule_free(schedule);
}
char *json_str = cJSON_Print(json);
if (json_str == NULL)
{
LOG_ERROR("failed to print schedules json\n");
static const char content[] = "failed to print json for schedules";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
}
else
{
response->status_code = 200;
response->content_type = "application/json";
response->content_length = strlen(json_str);
response->content = json_str;
response->alloced_content = true;
}
cJSON_Delete(json);
free(schedules_ids);
}

View file

@ -1,34 +0,0 @@
#include <models/relay_dbo.h>
#include "controllers_valid_json.h"
using namespace drogon;
using namespace filters::controllers;
void valid_json::doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb)
{
if(req->getMethod() == Options)
{
fccb();
return;
}
Json::Value body = *req->jsonObject();
bool is_valid = true;
is_valid &= body["name"].type() == Json::ValueType::stringValue;
is_valid &= body["ip"].type() == Json::ValueType::stringValue;
if(is_valid)
{
//Passed
fccb();
return;
}
//Check failed
auto res = drogon::HttpResponse::newHttpResponse();
res->setStatusCode(k400BadRequest);
fcb(res);
}

View file

@ -1,17 +0,0 @@
#pragma once
#include <drogon/HttpFilter.h>
using namespace drogon;
namespace filters::controllers
{
class valid_json : public HttpFilter<valid_json>
{
public:
valid_json() = default;
void doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb) override;
};
}

View file

@ -1,29 +0,0 @@
#include "json_required.h"
using namespace drogon;
using namespace filters;
void json_required::doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb)
{
if(req->getMethod() == Options)
{
fccb();
return;
}
// TODO remove this workaround
HttpMethod original_method = req->getMethod();
req->setMethod(Post);
if(req->jsonObject())
{
req->setMethod(original_method);
fccb();
return;
}
auto res = drogon::HttpResponse::newHttpResponse();
res->setStatusCode(k400BadRequest);
fcb(res);
}

View file

@ -1,14 +0,0 @@
#pragma once
#include <drogon/HttpFilter.h>
using namespace drogon;
namespace filters
{
class json_required : public HttpFilter<json_required>
{
public:
json_required() = default;
void
doFilter(const HttpRequestPtr &req, FilterCallback &&fcb, FilterChainCallback &&fccb) override;
};
}

View file

@ -1,126 +0,0 @@
#include <models/schedule_dbo.h>
#include "relays_valid_json.h"
using namespace drogon;
using namespace filters::relays;
void valid_json::doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb)
{
if(req->getMethod() == Options)
{
fccb();
return;
}
Json::Value body = *req->jsonObject();
bool is_valid = true;
// level 1
bool set_name = body["name"].type() == Json::ValueType::stringValue;
bool set_tags = body["tags"].type() == Json::ValueType::arrayValue;
bool set_schedules = body["schedules"].type() == Json::ValueType::arrayValue;
bool set_active_schedule = body["active_schedule"].type() == Json::ValueType::objectValue;
// level 2
if(is_valid)
{
if(set_tags)
{
for(int i = 0; i < body["tags"].size(); ++i)
{
is_valid &= body["tags"][i].type() == Json::ValueType::stringValue;
}
}
if(set_active_schedule)
{
is_valid &= body["active_schedule"]["id"].type() == Json::ValueType::stringValue;
}
if(set_schedules)
{
for(int i = 0; i < 7; ++i)
{
is_valid &= body["schedules"][i].type() == Json::ValueType::objectValue;
}
}
}
// level 3
if(is_valid)
{
if(set_schedules)
{
for(int i = 0; i < 7; ++i)
{
is_valid &= body["schedules"][i]["id"].type() == Json::ValueType::stringValue;
}
}
}
if(!is_valid)
{
LOG_DEBUG << "basic structe is wrong";
auto res = drogon::HttpResponse::newHttpResponse();
res->setStatusCode(k400BadRequest);
fcb(res);
return;
}
if(set_active_schedule)
{
uuid_t active_schedule_id;
if(schedule_dbo::parse_uid(body["active_schedule"]["id"].asCString(), active_schedule_id))
{
LOG_DEBUG << "parse_uid failed for active_schedule";
auto res = drogon::HttpResponse::newHttpResponse();
res->setStatusCode(k400BadRequest);
fcb(res);
return;
}
schedule_dbo **schedules = schedule_dbo::get_by_simple("uid", active_schedule_id, (intptr_t)&sqlite3_bind_blob, sizeof(uuid_t));
bool schedule_found = schedules[0] != nullptr;
schedule_dbo::free_list(schedules);
if(!schedule_found)
{
LOG_DEBUG << "could not find active_schedule";
auto res = drogon::HttpResponse::newHttpResponse();
res->setStatusCode(k400BadRequest);
fcb(res);
return;
}
}
if(set_schedules)
{
uuid_t schedules_ids[7];
for(int i = 0; i < 7; ++i)
{
if(schedule_dbo::parse_uid(body["schedules"][i]["id"].asCString(), schedules_ids[i]))
{
LOG_DEBUG << "parse_uid failed for schedule " << i;
auto res = drogon::HttpResponse::newHttpResponse();
res->setStatusCode(k400BadRequest);
fcb(res);
return;
}
schedule_dbo **schedules = schedule_dbo::get_by_simple("uid", schedules_ids[i], (intptr_t)&sqlite3_bind_blob, sizeof(uuid_t));
bool schedule_found = schedules[0] != nullptr;
schedule_dbo::free_list(schedules);
if(!schedule_found)
{
LOG_DEBUG << "could not find schedule " << i;
auto res = drogon::HttpResponse::newHttpResponse();
res->setStatusCode(k400BadRequest);
fcb(res);
return;
}
}
}
//Passed
fccb();
return;
}

View file

@ -1,17 +0,0 @@
#pragma once
#include <drogon/HttpFilter.h>
using namespace drogon;
namespace filters::relays
{
class valid_json : public HttpFilter<valid_json>
{
public:
valid_json() = default;
void doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb) override;
};
}

View file

@ -1,66 +0,0 @@
#include "schedules_valid_json.h"
using namespace drogon;
using namespace filters::schedules;
void valid_json::doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb)
{
if(req->getMethod() == Options)
{
fccb();
return;
}
Json::Value body = *req->jsonObject();
bool is_valid = true;
bool set_name = body["name"].type() == Json::ValueType::stringValue;
bool set_tags = body["tags"].type() == Json::ValueType::arrayValue;
bool set_periods = body["periods"].type() == Json::ValueType::arrayValue;
// level 2
if(is_valid)
{
if(set_tags)
{
for(int i = 0; i < body["tags"].size(); ++i)
{
is_valid &= body["tags"][i].type() == Json::ValueType::stringValue;
}
}
if(set_periods)
{
for(int i = 0; i < body["periods"].size(); ++i)
{
is_valid &= body["periods"][i].type() == Json::ValueType::objectValue;
}
}
}
// level 3
if(is_valid)
{
if(set_periods)
{
for(int i = 0; i < body["periods"].size(); ++i)
{
is_valid &= body["periods"][i]["start"].type() == Json::ValueType::stringValue;
is_valid &= body["periods"][i]["end"].type() == Json::ValueType::stringValue;
}
}
}
if(is_valid)
{
//Passed
fccb();
return;
}
//Check failed
auto res = drogon::HttpResponse::newHttpResponse();
res->setStatusCode(k400BadRequest);
fcb(res);
}

View file

@ -1,17 +0,0 @@
#pragma once
#include <drogon/HttpFilter.h>
using namespace drogon;
namespace filters::schedules
{
class valid_json : public HttpFilter<valid_json>
{
public:
valid_json() = default;
void doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb) override;
};
}

View file

@ -1,7 +0,0 @@
#include <sqlite3.h>
#include "globals.h"
namespace globals
{
sqlite3 *db;
}

View file

@ -1,9 +0,0 @@
#ifndef EMGAUWA_CORE_GLOBALS_H
#define EMGAUWA_CORE_GLOBALS_H
namespace globals
{
extern sqlite3 *db;
}
#endif //EMGAUWA_CORE_GLOBALS_H

89
handlers/connection.c Normal file
View file

@ -0,0 +1,89 @@
#include <string.h>
#include <constants.h>
#include <mongoose.h>
#include <logger.h>
#include <router.h>
#include <handlers.h>
#define STD_HEADERS "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Headers: *\r\nAccess-Control-Allow-Methods: *\r\n"
#define HEADERS_FMT STD_HEADERS "Content-Type: %s"
// -2 for "%s" -1 for \0
#define HEADERS_FMT_LEN (sizeof(HEADERS_FMT) - 3)
#define STD_RESPONSE_CONTENT "the server did not create a response"
#define STD_RESPONSE_CONTENT_LEN (sizeof(STD_RESPONSE_CONTENT) - 1)
void
handler_connection(struct mg_connection *c, int ev, void *p)
{
if (ev == MG_EV_HTTP_REQUEST)
{
struct http_message *hm = (struct http_message *) p;
LOG_DEBUG("new http %.*s request for %.*s\n", hm->method.len, hm->method.p, hm->uri.len, hm->uri.p);
endpoint_t *endpoint = router_find_endpoint(hm->uri.p, hm->uri.len, &hm->method);
if(endpoint)
{
if(endpoint->func)
{
endpoint_response_t response;
response.status_code = 500;
response.content_type = "text/plain";
response.content_length = STD_RESPONSE_CONTENT_LEN;
response.content = STD_RESPONSE_CONTENT;
response.alloced_content = false;
endpoint->func(p, endpoint->args, &response);
char *response_headers = malloc(sizeof(char) * (HEADERS_FMT_LEN + strlen(response.content_type) + 1));
sprintf(response_headers, HEADERS_FMT, response.content_type);
mg_send_head(c, response.status_code, response.content_length, response_headers);
mg_printf(c, "%s", response.content);
free(response_headers);
if(response.alloced_content)
{
free((char*)response.content);
}
for(int i = 0; i < endpoint->args_count; ++i)
{
if(endpoint->args[i].type == ENDPOINT_ARG_TYPE_STR)
{
free((char*)endpoint->args[i].value.v_str);
}
}
}
else
{
if(endpoint->method == HTTP_METHOD_OPTIONS)
{
char options_header[256]; // TODO make more generic
sprintf(options_header, STD_HEADERS "Allow: OPTIONS%s%s%s%s",
endpoint->options & HTTP_METHOD_GET ? ", GET" : "",
endpoint->options & HTTP_METHOD_POST ? ", POST" : "",
endpoint->options & HTTP_METHOD_PUT ? ", PUT" : "",
endpoint->options & HTTP_METHOD_DELETE ? ", DELETE" : ""
);
mg_send_head(c, 204, 0, options_header);
}
else
{
mg_send_head(c, 501, 0, "Content-Type: text/plain");
}
}
}
else
{
mg_send_head(c, 500, 0, "Content-Type: text/plain");
}
//mg_printf(c, "%.*s", (int)hm->message.len, hm->message.p);
//mg_printf(c, "%.*s", (int)hm->body.len, hm->body.p);
}
}

View file

@ -1,47 +0,0 @@
#ifndef EMGAUWA_CORE_HELPERS_H
#define EMGAUWA_CORE_HELPERS_H
#include <json/value.h>
#include <models/period.h>
#include <models/period_list.h>
#include <sqlite3.h>
namespace helpers
{
int
bind_tcp_server(const char *addr, const char *port, int max_client_backlog);
int
get_server_port(int fd);
int
send_udp_broadcast(const char *addr, uint16_t port, void *message, size_t length);
period_list*
parse_periods(Json::Value periods_json);
typedef struct sql_filter_builder
{
sql_filter_builder(const char *col_name, const void *value, intptr_t bind_func, int bind_func_param, const char *logic);
const char *col_name;
const void *value;
intptr_t bind_func;
int bind_func_param;
const char *logic;
} sql_filter_builder;
sqlite3_stmt*
create_sql_filtered_query(const char *sql, sql_filter_builder **filters);
int
open_tcp_connection(char* host, char* port);
int
migrate_sql();
int
get_day_of_week();
}
#endif //EMGAUWA_CORE_HELPERS_H

View file

@ -1,43 +0,0 @@
#include <netdb.h>
#include <trantor/utils/Logger.h>
#include <helpers.h>
#include "config.h"
int
helpers::bind_tcp_server(const char *addr, const char *port, int max_client_backlog)
{
struct addrinfo hints{}, *res;
int fd;
int status;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if ((status = getaddrinfo(addr, port, &hints, &res)) != 0)
{
LOG_ERROR << "Error getting address info: " << gai_strerror(status);
}
fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if ((status = bind(fd, res->ai_addr, res->ai_addrlen)) == -1)
{
LOG_ERROR << "Error binding socket. " << status;
freeaddrinfo(res);
return -1;
}
if ((status = listen(fd, max_client_backlog)) == -1)
{
LOG_ERROR << "Error setting up listener. " << status;
freeaddrinfo(res);
return -1;
}
freeaddrinfo(res);
return fd;
}

39
helpers/connect_server.c Normal file
View file

@ -0,0 +1,39 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/socket.h>
#include <logger.h>
#include <helpers.h>
int
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;
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
if ((status = getaddrinfo(host, port_str, &hints, &res)) != 0) { //getaddrinfo() will evaluate the given address, using the hints-flags and port, and return an IP address and other server infos
LOG_ERROR("getaddrinfo: %s\n", gai_strerror(status));
return -1;
}
//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) {
LOG_ERROR("connect() failed\n");
freeaddrinfo(res);
return -1;
}
freeaddrinfo(res);
return s;
}

View file

@ -1,62 +0,0 @@
#include <helpers.h>
#include <drogon/drogon.h>
#include <globals.h>
sqlite3_stmt*
helpers::create_sql_filtered_query(const char *sql, sql_filter_builder **filters)
{
char *old_query = (char*)sql;
char *new_query;
sql_filter_builder *filter;
int filter_count = 0;
do
{
filter = filters[filter_count];
filter_count++;
asprintf(&new_query, " %s %s=?%d %s", old_query, filter->col_name, filter_count, filter->logic);
if(old_query != sql)
{
free(old_query);
}
old_query = new_query;
} while(filter->logic[0] != ';');
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, new_query, -1, &stmt, nullptr);
free(new_query);
for(int i = 0; i < filter_count; i++)
{
filter = filters[i];
if(filter->bind_func == (intptr_t)&sqlite3_bind_int)
{
sqlite3_bind_int(stmt, i + 1, *((int*)filter->value));
}
if(filter->bind_func == (intptr_t)&sqlite3_bind_text)
{
sqlite3_bind_text(stmt, i + 1, (char*)filter->value, filter->bind_func_param, SQLITE_STATIC);
}
if(filter->bind_func == (intptr_t)&sqlite3_bind_blob)
{
sqlite3_bind_blob(stmt, i + 1, filter->value, filter->bind_func_param, SQLITE_STATIC);
}
}
return stmt;
}
helpers::sql_filter_builder::sql_filter_builder(const char *col_name, const void *value, intptr_t bind_func, int bind_func_param, const char *logic)
{
this->col_name = col_name;
this->value = value;
this->bind_func = bind_func;
this->bind_func_param = bind_func_param;
this->logic = logic;
}

View file

@ -1,12 +0,0 @@
#include <netdb.h>
#include <helpers.h>
int
helpers::get_day_of_week()
{
time_t t = time(NULL);
struct tm *now = localtime(&t);
int wday_sun_sat = now->tm_wday;
int wday_mon_sun = (wday_sun_sat + 6) % 7;
return wday_mon_sun;
}

View file

@ -1,18 +0,0 @@
#include <netdb.h>
#include <helpers.h>
int
helpers::get_server_port(int fd)
{
if(fd == -1)
{
return -1;
}
struct sockaddr_in sin{};
socklen_t addr_len = sizeof(sin);
if(getsockname(fd, (struct sockaddr *)&sin, &addr_len) == 0)
{
return ntohs(sin.sin_port);
}
return -1;
}

11
helpers/get_weekday.c Normal file
View file

@ -0,0 +1,11 @@
#include <time.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;
return wday_mon_sun;
}

View file

@ -1,66 +0,0 @@
#include <helpers.h>
#include <globals.h>
#include <uuid/uuid.h>
#include <drogon/trantor/trantor/utils/Logger.h>
#include <drogon/utils/Utilities.h>
#include <config.h>
#include <sql/migration_0.h>
int
helpers::migrate_sql()
{
uint16_t version_num = 0;
int s, rc;
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, "SELECT version_num FROM meta LIMIT 1;", -1, &stmt, nullptr);
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
version_num = sqlite3_column_int(stmt, 0);
}
else
{
version_num = 0;
}
uint16_t new_version_num = version_num;
char* err_msg;
char* sql;
sqlite3_finalize(stmt);
switch(version_num)
{
case 0:
LOG_INFO << "Migrating LEVEL 0";
rc = sqlite3_exec(globals::db, (const char *)sql_migration_0_sql, nullptr, nullptr, &err_msg);
if(rc != 0)
{
LOG_FATAL << "Couldn't migrate LEVEL 0 (" << err_msg << ")";
break;
}
new_version_num = 1;
default:
break;
}
if(version_num == 0)
{
sqlite3_prepare_v2(globals::db, "INSERT INTO meta (version_num) VALUES (?1);", -1, &stmt, nullptr);
}
else
{
sqlite3_prepare_v2(globals::db, "UPDATE meta SET version_num=?1;", -1, &stmt, nullptr);
}
sqlite3_bind_int(stmt, 1, new_version_num);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
LOG_FATAL << "Couldn't write new Schema Version";
}
return rc != SQLITE_DONE;
}

View file

@ -1,35 +0,0 @@
#include <helpers.h>
#include <netdb.h>
#include <errno.h>
#include <string.h>
#include <trantor/utils/Logger.h>
int
helpers::open_tcp_connection(char *host, char *port)
{
int s, status;
struct addrinfo hints{}, *res;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
if ((status = getaddrinfo(host, port, &hints, &res)) != 0)
{
LOG_ERROR << "Error getting address info: " << gai_strerror(status);
freeaddrinfo(res);
return 0;
}
s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); //creating Socket
if ((status = connect(s, res->ai_addr, res->ai_addrlen)) != 0)
{
LOG_ERROR << "Error opening connection " << strerror(errno);
freeaddrinfo(res);
return 0;
}
freeaddrinfo(res);
return s;
}

54
helpers/parse_cli.c Normal file
View file

@ -0,0 +1,54 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <argparse.h>
#include <config.h>
#include <logger.h>
#include <helpers.h>
static const char *const usage[] = {
"core [options] [[--] args]",
"core [options]",
NULL,
};
void
helper_parse_cli(int argc, const char **argv, config_t *config)
{
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_END(),
};
struct argparse argparse;
argparse_init(&argparse, options, usage, 0);
argparse_describe(
&argparse,
"\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);
if(argc == 1)
{
config->run_type = RUN_TYPE_INVALID;
if(strcmp(argv[0], "start") == 0)
{
config->run_type = RUN_TYPE_START;
return;
}
LOG_FATAL("bad action '%s' given ('start')\n", argv[0]);
exit(1);
}
else
{
LOG_FATAL("no action given ('start')\n");
exit(1);
}
return;
}

View file

@ -1,68 +0,0 @@
#include <helpers.h>
#include <drogon/drogon.h>
#include <models/period_list.h>
static int
parse_HHMM(const char *begin, uint16_t *h, uint16_t *m)
{
uint16_t tmp_h, tmp_m;
char *check = nullptr;
tmp_h = (uint16_t)strtol(begin, &check, 10);
if(begin == check)
{
return 1;
}
begin = check + 1;
tmp_m = (uint16_t)strtol(begin, &check, 10);
if(begin == check)
{
return 1;
}
*h = tmp_h;
*m = tmp_m;
return 0;
}
period_list*
helpers::parse_periods(Json::Value periods_json)
{
auto result = new period_list();
for (Json::Value::ArrayIndex i = 0; i != periods_json.size(); i++)
{
Json::Value p = periods_json[i];
if(!(p.isMember("start") && p.isMember("end")))
{
continue;
}
const char *start_str = p["start"].asCString();
const char *end_str = p["end"].asCString();
uint16_t h, m, start, end;
if(parse_HHMM(start_str, &h, &m))
{
continue;
}
start = (uint16_t)((h * 60) + m);
if(parse_HHMM(end_str, &h, &m))
{
continue;
}
end = (uint16_t)((h * 60) + m);
if(start < 0 || start > 24 * 60 || end < 0 || end > 24 * 60)
{
continue;
}
result->add_period(start, end);
}
return result;
}

View file

@ -1,39 +0,0 @@
#include <arpa/inet.h>
#include <trantor/utils/Logger.h>
#include "config.h"
#include <unistd.h>
#include <helpers.h>
int
helpers::send_udp_broadcast(const char *addr, uint16_t port, void *message, size_t length)
{
struct sockaddr_in their_addr{};
int fd;
if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
LOG_ERROR << "Error creating socket";
return -1;
}
int broadcast = 1;
if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof broadcast) < 0)
{
LOG_ERROR << "Error setting broadcast";
return -1;
}
memset(&their_addr, 0, sizeof(their_addr));
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(port);
their_addr.sin_addr.s_addr = inet_addr(addr);
if(sendto(fd, message, length, 0, (struct sockaddr *)&their_addr, sizeof(their_addr)) < 0)
{
LOG_ERROR << "Error sending broadcast " << errno << " " << strerror(errno);
return -1;
}
close(fd);
return 0;
}

18
include/colors.h Normal file
View file

@ -0,0 +1,18 @@
#ifndef CORE_COLORS_H
#define CORE_COLORS_H
#define COLOR_RED "\033[0;31m"
#define COLORB_RED "\033[1;31m"
#define COLOR_GREEN "\033[0;32m"
#define COLORB_GREEN "\033[1;32m"
#define COLOR_YELLOW "\033[0;33m"
#define COLORB_YELLOW "\033[1;33m"
#define COLOR_BLUE "\033[0;34m"
#define COLORB_BLUE "\033[1;34m"
#define COLOR_MAGENTA "\033[0;35m"
#define COLORB_MAGENTA "\033[1;35m"
#define COLOR_CYAN "\033[0;36m"
#define COLORB_CYAN "\033[1;36m"
#define COLOR_NONE "\033[0m"
#endif //CORE_COLORS_H

16
include/command.h Normal file
View file

@ -0,0 +1,16 @@
#ifndef CORE_COMMAND_H
#define CORE_COMMAND_H
#include <models/controller.h>
#include <models/relay.h>
int
command_set_relay_schedule(relay_t *relay);
int
command_set_controller_name(controller_t *controller);
int
command_send(controller_t *controller, int command_code, char *payload, uint32_t payload_size);
#endif /* CORE_COMMAND_H */

39
include/config.h Normal file
View file

@ -0,0 +1,39 @@
#ifndef CORE_CONFIG_H
#define CORE_CONFIG_H
#include <stdint.h>
#include <confini.h>
typedef enum
{
RUN_TYPE_START,
RUN_TYPE_INVALID,
} run_type_t;
typedef enum
{
LOG_LEVEL_TRACE = 5,
LOG_LEVEL_DEBUG = 4,
LOG_LEVEL_INFO = 3,
LOG_LEVEL_WARN = 2,
LOG_LEVEL_ERROR = 1,
LOG_LEVEL_FATAL = 0,
} log_level_t;
typedef struct
{
char *file;
char *database;
log_level_t log_level;
run_type_t run_type;
char server_port[6];
uint16_t discovery_port;
} config_t;
extern config_t global_config;
int
config_load(IniDispatch *disp, void *config_void);
#endif /* CORE_CONFIG_H */

31
include/constants.h Normal file
View file

@ -0,0 +1,31 @@
#ifndef CORE_CONTANTS_H
#define CORE_CONTANTS_H
#define SECONDS_PER_DAY 86400 // 60 * 60 * 24
#define SECONDS_PER_MINUTE 60
#define POLL_FDS_COUNT 2
/**
* @brief Limit the maximum length of a controller/relay/etc name
*
* The NULL terminator is not included. Arrays of length #MAX_NAME_LENGTH + 1 are required.
*/
#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
*/
#define ACCEPT_TIMEOUT_MSECONDS 1000
#define PIFACE_GPIO_BASE 200
#endif /* CORE_CONTANTS_H */

11
include/database.h Normal file
View file

@ -0,0 +1,11 @@
#ifndef CORE_DATABASE_H
#define CORE_DATABASE_H
#include <sqlite3.h>
extern sqlite3 *global_database;
int
database_migrate();
#endif /* CORE_DATABASE_H */

13
include/drivers.h Normal file
View file

@ -0,0 +1,13 @@
#ifndef CORE_DRIVERS_H
#define CORE_DRIVERS_H
#include <models/relay.h>
#include <enums.h>
void
driver_piface_set(int pin, int value);
void
driver_gpio_set(int pin, int value);
#endif /* CORE_DRIVERS_H */

View file

@ -0,0 +1,30 @@
#ifndef CORE_ENDPOINTS_API_V1_CONTROLLERS_H
#define CORE_ENDPOINTS_API_V1_CONTROLLERS_H
#include <router.h>
void
api_v1_controllers_discover_POST(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_controllers_GET(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_controllers_STR_GET(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_controllers_STR_PUT(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_controllers_STR_DELETE(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_controllers_STR_relays_GET(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_controllers_STR_relays_INT_GET(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_controllers_STR_relays_INT_PUT(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
#endif /* CORE_ENDPOINTS_API_V1_CONTROLLERS_H */

View file

@ -0,0 +1,12 @@
#ifndef CORE_ENDPOINTS_API_V1_RELAYS_H
#define CORE_ENDPOINTS_API_V1_RELAYS_H
#include <router.h>
void
api_v1_relays_GET(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_relays_tag_STR_GET(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
#endif /* CORE_ENDPOINTS_API_V1_RELAYS_H */

View file

@ -0,0 +1,24 @@
#ifndef CORE_ENDPOINTS_API_V1_SCHEDULES_H
#define CORE_ENDPOINTS_API_V1_SCHEDULES_H
#include <router.h>
void
api_v1_schedules_POST(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_schedules_GET(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_schedules_STR_GET(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_schedules_STR_PUT(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_schedules_STR_DELETE(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_schedules_tag_STR_GET(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
#endif /* CORE_ENDPOINTS_API_V1_SCHEDULES_H */

40
include/enums.h Normal file
View file

@ -0,0 +1,40 @@
#ifndef CORE_ENUMS_H
#define CORE_ENUMS_H
typedef enum
{
POLL_FDS_DISCOVERY,
POLL_FDS_COMMAND
} poll_fds_t;
typedef enum
{
COMMAND_MAPPING_CODE = 0,
COMMAND_MAPPING_NAME = 1,
COMMAND_MAPPING_RELAY_NUM = 2,
COMMAND_MAPPING_SCHEDULES_ARRAY = 3,
COMMAND_MAPPING_SCHEDULE_ID = 4,
COMMAND_MAPPING_PERIODS_COUNT = 5,
COMMAND_MAPPING_PERIODS_BLOB = 6,
} control_mapping_t;
typedef enum
{
COMMAND_CODE_GET_TIME = 1,
COMMAND_CODE_GET_ID = 2,
COMMAND_CODE_SET_NAME = 100,
COMMAND_CODE_GET_NAME = 101,
COMMAND_CODE_SET_SCHEDULE = 102,
COMMAND_CODE_GET_SCHEDULE = 103,
COMMAND_CODE_SET_RELAY_NAME = 104,
COMMAND_CODE_GET_RELAY_NAME = 105,
} command_code_t;
typedef enum
{
RELAY_DRIVER_NONE,
RELAY_DRIVER_GPIO,
RELAY_DRIVER_PIFACE,
} relay_driver_t;
#endif /* CORE_ENUMS_H */

7
include/handlers.h Normal file
View file

@ -0,0 +1,7 @@
#ifndef CORE_HANDLERS_H
#define CORE_HANDLERS_H
void
handler_connection(struct mg_connection *c, int ev, void *p);
#endif /* CORE_HANDLERS_H */

17
include/helpers.h Normal file
View file

@ -0,0 +1,17 @@
#ifndef CORE_HELPERS_H
#define CORE_HELPERS_H
#include <time.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);
#endif /* CORE_HELPERS_H */

26
include/logger.h Normal file
View file

@ -0,0 +1,26 @@
#ifndef CORE_LOGGER_H
#define CORE_LOGGER_H
#include <stdio.h>
#include <time.h>
#include <colors.h>
#include <config.h>
#ifndef SOURCE_PATH_SIZE
#define SOURCE_PATH_SIZE 0
#endif
#define __FILENAME__ (__FILE__ + SOURCE_PATH_SIZE)
void
logger_log(FILE *stream, log_level_t level, const char *filename, int line, const char *func, const char *msg, ...);
#define LOG_TRACE(...) logger_log(stdout, LOG_LEVEL_TRACE, __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOG_DEBUG(...) logger_log(stdout, LOG_LEVEL_DEBUG, __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOG_INFO(...) logger_log(stdout, LOG_LEVEL_INFO , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOG_WARN(...) logger_log(stdout, LOG_LEVEL_WARN , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOG_ERROR(...) logger_log(stderr, LOG_LEVEL_ERROR, __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOG_FATAL(...) logger_log(stderr, LOG_LEVEL_FATAL, __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#endif //CORE_LOGGER_H

1
include/macros.h Normal file
View file

@ -0,0 +1 @@
#define STRLEN(s) ((sizeof(s)/sizeof(s[0])) - sizeof(s[0]))

View file

View file

@ -0,0 +1,53 @@
#ifndef CORE_MODELS_CONTROLLER_H
#define CORE_MODELS_CONTROLLER_H
#include <uuid/uuid.h>
#include <sqlite3.h>
#include <constants.h>
#include <cJSON.h>
#include <helpers.h>
#include <models/relay.h>
#define IP_LENGTH 16
typedef struct
{
int id;
uuid_t uid;
char name[MAX_NAME_LENGTH + 1];
char ip[IP_LENGTH + 1];
int active;
int port;
int relay_count;
relay_t **relays;
} controller_t;
void
controller_free(controller_t* contoller);
int
controller_save(controller_t* contoller);
int
controller_remove(controller_t* contoller);
cJSON*
controller_to_json(controller_t* contoller);
controller_t*
controller_get_by_id(int id);
controller_t*
controller_get_by_uid(uuid_t uid);
controller_t**
controller_get_all();
int
controller_command(int command_code, char *payload, uint32_t payload_size);
void
controller_free_list(controller_t **controllers_list);
#endif /* CORE_MODELS_CONTROLLER_H */

View file

@ -0,0 +1,22 @@
#ifndef CORE_MODELS_JUNCTION_RELAY_SCHEDULE_H
#define CORE_MODELS_JUNCTION_RELAY_SCHEDULE_H
int
junction_relay_schedule_get_schedule_id(uint8_t weekday, int relay_id);
int*
junction_relay_schedule_get_relays_ids(int schedule_id);
int
junction_relay_schedule_insert(uint8_t weekday, int relay_id, int schedule_id);
int
junction_relay_schedule_remove(uint8_t weekday, int relay_id, int schedule_id);
int
junction_relay_schedule_remove_for_relay(int relay_id);
int
junction_relay_schedule_remove_for_schedule(int schedule_id);
#endif /* CORE_MODELS_JUNCTION_RELAY_SCHEDULE_H */

View file

@ -0,0 +1,32 @@
#ifndef CORE_MODELS_JUNCTION_TAG_H
#define CORE_MODELS_JUNCTION_TAG_H
int*
junction_tag_get_relays_for_tag_id(int tag_id);
int*
junction_tag_get_schedules_for_tag_id(int tag_id);
int*
junction_tag_get_tags_for_relay_id(int relay_id);
int*
junction_tag_get_tags_for_schedule_id(int schedule_id);
int
junction_tag_insert(int tag_id, int relay_id, int schedule_id);
int
junction_tag_remove(int tag_id, int relay_id, int schedule_id);
int
junction_tag_remove_for_tag(int tag_id);
int
junction_tag_remove_for_relay(int relay_id);
int
junction_tag_remove_for_schedule(int schedule_id);
#endif /* CORE_MODELS_JUNCTION_TAG_H */

21
include/models/period.h Normal file
View file

@ -0,0 +1,21 @@
#ifndef CORE_PERIOD_H
#define CORE_PERIOD_H
#include <stdint.h>
typedef struct
{
uint16_t start;
uint16_t end;
} period_t;
period_t*
period_create(uint16_t start, uint16_t end);
int
period_includes_time(period_t *period, uint16_t timestamp);
int
period_helper_parse_hhmm(const char *hhmm_str, uint16_t *hhmm);
#endif /* CORE_PERIOD_H */

60
include/models/relay.h Normal file
View file

@ -0,0 +1,60 @@
#ifndef CORE_RELAY_H
#define CORE_RELAY_H
#include <string.h>
#include <uuid/uuid.h>
#include <constants.h>
#include <cJSON.h>
#include <helpers.h>
#include <database.h>
#include <models/schedule.h>
typedef struct
{
int id;
char name[MAX_NAME_LENGTH + 1];
int number;
int controller_id;
int active_schedule_id;
schedule_t *active_schedule;
schedule_t *schedules[7];
} relay_t;
int
relay_save();
int
relay_remove();
void
relay_reload_active_schedule(relay_t *relay);
cJSON*
relay_to_json();
void
relay_free(relay_t *relay);
void
relay_free_list(relay_t **relays_list);
relay_t**
relay_get_by_simple(const char *key, const void *value, intptr_t bind_func, int bind_func_param);
relay_t*
relay_get_by_id(int id);
relay_t*
relay_get_for_controller(int controller_id, int relay_num);
relay_t**
relay_get_with_schedule(int schedule_id);
relay_t**
relay_get_all();
relay_t**
relay_get_by_controller_id(int controller_id);
#endif /* CORE_RELAY_H */

67
include/models/schedule.h Normal file
View file

@ -0,0 +1,67 @@
#ifndef CORE_SCHEDULE_H
#define CORE_SCHEDULE_H
#include <uuid/uuid.h>
#include <cJSON.h>
#include <constants.h>
#include <models/period.h>
typedef struct
{
int id;
uuid_t uid;
char name[MAX_NAME_LENGTH + 1];
uint16_t periods_count;
period_t *periods;
} schedule_t;
int
schedule_save(schedule_t *schedule);
int
schedule_remove(schedule_t *schedule);
int
schedule_is_protected(schedule_t *schedule);
void
schedule_free(schedule_t *schedule);
void
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);
schedule_t*
schedule_get_by_id_or_off(int id);
schedule_t*
schedule_get_by_id(int id);
schedule_t*
schedule_get_by_uid_or_off(uuid_t uid);
schedule_t*
schedule_get_by_uid(uuid_t uid);
schedule_t**
schedule_get_relay_weekdays(int relay_id);
schedule_t**
schedule_get_all();
int
schedule_uid_parse(const char *uid_str, uuid_t result);
void
schedule_uid_unparse(const uuid_t uid, char *result);
#endif /* CORE_SCHEDULE_H */

17
include/models/tag.h Normal file
View file

@ -0,0 +1,17 @@
#ifndef CORE_MODELS_TAG_H
#define CORE_MODELS_TAG_H
int
tag_save(int id, const char *tag);
int
tag_remove(int id);
char*
tag_get_tag(int id);
int
tag_get_id(const char* tag);
#endif /* CORE_MODELS_TAG_H */

73
include/router.h Normal file
View file

@ -0,0 +1,73 @@
#ifndef CORE_ROUTER_H
#define CORE_ROUTER_H
#include <mongoose.h>
#define ENDPOINTS_MAX_COUNT 128
typedef enum
{
ENDPOINT_ARG_TYPE_INT,
ENDPOINT_ARG_TYPE_STR
} endpoint_arg_type_e;
typedef enum
{
HTTP_METHOD_GET = (1 << 0),
HTTP_METHOD_POST = (1 << 1),
HTTP_METHOD_PUT = (1 << 2),
HTTP_METHOD_DELETE = (1 << 3),
HTTP_METHOD_OPTIONS = (1 << 4)
} http_method_e;
typedef struct
{
endpoint_arg_type_e type;
union
{
int v_int;
const char *v_str;
} value;
} endpoint_args_t;
typedef struct
{
int status_code;
const char *content_type;
size_t content_length;
const char *content;
int alloced_content;
} endpoint_response_t;
typedef void (*endpoint_func_f)(struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
typedef struct
{
const char *full_route;
char **route;
char *route_keeper;
int method;
int options;
endpoint_func_f func;
int trailing_slash;
int args_count;
endpoint_args_t *args;
int possible_route;
int args_found;
} endpoint_t;
void
router_init();
endpoint_t*
router_register_endpoint(const char *route, int method, endpoint_func_f func);
endpoint_t*
router_find_endpoint(const char *uri_str, size_t uri_len, struct mg_str *method_str);
void
router_free();
#endif /* CORE_ROUTER_H */

59
logger.c Normal file
View file

@ -0,0 +1,59 @@
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <config.h>
#include <logger.h>
#define COLOR_TRACE COLOR_GREEN
#define COLOR_DEBUG COLOR_BLUE
#define COLOR_INFO COLOR_CYAN
#define COLOR_WARN COLOR_YELLOW
#define COLOR_ERROR COLOR_RED
#define COLOR_FATAL COLOR_MAGENTA
void
logger_log(FILE *stream, log_level_t level, const char *filename, int line, const char *func, const char *msg, ...)
{
if(global_config.log_level < level)
{
return;
}
switch(level)
{
case LOG_LEVEL_TRACE:
fprintf(stream, COLOR_TRACE "[TRACE] ");
break;
case LOG_LEVEL_DEBUG:
fprintf(stream, COLOR_DEBUG "[DEBUG] ");
break;
case LOG_LEVEL_INFO:
fprintf(stream, COLOR_INFO "[INFO ] ");
break;
case LOG_LEVEL_WARN:
fprintf(stream, COLOR_WARN "[WARN ] ");
break;
case LOG_LEVEL_ERROR:
fprintf(stream, COLOR_ERROR "[ERROR] ");
break;
case LOG_LEVEL_FATAL:
fprintf(stream, COLOR_FATAL "[FATAL] ");
break;
default:
fprintf(stream, COLOR_NONE "[LOG ] ");
break;
}
char timestamp_str[32];
time_t rawtime;
time(&rawtime);
strftime(timestamp_str, 32, "%Y-%m-%d %H:%M:%S", localtime(&rawtime));
fprintf(stream, "%s %s:%d:%s " COLOR_NONE, timestamp_str, filename, line, func);
va_list args;
va_start(args, msg);
vfprintf(stream, msg, args);
va_end(args);
}

109
main.c Normal file
View file

@ -0,0 +1,109 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <mongoose.h>
#include <router.h>
#include <logger.h>
#include <config.h>
#include <database.h>
#include <handlers.h>
#include <enums.h>
#include <helpers.h>
#include <confini.h>
#include <models/controller.h>
static struct mg_mgr mgr;
static void
terminate(int signum)
{
LOG_INFO("terminating controller (%d)\n", signum);
mg_mgr_free(&mgr);
sqlite3_close(global_database);
free(global_config.database);
router_free();
exit(signum);
}
/**
* @brief The main function
*
* @param argc UNUSED
* @param argv UNUSED
*
* @return Statuscode to indicate success (0) or failure (!0)
*/
int
main(int argc, const char** argv)
{
signal(SIGINT, terminate);
signal(SIGABRT, terminate);
signal(SIGTERM, terminate);
/******************** LOAD CONFIG ********************/
global_config.file = "core.ini";
global_config.log_level = LOG_LEVEL_INFO;
helper_parse_cli(argc, argv, &global_config);
FILE * const ini_file = fopen(global_config.file, "rb");
if(ini_file == NULL)
{
LOG_FATAL("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))
{
LOG_FATAL("unable to parse ini file\n");
exit(1);
}
fclose(ini_file);
/******************** SETUP DATABASE ********************/
int rc = sqlite3_open(global_config.database, &global_database);
if(rc) {
LOG_FATAL("can't open database: %s\n", sqlite3_errmsg(global_database));
return 1;
}
if(database_migrate())
{
terminate(1);
}
sqlite3_exec(global_database, "PRAGMA foreign_keys = ON", 0, 0, 0);
/******************** INIT ROUTER ********************/
router_init();
/******************** START MAIN LOOP ********************/
struct mg_connection *c;
mg_mgr_init(&mgr, NULL);
c = mg_bind(&mgr, global_config.server_port, handler_connection);
mg_set_protocol_http_websocket(c);
for (;;)
{
mg_mgr_poll(&mgr, 1000);
}
terminate(0);
}

74
main.cc
View file

@ -1,74 +0,0 @@
#include <drogon/drogon.h>
#include <sqlite3.h>
#include <models/controller_dbo.h>
#include <csignal>
#include "globals.h"
#include "config.h"
static void
add_cors_headers(const drogon::HttpRequestPtr &requestPtr, const drogon::HttpResponsePtr &responsePtr)
{
responsePtr->addHeader("Access-Control-Allow-Origin", "*");
}
static void
terminate(int signum)
{
LOG_INFO << "Terminating Server (" << signum << ")";
sqlite3_close(globals::db);
quick_exit(signum);
}
/*static void test()
{
LOG_DEBUG << "LOOP";
}*/
int
main(int argc, char** argv)
{
signal(SIGINT, terminate);
signal(SIGABRT, terminate);
signal(SIGTERM, terminate);
const char* config_file_name = "config.json";
if(argc == 2)
{
config_file_name = argv[1];
}
//Load config file
drogon::app().loadConfigFile(config_file_name);
const char* db_name = drogon::app().instance().getCustomConfig()["db_name"].asCString();
/* Open database */
int rc = sqlite3_open(db_name, &globals::db);
if(rc) {
LOG_FATAL << "Can't open database: " << sqlite3_errmsg(globals::db);
return 1;
}
if(helpers::migrate_sql())
{
terminate(1);
}
drogon::app().registerPostHandlingAdvice(add_cors_headers);
drogon::app().instance().setCustom404Page(drogon::HttpResponse::newFileResponse("./static/index.html", "", drogon::CT_TEXT_HTML));
//drogon::app().getLoop()->runEvery(1, &test);
//Run HTTP framework,the method will block in the internal event loop
LOG_INFO << "Starting Emgauwa Core (" << config::version << ")";
drogon::app().run();
return 0;
}

293
models/controller.c Normal file
View file

@ -0,0 +1,293 @@
#include <stdlib.h>
#include <string.h>
#include <sqlite3.h>
#include <cJSON.h>
#include <logger.h>
#include <database.h>
#include <models/controller.h>
#include <models/junction_tag.h>
#include <models/tag.h>
static int
db_update_insert(controller_t *controller, sqlite3_stmt *stmt)
{
int rc;
sqlite3_bind_int(stmt, 1, controller->id);
sqlite3_bind_blob(stmt, 2, controller->uid, sizeof(uuid_t), SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, controller->name, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 4, controller->ip, -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 5, controller->active);
sqlite3_bind_int(stmt, 6, controller->port);
sqlite3_bind_int(stmt, 7, controller->relay_count);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc != SQLITE_DONE;
}
static controller_t*
controller_db_select_mapper(sqlite3_stmt *stmt)
{
controller_t *new_controller = malloc(sizeof(controller_t));
for(int i = 0; i < sqlite3_column_count(stmt); i++)
{
const char *name = sqlite3_column_name(stmt, i);
switch(name[0])
{
case 'a': // active
new_controller->active = (bool)sqlite3_column_int(stmt, i);
break;
case 'i':
switch(name[1])
{
case 'd': // id
new_controller->id = sqlite3_column_int(stmt, i);
break;
case 'p': // ip
strncpy(new_controller->ip, (const char*)sqlite3_column_text(stmt, i), 16);
break;
default: // ignore columns not implemented
break;
}
break;
case 'n': // name
strncpy(new_controller->name, (const char*)sqlite3_column_text(stmt, i), 127);
break;
case 'p': // port
new_controller->port = sqlite3_column_int(stmt, i);
break;
case 'r': // relay_count
new_controller->relay_count = sqlite3_column_int(stmt, i);
break;
case 'u': // uid
uuid_copy(new_controller->uid, (const unsigned char*)sqlite3_column_blob(stmt, i));
break;
default: // ignore columns not implemented
break;
}
}
new_controller->relays = relay_get_by_controller_id(new_controller->id);
return new_controller;
}
static controller_t**
controller_db_select(sqlite3_stmt *stmt)
{
controller_t **all_controllers = malloc(sizeof(controller_t*));
int row = 0;
while(true)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
controller_t *new_controller = controller_db_select_mapper(stmt);
row++;
all_controllers = (controller_t**)realloc(all_controllers, sizeof(controller_t*) * (row + 1));
all_controllers[row - 1] = new_controller;
}
else
{
if(s == SQLITE_DONE)
{
break;
}
else
{
LOG_ERROR("error selecting controllers from database: %s\n", sqlite3_errstr(s));
break;
}
}
}
sqlite3_finalize(stmt);
all_controllers[row] = NULL;
return all_controllers;
}
int
controller_save(controller_t *controller)
{
sqlite3_stmt *stmt;
if(controller->id)
{
sqlite3_prepare_v2(global_database, "UPDATE controllers set uid = ?2, name = ?3, ip = ?4, active = ?5, port = ?6, relay_count = ?7 WHERE id = ?1;", -1, &stmt, NULL);
}
else
{
sqlite3_prepare_v2(global_database, "INSERT INTO controllers(uid, name, ip, active, port, relay_count) values (?2, ?3, ?4, ?5, ?6, ?7);", -1, &stmt, NULL);
}
int result = db_update_insert(controller, stmt);
if(result)
{
if(controller->id)
{
LOG_ERROR("error inserting data: %s\n", sqlite3_errmsg(global_database));
}
else
{
LOG_ERROR("error updating data: %s\n", sqlite3_errmsg(global_database));
}
}
else
{
if(!controller->id)
{
controller->id = sqlite3_last_insert_rowid(global_database);
}
}
return result;
}
int
controller_remove(controller_t *controller)
{
sqlite3_stmt *stmt;
if(!controller->id)
{
return 0;
}
sqlite3_prepare_v2(global_database, "DELETE FROM controllers WHERE id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, controller->id);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc != SQLITE_DONE;
}
void
controller_free(controller_t *controller)
{
relay_free_list(controller->relays);
free(controller);
}
void
controller_free_list(controller_t **controllers)
{
for(int i = 0; controllers[i] != NULL; ++i)
{
controller_free(controllers[i]);
}
free(controllers);
}
cJSON*
controller_to_json(controller_t *controller)
{
cJSON *json = cJSON_CreateObject();
cJSON *json_name = cJSON_CreateString(controller->name);
if(json_name == NULL)
{
cJSON_Delete(json);
return NULL;
}
cJSON_AddItemToObject(json, "name", json_name);
char uuid_str[UUID_STR_LEN];
uuid_unparse(controller->uid, uuid_str);
cJSON *json_id = cJSON_CreateString(uuid_str);
if(json_name == NULL)
{
cJSON_Delete(json);
return NULL;
}
cJSON_AddItemToObject(json, "id", json_id);
cJSON *json_ip = cJSON_CreateString(controller->ip);
if(json_ip == NULL)
{
cJSON_Delete(json);
return NULL;
}
cJSON_AddItemToObject(json, "ip", json_ip);
cJSON *json_port = cJSON_CreateNumber(controller->port);
if(json_port == NULL)
{
cJSON_Delete(json);
return NULL;
}
cJSON_AddItemToObject(json, "port", json_port);
cJSON *json_relay_count = cJSON_CreateNumber(controller->relay_count);
if(json_relay_count == NULL)
{
cJSON_Delete(json);
return NULL;
}
cJSON_AddItemToObject(json, "relay_count", json_relay_count);
cJSON *json_active = cJSON_CreateBool(controller->active);
if(json_active == NULL)
{
cJSON_Delete(json);
return NULL;
}
cJSON_AddItemToObject(json, "active", json_active);
relay_t **relays = relay_get_by_controller_id(controller->id);
cJSON *json_relays = cJSON_CreateArray();
for(int i = 0; relays[i] != NULL; ++i)
{
cJSON_AddItemToArray(json_relays, relay_to_json(relays[i]));
}
cJSON_AddItemToObject(json, "relays", json_relays);
relay_free_list(relays);
return json;
}
controller_t*
controller_get_by_id(int id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM controllers WHERE id = ?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, id);
controller_t **sql_result = controller_db_select(stmt);
controller_t *result = sql_result[0];
free(sql_result);
return result;
}
controller_t*
controller_get_by_uid(uuid_t uid)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM controllers WHERE uid = ?1;", -1, &stmt, NULL);
sqlite3_bind_blob(stmt, 1, uid, sizeof(uuid_t), SQLITE_STATIC);
controller_t **sql_result = controller_db_select(stmt);
controller_t *result = sql_result[0];
free(sql_result);
return result;
}
controller_t**
controller_get_all()
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM controllers;", -1, &stmt, NULL);
return controller_db_select(stmt);
}

View file

@ -1,263 +0,0 @@
#include <cstdio>
#include <cstring>
#include <trantor/utils/Logger.h>
#include <sys/socket.h>
#include <unistd.h>
#include <uuid/uuid.h>
#include <helpers.h>
#include <mpack.h>
#include <enums.h>
#include "controller_dbo.h"
#include "globals.h"
controller_dbo::~controller_dbo()
{
if(this->relays)
{
relay_dbo::free_list(this->relays);
}
}
static bool controller_db_update_insert(controller_dbo *controller, sqlite3_stmt *stmt)
{
int rc;
sqlite3_bind_blob(stmt, 1, controller->id, sizeof(uuid_t), SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, controller->name, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, controller->ip, -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 4, controller->active);
sqlite3_bind_int(stmt, 5, controller->port);
sqlite3_bind_int(stmt, 6, controller->relay_count);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
printf("ERROR inserting data: %s\n", sqlite3_errmsg(globals::db));
return false;
}
sqlite3_finalize(stmt);
return true;
}
static controller_dbo*
controller_db_select_mapper(sqlite3_stmt *stmt)
{
auto *new_controller = new controller_dbo();
for(int i = 0; i < sqlite3_column_count(stmt); i++)
{
const char *name = sqlite3_column_name(stmt, i);
switch(name[0])
{
case 'a': // active
new_controller->active = (bool)sqlite3_column_int(stmt, i);
break;
case 'i':
switch(name[1])
{
case 'd': // id
uuid_copy(new_controller->id, (const unsigned char*)sqlite3_column_blob(stmt, i));
break;
case 'p': // ip
strncpy(new_controller->ip, (const char*)sqlite3_column_text(stmt, i), 16);
break;
default: // ignore columns not implemented
break;
}
break;
case 'n': // name
strncpy(new_controller->name, (const char*)sqlite3_column_text(stmt, i), 127);
break;
case 'p': // port
new_controller->port = sqlite3_column_int(stmt, i);
break;
case 'r': // relay_count
new_controller->relay_count = sqlite3_column_int(stmt, i);
break;
default: // ignore columns not implemented
break;
}
}
return new_controller;
}
static controller_dbo**
controller_db_select(sqlite3_stmt *stmt)
{
auto **all_controllers = (controller_dbo**)malloc(sizeof(controller_dbo*));
int row = 0;
while(true)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
controller_dbo *new_controller = controller_db_select_mapper(stmt);
new_controller->relays = relay_dbo::get_by_simple("controller_id", new_controller->id, (intptr_t)&sqlite3_bind_blob, sizeof(uuid_t));
row++;
all_controllers = (controller_dbo**)realloc(all_controllers, sizeof(controller_dbo*) * (row + 1));
all_controllers[row - 1] = new_controller;
}
else
{
if (s == SQLITE_DONE)
{
break;
}
else
{
LOG_ERROR << "Error Selecting controllers from database";
sqlite3_finalize(stmt);
return nullptr;
}
}
}
sqlite3_finalize(stmt);
all_controllers[row] = nullptr;
return all_controllers;
}
bool
controller_dbo::update()
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, "UPDATE controllers set name = ?2, ip = ?3, active = ?4, port = ?5, relay_count = ?6 WHERE id = ?1;", -1, &stmt, nullptr);
return controller_db_update_insert(this, stmt);
}
bool
controller_dbo::insert()
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, "INSERT INTO controllers(id, name, ip, active, port, relay_count) values (?1, ?2, ?3, ?4, ?5, ?6);", -1, &stmt, nullptr);
return controller_db_update_insert(this, stmt);
}
bool
controller_dbo::remove()
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(globals::db, "DELETE FROM controllers WHERE id=?1;", -1, &stmt, nullptr);
sqlite3_bind_blob(stmt, 1, this->id, sizeof(uuid_t), SQLITE_STATIC);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
Json::Value
controller_dbo::to_json()
{
char id_str[37];
uuid_unparse(this->id, id_str);
Json::Value controller_json;
controller_json["name"] = this->name;
controller_json["id"] = id_str;
controller_json["ip"] = this->ip;
controller_json["relay_count"] = this->relay_count;
controller_json["active"] = this->active;
controller_json["relays"] = Json::arrayValue;
for(int i = 0; this->relays[i] != nullptr; i++)
{
controller_json["relays"].append(this->relays[i]->to_json());
}
return controller_json;
}
controller_dbo**
controller_dbo::get_all()
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, "SELECT * FROM controllers;", -1, &stmt, nullptr);
return controller_db_select(stmt);
}
controller_dbo**
controller_dbo::get_by_simple(const char *key, const void *value, intptr_t bind_func, int bind_func_param)
{
helpers::sql_filter_builder *filters[1];
helpers::sql_filter_builder filter
{
key,
value,
bind_func,
bind_func_param,
";"
};
filters[0] = &filter;
sqlite3_stmt *stmt = helpers::create_sql_filtered_query("SELECT * FROM controllers WHERE", filters);
return controller_db_select(stmt);
}
controller_dbo**
controller_dbo::get_by(helpers::sql_filter_builder **filters)
{
sqlite3_stmt *stmt = helpers::create_sql_filtered_query("SELECT * FROM controllers WHERE", filters);
return controller_db_select(stmt);
}
bool
controller_dbo::command(int command_code, char *payload, uint32_t payload_size)
{
LOG_DEBUG << "Commanding " << command_code;
int bytes_transferred;
char port_str[6];
sprintf(port_str, "%d", this->port);
int fd_controller = helpers::open_tcp_connection(this->ip, port_str);
if(fd_controller == -1)
{
LOG_ERROR << "Can't open command socket " << this->ip << ":" << port_str;
return false;
}
if((bytes_transferred = send(fd_controller, &payload_size, sizeof(payload_size), 0)) <= 0)
{
LOG_ERROR << "error during sending size";
return false;
}
if((bytes_transferred = send(fd_controller, payload, payload_size, 0)) <= 0)
{
LOG_ERROR << "error during sending";
return false;
}
close(fd_controller);
return true;
}
void
controller_dbo::free_list(controller_dbo **controllers_list)
{
for(int i = 0; controllers_list[i] != nullptr; i++)
{
delete controllers_list[i];
}
free(controllers_list);
}

View file

@ -1,54 +0,0 @@
#ifndef EMGAUWA_CORE_controller_DBO_H
#define EMGAUWA_CORE_controller_DBO_H
#include <string>
#include <uuid/uuid.h>
#include <sqlite3.h>
#include <json/value.h>
#include <helpers.h>
#include "relay_dbo.h"
class controller_dbo
{
public:
uuid_t id;
char name[128];
char ip[17];
bool active;
int port;
int relay_count;
relay_dbo **relays;
~controller_dbo();
bool
update();
bool
insert();
bool
remove();
Json::Value
to_json();
static controller_dbo**
get_by_simple(const char *key, const void *value, intptr_t bind_func, int bind_func_param);
static controller_dbo**
get_by(helpers::sql_filter_builder **filters);
static controller_dbo**
get_all();
bool
command(int command_code, char *payload, uint32_t payload_size);
static void
free_list(controller_dbo **controllers_list);
};
#endif //EMGAUWA_CORE_controller_DBO_H

View file

@ -0,0 +1,145 @@
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <models/junction_relay_schedule.h>
#include <logger.h>
#include <database.h>
int
junction_relay_schedule_insert(uint8_t weekday, int relay_id, int schedule_id)
{
int rc;
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "INSERT INTO junction_relay_schedule(weekday, schedule_id, relay_id) values (?1, ?2, ?3);", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, weekday);
sqlite3_bind_int(stmt, 2, schedule_id);
sqlite3_bind_int(stmt, 3, relay_id);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
LOG_ERROR("error inserting data: %s", sqlite3_errmsg(global_database));
return false;
}
sqlite3_finalize(stmt);
return true;
}
static int*
get_ids(sqlite3_stmt *stmt)
{
int *ids = malloc(sizeof(int));
int new_id;
int row = 0;
while(true)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
new_id = sqlite3_column_int(stmt, 0);
row++;
ids = (int*)realloc(ids, sizeof(int) * (row + 1));
ids[row - 1] = new_id;
}
else
{
if (s == SQLITE_DONE)
{
break;
}
else
{
LOG_ERROR("error selecting junction ids from database: %s\n", sqlite3_errstr(s));
sqlite3_finalize(stmt);
return NULL;
}
}
}
sqlite3_finalize(stmt);
ids[row] = 0;
return ids;
}
int
junction_relay_schedule_get_schedule_id(uint8_t weekday, int relay_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT schedule_id FROM junction_relay_schedule WHERE weekday=?1 AND relay_id=?2 LIMIT 1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, weekday);
sqlite3_bind_int(stmt, 2, relay_id);
int *id_list = get_ids(stmt);
int result = id_list[0];
free(id_list);
return result;
}
int*
junction_relay_schedule_get_relays_ids(int schedule_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT relay_id FROM junction_relay_schedule WHERE schedule_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, schedule_id);
return get_ids(stmt);
}
int
junction_relay_schedule_remove(uint8_t weekday, int relay_id, int schedule_id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(global_database, "DELETE FROM junction_relay_schedule WHERE weekday=?1 AND schedule_id=?2 AND relay_id=?3;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, weekday);
sqlite3_bind_int(stmt, 2, schedule_id);
sqlite3_bind_int(stmt, 3, relay_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
int
junction_relay_schedule_remove_for_relay(int relay_id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(global_database, "DELETE FROM junction_relay_schedule WHERE relay_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, relay_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
int
junction_relay_schedule_remove_for_schedule(int schedule_id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(global_database, "DELETE FROM junction_relay_schedule WHERE schedule_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, schedule_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}

View file

@ -1,112 +0,0 @@
#include <cstdio>
#include <cstring>
#include <trantor/utils/Logger.h>
#include <helpers.h>
#include "junction_relay_schedule_dbo.h"
#include "globals.h"
bool
junction_relay_schedule_dbo::insert(uint8_t weekday, int relay_id, int schedule_id)
{
int rc;
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, "INSERT INTO junction_relay_schedule(weekday, schedule_id, relay_id) values (?1, ?2, ?3);", -1, &stmt, nullptr);
sqlite3_bind_int(stmt, 1, weekday);
sqlite3_bind_int(stmt, 2, schedule_id);
sqlite3_bind_int(stmt, 3, relay_id);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
printf("ERROR inserting data: %s\n", sqlite3_errmsg(globals::db));
return false;
}
sqlite3_finalize(stmt);
return true;
}
int
junction_relay_schedule_dbo::get_schedule_id(uint8_t weekday, int relay_id)
{
int result = 0;
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, "SELECT schedule_id FROM junction_relay_schedule WHERE weekday=?1 AND relay_id=?2 LIMIT 1;", -1, &stmt, nullptr);
sqlite3_bind_int(stmt, 1, weekday);
sqlite3_bind_int(stmt, 2, relay_id);
while(true)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
result = sqlite3_column_int(stmt, 0);
}
else
{
if (s == SQLITE_DONE)
{
break;
}
else
{
LOG_ERROR << "Error Selecting relays from database: " << sqlite3_errstr(s);
break;
}
}
}
sqlite3_finalize(stmt);
return result;
}
bool
junction_relay_schedule_dbo::remove(uint8_t weekday, int relay_id, int schedule_id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(globals::db, "DELETE FROM junction_relay_schedule WHERE weekday=?1 AND schedule_id=?2 AND relay_id=?3;", -1, &stmt, nullptr);
sqlite3_bind_int(stmt, 1, weekday);
sqlite3_bind_int(stmt, 2, schedule_id);
sqlite3_bind_int(stmt, 3, relay_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
bool
junction_relay_schedule_dbo::remove_for_relay(int relay_id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(globals::db, "DELETE FROM junction_relay_schedule WHERE relay_id=?1;", -1, &stmt, nullptr);
sqlite3_bind_int(stmt, 1, relay_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
bool
junction_relay_schedule_dbo::remove_for_schedule(int schedule_id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(globals::db, "DELETE FROM junction_relay_schedule WHERE schedule_id=?1;", -1, &stmt, nullptr);
sqlite3_bind_int(stmt, 1, schedule_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}

View file

@ -1,29 +0,0 @@
#ifndef EMGAUWA_CORE_JUNCTION_RELAY_SCHEDULE_DBO_H
#define EMGAUWA_CORE_JUNCTION_RELAY_SCHEDULE_DBO_H
#include <string>
#include <sqlite3.h>
#include <json/value.h>
#include <uuid/uuid.h>
#include <helpers.h>
namespace junction_relay_schedule_dbo
{
int
get_schedule_id(uint8_t weekday, int relay_id);
bool
insert(uint8_t weekday, int relay_id, int schedule_id);
bool
remove(uint8_t weekday, int relay_id, int schedule_id);
bool
remove_for_relay(int relay_id);
bool
remove_for_schedule(int schedule_id);
};
#endif //EMGAUWA_CORE_JUNCTION_RELAY_SCHEDULE_DBO_H

194
models/junction_tag.c Normal file
View file

@ -0,0 +1,194 @@
#include <stdlib.h>
#include <stddef.h>
#include <sqlite3.h>
#include <logger.h>
#include <models/junction_tag.h>
#include <database.h>
int
junction_tag_insert(int tag_id, int relay_id, int schedule_id)
{
int rc;
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "INSERT INTO junction_tag(tag_id, schedule_id, relay_id) values (?1, ?2, ?3);", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, tag_id);
if(schedule_id)
{
sqlite3_bind_int(stmt, 2, schedule_id);
}
else
{
sqlite3_bind_null(stmt, 2);
}
if(relay_id)
{
sqlite3_bind_int(stmt, 3, relay_id);
}
else
{
sqlite3_bind_null(stmt, 3);
}
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
printf("ERROR inserting data: %s\n", sqlite3_errmsg(global_database));
return false;
}
sqlite3_finalize(stmt);
return true;
}
static int*
get_ids(sqlite3_stmt *stmt)
{
int *ids = malloc(sizeof(int));
int new_id;
int row = 0;
while(true)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
new_id = sqlite3_column_int(stmt, 0);
if(new_id != 0) // found row for other target (relay <> schedule)
{
row++;
ids = (int*)realloc(ids, sizeof(int) * (row + 1));
ids[row - 1] = new_id;
}
}
else
{
if (s == SQLITE_DONE)
{
break;
}
else
{
LOG_ERROR("error selecting relays from database: %s\n", sqlite3_errstr(s));
sqlite3_finalize(stmt);
return NULL;
}
}
}
sqlite3_finalize(stmt);
ids[row] = 0;
return ids;
}
int*
junction_tag_get_relays_for_tag_id(int tag_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT relay_id FROM junction_tag WHERE tag_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, tag_id);
return get_ids(stmt);
}
int*
junction_tag_get_schedules_for_tag_id(int tag_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT schedule_id FROM junction_tag WHERE tag_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, tag_id);
return get_ids(stmt);
}
int*
junction_tag_get_tags_for_relay_id(int relay_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT tag_id FROM junction_tag WHERE relay_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, relay_id);
return get_ids(stmt);
}
int*
junction_tag_get_tags_for_schedule_id(int schedule_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT tag_id FROM junction_tag WHERE schedule_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, schedule_id);
return get_ids(stmt);
}
int
junction_tag_remove(int tag_id, int relay_id, int schedule_id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(global_database, "DELETE FROM junction_tag WHERE tag_id=?1 AND schedule_id=?2 AND relay_id=?3;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, tag_id);
sqlite3_bind_int(stmt, 2, schedule_id);
sqlite3_bind_int(stmt, 3, relay_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
int
junction_tag_remove_for_tag(int tag_id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(global_database, "DELETE FROM junction_tag WHERE tag_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, tag_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
int
junction_tag_remove_for_relay(int relay_id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(global_database, "DELETE FROM junction_tag WHERE relay_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, relay_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
int
junction_tag_remove_for_schedule(int schedule_id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(global_database, "DELETE FROM junction_tag WHERE schedule_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, schedule_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}

View file

@ -1,174 +0,0 @@
#include <cstdio>
#include <cstring>
#include <trantor/utils/Logger.h>
#include <helpers.h>
#include "junction_tag_dbo.h"
#include "globals.h"
bool
junction_tag_dbo::insert(int tag_id, int relay_id, int schedule_id)
{
int rc;
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, "INSERT INTO junction_tag(tag_id, schedule_id, relay_id) values (?1, ?2, ?3);", -1, &stmt, nullptr);
sqlite3_bind_int(stmt, 1, tag_id);
sqlite3_bind_int(stmt, 2, schedule_id);
sqlite3_bind_int(stmt, 3, relay_id);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
printf("ERROR inserting data: %s\n", sqlite3_errmsg(globals::db));
return false;
}
sqlite3_finalize(stmt);
return true;
}
static int*
get_ids(sqlite3_stmt *stmt)
{
auto *ids = (int*)malloc(sizeof(int));
int new_id;
int row = 0;
while(true)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
new_id = sqlite3_column_int(stmt, 0);
row++;
ids = (int*)realloc(ids, sizeof(int) * (row + 1));
ids[row - 1] = new_id;
}
else
{
if (s == SQLITE_DONE)
{
break;
}
else
{
LOG_ERROR << "Error Selecting relays from database: " << sqlite3_errstr(s);
sqlite3_finalize(stmt);
return nullptr;
}
}
}
sqlite3_finalize(stmt);
ids[row] = 0;
return ids;
}
int*
junction_tag_dbo::get_relays_for_tag_id(int tag_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, "SELECT relay_id FROM junction_tag WHERE tag_id=?1;", -1, &stmt, nullptr);
sqlite3_bind_int(stmt, 1, tag_id);
return get_ids(stmt);
}
int*
junction_tag_dbo::get_schedules_for_tag_id(int tag_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, "SELECT schedule_id FROM junction_tag WHERE tag_id=?1;", -1, &stmt, nullptr);
sqlite3_bind_int(stmt, 1, tag_id);
return get_ids(stmt);
}
int*
junction_tag_dbo::get_tags_for_relay_id(int relay_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, "SELECT tag_id FROM junction_tag WHERE relay_id=?1;", -1, &stmt, nullptr);
sqlite3_bind_int(stmt, 1, relay_id);
return get_ids(stmt);
}
int*
junction_tag_dbo::get_tags_for_schedule_id(int schedule_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, "SELECT tag_id FROM junction_tag WHERE schedule_id=?1;", -1, &stmt, nullptr);
sqlite3_bind_int(stmt, 1, schedule_id);
return get_ids(stmt);
}
bool
junction_tag_dbo::remove(int tag_id, int relay_id, int schedule_id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(globals::db, "DELETE FROM junction_tag WHERE tag_id=?1 AND schedule_id=?2 AND relay_id=?3;", -1, &stmt, nullptr);
sqlite3_bind_int(stmt, 1, tag_id);
sqlite3_bind_int(stmt, 2, schedule_id);
sqlite3_bind_int(stmt, 3, relay_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
bool
junction_tag_dbo::remove_for_tag(int tag_id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(globals::db, "DELETE FROM junction_tag WHERE tag_id=?1;", -1, &stmt, nullptr);
sqlite3_bind_int(stmt, 1, tag_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
bool
junction_tag_dbo::remove_for_relay(int relay_id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(globals::db, "DELETE FROM junction_tag WHERE relay_id=?1;", -1, &stmt, nullptr);
sqlite3_bind_int(stmt, 1, relay_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
bool
junction_tag_dbo::remove_for_schedule(int schedule_id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(globals::db, "DELETE FROM junction_tag WHERE schedule_id=?1;", -1, &stmt, nullptr);
sqlite3_bind_int(stmt, 1, schedule_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}

View file

@ -1,41 +0,0 @@
#ifndef EMGAUWA_CORE_JUNCTION_TAG_DBO_H
#define EMGAUWA_CORE_JUNCTION_TAG_DBO_H
#include <string>
#include <sqlite3.h>
#include <json/value.h>
#include <uuid/uuid.h>
#include <helpers.h>
namespace junction_tag_dbo
{
int*
get_relays_for_tag_id(int tag_id);
int*
get_schedules_for_tag_id(int tag_id);
int*
get_tags_for_relay_id(int relay_id);
int*
get_tags_for_schedule_id(int schedule_id);
bool
insert(int tag_id, int relay_id, int schedule_id);
bool
remove(int tag_id, int relay_id, int schedule_id);
bool
remove_for_tag(int tag_id);
bool
remove_for_relay(int relay_id);
bool
remove_for_schedule(int schedule_id);
};
#endif //EMGAUWA_CORE_JUNCTION_TAG_DBO_H

37
models/period.c Normal file
View file

@ -0,0 +1,37 @@
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <models/period.h>
int
period_helper_parse_hhmm(const char *hhmm_str, uint16_t *hhmm)
{
if(strlen(hhmm_str) != 5)
{
return 1;
}
if(hhmm_str[2] != ':')
{
return 1;
}
for(int i = 0; i < 5; ++i)
{
if(i != 2)
{
if(hhmm_str[i] < '0' || hhmm_str[i] > '9')
{
return 1;
}
}
}
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);
*hhmm = (tmp_h * 60) + tmp_m;
return 0;
}

View file

@ -1,39 +0,0 @@
#include <cstdio>
#include <cstdint>
#include "period.h"
period::period(uint16_t start, uint16_t end)
{
this->start = start;
this->end = end;
}
Json::Value
period::to_json()
{
Json::Value result;
char start_str[8], end_str[8];
sprintf(start_str, "%02d:%02d", this->start / 60, this->start % 60);
sprintf(end_str, "%02d:%02d", this->end / 60, this->end % 60);
result["start"] = std::string(start_str);
result["end"] = std::string(end_str);
return result;
}
bool
period::is_in_period(uint16_t timestamp)
{
if(this->start < this->end)
{
return this->start <= timestamp and timestamp <= this->end;
}
if(this->start > this->end)
{
return this->end <= timestamp and timestamp <= this->start;
}
return this->start == timestamp;
}

View file

@ -1,23 +0,0 @@
#ifndef EMGAUWA_CORE_PERIOD_H
#define EMGAUWA_CORE_PERIOD_H
#include <json/json.h>
class period
{
public:
uint16_t start;
uint16_t end;
period(uint16_t start, uint16_t end);
Json::Value
to_json();
bool
is_in_period(uint16_t timestamp);
};
#endif //EMGAUWA_CORE_PERIOD_H

View file

@ -1,70 +0,0 @@
#include <cstdint>
#include <cstdlib>
#include "period_list.h"
period_list::period_list()
{
this->periods = (period**)malloc(0);
this->length = 0;
}
period_list::period_list(const uint16_t* periods_blob)
{
this->length = periods_blob[0];
this->periods = (period**)malloc(sizeof(period*) * this->length);
for(int i = 0; i < length; i++)
{
auto new_period = new period(periods_blob[(i * 2) + 1], periods_blob[(i * 2) + 2]);
periods[i] = new_period;
}
}
period_list::period_list(period **periods, uint16_t length)
{
this->periods = periods;
this->length = length;
}
period_list::~period_list()
{
for(int i = 0; i < length; i++)
{
delete this->periods[i];
}
free(this->periods);
}
void
period_list::add_period(uint16_t start, uint16_t end)
{
this->length++;
this->periods = (period**)realloc(this->periods, sizeof(period*) * this->length);
this->periods[this->length - 1] = new period(start, end);
}
Json::Value
period_list::to_json()
{
Json::Value result(Json::arrayValue);
for(int i = 0; i < this->length; i++)
{
result.append(this->periods[i]->to_json());
}
return result;
}
uint16_t*
period_list::to_blob()
{
auto result = (uint16_t*)malloc(sizeof(uint16_t) * ((this->length * 2) + 1));
result[0] = this->length;
for(int i = 0; i < this->length; i++)
{
result[(i * 2) + 1] = this->periods[i]->start;
result[(i * 2) + 2] = this->periods[i]->end;
}
return result;
}

View file

@ -1,28 +0,0 @@
#ifndef EMGAUWA_CORE_PERIOD_LIST_H
#define EMGAUWA_CORE_PERIOD_LIST_H
#include <json/json.h>
#include "period.h"
class period_list
{
public:
period **periods;
uint16_t length;
period_list();
explicit period_list(const uint16_t *periods_blob);
period_list(period **periods, uint16_t length);
~period_list();
void
add_period(uint16_t start, uint16_t end);
Json::Value
to_json();
uint16_t*
to_blob();
};
#endif //EMGAUWA_CORE_PERIOD_LIST_H

376
models/relay.c Normal file
View file

@ -0,0 +1,376 @@
#include <stdlib.h>
#include <string.h>
#include <sqlite3.h>
#include <cJSON.h>
#include <logger.h>
#include <database.h>
#include <models/relay.h>
#include <models/controller.h>
#include <models/schedule.h>
#include <models/junction_tag.h>
#include <models/junction_relay_schedule.h>
#include <models/tag.h>
static int
db_update_insert(relay_t *relay, sqlite3_stmt *stmt)
{
int rc;
sqlite3_bind_int(stmt, 1, relay->id);
sqlite3_bind_int(stmt, 2, relay->number);
sqlite3_bind_text(stmt, 3, relay->name, -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 4, relay->controller_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc != SQLITE_DONE;
}
static relay_t*
relay_db_select_mapper(sqlite3_stmt *stmt)
{
relay_t *new_relay = malloc(sizeof(relay_t));
for(int i = 0; i < sqlite3_column_count(stmt); i++)
{
const char *name = sqlite3_column_name(stmt, i);
switch(name[0])
{
case 'c': // controller_id
new_relay->controller_id = sqlite3_column_int(stmt, i);
break;
case 'i':
new_relay->id = sqlite3_column_int(stmt, i);
break;
case 'n':
switch(name[1])
{
case 'a': // name
strncpy(new_relay->name, (const char*)sqlite3_column_text(stmt, i), 127);
break;
case 'u': // number
new_relay->number = sqlite3_column_int(stmt, i);
break;
default:
break;
}
break;
default: // ignore columns not implemented
break;
}
}
schedule_t **schedules = schedule_get_relay_weekdays(new_relay->id);
for(int i = 0; i < 7; ++i)
{
if(schedules[i] == NULL)
{
LOG_ERROR("got only %d/7 schedules for relay_id %d\n", i, new_relay->id);
relay_free(new_relay);
free(schedules);
return NULL;
}
new_relay->schedules[i] = schedules[i];
}
free(schedules); // don't free list, because contents are kept in relay->schedules
relay_reload_active_schedule(new_relay);
return new_relay;
}
static relay_t**
relay_db_select(sqlite3_stmt *stmt)
{
relay_t **all_relays = malloc(sizeof(relay_t*));
int row = 0;
while(true)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
relay_t *new_relay = relay_db_select_mapper(stmt);
row++;
all_relays = (relay_t**)realloc(all_relays, sizeof(relay_t*) * (row + 1));
all_relays[row - 1] = new_relay;
}
else
{
if(s == SQLITE_DONE)
{
break;
}
else
{
LOG_ERROR("error selecting relays from database: %s\n", sqlite3_errstr(s));
break;
}
}
}
sqlite3_finalize(stmt);
all_relays[row] = NULL;
return all_relays;
}
int
relay_save(relay_t *relay)
{
sqlite3_stmt *stmt;
if(relay->id)
{
sqlite3_prepare_v2(global_database, "UPDATE relays set number = ?2, name = ?3, controller_id = ?4 WHERE id = ?1;", -1, &stmt, NULL);
}
else
{
sqlite3_prepare_v2(global_database, "INSERT INTO relays(number, name, controller_id) values (?2, ?3, ?4);", -1, &stmt, NULL);
}
int result = db_update_insert(relay, stmt);
if(result)
{
if(relay->id)
{
LOG_ERROR("error inserting data: %s\n", sqlite3_errmsg(global_database));
}
else
{
LOG_ERROR("error updating data: %s\n", sqlite3_errmsg(global_database));
}
}
else
{
if(relay->id == 0)
{
relay->id = sqlite3_last_insert_rowid(global_database);
}
}
junction_relay_schedule_remove_for_relay(relay->id);
for(int i = 0; i < 7; ++i)
{
junction_relay_schedule_insert(i, relay->id, relay->schedules[i]->id);
}
return result;
}
int
relay_remove(relay_t *relay)
{
sqlite3_stmt *stmt;
if(!relay->id)
{
return 0;
}
sqlite3_prepare_v2(global_database, "DELETE FROM relays WHERE id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, relay->id);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc != SQLITE_DONE;
}
void
relay_reload_active_schedule(relay_t *relay)
{
time_t timestamp = time(NULL);
struct tm *time_struct = localtime(&timestamp);
relay->active_schedule = relay->schedules[helper_get_weekday(time_struct)];
}
void
relay_free(relay_t *relay)
{
for(int i = 0; i < 7; ++i)
{
schedule_free(relay->schedules[i]);
}
free(relay);
}
void
relay_free_list(relay_t **relays)
{
for(int i = 0; relays[i] != NULL; ++i)
{
relay_free(relays[i]);
}
free(relays);
}
cJSON*
relay_to_json(relay_t *relay)
{
relay_reload_active_schedule(relay);
cJSON *json = cJSON_CreateObject();
cJSON *json_number = cJSON_CreateNumber(relay->number);
if(json_number == NULL)
{
LOG_DEBUG("failed to make number\n");
cJSON_Delete(json);
return NULL;
}
cJSON_AddItemToObject(json, "number", json_number);
cJSON *json_name = cJSON_CreateString(relay->name);
if(json_name == NULL)
{
LOG_DEBUG("failed to make name\n");
cJSON_Delete(json);
return NULL;
}
cJSON_AddItemToObject(json, "name", json_name);
controller_t *controller = controller_get_by_id(relay->controller_id);
if(!controller)
{
LOG_WARN("failed to get controller\n");
cJSON_Delete(json);
return NULL;
}
char uuid_str[UUID_STR_LEN];
uuid_unparse(controller->uid, uuid_str);
cJSON *json_controller_id = cJSON_CreateString(uuid_str);
if(json_controller_id == NULL)
{
LOG_DEBUG("failed to make controller id\n");
cJSON_Delete(json);
return NULL;
}
cJSON_AddItemToObject(json, "controller_id", json_controller_id);
controller_free(controller);
cJSON_AddItemToObject(json, "active_schedule", schedule_to_json(relay->active_schedule));
cJSON *json_schedules = cJSON_CreateArray();
for(int i = 0; i < 7; ++i)
{
cJSON_AddItemToArray(json_schedules, schedule_to_json(relay->schedules[i]));
}
cJSON_AddItemToObject(json, "schedules", json_schedules);
cJSON *json_tags = cJSON_CreateArray();
int *tags_ids = junction_tag_get_tags_for_relay_id(relay->id);
if(tags_ids != NULL)
{
for(int i = 0; tags_ids[i] != 0; ++i)
{
char *tag = tag_get_tag(tags_ids[i]);
if(tag == NULL)
{
continue;
}
cJSON *json_tag = cJSON_CreateString(tag);
if (json_tag == NULL)
{
LOG_DEBUG("failed to add tag from string '%s'\n", tag);
free(tag);
continue;
}
cJSON_AddItemToArray(json_tags, json_tag);
free(tag);
}
free(tags_ids);
}
cJSON_AddItemToObject(json, "tags", json_tags);
return json;
}
relay_t*
relay_get_by_id(int id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM relays WHERE id = ?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, id);
relay_t **sql_result = relay_db_select(stmt);
relay_t *result = sql_result[0];
free(sql_result);
return result;
}
relay_t*
relay_get_by_uid(uuid_t uid)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM relays WHERE uid = ?1;", -1, &stmt, NULL);
sqlite3_bind_blob(stmt, 1, uid, sizeof(uuid_t), SQLITE_STATIC);
relay_t **sql_result = relay_db_select(stmt);
relay_t *result = sql_result[0];
free(sql_result);
return result;
}
relay_t**
relay_get_all()
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM relays;", -1, &stmt, NULL);
return relay_db_select(stmt);
}
relay_t**
relay_get_with_schedule(int schedule_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT DISTINCT relays.* FROM relays INNER JOIN junction_relay_schedule ON relays.id == junction_relay_schedule.relay_id WHERE junction_relay_schedule.schedule_id = ?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, schedule_id);
return relay_db_select(stmt);
}
relay_t*
relay_get_for_controller(int controller_id, int relay_num)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM relays WHERE controller_id = ?1 AND number = ?2;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, controller_id);
sqlite3_bind_int(stmt, 2, relay_num);
relay_t **sql_result = relay_db_select(stmt);
relay_t *result = sql_result[0];
free(sql_result);
return result;
}
relay_t**
relay_get_by_controller_id(int controller_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM relays WHERE controller_id = ?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, controller_id);
return relay_db_select(stmt);
}

View file

@ -1,290 +0,0 @@
#include <cstdio>
#include <cstring>
#include <trantor/utils/Logger.h>
#include <helpers.h>
#include "relay_dbo.h"
#include "globals.h"
#include "junction_relay_schedule_dbo.h"
#include "junction_tag_dbo.h"
#include "tag_dbo.h"
#include "controller_dbo.h"
#include "schedule_dbo.h"
static bool relay_db_update_insert(relay_dbo *relay, sqlite3_stmt *stmt)
{
int rc;
junction_relay_schedule_dbo::remove_for_relay(relay->id);
for(int i = 0; i < 7; ++i)
{
junction_relay_schedule_dbo::insert(i, relay->id, relay->schedules[i]->id);
}
sqlite3_bind_int(stmt, 1, relay->id);
sqlite3_bind_int(stmt, 2, relay->number);
sqlite3_bind_text(stmt, 3, relay->name, -1, SQLITE_STATIC);
sqlite3_bind_blob(stmt, 4, relay->controller_id, sizeof(uuid_t), SQLITE_STATIC);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
printf("ERROR inserting data: %s\n", sqlite3_errmsg(globals::db));
return false;
}
sqlite3_finalize(stmt);
return true;
}
static relay_dbo*
relay_db_select_mapper(sqlite3_stmt *stmt)
{
auto *new_relay = new relay_dbo();
for(int i = 0; i < sqlite3_column_count(stmt); i++)
{
const char *name = sqlite3_column_name(stmt, i);
switch(name[0])
{
case 'c': // controller_id
uuid_copy(new_relay->controller_id, (const unsigned char*)sqlite3_column_blob(stmt, i));
break;
case 'i':
new_relay->id = sqlite3_column_int(stmt, i);
break;
case 'n':
switch(name[1])
{
case 'a': // name
strncpy(new_relay->name, (const char*)sqlite3_column_text(stmt, i), 127);
break;
case 'u': // number
new_relay->number = sqlite3_column_int(stmt, i);
break;
default:
break;
}
break;
default: // ignore columns not implemented
break;
}
}
for(int i = 0; i < 7; ++i)
{
int schedule_id = junction_relay_schedule_dbo::get_schedule_id(i, new_relay->id);
new_relay->schedules[i] = schedule_dbo::get_by_id_or_off(schedule_id);
}
return new_relay;
}
static relay_dbo**
relay_db_select(sqlite3_stmt *stmt)
{
auto **all_relays = (relay_dbo**)malloc(sizeof(relay_dbo*));
int row = 0;
while(true)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
relay_dbo *new_relay = relay_db_select_mapper(stmt);
row++;
all_relays = (relay_dbo**)realloc(all_relays, sizeof(relay_dbo*) * (row + 1));
all_relays[row - 1] = new_relay;
}
else
{
if (s == SQLITE_DONE)
{
break;
}
else
{
LOG_ERROR << "Error Selecting relays from database: " << sqlite3_errstr(s);
break;
}
}
}
sqlite3_finalize(stmt);
all_relays[row] = nullptr;
return all_relays;
}
bool
relay_dbo::save()
{
sqlite3_stmt *stmt;
if(this->id)
{
sqlite3_prepare_v2(globals::db, "UPDATE relays set number = ?2, name = ?3, controller_id = ?4 WHERE id = ?1;", -1, &stmt, nullptr);
}
else
{
sqlite3_prepare_v2(globals::db, "INSERT INTO relays(number, name, controller_id) values (?2, ?3, ?4);", -1, &stmt, nullptr);
}
return relay_db_update_insert(this, stmt);
}
bool
relay_dbo::remove()
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(globals::db, "DELETE FROM relays WHERE id=?1;", -1, &stmt, nullptr);
sqlite3_bind_int(stmt, 1, this->id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
Json::Value
relay_dbo::to_json()
{
this->active_schedule = this->schedules[helpers::get_day_of_week()];
char controller_id_str[37];
uuid_unparse(this->controller_id, controller_id_str);
char active_schedule_uid_str[37];
schedule_dbo::unparse_uid(this->active_schedule->uid, active_schedule_uid_str);
Json::Value schedules_json(Json::arrayValue);
for(int i = 0; i < 7; ++i)
{
schedules_json.append(this->schedules[i]->to_json());
}
Json::Value relay_json;
relay_json["name"] = this->name;
relay_json["number"] = this->number;
relay_json["active_schedule_id"] = active_schedule_uid_str;
relay_json["controller_id"] = controller_id_str;
relay_json["active_schedule"] = this->active_schedule->to_json();
relay_json["schedules"] = schedules_json;
Json::Value tags_json(Json::arrayValue);
int *tags_ids = junction_tag_dbo::get_tags_for_relay_id(this->id);
if(tags_ids != nullptr)
{
int tags_count;
for(tags_count = 0; tags_ids[tags_count] != 0; ++tags_count);
char **tags = (char**)malloc(sizeof(char*) * tags_count);
for(int i = 0; i < tags_count; ++i)
{
tags[i] = tag_dbo::get_tag(tags_ids[i]);
tags_json[i] = tags[i];
}
}
relay_json["tags"] = tags_json;
return relay_json;
}
relay_dbo**
relay_dbo::get_all()
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, "SELECT * FROM relays;", -1, &stmt, nullptr);
return relay_db_select(stmt);
}
relay_dbo**
relay_dbo::get_by_simple(const char *key, const void *value, intptr_t bind_func, int bind_func_param)
{
helpers::sql_filter_builder *filters[1];
helpers::sql_filter_builder filter
{
key,
value,
bind_func,
bind_func_param,
";"
};
filters[0] = &filter;
sqlite3_stmt *stmt = helpers::create_sql_filtered_query("SELECT * FROM relays WHERE", filters);
return relay_db_select(stmt);
}
relay_dbo**
relay_dbo::get_by(helpers::sql_filter_builder **filters)
{
sqlite3_stmt *stmt = helpers::create_sql_filtered_query("SELECT * FROM relays WHERE", filters);
return relay_db_select(stmt);
}
relay_dbo*
relay_dbo::get_by_id(int id)
{
relay_dbo **relays = relay_dbo::get_by_simple("id", &id, (intptr_t)&sqlite3_bind_int, 0);
relay_dbo *result = relays[0];
free(relays);
return result;
}
relay_dbo*
relay_dbo::get_relay_for_controller(uuid_t controller_id, int relay_num)
{
helpers::sql_filter_builder *filters[2];
helpers::sql_filter_builder filter(
"number",
&relay_num,
(intptr_t)&sqlite3_bind_int,
0,
"AND"
);
helpers::sql_filter_builder filter2(
"controller_id",
(void*)controller_id,
(intptr_t)sqlite3_bind_blob,
sizeof(uuid_t),
";"
);
filters[0] = &filter;
filters[1] = &filter2;
auto relays = relay_dbo::get_by(filters);
relay_dbo *relay = relays[0];
free(relays);
return relay;
}
bool
relay_dbo::valid_num_for_controller(uuid_t search_controller_id, int relay_num)
{
controller_dbo **controllers = controller_dbo::get_by_simple("id", search_controller_id, (intptr_t)&sqlite3_bind_blob, sizeof(uuid_t));
bool valid_id_and_num = controllers[0] && controllers[0]->relay_count > relay_num;
controller_dbo::free_list(controllers);
return valid_id_and_num;
}
void
relay_dbo::free_list(relay_dbo **relays_list)
{
for(int i = 0; relays_list[i] != nullptr; i++)
{
delete relays_list[i];
}
free(relays_list);
}

View file

@ -1,58 +0,0 @@
#ifndef EMGAUWA_CORE_RELAY_DBO_H
#define EMGAUWA_CORE_RELAY_DBO_H
#include <string>
#include <sqlite3.h>
#include <json/value.h>
#include <uuid/uuid.h>
#include <helpers.h>
#include "schedule_dbo.h"
class relay_dbo
{
public:
int id;
char name[128];
int number;
uuid_t controller_id;
int active_schedule_id;
schedule_dbo *active_schedule;
schedule_dbo *schedules[7];
void
reload_active_schedule();
bool
save();
bool
remove();
Json::Value
to_json();
static void
free_list(relay_dbo **relays_list);
static relay_dbo**
get_by_simple(const char *key, const void *value, intptr_t bind_func, int bind_func_param);
static relay_dbo*
get_by_id(int id);
static relay_dbo**
get_by(helpers::sql_filter_builder **filters);
static relay_dbo*
get_relay_for_controller(uuid_t controller_id, int relay_num);
static bool
valid_num_for_controller(uuid_t search_controller_id, int relay_num);
static relay_dbo**
get_all();
};
#endif //EMGAUWA_CORE_RELAY_DBO_H

461
models/schedule.c Normal file
View file

@ -0,0 +1,461 @@
#include <stdlib.h>
#include <string.h>
#include <sqlite3.h>
#include <cJSON.h>
#include <logger.h>
#include <database.h>
#include <models/schedule.h>
#include <models/junction_tag.h>
#include <models/tag.h>
static int
db_update_insert(schedule_t *schedule, sqlite3_stmt *stmt)
{
int rc;
uint16_t *periods_blob = schedule_periods_to_blob(schedule);
int blob_size = (int)sizeof(uint16_t) * ((periods_blob[0] * 2) + 1);
sqlite3_bind_int(stmt, 1, schedule->id);
sqlite3_bind_blob(stmt, 2, schedule->uid, sizeof(uuid_t), SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, schedule->name, -1, SQLITE_STATIC);
sqlite3_bind_blob(stmt, 4, periods_blob, blob_size, SQLITE_STATIC);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
free(periods_blob);
return rc != SQLITE_DONE;
}
static schedule_t*
schedule_db_select_mapper(sqlite3_stmt *stmt)
{
const uint16_t *periods_blob;
schedule_t *new_schedule = malloc(sizeof(schedule_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_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';
break;
case 'p': // periods
periods_blob = sqlite3_column_blob(stmt, i);
new_schedule->periods_count = periods_blob[0];
new_schedule->periods = malloc(sizeof(period_t) * periods_blob[0]);
for(int i = 0; i < periods_blob[0]; ++i)
{
new_schedule->periods[i].start = periods_blob[(i * 2) + 1];
new_schedule->periods[i].end = periods_blob[(i * 2) + 2];
}
break;
case 'u': // uid
uuid_copy(new_schedule->uid, (const unsigned char*)sqlite3_column_blob(stmt, i));
break;
default: // ignore columns not implemented
break;
}
}
return new_schedule;
}
static schedule_t**
schedule_db_select(sqlite3_stmt *stmt)
{
schedule_t **all_schedules = malloc(sizeof(schedule_t*));
int row = 0;
while(true)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
schedule_t *new_schedule = schedule_db_select_mapper(stmt);
row++;
all_schedules = (schedule_t**)realloc(all_schedules, sizeof(schedule_t*) * (row + 1));
all_schedules[row - 1] = new_schedule;
}
else
{
if(s == SQLITE_DONE)
{
break;
}
else
{
LOG_ERROR("error selecting schedules from database: %s\n", sqlite3_errstr(s));
break;
}
}
}
sqlite3_finalize(stmt);
all_schedules[row] = NULL;
return all_schedules;
}
int
schedule_save(schedule_t *schedule)
{
sqlite3_stmt *stmt;
if(schedule->id)
{
sqlite3_prepare_v2(global_database, "UPDATE schedules SET uid = ?2, name = ?3, periods = ?4 WHERE id=?1;", -1, &stmt, NULL);
}
else
{
sqlite3_prepare_v2(global_database, "INSERT INTO schedules(uid, name, periods) values (?2, ?3, ?4);", -1, &stmt, NULL);
}
int result = db_update_insert(schedule, stmt);
if(result)
{
if(schedule->id)
{
LOG_ERROR("error inserting data: %s\n", sqlite3_errmsg(global_database));
}
else
{
LOG_ERROR("error updating data: %s\n", sqlite3_errmsg(global_database));
}
}
else
{
if(!schedule->id)
{
schedule->id = sqlite3_last_insert_rowid(global_database);
}
}
return result;
}
int
schedule_remove(schedule_t *schedule)
{
sqlite3_stmt *stmt;
if(!schedule->id)
{
return 0;
}
sqlite3_prepare_v2(global_database, "DELETE FROM schedules WHERE id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, schedule->id);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc != SQLITE_DONE;
}
int
schedule_is_protected(schedule_t *schedule)
{
uuid_t tmp_uuid;
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "off", 3);
if(uuid_compare(schedule->uid, tmp_uuid) == 0)
{
return 1;
}
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "on", 2);
if(uuid_compare(schedule->uid, tmp_uuid) == 0)
{
return 1;
}
return 0;
}
void
schedule_free(schedule_t *schedule)
{
free(schedule->periods);
free(schedule);
}
void
schedule_free_list(schedule_t **schedules)
{
for(int i = 0; schedules[i] != NULL; ++i)
{
schedule_free(schedules[i]);
}
free(schedules);
}
uint16_t*
schedule_periods_to_blob(schedule_t *schedule)
{
uint16_t *blob = malloc(sizeof(uint16_t) * ((schedule->periods_count * 2) + 1));
blob[0] = schedule->periods_count;
for(int i = 0; i < schedule->periods_count; i++)
{
blob[(i * 2) + 1] = schedule->periods[i].start;
blob[(i * 2) + 2] = schedule->periods[i].end;
}
return blob;
}
cJSON*
schedule_to_json(schedule_t *schedule)
{
cJSON *json = cJSON_CreateObject();
cJSON *json_name = cJSON_CreateString(schedule->name);
if(json_name == NULL)
{
cJSON_Delete(json);
return NULL;
}
cJSON_AddItemToObject(json, "name", json_name);
char uuid_str[UUID_STR_LEN];
schedule_uid_unparse(schedule->uid, uuid_str);
cJSON *json_id = cJSON_CreateString(uuid_str);
if(json_name == NULL)
{
cJSON_Delete(json);
return NULL;
}
cJSON_AddItemToObject(json, "id", json_id);
cJSON *json_periods = cJSON_CreateArray();
if(json_periods == NULL)
{
cJSON_Delete(json);
return NULL;
}
cJSON_AddItemToObject(json, "periods", json_periods);
for(int i = 0; i < schedule->periods_count; ++i)
{
cJSON *json_period = cJSON_CreateObject();
if (json_period == NULL)
{
continue;
}
char start_str[8], 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);
cJSON *json_period_start = cJSON_CreateString(start_str);
if (json_period_start == NULL)
{
LOG_DEBUG("failed to add start period from string '%s'\n", start_str);
cJSON_Delete(json_period);
continue;
}
cJSON_AddItemToObject(json_period, "start", json_period_start);
cJSON *json_period_end = cJSON_CreateString(end_str);
if (json_period_end == NULL)
{
LOG_DEBUG("failed to add end period from string '%s'\n", end_str);
cJSON_Delete(json_period);
continue;
}
cJSON_AddItemToObject(json_period, "end", json_period_end);
cJSON_AddItemToArray(json_periods, json_period);
}
cJSON *json_tags = cJSON_CreateArray();
int *tags_ids = junction_tag_get_tags_for_schedule_id(schedule->id);
if(tags_ids != NULL)
{
for(int i = 0; tags_ids[i] != 0; ++i)
{
char *tag = tag_get_tag(tags_ids[i]);
if(tag == NULL)
{
continue;
}
cJSON *json_tag = cJSON_CreateString(tag);
if (json_tag == NULL)
{
LOG_DEBUG("failed to add tag from string '%s'\n", tag);
free(tag);
continue;
}
cJSON_AddItemToArray(json_tags, json_tag);
free(tag);
}
free(tags_ids);
}
cJSON_AddItemToObject(json, "tags", json_tags);
return json;
}
schedule_t*
schedule_get_by_id_or_off(int id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM schedules WHERE id = ?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, id);
schedule_t **sql_result = schedule_db_select(stmt);
schedule_t *result = sql_result[0];
free(sql_result);
if(result)
{
return result;
}
uuid_t tmp_uuid;
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "off", 3);
return schedule_get_by_uid(tmp_uuid);
}
schedule_t*
schedule_get_by_id(int id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM schedules WHERE id = ?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, id);
schedule_t **sql_result = schedule_db_select(stmt);
schedule_t *result = sql_result[0];
free(sql_result);
return result;
}
schedule_t*
schedule_get_by_uid_or_off(uuid_t uid)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM schedules WHERE uid = ?1;", -1, &stmt, NULL);
sqlite3_bind_blob(stmt, 1, uid, sizeof(uuid_t), SQLITE_STATIC);
schedule_t **sql_result = schedule_db_select(stmt);
schedule_t *result = sql_result[0];
free(sql_result);
if(result)
{
return result;
}
uuid_t tmp_uuid;
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "off", 3);
return schedule_get_by_uid(tmp_uuid);
}
schedule_t*
schedule_get_by_uid(uuid_t uid)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM schedules WHERE uid = ?1;", -1, &stmt, NULL);
sqlite3_bind_blob(stmt, 1, uid, sizeof(uuid_t), SQLITE_STATIC);
schedule_t **sql_result = schedule_db_select(stmt);
schedule_t *result = sql_result[0];
free(sql_result);
return result;
}
schedule_t**
schedule_get_relay_weekdays(int relay_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT schedules.* FROM schedules INNER JOIN junction_relay_schedule ON schedules.id == junction_relay_schedule.schedule_id WHERE junction_relay_schedule.relay_id = ?1 ORDER BY junction_relay_schedule.weekday ASC", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, relay_id);
return schedule_db_select(stmt);
}
schedule_t**
schedule_get_all()
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM schedules;", -1, &stmt, NULL);
return schedule_db_select(stmt);
}
int
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);
return 0;
}
if(strcmp("on", uid_str) == 0)
{
memset(result, 0, sizeof(uuid_t));
memcpy(result, "on", 2);
return 0;
}
if(uuid_parse(uid_str, result))
{
return 1;
}
return 0;
}
void
schedule_uid_unparse(const uuid_t uid, char *result)
{
uuid_t tmp_uuid;
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "off", 3);
if(uuid_compare(uid, tmp_uuid) == 0)
{
strcpy(result, "off");
return;
}
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "on", 2);
if(uuid_compare(uid, tmp_uuid) == 0)
{
strcpy(result, "on");
return;
}
uuid_unparse(uid, result);
}

Some files were not shown because too many files have changed in this diff Show more