summaryrefslogtreecommitdiffstats
path: root/sources
diff options
context:
space:
mode:
authorOpenGnSys Support Team <soporte-og@soleta.eu>2020-01-22 13:32:12 +0100
committerOpenGnSys Support Team <soporte-og@soleta.eu>2020-06-02 12:32:36 +0200
commit83b242ce587fa910861bb2e6b4cdf2cffd38df65 (patch)
treec6fd25d74ac98afb8101913d810ca8798dc800d7 /sources
parentaf30cc7dbbd9ee4d6ae2c93fd9ecd46975bf16a6 (diff)
#942 Add support for scheduled tasks and commands
This field needs to be at least 31 bits long to store all days in a month. Other fields are also set to 32 bits because unsigned int length can change depending on the system. We also need to support the three ways that the ogAdmAgent and the WebConsole have to create an schedule. At first, we only supported the easiest method: * Hour, day, month and year -> 10:00, 28, february, 2020 This commit adds these two ways to create an schedule: * Hour, week day, month and year -> 10:00, Monday, february, 2020 * Hour, week, month and year -> 10:00, first week, february, 2020
Diffstat (limited to 'sources')
-rw-r--r--sources/ogAdmServer.c428
-rw-r--r--sources/schedule.c394
-rw-r--r--sources/schedule.h37
3 files changed, 847 insertions, 12 deletions
diff --git a/sources/ogAdmServer.c b/sources/ogAdmServer.c
index b6fb375..116852c 100644
--- a/sources/ogAdmServer.c
+++ b/sources/ogAdmServer.c
@@ -10,6 +10,7 @@
#include "ogAdmLib.c"
#include "dbi.h"
#include "list.h"
+#include "schedule.h"
#include <ev.h>
#include <syslog.h>
#include <sys/ioctl.h>
@@ -18,6 +19,7 @@
#include <sys/stat.h>
#include <fcntl.h>
#include <jansson.h>
+#include <time.h>
static char usuario[LONPRM]; // Usuario de acceso a la base de datos
static char pasguor[LONPRM]; // Password del usuario
@@ -3028,6 +3030,7 @@ struct og_msg_params {
bool echo;
struct og_partition partition_setup[OG_PARTITION_MAX];
struct og_sync_params sync_setup;
+ struct og_schedule_time time;
const char *task_id;
uint64_t flags;
};
@@ -3073,6 +3076,12 @@ struct og_computer {
#define OG_REST_PARAM_SYNC_METHOD (1UL << 29)
#define OG_REST_PARAM_ECHO (1UL << 30)
#define OG_REST_PARAM_TASK (1UL << 31)
+#define OG_REST_PARAM_TIME_YEARS (1UL << 32)
+#define OG_REST_PARAM_TIME_MONTHS (1UL << 33)
+#define OG_REST_PARAM_TIME_DAYS (1UL << 34)
+#define OG_REST_PARAM_TIME_HOURS (1UL << 35)
+#define OG_REST_PARAM_TIME_AM_PM (1UL << 36)
+#define OG_REST_PARAM_TIME_MINUTES (1UL << 37)
enum og_rest_method {
OG_METHOD_GET = 0,
@@ -3190,6 +3199,15 @@ static int og_json_parse_string(json_t *element, const char **str)
return 0;
}
+static int og_json_parse_uint(json_t *element, uint32_t *integer)
+{
+ if (json_typeof(element) != JSON_INTEGER)
+ return -1;
+
+ *integer = json_integer_value(element);
+ return 0;
+}
+
static int og_json_parse_bool(json_t *element, bool *value)
{
if (json_typeof(element) == JSON_TRUE)
@@ -3337,6 +3355,40 @@ static int og_json_parse_partition_setup(json_t *element,
return 0;
}
+static int og_json_parse_time_params(json_t *element,
+ struct og_msg_params *params)
+{
+ const char *key;
+ json_t *value;
+ int err = 0;
+
+ json_object_foreach(element, key, value) {
+ if (!strcmp(key, "years")) {
+ err = og_json_parse_uint(value, &params->time.years);
+ params->flags |= OG_REST_PARAM_TIME_YEARS;
+ } else if (!strcmp(key, "months")) {
+ err = og_json_parse_uint(value, &params->time.months);
+ params->flags |= OG_REST_PARAM_TIME_MONTHS;
+ } else if (!strcmp(key, "days")) {
+ err = og_json_parse_uint(value, &params->time.days);
+ params->flags |= OG_REST_PARAM_TIME_DAYS;
+ } else if (!strcmp(key, "hours")) {
+ err = og_json_parse_uint(value, &params->time.hours);
+ params->flags |= OG_REST_PARAM_TIME_HOURS;
+ } else if (!strcmp(key, "am_pm")) {
+ err = og_json_parse_uint(value, &params->time.am_pm);
+ params->flags |= OG_REST_PARAM_TIME_AM_PM;
+ } else if (!strcmp(key, "minutes")) {
+ err = og_json_parse_uint(value, &params->time.minutes);
+ params->flags |= OG_REST_PARAM_TIME_MINUTES;
+ }
+ if (err != 0)
+ return err;
+ }
+
+ return err;
+}
+
static int og_cmd_post_clients(json_t *element, struct og_msg_params *params)
{
const char *key;
@@ -4571,7 +4623,7 @@ static int og_queue_task_clients(struct og_dbi *dbi, struct og_task *task)
return 0;
}
-static int og_queue_procedure(struct og_dbi *dbi, struct og_task *task)
+static int og_dbi_queue_procedure(struct og_dbi *dbi, struct og_task *task)
{
uint32_t procedure_id;
const char *msglog;
@@ -4592,7 +4644,7 @@ static int og_queue_procedure(struct og_dbi *dbi, struct og_task *task)
procedure_id = dbi_result_get_uint(result, "procedimientoid");
if (procedure_id > 0) {
task->procedure_id = procedure_id;
- if (og_queue_procedure(dbi, task))
+ if (og_dbi_queue_procedure(dbi, task))
return -1;
continue;
}
@@ -4607,7 +4659,7 @@ static int og_queue_procedure(struct og_dbi *dbi, struct og_task *task)
return 0;
}
-static int og_queue_task(struct og_dbi *dbi, uint32_t task_id)
+static int og_dbi_queue_task(struct og_dbi *dbi, uint32_t task_id)
{
struct og_task task = {};
uint32_t task_id_next;
@@ -4635,7 +4687,7 @@ static int og_queue_task(struct og_dbi *dbi, uint32_t task_id)
task_id_next = dbi_result_get_uint(result, "procedimientoid");
if (task_id_next > 0) {
- if (og_queue_task(dbi, task_id_next))
+ if (og_dbi_queue_task(dbi, task_id_next))
return -1;
continue;
@@ -4645,8 +4697,7 @@ static int og_queue_task(struct og_dbi *dbi, uint32_t task_id)
task.scope = dbi_result_get_uint(result, "idambito");
task.filtered_scope = dbi_result_get_string(result, "restrambito");
- og_queue_procedure(dbi, &task);
-
+ og_dbi_queue_procedure(dbi, &task);
}
dbi_result_free(result);
@@ -4654,6 +4705,41 @@ static int og_queue_task(struct og_dbi *dbi, uint32_t task_id)
return 0;
}
+void og_dbi_schedule_task(unsigned int task_id)
+{
+ struct og_msg_params params = {};
+ bool duplicated = false;
+ struct og_cmd *cmd;
+ struct og_dbi *dbi;
+ unsigned int i;
+
+ dbi = og_dbi_open(&dbi_config);
+ if (!dbi) {
+ syslog(LOG_ERR, "cannot open connection database (%s:%d)\n",
+ __func__, __LINE__);
+ return;
+ }
+ og_dbi_queue_task(dbi, task_id);
+ og_dbi_close(dbi);
+
+ list_for_each_entry(cmd, &cmd_list, list) {
+ for (i = 0; i < params.ips_array_len; i++) {
+ if (!strncmp(cmd->ip, params.ips_array[i],
+ OG_DB_IP_MAXLEN)) {
+ duplicated = true;
+ break;
+ }
+ }
+
+ if (!duplicated)
+ params.ips_array[params.ips_array_len++] = cmd->ip;
+ else
+ duplicated = false;
+ }
+
+ og_send_request("run/schedule", OG_METHOD_GET, &params, NULL);
+}
+
static int og_cmd_task_post(json_t *element, struct og_msg_params *params)
{
struct og_cmd *cmd;
@@ -4685,7 +4771,7 @@ static int og_cmd_task_post(json_t *element, struct og_msg_params *params)
return -1;
}
- og_queue_task(dbi, atoi(params->task_id));
+ og_dbi_queue_task(dbi, atoi(params->task_id));
og_dbi_close(dbi);
list_for_each_entry(cmd, &cmd_list, list)
@@ -4694,6 +4780,288 @@ static int og_cmd_task_post(json_t *element, struct og_msg_params *params)
return og_send_request("run/schedule", OG_METHOD_GET, params, NULL);
}
+static int og_dbi_schedule_get(void)
+{
+ uint32_t schedule_id, task_id;
+ struct og_schedule_time time;
+ struct og_dbi *dbi;
+ const char *msglog;
+ dbi_result result;
+
+ dbi = og_dbi_open(&dbi_config);
+ if (!dbi) {
+ syslog(LOG_ERR, "cannot open connection database (%s:%d)\n",
+ __func__, __LINE__);
+ return -1;
+ }
+
+ result = dbi_conn_queryf(dbi->conn,
+ "SELECT idprogramacion, tipoaccion, identificador, "
+ "sesion, annos, meses, diario, dias, semanas, horas, "
+ "ampm, minutos FROM programaciones "
+ "WHERE suspendida = 0");
+ if (!result) {
+ dbi_conn_error(dbi->conn, &msglog);
+ syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
+ __func__, __LINE__, msglog);
+ return -1;
+ }
+
+ while (dbi_result_next_row(result)) {
+ memset(&time, 0, sizeof(time));
+ schedule_id = dbi_result_get_uint(result, "idprogramacion");
+ task_id = dbi_result_get_uint(result, "identificador");
+ time.years = dbi_result_get_uint(result, "annos");
+ time.months = dbi_result_get_uint(result, "meses");
+ time.weeks = dbi_result_get_uint(result, "semanas");
+ time.week_days = dbi_result_get_uint(result, "dias");
+ time.days = dbi_result_get_uint(result, "diario");
+ time.hours = dbi_result_get_uint(result, "horas");
+ time.am_pm = dbi_result_get_uint(result, "ampm");
+ time.minutes = dbi_result_get_uint(result, "minutos");
+
+ og_schedule_create(schedule_id, task_id, &time);
+ }
+
+ dbi_result_free(result);
+
+ return 0;
+}
+
+static int og_dbi_schedule_create(struct og_dbi *dbi,
+ struct og_msg_params *params,
+ uint32_t *schedule_id)
+{
+ const char *msglog;
+ dbi_result result;
+ uint8_t suspended = 0;
+ uint8_t type = 3;
+
+ result = dbi_conn_queryf(dbi->conn,
+ "INSERT INTO programaciones (tipoaccion,"
+ " identificador, nombrebloque, annos, meses,"
+ " diario, horas, ampm, minutos, suspendida) VALUES (%d,"
+ " %s, '%s', %d, %d, %d, %d, %d, %d, %d)", type,
+ params->task_id, params->name, params->time.years,
+ params->time.months, params->time.days,
+ params->time.hours, params->time.am_pm,
+ params->time.minutes, suspended);
+ if (!result) {
+ dbi_conn_error(dbi->conn, &msglog);
+ syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
+ __func__, __LINE__, msglog);
+ return -1;
+ }
+ dbi_result_free(result);
+
+ *schedule_id = dbi_conn_sequence_last(dbi->conn, NULL);
+
+ return 0;
+}
+
+static int og_dbi_schedule_update(struct og_dbi *dbi,
+ struct og_msg_params *params)
+{
+ const char *msglog;
+ dbi_result result;
+ uint8_t type = 3;
+
+ result = dbi_conn_queryf(dbi->conn,
+ "UPDATE programaciones SET tipoaccion=%d, "
+ "identificador='%s', nombrebloque='%s', "
+ "annos=%d, meses=%d, "
+ "diario=%d, horas=%d, ampm=%d, minutos=%d "
+ "WHERE idprogramacion='%s'",
+ type, params->task_id, params->name,
+ params->time.years, params->time.months,
+ params->time.days, params->time.hours,
+ params->time.am_pm, params->time.minutes,
+ params->id);
+
+ if (!result) {
+ dbi_conn_error(dbi->conn, &msglog);
+ syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
+ __func__, __LINE__, msglog);
+ return -1;
+ }
+ dbi_result_free(result);
+
+ return 0;
+}
+
+static int og_dbi_schedule_delete(struct og_dbi *dbi, uint32_t id)
+{
+ const char *msglog;
+ dbi_result result;
+
+ result = dbi_conn_queryf(dbi->conn,
+ "DELETE FROM programaciones WHERE idprogramacion=%d",
+ id);
+ if (!result) {
+ dbi_conn_error(dbi->conn, &msglog);
+ syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
+ __func__, __LINE__, msglog);
+ return -1;
+ }
+ dbi_result_free(result);
+
+ return 0;
+}
+
+static struct ev_loop *og_loop;
+
+static int og_cmd_schedule_create(json_t *element, struct og_msg_params *params)
+{
+ uint32_t schedule_id;
+ struct og_dbi *dbi;
+ const char *key;
+ json_t *value;
+ int err;
+
+ if (json_typeof(element) != JSON_OBJECT)
+ return -1;
+
+ json_object_foreach(element, key, value) {
+ if (!strcmp(key, "task")) {
+ err = og_json_parse_string(value, &params->task_id);
+ params->flags |= OG_REST_PARAM_TASK;
+ } else if (!strcmp(key, "name")) {
+ err = og_json_parse_string(value, &params->name);
+ params->flags |= OG_REST_PARAM_NAME;
+ } else if (!strcmp(key, "time_params"))
+ err = og_json_parse_time_params(value, params);
+
+ if (err < 0)
+ break;
+ }
+
+ if (!og_msg_params_validate(params, OG_REST_PARAM_TASK |
+ OG_REST_PARAM_NAME |
+ OG_REST_PARAM_TIME_YEARS |
+ OG_REST_PARAM_TIME_MONTHS |
+ OG_REST_PARAM_TIME_DAYS |
+ OG_REST_PARAM_TIME_HOURS |
+ OG_REST_PARAM_TIME_MINUTES |
+ OG_REST_PARAM_TIME_AM_PM))
+ return -1;
+
+ dbi = og_dbi_open(&dbi_config);
+ if (!dbi) {
+ syslog(LOG_ERR, "cannot open connection database (%s:%d)\n",
+ __func__, __LINE__);
+ return -1;
+ }
+
+ err = og_dbi_schedule_create(dbi, params, &schedule_id);
+ og_dbi_close(dbi);
+
+ if (err < 0)
+ return -1;
+
+ og_schedule_create(schedule_id, atoi(params->task_id), &params->time);
+ og_schedule_refresh(og_loop);
+
+ return err;
+}
+
+static int og_cmd_schedule_update(json_t *element, struct og_msg_params *params)
+{
+ struct og_dbi *dbi;
+ const char *key;
+ json_t *value;
+ int err;
+
+ if (json_typeof(element) != JSON_OBJECT)
+ return -1;
+
+ json_object_foreach(element, key, value) {
+ if (!strcmp(key, "id")) {
+ err = og_json_parse_string(value, &params->id);
+ params->flags |= OG_REST_PARAM_ID;
+ } else if (!strcmp(key, "task")) {
+ err = og_json_parse_string(value, &params->task_id);
+ params->flags |= OG_REST_PARAM_TASK;
+ } else if (!strcmp(key, "name")) {
+ err = og_json_parse_string(value, &params->name);
+ params->flags |= OG_REST_PARAM_NAME;
+ } else if (!strcmp(key, "time_params"))
+ err = og_json_parse_time_params(value, params);
+
+ if (err < 0)
+ break;
+ }
+
+ if (!og_msg_params_validate(params, OG_REST_PARAM_ID |
+ OG_REST_PARAM_TASK |
+ OG_REST_PARAM_NAME |
+ OG_REST_PARAM_TIME_YEARS |
+ OG_REST_PARAM_TIME_MONTHS |
+ OG_REST_PARAM_TIME_DAYS |
+ OG_REST_PARAM_TIME_HOURS |
+ OG_REST_PARAM_TIME_MINUTES |
+ OG_REST_PARAM_TIME_AM_PM))
+ return -1;
+
+ dbi = og_dbi_open(&dbi_config);
+ if (!dbi) {
+ syslog(LOG_ERR, "cannot open connection database (%s:%d)\n",
+ __func__, __LINE__);
+ return -1;
+ }
+
+ err = og_dbi_schedule_update(dbi, params);
+ og_dbi_close(dbi);
+
+ if (err < 0)
+ return err;
+
+ og_schedule_update(og_loop, atoi(params->id), atoi(params->task_id),
+ &params->time);
+ og_schedule_refresh(og_loop);
+
+ return err;
+}
+
+static int og_cmd_schedule_delete(json_t *element, struct og_msg_params *params)
+{
+ struct og_dbi *dbi;
+ const char *key;
+ json_t *value;
+ int err;
+
+ if (json_typeof(element) != JSON_OBJECT)
+ return -1;
+
+ json_object_foreach(element, key, value) {
+ if (!strcmp(key, "id")) {
+ err = og_json_parse_string(value, &params->id);
+ params->flags |= OG_REST_PARAM_ID;
+ } else {
+ return -1;
+ }
+
+ if (err < 0)
+ break;
+ }
+
+ if (!og_msg_params_validate(params, OG_REST_PARAM_ID))
+ return -1;
+
+ dbi = og_dbi_open(&dbi_config);
+ if (!dbi) {
+ syslog(LOG_ERR, "cannot open connection database (%s:%d)\n",
+ __func__, __LINE__);
+ return -1;
+ }
+
+ err = og_dbi_schedule_delete(dbi, atoi(params->id));
+ og_dbi_close(dbi);
+
+ og_schedule_delete(og_loop, atoi(params->id));
+
+ return err;
+}
+
static int og_client_method_not_found(struct og_client *cli)
{
/* To meet RFC 7231, this function MUST generate an Allow header field
@@ -5007,6 +5375,36 @@ static int og_client_state_process_payload_rest(struct og_client *cli)
return og_client_bad_request(cli);
}
err = og_cmd_task_post(root, &params);
+ } else if (!strncmp(cmd, "schedule/create",
+ strlen("schedule/create"))) {
+ if (method != OG_METHOD_POST)
+ return og_client_method_not_found(cli);
+
+ if (!root) {
+ syslog(LOG_ERR, "command task with no payload\n");
+ return og_client_bad_request(cli);
+ }
+ err = og_cmd_schedule_create(root, &params);
+ } else if (!strncmp(cmd, "schedule/delete",
+ strlen("schedule/delete"))) {
+ if (method != OG_METHOD_POST)
+ return og_client_method_not_found(cli);
+
+ if (!root) {
+ syslog(LOG_ERR, "command task with no payload\n");
+ return og_client_bad_request(cli);
+ }
+ err = og_cmd_schedule_delete(root, &params);
+ } else if (!strncmp(cmd, "schedule/update",
+ strlen("schedule/update"))) {
+ if (method != OG_METHOD_POST)
+ return og_client_method_not_found(cli);
+
+ if (!root) {
+ syslog(LOG_ERR, "command task with no payload\n");
+ return og_client_bad_request(cli);
+ }
+ err = og_cmd_schedule_update(root, &params);
} else {
syslog(LOG_ERR, "unknown command: %.32s ...\n", cmd);
err = og_client_not_found(cli);
@@ -6015,9 +6413,10 @@ static int og_socket_server_init(const char *port)
int main(int argc, char *argv[])
{
struct ev_io ev_io_server, ev_io_server_rest, ev_io_agent_rest;
- struct ev_loop *loop = ev_default_loop(0);
int i;
+ og_loop = ev_default_loop(0);
+
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
exit(EXIT_FAILURE);
@@ -6048,21 +6447,26 @@ int main(int argc, char *argv[])
exit(EXIT_FAILURE);
ev_io_init(&ev_io_server, og_server_accept_cb, socket_s, EV_READ);
- ev_io_start(loop, &ev_io_server);
+ ev_io_start(og_loop, &ev_io_server);
socket_rest = og_socket_server_init("8888");
if (socket_rest < 0)
exit(EXIT_FAILURE);
ev_io_init(&ev_io_server_rest, og_server_accept_cb, socket_rest, EV_READ);
- ev_io_start(loop, &ev_io_server_rest);
+ ev_io_start(og_loop, &ev_io_server_rest);
socket_agent_rest = og_socket_server_init("8889");
if (socket_agent_rest < 0)
exit(EXIT_FAILURE);
ev_io_init(&ev_io_agent_rest, og_server_accept_cb, socket_agent_rest, EV_READ);
- ev_io_start(loop, &ev_io_agent_rest);
+ ev_io_start(og_loop, &ev_io_agent_rest);
+
+ if (og_dbi_schedule_get() < 0)
+ exit(EXIT_FAILURE);
+
+ og_schedule_next(og_loop);
infoLog(1); // Inicio de sesiĆ³n
@@ -6072,7 +6476,7 @@ int main(int argc, char *argv[])
syslog(LOG_INFO, "Waiting for connections\n");
while (1)
- ev_loop(loop, 0);
+ ev_loop(og_loop, 0);
exit(EXIT_SUCCESS);
}
diff --git a/sources/schedule.c b/sources/schedule.c
new file mode 100644
index 0000000..21db921
--- /dev/null
+++ b/sources/schedule.c
@@ -0,0 +1,394 @@
+#include "schedule.h"
+#include "list.h"
+#include <sys/types.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <time.h>
+#include <ev.h>
+
+struct og_schedule *current_schedule = NULL;
+static LIST_HEAD(schedule_list);
+
+static void og_schedule_add(struct og_schedule *new)
+{
+ struct og_schedule *schedule, *next;
+ time_t now;
+
+ now = time(NULL);
+ if (new->seconds < now) {
+ free(new);
+ return;
+ }
+ list_for_each_entry_safe(schedule, next, &schedule_list, list) {
+ if (new->seconds < schedule->seconds) {
+ list_add_tail(&new->list, &schedule->list);
+ return;
+ }
+ }
+ list_add_tail(&new->list, &schedule_list);
+}
+
+/* Returns the days in a month from the weekday. */
+static void get_days_from_weekday(struct tm *tm, int wday, int *days, int *j)
+{
+ int i, mday = 0;
+
+ tm->tm_mday = 1;
+
+ //Shift week to start on Sunday instead of Monday
+ if (wday == 6)
+ wday = 0;
+ else
+ wday++;
+
+ /* A bit bruteforce, but simple. */
+ for (i = 0; i <= 30; i++) {
+ mktime(tm);
+ /* Not this weekday, skip. */
+ if (tm->tm_wday != wday) {
+ tm->tm_mday++;
+ continue;
+ }
+ /* Not interested in next month. */
+ if (tm->tm_mday < mday)
+ break;
+
+ /* Found a matching. */
+ mday = tm->tm_mday;
+ days[(*j)++] = tm->tm_mday;
+ tm->tm_mday++;
+ }
+}
+
+/* Returns the days in the given week. */
+static void get_days_from_week(struct tm *tm, int week, int *days, int *k)
+{
+ int i, j, week_counter = 0;
+ bool week_over = false;
+
+ tm->tm_mday = 1;
+
+ /* Remaining days of this month. */
+ for (i = 0; i <= 30; i++) {
+ mktime(tm);
+
+ /* Last day of this week? */
+ if (tm->tm_wday == 6)
+ week_over = true;
+
+ /* Not the week we are searching for. */
+ if (week != week_counter) {
+ tm->tm_mday++;
+ if (week_over) {
+ week_counter++;
+ week_over = false;
+ }
+ continue;
+ }
+
+ /* Found matching. */
+ for (j = tm->tm_wday; j <= 6; j++) {
+ days[(*k)++] = tm->tm_mday++;
+ mktime(tm);
+ }
+ break;
+ }
+}
+
+static int monthdays[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+
+static int last_month_day(struct tm *tm)
+{
+ /* Leap year? Adjust it. */
+ if (tm->tm_mon == 1) {
+ tm->tm_mday = 29;
+ mktime(tm);
+ if (tm->tm_mday == 29)
+ return 29;
+
+ tm->tm_mon = 1;
+ }
+
+ return monthdays[tm->tm_mon];
+}
+
+/* Returns the days in the given week. */
+static void get_last_week(struct tm *tm, int *days, int *j)
+{
+ int i, last_day;
+
+ last_day = last_month_day(tm);
+ tm->tm_mday = last_day;
+
+ for (i = last_day; i >= last_day - 6; i--) {
+ mktime(tm);
+
+ days[(*j)++] = tm->tm_mday;
+
+
+ syslog(LOG_ERR, "TM_WDAY: %d", tm->tm_wday);//XXX
+ syslog(LOG_ERR, "TM_MDAY: %d", tm->tm_mday);//XXX
+ /* Last day of this week? */
+ if (tm->tm_wday == 1) {
+ syslog(LOG_ERR, "break week");//XXX
+ break;
+ }
+
+ tm->tm_mday--;
+ }
+}
+
+static void og_parse_years(uint16_t years_mask, int years[])
+{
+ int i, j = 0;
+
+ for (i = 0; i < 16; i++) {
+ if ((1 << i) & years_mask)
+ years[j++] = 2010 + i - 1900;
+ }
+}
+
+static void og_parse_months(uint16_t months_mask, int months[])
+{
+ int i, j = 0;
+
+ for (i = 0; i < 12; i++) {
+ if ((1 << i) & months_mask)
+ months[j++] = i + 1;
+ }
+}
+
+static void og_parse_days(uint32_t days_mask, int *days)
+{
+ int i, j = 0;
+
+ for (i = 0; i < 31; i++) {
+ if ((1 << i) & days_mask)
+ days[j++] = i + 1;
+ }
+}
+
+static void og_parse_hours(uint16_t hours_mask, uint8_t am_pm, int hours[])
+{
+ int pm = 12 * am_pm;
+ int i, j = 0;
+
+ for (i = 0; i < 12; i++) {
+ if ((1 << i) & hours_mask)
+ hours[j++] = i + pm + 1;
+ }
+}
+
+static void og_schedule_remove_duplicates()
+{
+ struct og_schedule *schedule, *next, *prev = NULL;
+
+ list_for_each_entry_safe(schedule, next, &schedule_list, list) {
+ if (!prev) {
+ prev = schedule;
+ continue;
+ }
+ if (prev->seconds == schedule->seconds &&
+ prev->task_id == schedule->task_id) {
+ list_del(&prev->list);
+ free(prev);
+ }
+ prev = schedule;
+ }
+}
+
+void og_schedule_create(unsigned int schedule_id, unsigned int task_id,
+ struct og_schedule_time *time)
+{
+ struct og_schedule *schedule;
+ int months[12] = {};
+ int years[12] = {};
+ int hours[12] = {};
+ int days[31] = {};
+ struct tm tm = {};
+ int i, j, k, l = 0;
+ int minutes;
+
+ og_parse_years(time->years, years);
+ og_parse_months(time->months, months);
+ og_parse_days(time->days, days);
+ og_parse_hours(time->hours, time->am_pm, hours);
+ minutes = time->minutes;
+
+ for (i = 0; years[i] != 0 && i < 12; i++) {
+ for (j = 0; months[j] != 0 && j < 12; j++) {
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_year = years[i];
+ tm.tm_mon = months[j] - 1;
+ if (time->week_days) {
+ for (int wday = 0; wday < 7; wday++) {
+ if ((1 << wday) & time->week_days) {
+ int specific_month_days[5] = {};
+ int n_month_days = 0;
+ get_days_from_weekday(&tm,
+ wday,
+ specific_month_days,
+ &n_month_days);
+
+ for (k = 0; specific_month_days[k] != 0 && k < n_month_days; k++) {
+ for (l = 0; hours[l] != 0 && l < 31; l++) {
+ schedule = (struct og_schedule *)
+ calloc(1, sizeof(struct og_schedule));
+ if (!schedule)
+ return;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_year = years[i];
+ tm.tm_mon = months[j] - 1;
+ tm.tm_mday = specific_month_days[k];
+ tm.tm_hour = hours[l] - 1;
+ tm.tm_min = minutes;
+
+ schedule->seconds = mktime(&tm);
+ schedule->task_id = task_id;
+ schedule->schedule_id = schedule_id;
+ og_schedule_add(schedule);
+ }
+ }
+ }
+ }
+ }
+
+ if (time->weeks) {
+ for (int week = 0; week < 5; week++) {
+ if ((1 << week) & time->weeks) {
+ int specific_month_days[7] = {};
+ int n_month_days = 0;
+
+ if (week == 5)
+ get_last_week(&tm,
+ specific_month_days,
+ &n_month_days);
+ else
+ get_days_from_week(&tm,
+ week,
+ specific_month_days,
+ &n_month_days);
+
+ for (k = 0; specific_month_days[k] != 0 && k < n_month_days; k++) {
+ for (l = 0; hours[l] != 0 && l < 31; l++) {
+ schedule = (struct og_schedule *)
+ calloc(1, sizeof(struct og_schedule));
+ if (!schedule)
+ return;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_year = years[i];
+ tm.tm_mon = months[j] - 1;
+ tm.tm_mday = specific_month_days[k];
+ tm.tm_hour = hours[l] - 1;
+ tm.tm_min = minutes;
+
+ schedule->seconds = mktime(&tm);
+ schedule->task_id = task_id;
+ schedule->schedule_id = schedule_id;
+ og_schedule_add(schedule);
+ }
+ }
+ }
+ }
+ }
+
+ if (time->days) {
+ for (k = 0; days[k] != 0 && k < 31; k++) {
+ for (l = 0; hours[l] != 0 && l < 31; l++) {
+ schedule = (struct og_schedule *)
+ calloc(1, sizeof(struct og_schedule));
+ if (!schedule)
+ return;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_year = years[i];
+ tm.tm_mon = months[j] - 1;
+ tm.tm_mday = days[k];
+ tm.tm_hour = hours[l] - 1;
+ tm.tm_min = minutes;
+
+ schedule->seconds = mktime(&tm);
+ schedule->task_id = task_id;
+ schedule->schedule_id = schedule_id;
+ og_schedule_add(schedule);
+ }
+ }
+ }
+ }
+ }
+
+ og_schedule_remove_duplicates();
+}
+
+void og_schedule_delete(struct ev_loop *loop, uint32_t schedule_id)
+{
+ struct og_schedule *schedule, *next;
+
+ list_for_each_entry_safe(schedule, next, &schedule_list, list) {
+ if (schedule->schedule_id != schedule_id)
+ continue;
+
+ list_del(&schedule->list);
+ if (current_schedule == schedule) {
+ ev_timer_stop(loop, &schedule->timer);
+ current_schedule = NULL;
+ og_schedule_refresh(loop);
+ }
+ free(schedule);
+ }
+}
+
+void og_schedule_update(struct ev_loop *loop, unsigned int schedule_id,
+ unsigned int task_id, struct og_schedule_time *time)
+{
+ og_schedule_delete(loop, schedule_id);
+ og_schedule_create(schedule_id, task_id, time);
+}
+
+static void og_agent_timer_cb(struct ev_loop *loop, ev_timer *timer, int events)
+{
+ struct og_schedule *current;
+
+ current = container_of(timer, struct og_schedule, timer);
+ og_dbi_schedule_task(current->task_id);
+
+ ev_timer_stop(loop, timer);
+ list_del(&current->list);
+ free(current);
+
+ og_schedule_next(loop);
+}
+
+void og_schedule_next(struct ev_loop *loop)
+{
+ struct og_schedule *schedule;
+ time_t now, seconds;
+
+ if (list_empty(&schedule_list)) {
+ current_schedule = NULL;
+ return;
+ }
+
+ schedule = list_first_entry(&schedule_list, struct og_schedule, list);
+ now = time(NULL);
+ if (schedule->seconds <= now)
+ seconds = 0;
+ else
+ seconds = schedule->seconds - now;
+
+ ev_timer_init(&schedule->timer, og_agent_timer_cb, seconds, 0.);
+ ev_timer_start(loop, &schedule->timer);
+ current_schedule = schedule;
+}
+
+void og_schedule_refresh(struct ev_loop *loop)
+{
+ if (current_schedule)
+ ev_timer_stop(loop, &current_schedule->timer);
+
+ og_schedule_next(loop);
+}
diff --git a/sources/schedule.h b/sources/schedule.h
new file mode 100644
index 0000000..7195a05
--- /dev/null
+++ b/sources/schedule.h
@@ -0,0 +1,37 @@
+#ifndef _OG_SCHEDULE_H_
+#define _OG_SCHEDULE_H_
+
+#include <stdint.h>
+#include "dbi.h"
+#include "list.h"
+#include <ev.h>
+
+struct og_schedule_time {
+ uint32_t years;
+ uint32_t months;
+ uint32_t weeks;
+ uint32_t week_days;
+ uint32_t days;
+ uint32_t hours;
+ uint32_t am_pm;
+ uint32_t minutes;
+};
+
+struct og_schedule {
+ struct list_head list;
+ struct ev_timer timer;
+ time_t seconds;
+ unsigned int task_id;
+ unsigned int schedule_id;
+};
+
+void og_schedule_create(unsigned int schedule_id, unsigned int task_id,
+ struct og_schedule_time *time);
+void og_schedule_update(struct ev_loop *loop, unsigned int schedule_id,
+ unsigned int task_id, struct og_schedule_time *time);
+void og_schedule_delete(struct ev_loop *loop, uint32_t schedule_id);
+void og_schedule_next(struct ev_loop *loop);
+void og_schedule_refresh(struct ev_loop *loop);
+void og_dbi_schedule_task(unsigned int task_id);
+
+#endif