From 000894e00a8bb8e9b69d2515dcd12ac8f85074c3 Mon Sep 17 00:00:00 2001 From: lucas Date: Tue, 18 Oct 2022 18:26:26 +0800 Subject: [PATCH 01/11] cron timer supported --- modules/eventx/Makefile | 2 + modules/eventx/cron_timer/ccronexpr.cpp | 1227 ++++++++++++++++++++++ modules/eventx/cron_timer/ccronexpr.h | 93 ++ modules/eventx/cron_timer/cron_timer.cpp | 97 ++ modules/eventx/cron_timer/cron_timer.h | 73 ++ 5 files changed, 1492 insertions(+) create mode 100644 modules/eventx/cron_timer/ccronexpr.cpp create mode 100644 modules/eventx/cron_timer/ccronexpr.h create mode 100644 modules/eventx/cron_timer/cron_timer.cpp create mode 100644 modules/eventx/cron_timer/cron_timer.h diff --git a/modules/eventx/Makefile b/modules/eventx/Makefile index 1836f23..e14c410 100644 --- a/modules/eventx/Makefile +++ b/modules/eventx/Makefile @@ -8,11 +8,13 @@ HEAD_FILES = \ timer_pool.h \ timeout_monitor.h \ request_pool.hpp \ + cron_timer/cron_timer.h CPP_SRC_FILES = \ thread_pool.cpp \ timer_pool.cpp \ timeout_monitor.cpp \ + cron_timer/cron_timer.cpp CXXFLAGS := -DLOG_MODULE_ID='"eventx"' $(CXXFLAGS) diff --git a/modules/eventx/cron_timer/ccronexpr.cpp b/modules/eventx/cron_timer/ccronexpr.cpp new file mode 100644 index 0000000..09f69bc --- /dev/null +++ b/modules/eventx/cron_timer/ccronexpr.cpp @@ -0,0 +1,1227 @@ +/* + * Copyright 2015, alex at staticlibs.net + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * File: ccronexpr.c + * Author: alex + * + * Created on February 24, 2015, 9:35 AM + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "ccronexpr.h" + +#define CRON_MAX_SECONDS 60 +#define CRON_MAX_MINUTES 60 +#define CRON_MAX_HOURS 24 +#define CRON_MAX_DAYS_OF_WEEK 8 +#define CRON_MAX_DAYS_OF_MONTH 32 +#define CRON_MAX_MONTHS 12 +#define CRON_MAX_YEARS_DIFF 4 + +#define CRON_CF_SECOND 0 +#define CRON_CF_MINUTE 1 +#define CRON_CF_HOUR_OF_DAY 2 +#define CRON_CF_DAY_OF_WEEK 3 +#define CRON_CF_DAY_OF_MONTH 4 +#define CRON_CF_MONTH 5 +#define CRON_CF_YEAR 6 + +#define CRON_CF_ARR_LEN 7 + +#define CRON_INVALID_INSTANT ((time_t) -1) + +static const char* const DAYS_ARR[] = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; +#define CRON_DAYS_ARR_LEN 7 +static const char* const MONTHS_ARR[] = { "FOO", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; +#define CRON_MONTHS_ARR_LEN 13 + +#define CRON_MAX_STR_LEN_TO_SPLIT 256 +#define CRON_MAX_NUM_TO_SRING 1000000000 +/* computes number of digits in decimal number */ +#define CRON_NUM_OF_DIGITS(num) (abs(num) < 10 ? 1 : \ + (abs(num) < 100 ? 2 : \ + (abs(num) < 1000 ? 3 : \ + (abs(num) < 10000 ? 4 : \ + (abs(num) < 100000 ? 5 : \ + (abs(num) < 1000000 ? 6 : \ + (abs(num) < 10000000 ? 7 : \ + (abs(num) < 100000000 ? 8 : \ + (abs(num) < 1000000000 ? 9 : 10))))))))) + +#ifndef _WIN32 +struct tm *gmtime_r(const time_t *timep, struct tm *result); +#ifdef CRON_USE_LOCAL_TIME +struct tm *localtime_r(const time_t *timep, struct tm *result); +#endif /* CRON_USE_LOCAL_TIME */ +#endif /* _WIN32 */ + +#ifndef CRON_TEST_MALLOC +#define cron_malloc(x) malloc(x); +#define cron_free(x) free(x); +#else /* CRON_TEST_MALLOC */ +void* cron_malloc(size_t n); +void cron_free(void* p); +#endif /* CRON_TEST_MALLOC */ + +#ifdef __MINGW32__ +/* To avoid warning when building with mingw */ +time_t _mkgmtime(struct tm* tm); +#endif /* __MINGW32__ */ + +/* Defining 'cron_mktime' to use use UTC (default) or local time */ +#ifndef CRON_USE_LOCAL_TIME + +/* http://stackoverflow.com/a/22557778 */ +#ifdef _WIN32 +time_t cron_mktime(struct tm* tm) { + return _mkgmtime(tm); +} +#else /* !_WIN32 */ +#ifndef ANDROID +/* can be hidden in time.h */ +time_t timegm(struct tm* __tp); +#endif /* ANDROID */ +time_t cron_mktime(struct tm* tm) { +#ifndef ANDROID + return timegm(tm); +#else /* ANDROID */ + /* https://github.com/adobe/chromium/blob/cfe5bf0b51b1f6b9fe239c2a3c2f2364da9967d7/base/os_compat_android.cc#L20 */ + static const time_t kTimeMax = ~(1L << (sizeof (time_t) * CHAR_BIT - 1)); + static const time_t kTimeMin = (1L << (sizeof (time_t) * CHAR_BIT - 1)); + time64_t result = timegm64(tm); + if (result < kTimeMin || result > kTimeMax) return -1; + return result; +#endif /* ANDROID */ +} +#endif /* _WIN32 */ + +struct tm* cron_time(time_t* date, struct tm* out) { +#ifdef __MINGW32__ + (void)(out); /* To avoid unused warning */ + return gmtime(date); +#else /* !__MINGW32__ */ +#ifdef _WIN32 + errno_t err = gmtime_s(out, date); + return 0 == err ? out : NULL; +#else /* !_WIN32 */ + return gmtime_r(date, out); +#endif /* _WIN32 */ +#endif /* __MINGW32__ */ +} + +#else /* CRON_USE_LOCAL_TIME */ + +time_t cron_mktime(struct tm* tm) { + tm->tm_isdst = -1; + return mktime(tm); +} + +struct tm* cron_time(time_t* date, struct tm* out) { +#ifdef _WIN32 + errno_t err = localtime_s(out, date); + return 0 == err ? out : NULL; +#else /* _WIN32 */ + return localtime_r(date, out); +#endif /* _WIN32 */ +} + +#endif /* CRON_USE_LOCAL_TIME */ + +void cron_set_bit(uint8_t* rbyte, int idx) { + uint8_t j = (uint8_t) (idx / 8); + uint8_t k = (uint8_t) (idx % 8); + + rbyte[j] |= (1 << k); +} + +void cron_del_bit(uint8_t* rbyte, int idx) { + uint8_t j = (uint8_t) (idx / 8); + uint8_t k = (uint8_t) (idx % 8); + + rbyte[j] &= ~(1 << k); +} + +uint8_t cron_get_bit(uint8_t* rbyte, int idx) { + uint8_t j = (uint8_t) (idx / 8); + uint8_t k = (uint8_t) (idx % 8); + + if (rbyte[j] & (1 << k)) { + return 1; + } else { + return 0; + } +} + +static void free_splitted(char** splitted, size_t len) { + size_t i; + if (!splitted) return; + for (i = 0; i < len; i++) { + if (splitted[i]) { + cron_free(splitted[i]); + } + } + cron_free(splitted); +} + +static char* strdupl(const char* str, size_t len) { + if (!str) return NULL; + char* res = (char*) cron_malloc(len + 1); + if (!res) return NULL; + memset(res, 0, len + 1); + memcpy(res, str, len); + return res; +} + +static unsigned int next_set_bit(uint8_t* bits, unsigned int max, unsigned int from_index, int* notfound) { + unsigned int i; + if (!bits) { + *notfound = 1; + return 0; + } + for (i = from_index; i < max; i++) { + if (cron_get_bit(bits, i)) return i; + } + *notfound = 1; + return 0; +} + +static void push_to_fields_arr(int* arr, int fi) { + int i; + if (!arr || -1 == fi) { + return; + } + for (i = 0; i < CRON_CF_ARR_LEN; i++) { + if (arr[i] == fi) return; + } + for (i = 0; i < CRON_CF_ARR_LEN; i++) { + if (-1 == arr[i]) { + arr[i] = fi; + return; + } + } +} + +static int add_to_field(struct tm* calendar, int field, int val) { + if (!calendar || -1 == field) { + return 1; + } + switch (field) { + case CRON_CF_SECOND: + calendar->tm_sec = calendar->tm_sec + val; + break; + case CRON_CF_MINUTE: + calendar->tm_min = calendar->tm_min + val; + break; + case CRON_CF_HOUR_OF_DAY: + calendar->tm_hour = calendar->tm_hour + val; + break; + case CRON_CF_DAY_OF_WEEK: /* mkgmtime ignores this field */ + case CRON_CF_DAY_OF_MONTH: + calendar->tm_mday = calendar->tm_mday + val; + break; + case CRON_CF_MONTH: + calendar->tm_mon = calendar->tm_mon + val; + break; + case CRON_CF_YEAR: + calendar->tm_year = calendar->tm_year + val; + break; + default: + return 1; /* unknown field */ + } + time_t res = cron_mktime(calendar); + if (CRON_INVALID_INSTANT == res) { + return 1; + } + return 0; +} + +/** + * Reset the calendar setting all the fields provided to zero. + */ +static int reset_min(struct tm* calendar, int field) { + if (!calendar || -1 == field) { + return 1; + } + switch (field) { + case CRON_CF_SECOND: + calendar->tm_sec = 0; + break; + case CRON_CF_MINUTE: + calendar->tm_min = 0; + break; + case CRON_CF_HOUR_OF_DAY: + calendar->tm_hour = 0; + break; + case CRON_CF_DAY_OF_WEEK: + calendar->tm_wday = 0; + break; + case CRON_CF_DAY_OF_MONTH: + calendar->tm_mday = 1; + break; + case CRON_CF_MONTH: + calendar->tm_mon = 0; + break; + case CRON_CF_YEAR: + calendar->tm_year = 0; + break; + default: + return 1; /* unknown field */ + } + time_t res = cron_mktime(calendar); + if (CRON_INVALID_INSTANT == res) { + return 1; + } + return 0; +} + +static int reset_all_min(struct tm* calendar, int* fields) { + int i; + int res = 0; + if (!calendar || !fields) { + return 1; + } + for (i = 0; i < CRON_CF_ARR_LEN; i++) { + if (-1 != fields[i]) { + res = reset_min(calendar, fields[i]); + if (0 != res) return res; + } + } + return 0; +} + +static int set_field(struct tm* calendar, int field, int val) { + if (!calendar || -1 == field) { + return 1; + } + switch (field) { + case CRON_CF_SECOND: + calendar->tm_sec = val; + break; + case CRON_CF_MINUTE: + calendar->tm_min = val; + break; + case CRON_CF_HOUR_OF_DAY: + calendar->tm_hour = val; + break; + case CRON_CF_DAY_OF_WEEK: + calendar->tm_wday = val; + break; + case CRON_CF_DAY_OF_MONTH: + calendar->tm_mday = val; + break; + case CRON_CF_MONTH: + calendar->tm_mon = val; + break; + case CRON_CF_YEAR: + calendar->tm_year = val; + break; + default: + return 1; /* unknown field */ + } + time_t res = cron_mktime(calendar); + if (CRON_INVALID_INSTANT == res) { + return 1; + } + return 0; +} + +/** + * Search the bits provided for the next set bit after the value provided, + * and reset the calendar. + */ +static unsigned int find_next(uint8_t* bits, unsigned int max, unsigned int value, struct tm* calendar, unsigned int field, unsigned int nextField, int* lower_orders, int* res_out) { + int notfound = 0; + int err = 0; + unsigned int next_value = next_set_bit(bits, max, value, ¬found); + /* roll over if needed */ + if (notfound) { + err = add_to_field(calendar, nextField, 1); + if (err) goto return_error; + err = reset_min(calendar, field); + if (err) goto return_error; + notfound = 0; + next_value = next_set_bit(bits, max, 0, ¬found); + } + if (notfound || next_value != value) { + err = set_field(calendar, field, next_value); + if (err) goto return_error; + err = reset_all_min(calendar, lower_orders); + if (err) goto return_error; + } + return next_value; + + return_error: + *res_out = 1; + return 0; +} + +static unsigned int find_next_day(struct tm* calendar, uint8_t* days_of_month, unsigned int day_of_month, uint8_t* days_of_week, unsigned int day_of_week, int* resets, int* res_out) { + int err; + unsigned int count = 0; + unsigned int max = 366; + while ((!cron_get_bit(days_of_month, day_of_month) || !cron_get_bit(days_of_week, day_of_week)) && count++ < max) { + err = add_to_field(calendar, CRON_CF_DAY_OF_MONTH, 1); + + if (err) goto return_error; + day_of_month = calendar->tm_mday; + day_of_week = calendar->tm_wday; + reset_all_min(calendar, resets); + } + return day_of_month; + + return_error: + *res_out = 1; + return 0; +} + +static int do_next(cron_expr* expr, struct tm* calendar, unsigned int dot) { + int i; + int res = 0; + int* resets = NULL; + int* empty_list = NULL; + unsigned int second = 0; + unsigned int update_second = 0; + unsigned int minute = 0; + unsigned int update_minute = 0; + unsigned int hour = 0; + unsigned int update_hour = 0; + unsigned int day_of_week = 0; + unsigned int day_of_month = 0; + unsigned int update_day_of_month = 0; + unsigned int month = 0; + unsigned int update_month = 0; + + resets = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); + if (!resets) goto return_result; + empty_list = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); + if (!empty_list) goto return_result; + for (i = 0; i < CRON_CF_ARR_LEN; i++) { + resets[i] = -1; + empty_list[i] = -1; + } + + second = calendar->tm_sec; + update_second = find_next(expr->seconds, CRON_MAX_SECONDS, second, calendar, CRON_CF_SECOND, CRON_CF_MINUTE, empty_list, &res); + if (0 != res) goto return_result; + if (second == update_second) { + push_to_fields_arr(resets, CRON_CF_SECOND); + } + + minute = calendar->tm_min; + update_minute = find_next(expr->minutes, CRON_MAX_MINUTES, minute, calendar, CRON_CF_MINUTE, CRON_CF_HOUR_OF_DAY, resets, &res); + if (0 != res) goto return_result; + if (minute == update_minute) { + push_to_fields_arr(resets, CRON_CF_MINUTE); + } else { + res = do_next(expr, calendar, dot); + if (0 != res) goto return_result; + } + + hour = calendar->tm_hour; + update_hour = find_next(expr->hours, CRON_MAX_HOURS, hour, calendar, CRON_CF_HOUR_OF_DAY, CRON_CF_DAY_OF_WEEK, resets, &res); + if (0 != res) goto return_result; + if (hour == update_hour) { + push_to_fields_arr(resets, CRON_CF_HOUR_OF_DAY); + } else { + res = do_next(expr, calendar, dot); + if (0 != res) goto return_result; + } + + day_of_week = calendar->tm_wday; + day_of_month = calendar->tm_mday; + update_day_of_month = find_next_day(calendar, expr->days_of_month, day_of_month, expr->days_of_week, day_of_week, resets, &res); + if (0 != res) goto return_result; + if (day_of_month == update_day_of_month) { + push_to_fields_arr(resets, CRON_CF_DAY_OF_MONTH); + } else { + res = do_next(expr, calendar, dot); + if (0 != res) goto return_result; + } + + month = calendar->tm_mon; /*day already adds one if no day in same month is found*/ + update_month = find_next(expr->months, CRON_MAX_MONTHS, month, calendar, CRON_CF_MONTH, CRON_CF_YEAR, resets, &res); + if (0 != res) goto return_result; + if (month != update_month) { + if (calendar->tm_year - dot > 4) { + res = -1; + goto return_result; + } + res = do_next(expr, calendar, dot); + if (0 != res) goto return_result; + } + goto return_result; + + return_result: + if (!resets || !empty_list) { + res = -1; + } + if (resets) { + cron_free(resets); + } + if (empty_list) { + cron_free(empty_list); + } + return res; +} + +static int to_upper(char* str) { + if (!str) return 1; + int i; + for (i = 0; '\0' != str[i]; i++) { + int c = (int)str[i]; + str[i] = (char) toupper(c); + } + return 0; +} + +static char* to_string(int num) { + if (abs(num) >= CRON_MAX_NUM_TO_SRING) return NULL; + char* str = (char*) cron_malloc(CRON_NUM_OF_DIGITS(num) + 1); + if (!str) return NULL; + int res = sprintf(str, "%d", num); + if (res < 0) { + cron_free(str); + return NULL; + } + return str; +} + +static char* str_replace(char *orig, const char *rep, const char *with) { + char *result; /* the return string */ + char *ins; /* the next insert point */ + char *tmp; /* varies */ + size_t len_rep; /* length of rep */ + size_t len_with; /* length of with */ + size_t len_front; /* distance between rep and end of last rep */ + int count; /* number of replacements */ + if (!orig) return NULL; + if (!rep) rep = ""; + if (!with) with = ""; + len_rep = strlen(rep); + len_with = strlen(with); + + ins = orig; + for (count = 0; NULL != (tmp = strstr(ins, rep)); ++count) { + ins = tmp + len_rep; + } + + /* first time through the loop, all the variable are set correctly + from here on, + tmp points to the end of the result string + ins points to the next occurrence of rep in orig + orig points to the remainder of orig after "end of rep" + */ + tmp = result = (char*) cron_malloc(strlen(orig) + (len_with - len_rep) * count + 1); + if (!result) return NULL; + + while (count--) { + ins = strstr(orig, rep); + len_front = ins - orig; + tmp = strncpy(tmp, orig, len_front) + len_front; + tmp = strcpy(tmp, with) + len_with; + orig += len_front + len_rep; /* move to next "end of rep" */ + } + strcpy(tmp, orig); + return result; +} + +static unsigned int parse_uint(const char* str, int* errcode) { + char* endptr; + errno = 0; + long int l = strtol(str, &endptr, 0); + if (errno == ERANGE || *endptr != '\0' || l < 0 || l > INT_MAX) { + *errcode = 1; + return 0; + } else { + *errcode = 0; + return (unsigned int) l; + } +} + +static char** split_str(const char* str, char del, size_t* len_out) { + size_t i; + size_t stlen = 0; + size_t len = 0; + int accum = 0; + char* buf = NULL; + char** res = NULL; + size_t bi = 0; + size_t ri = 0; + char* tmp; + + if (!str) goto return_error; + for (i = 0; '\0' != str[i]; i++) { + stlen += 1; + if (stlen >= CRON_MAX_STR_LEN_TO_SPLIT) goto return_error; + } + + for (i = 0; i < stlen; i++) { + int c = str[i]; + if (del == str[i]) { + if (accum > 0) { + len += 1; + accum = 0; + } + } else if (!isspace(c)) { + accum += 1; + } + } + /* tail */ + if (accum > 0) { + len += 1; + } + if (0 == len) return NULL; + + buf = (char*) cron_malloc(stlen + 1); + if (!buf) goto return_error; + memset(buf, 0, stlen + 1); + res = (char**) cron_malloc(len * sizeof(char*)); + if (!res) goto return_error; + memset(res, 0, len * sizeof(char*)); + + for (i = 0; i < stlen; i++) { + int c = str[i]; + if (del == str[i]) { + if (bi > 0) { + tmp = strdupl(buf, bi); + if (!tmp) goto return_error; + res[ri++] = tmp; + memset(buf, 0, stlen + 1); + bi = 0; + } + } else if (!isspace(c)) { + buf[bi++] = str[i]; + } + } + /* tail */ + if (bi > 0) { + tmp = strdupl(buf, bi); + if (!tmp) goto return_error; + res[ri++] = tmp; + } + cron_free(buf); + *len_out = len; + return res; + + return_error: + if (buf) { + cron_free(buf); + } + free_splitted(res, len); + *len_out = 0; + return NULL; +} + +static char* replace_ordinals(char* value, const char* const * arr, size_t arr_len) { + size_t i; + char* cur = value; + char* res = NULL; + int first = 1; + for (i = 0; i < arr_len; i++) { + char* strnum = to_string((int) i); + if (!strnum) { + if (!first) { + cron_free(cur); + } + return NULL; + } + res = str_replace(cur, arr[i], strnum); + cron_free(strnum); + if (!first) { + cron_free(cur); + } + if (!res) { + return NULL; + } + cur = res; + if (first) { + first = 0; + } + } + return res; +} + +static int has_char(char* str, char ch) { + size_t i; + size_t len = 0; + if (!str) return 0; + len = strlen(str); + for (i = 0; i < len; i++) { + if (str[i] == ch) return 1; + } + return 0; +} + +static unsigned int* get_range(char* field, unsigned int min, unsigned int max, const char** error) { + + char** parts = NULL; + size_t len = 0; + unsigned int* res = (unsigned int*) cron_malloc(2 * sizeof(unsigned int)); + if (!res) goto return_error; + + res[0] = 0; + res[1] = 0; + if (1 == strlen(field) && '*' == field[0]) { + res[0] = min; + res[1] = max - 1; + } else if (!has_char(field, '-')) { + int err = 0; + unsigned int val = parse_uint(field, &err); + if (err) { + *error = "Unsigned integer parse error 1"; + goto return_error; + } + + res[0] = val; + res[1] = val; + } else { + parts = split_str(field, '-', &len); + if (2 != len) { + *error = "Specified range requires two fields"; + goto return_error; + } + int err = 0; + res[0] = parse_uint(parts[0], &err); + if (err) { + *error = "Unsigned integer parse error 2"; + goto return_error; + } + res[1] = parse_uint(parts[1], &err); + if (err) { + *error = "Unsigned integer parse error 3"; + goto return_error; + } + } + if (res[0] >= max || res[1] >= max) { + *error = "Specified range exceeds maximum"; + goto return_error; + } + if (res[0] < min || res[1] < min) { + *error = "Specified range is less than minimum"; + goto return_error; + } + if (res[0] > res[1]) { + *error = "Specified range start exceeds range end"; + goto return_error; + } + + free_splitted(parts, len); + *error = NULL; + return res; + + return_error: + free_splitted(parts, len); + if (res) { + cron_free(res); + } + + return NULL; +} + +static void set_number_hits(const char* value, uint8_t* target, unsigned int min, unsigned int max, const char** error) { + size_t i; + unsigned int i1; + size_t len = 0; + + char** fields = split_str(value, ',', &len); + if (!fields) { + *error = "Comma split error"; + goto return_result; + } + + for (i = 0; i < len; i++) { + if (!has_char(fields[i], '/')) { + /* Not an incrementer so it must be a range (possibly empty) */ + + unsigned int* range = get_range(fields[i], min, max, error); + + if (*error) { + if (range) { + cron_free(range); + } + goto return_result; + + } + + for (i1 = range[0]; i1 <= range[1]; i1++) { + cron_set_bit(target, i1); + + } + cron_free(range); + + } else { + size_t len2 = 0; + char** split = split_str(fields[i], '/', &len2); + if (2 != len2) { + *error = "Incrementer must have two fields"; + free_splitted(split, len2); + goto return_result; + } + unsigned int* range = get_range(split[0], min, max, error); + if (*error) { + if (range) { + cron_free(range); + } + free_splitted(split, len2); + goto return_result; + } + if (!has_char(split[0], '-')) { + range[1] = max - 1; + } + int err = 0; + unsigned int delta = parse_uint(split[1], &err); + if (err) { + *error = "Unsigned integer parse error 4"; + cron_free(range); + free_splitted(split, len2); + goto return_result; + } + if (0 == delta) { + *error = "Incrementer may not be zero"; + cron_free(range); + free_splitted(split, len2); + goto return_result; + } + for (i1 = range[0]; i1 <= range[1]; i1 += delta) { + cron_set_bit(target, i1); + } + free_splitted(split, len2); + cron_free(range); + + } + } + goto return_result; + + return_result: + free_splitted(fields, len); + +} + +static void set_months(char* value, uint8_t* targ, const char** error) { + unsigned int i; + unsigned int max = 12; + + char* replaced = NULL; + + to_upper(value); + replaced = replace_ordinals(value, MONTHS_ARR, CRON_MONTHS_ARR_LEN); + if (!replaced) { + *error = "Invalid month format"; + return; + } + set_number_hits(replaced, targ, 1, max + 1, error); + cron_free(replaced); + + /* ... and then rotate it to the front of the months */ + for (i = 1; i <= max; i++) { + if (cron_get_bit(targ, i)) { + cron_set_bit(targ, i - 1); + cron_del_bit(targ, i); + } + } +} + +static void set_days_of_week(char* field, uint8_t* targ, const char** error) { + unsigned int max = 7; + char* replaced = NULL; + + if (1 == strlen(field) && '?' == field[0]) { + field[0] = '*'; + } + to_upper(field); + replaced = replace_ordinals(field, DAYS_ARR, CRON_DAYS_ARR_LEN); + if (!replaced) { + *error = "Invalid day format"; + return; + } + set_number_hits(replaced, targ, 0, max + 1, error); + cron_free(replaced); + if (cron_get_bit(targ, 7)) { + /* Sunday can be represented as 0 or 7*/ + cron_set_bit(targ, 0); + cron_del_bit(targ, 7); + } +} + +static void set_days_of_month(char* field, uint8_t* targ, const char** error) { + /* Days of month start with 1 (in Cron and Calendar) so add one */ + if (1 == strlen(field) && '?' == field[0]) { + field[0] = '*'; + } + set_number_hits(field, targ, 1, CRON_MAX_DAYS_OF_MONTH, error); +} + +void cron_parse_expr(const char* expression, cron_expr* target, const char** error) { + const char* err_local; + size_t len = 0; + char** fields = NULL; + if (!error) { + error = &err_local; + } + *error = NULL; + if (!expression) { + *error = "Invalid NULL expression"; + goto return_res; + } + if (!target) { + *error = "Invalid NULL target"; + goto return_res; + } + + fields = split_str(expression, ' ', &len); + if (len != 6) { + *error = "Invalid number of fields, expression must consist of 6 fields"; + goto return_res; + } + memset(target, 0, sizeof(*target)); + set_number_hits(fields[0], target->seconds, 0, 60, error); + if (*error) goto return_res; + set_number_hits(fields[1], target->minutes, 0, 60, error); + if (*error) goto return_res; + set_number_hits(fields[2], target->hours, 0, 24, error); + if (*error) goto return_res; + set_days_of_month(fields[3], target->days_of_month, error); + if (*error) goto return_res; + set_months(fields[4], target->months, error); + if (*error) goto return_res; + set_days_of_week(fields[5], target->days_of_week, error); + if (*error) goto return_res; + + goto return_res; + + return_res: + free_splitted(fields, len); +} + +time_t cron_next(cron_expr* expr, time_t date) { + /* + The plan: + + 1 Round up to the next whole second + + 2 If seconds match move on, otherwise find the next match: + 2.1 If next match is in the next minute then roll forwards + + 3 If minute matches move on, otherwise find the next match + 3.1 If next match is in the next hour then roll forwards + 3.2 Reset the seconds and go to 2 + + 4 If hour matches move on, otherwise find the next match + 4.1 If next match is in the next day then roll forwards, + 4.2 Reset the minutes and seconds and go to 2 + + ... + */ + if (!expr) return CRON_INVALID_INSTANT; + struct tm calval; + memset(&calval, 0, sizeof(struct tm)); + struct tm* calendar = cron_time(&date, &calval); + if (!calendar) return CRON_INVALID_INSTANT; + time_t original = cron_mktime(calendar); + if (CRON_INVALID_INSTANT == original) return CRON_INVALID_INSTANT; + + int res = do_next(expr, calendar, calendar->tm_year); + if (0 != res) return CRON_INVALID_INSTANT; + + time_t calculated = cron_mktime(calendar); + if (CRON_INVALID_INSTANT == calculated) return CRON_INVALID_INSTANT; + if (calculated == original) { + /* We arrived at the original timestamp - round up to the next whole second and try again... */ + res = add_to_field(calendar, CRON_CF_SECOND, 1); + if (0 != res) return CRON_INVALID_INSTANT; + res = do_next(expr, calendar, calendar->tm_year); + if (0 != res) return CRON_INVALID_INSTANT; + } + + return cron_mktime(calendar); +} + + +/* https://github.com/staticlibs/ccronexpr/pull/8 */ + +static unsigned int prev_set_bit(uint8_t* bits, int from_index, int to_index, int* notfound) { + int i; + if (!bits) { + *notfound = 1; + return 0; + } + for (i = from_index; i >= to_index; i--) { + if (cron_get_bit(bits, i)) return i; + } + *notfound = 1; + return 0; +} + +static int last_day_of_month(int month, int year) { + struct tm cal; + time_t t; + memset(&cal,0,sizeof(cal)); + cal.tm_sec=0; + cal.tm_min=0; + cal.tm_hour=0; + cal.tm_mon = month+1; + cal.tm_mday = 0; + cal.tm_year=year; + t=mktime(&cal); + return gmtime(&t)->tm_mday; +} + +/** + * Reset the calendar setting all the fields provided to zero. + */ +static int reset_max(struct tm* calendar, int field) { + if (!calendar || -1 == field) { + return 1; + } + switch (field) { + case CRON_CF_SECOND: + calendar->tm_sec = 59; + break; + case CRON_CF_MINUTE: + calendar->tm_min = 59; + break; + case CRON_CF_HOUR_OF_DAY: + calendar->tm_hour = 23; + break; + case CRON_CF_DAY_OF_WEEK: + calendar->tm_wday = 6; + break; + case CRON_CF_DAY_OF_MONTH: + calendar->tm_mday = last_day_of_month(calendar->tm_mon, calendar->tm_year); + break; + case CRON_CF_MONTH: + calendar->tm_mon = 11; + break; + case CRON_CF_YEAR: + /* I don't think this is supposed to happen ... */ + fprintf(stderr, "reset CRON_CF_YEAR\n"); + break; + default: + return 1; /* unknown field */ + } + time_t res = cron_mktime(calendar); + if (CRON_INVALID_INSTANT == res) { + return 1; + } + return 0; +} + +static int reset_all_max(struct tm* calendar, int* fields) { + int i; + int res = 0; + if (!calendar || !fields) { + return 1; + } + for (i = 0; i < CRON_CF_ARR_LEN; i++) { + if (-1 != fields[i]) { + res = reset_max(calendar, fields[i]); + if (0 != res) return res; + } + } + return 0; +} + +/** + * Search the bits provided for the next set bit after the value provided, + * and reset the calendar. + */ +static unsigned int find_prev(uint8_t* bits, unsigned int max, unsigned int value, struct tm* calendar, unsigned int field, unsigned int nextField, int* lower_orders, int* res_out) { + int notfound = 0; + int err = 0; + unsigned int next_value = prev_set_bit(bits, value, 0, ¬found); + /* roll under if needed */ + if (notfound) { + err = add_to_field(calendar, nextField, -1); + if (err) goto return_error; + err = reset_max(calendar, field); + if (err) goto return_error; + notfound = 0; + next_value = prev_set_bit(bits, max - 1, value, ¬found); + } + if (notfound || next_value != value) { + err = set_field(calendar, field, next_value); + if (err) goto return_error; + err = reset_all_max(calendar, lower_orders); + if (err) goto return_error; + } + return next_value; + + return_error: + *res_out = 1; + return 0; +} + +static unsigned int find_prev_day(struct tm* calendar, uint8_t* days_of_month, unsigned int day_of_month, uint8_t* days_of_week, unsigned int day_of_week, int* resets, int* res_out) { + int err; + unsigned int count = 0; + unsigned int max = 366; + while ((!cron_get_bit(days_of_month, day_of_month) || !cron_get_bit(days_of_week, day_of_week)) && count++ < max) { + err = add_to_field(calendar, CRON_CF_DAY_OF_MONTH, -1); + + if (err) goto return_error; + day_of_month = calendar->tm_mday; + day_of_week = calendar->tm_wday; + reset_all_max(calendar, resets); + } + return day_of_month; + + return_error: + *res_out = 1; + return 0; +} + +static int do_prev(cron_expr* expr, struct tm* calendar, unsigned int dot) { + int i; + int res = 0; + int* resets = NULL; + int* empty_list = NULL; + unsigned int second = 0; + unsigned int update_second = 0; + unsigned int minute = 0; + unsigned int update_minute = 0; + unsigned int hour = 0; + unsigned int update_hour = 0; + unsigned int day_of_week = 0; + unsigned int day_of_month = 0; + unsigned int update_day_of_month = 0; + unsigned int month = 0; + unsigned int update_month = 0; + + resets = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); + if (!resets) goto return_result; + empty_list = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); + if (!empty_list) goto return_result; + for (i = 0; i < CRON_CF_ARR_LEN; i++) { + resets[i] = -1; + empty_list[i] = -1; + } + + second = calendar->tm_sec; + update_second = find_prev(expr->seconds, CRON_MAX_SECONDS, second, calendar, CRON_CF_SECOND, CRON_CF_MINUTE, empty_list, &res); + if (0 != res) goto return_result; + if (second == update_second) { + push_to_fields_arr(resets, CRON_CF_SECOND); + } + + minute = calendar->tm_min; + update_minute = find_prev(expr->minutes, CRON_MAX_MINUTES, minute, calendar, CRON_CF_MINUTE, CRON_CF_HOUR_OF_DAY, resets, &res); + if (0 != res) goto return_result; + if (minute == update_minute) { + push_to_fields_arr(resets, CRON_CF_MINUTE); + } else { + res = do_prev(expr, calendar, dot); + if (0 != res) goto return_result; + } + + hour = calendar->tm_hour; + update_hour = find_prev(expr->hours, CRON_MAX_HOURS, hour, calendar, CRON_CF_HOUR_OF_DAY, CRON_CF_DAY_OF_WEEK, resets, &res); + if (0 != res) goto return_result; + if (hour == update_hour) { + push_to_fields_arr(resets, CRON_CF_HOUR_OF_DAY); + } else { + res = do_prev(expr, calendar, dot); + if (0 != res) goto return_result; + } + + day_of_week = calendar->tm_wday; + day_of_month = calendar->tm_mday; + update_day_of_month = find_prev_day(calendar, expr->days_of_month, day_of_month, expr->days_of_week, day_of_week, resets, &res); + if (0 != res) goto return_result; + if (day_of_month == update_day_of_month) { + push_to_fields_arr(resets, CRON_CF_DAY_OF_MONTH); + } else { + res = do_prev(expr, calendar, dot); + if (0 != res) goto return_result; + } + + month = calendar->tm_mon; /*day already adds one if no day in same month is found*/ + update_month = find_prev(expr->months, CRON_MAX_MONTHS, month, calendar, CRON_CF_MONTH, CRON_CF_YEAR, resets, &res); + if (0 != res) goto return_result; + if (month != update_month) { + if (dot - calendar->tm_year > CRON_MAX_YEARS_DIFF) { + res = -1; + goto return_result; + } + res = do_prev(expr, calendar, dot); + if (0 != res) goto return_result; + } + goto return_result; + + return_result: + if (!resets || !empty_list) { + res = -1; + } + if (resets) { + cron_free(resets); + } + if (empty_list) { + cron_free(empty_list); + } + return res; +} + +time_t cron_prev(cron_expr* expr, time_t date) { + /* + The plan: + + 1 Round down to a whole second + + 2 If seconds match move on, otherwise find the next match: + 2.1 If next match is in the next minute then roll forwards + + 3 If minute matches move on, otherwise find the next match + 3.1 If next match is in the next hour then roll forwards + 3.2 Reset the seconds and go to 2 + + 4 If hour matches move on, otherwise find the next match + 4.1 If next match is in the next day then roll forwards, + 4.2 Reset the minutes and seconds and go to 2 + + ... + */ + if (!expr) return CRON_INVALID_INSTANT; + struct tm calval; + memset(&calval, 0, sizeof(struct tm)); + struct tm* calendar = cron_time(&date, &calval); + if (!calendar) return CRON_INVALID_INSTANT; + time_t original = cron_mktime(calendar); + if (CRON_INVALID_INSTANT == original) return CRON_INVALID_INSTANT; + + /* calculate the previous occurrence */ + int res = do_prev(expr, calendar, calendar->tm_year); + if (0 != res) return CRON_INVALID_INSTANT; + + /* check for a match, try from the next second if one wasn't found */ + time_t calculated = cron_mktime(calendar); + if (CRON_INVALID_INSTANT == calculated) return CRON_INVALID_INSTANT; + if (calculated == original) { + /* We arrived at the original timestamp - round up to the next whole second and try again... */ + res = add_to_field(calendar, CRON_CF_SECOND, -1); + if (0 != res) return CRON_INVALID_INSTANT; + res = do_prev(expr, calendar, calendar->tm_year); + if (0 != res) return CRON_INVALID_INSTANT; + } + + return cron_mktime(calendar); +} diff --git a/modules/eventx/cron_timer/ccronexpr.h b/modules/eventx/cron_timer/ccronexpr.h new file mode 100644 index 0000000..afa8ab3 --- /dev/null +++ b/modules/eventx/cron_timer/ccronexpr.h @@ -0,0 +1,93 @@ +/* + * Copyright 2015, alex at staticlibs.net + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * File: ccronexpr.h + * Author: alex + * + * Created on February 24, 2015, 9:35 AM + */ + +#ifndef CCRONEXPR_H +#define CCRONEXPR_H + +#if defined(__cplusplus) && !defined(CRON_COMPILE_AS_CXX) +extern "C" { +#endif + +#ifndef ANDROID +#include +#else /* ANDROID */ +#include +#endif /* ANDROID */ + +#include /*added for use if uint*_t data types*/ + +/** + * Parsed cron expression + */ +typedef struct { + uint8_t seconds[8]; + uint8_t minutes[8]; + uint8_t hours[3]; + uint8_t days_of_week[1]; + uint8_t days_of_month[4]; + uint8_t months[2]; +} cron_expr; + +/** + * Parses specified cron expression. + * + * @param expression cron expression as nul-terminated string, + * should be no longer that 256 bytes + * @param pointer to cron expression structure, it's client code responsibility + * to free/destroy it afterwards + * @param error output error message, will be set to string literal + * error message in case of error. Will be set to NULL on success. + * The error message should NOT be freed by client. + */ +void cron_parse_expr(const char* expression, cron_expr* target, const char** error); + +/** + * Uses the specified expression to calculate the next 'fire' date after + * the specified date. All dates are processed as UTC (GMT) dates + * without timezones information. To use local dates (current system timezone) + * instead of GMT compile with '-DCRON_USE_LOCAL_TIME' + * + * @param expr parsed cron expression to use in next date calculation + * @param date start date to start calculation from + * @return next 'fire' date in case of success, '((time_t) -1)' in case of error. + */ +time_t cron_next(cron_expr* expr, time_t date); + +/** + * Uses the specified expression to calculate the previous 'fire' date after + * the specified date. All dates are processed as UTC (GMT) dates + * without timezones information. To use local dates (current system timezone) + * instead of GMT compile with '-DCRON_USE_LOCAL_TIME' + * + * @param expr parsed cron expression to use in previous date calculation + * @param date start date to start calculation from + * @return previous 'fire' date in case of success, '((time_t) -1)' in case of error. + */ +time_t cron_prev(cron_expr* expr, time_t date); + + +#if defined(__cplusplus) && !defined(CRON_COMPILE_AS_CXX) +} /* extern "C"*/ +#endif + +#endif /* CCRONEXPR_H */ diff --git a/modules/eventx/cron_timer/cron_timer.cpp b/modules/eventx/cron_timer/cron_timer.cpp new file mode 100644 index 0000000..c7e9fda --- /dev/null +++ b/modules/eventx/cron_timer/cron_timer.cpp @@ -0,0 +1,97 @@ +#include +#include +#include +#include +#include + +#include "cron_timer.h" +#include "tbox/event/timer_event.h" +#include "tbox/event/loop.h" +#include "ccronexpr.h" + +namespace tbox +{ +namespace event +{ + +CrontabTimer::CrontabTimer(Loop *loop) : + wp_loop_(loop), + sp_timer_ev_(loop->newTimerEvent()) +{ + assert(sp_timer_ev_ != nullptr); + sp_cron_expr_ = malloc(sizeof(cron_expr)); + assert(sp_cron_expr_ != nullptr); + memset(sp_cron_expr_, 0, sizeof(cron_expr)); +} + +CrontabTimer::~CrontabTimer() +{ + free(sp_cron_expr_); + delete sp_timer_ev_; +} + +bool CrontabTimer::isEnabled() const +{ + return sp_timer_ev_->isEnabled(); +} + +bool CrontabTimer::enable() +{ + return setNextAlarm(); +} + +bool CrontabTimer::disable() +{ + return sp_timer_ev_->disable(); +} + +bool CrontabTimer::initialize(const std::string &crontab_str, Callback cb) +{ + const char *error_str = nullptr; + memset(sp_cron_expr_, 0, sizeof(cron_expr)); + + // check validity of crontab str + cron_parse_expr(crontab_str.c_str(), static_cast(sp_cron_expr_), &error_str); + if (error_str != nullptr) { // Invalid expression. + return false; + } + + cb_ = cb; + sp_timer_ev_->setCallback([this] { this->onTimeExpired(); }); + + return true; +} + +void CrontabTimer::cleanup() +{ + cb_ = nullptr; +} + +void CrontabTimer::refresh() +{ + if (sp_timer_ev_->isEnabled()) + setNextAlarm(); +} + +void CrontabTimer::onTimeExpired() +{ + setNextAlarm(); + + if (cb_) + cb_(); +} + +bool CrontabTimer::setNextAlarm() +{ + struct timeval now_tv; + gettimeofday(&now_tv, nullptr); + auto next_ts = cron_next(static_cast(sp_cron_expr_), now_tv.tv_sec); + auto duration_s = next_ts - now_tv.tv_sec; + auto duration_ms = duration_s * 1000 - now_tv.tv_usec / 1000; + + sp_timer_ev_->initialize(std::chrono::milliseconds(duration_ms), Mode::kOneshot); + return sp_timer_ev_->enable(); +} + +} +} diff --git a/modules/eventx/cron_timer/cron_timer.h b/modules/eventx/cron_timer/cron_timer.h new file mode 100644 index 0000000..99da292 --- /dev/null +++ b/modules/eventx/cron_timer/cron_timer.h @@ -0,0 +1,73 @@ +#ifndef TBOX_CRON_TIMER_H +#define TBOX_CRON_TIMER_H + +#include +#include +#include "tbox/event/event.h" +#include "tbox/event/forward.h" + +namespace tbox { +namespace event { +/* + * @brief The linux crontab timer. + * + * CrontabTimer allow the user to make plains by linux crontab expression. + * Linux-cron tab expression + * * * * * * + - - - - - - + | | | | | | + | | | | | +----- day of week (0 - 7) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat + | | | | +---------- month (1 - 12) OR jan,feb,mar,apr ... + | | | +--------------- day of month (1 - 31) + | | +-------------------- hour (0 - 23) + | +------------------------- minute (0 - 59) + +------------------------------ second (0 - 59) + + * + * code example: + * + * Loop *loop = Loop::New(); + * CrontabTimer *tm = new CrontabTimer(loop); + * tm->initialize("18 28 14 * * *", [] { std::cout << "timeout" << std::endl; }); // every day at 14:28:18 + * tm->enable(); + * loop->start(); + */ + +class CrontabTimer : public Event +{ + public: + CrontabTimer(Loop *wp_loop); + virtual ~CrontabTimer(); + + using Callback = std::function; + bool initialize(const std::string &crontab_str, Callback cb); + + virtual bool isEnabled() const override; + virtual bool enable() override; + virtual bool disable() override; + + /* + * @brief refresh the internal timer. + * + * In order not to affect the accuracy of the timer, + * you need to call this function to refresh the internal timer + * when the system clock was changed. + */ + void refresh(); + void cleanup(); + + private: + void onTimeExpired(); + bool setNextAlarm(); + + private: + Loop *wp_loop_; + TimerEvent *sp_timer_ev_; + Callback cb_; + void *sp_cron_expr_; +}; + +} +} + +#endif -- Gitee From d35df7db45c9cba0a308afd90a229ca496700b3a Mon Sep 17 00:00:00 2001 From: Hevake Date: Wed, 19 Oct 2022 08:51:46 +0800 Subject: [PATCH 02/11] feat: move cron_timer.* to timer submodule; add WeeklyTimer --- config.mk | 1 + modules/eventx/Makefile | 4 +- modules/timer/Makefile | 22 ++++++++ .../cron_timer => timer}/ccronexpr.cpp | 0 .../{eventx/cron_timer => timer}/ccronexpr.h | 0 .../cron_timer => timer}/cron_timer.cpp | 17 +++---- .../{eventx/cron_timer => timer}/cron_timer.h | 26 +++++----- modules/timer/weekly_timer.cpp | 29 +++++++++++ modules/timer/weekly_timer.h | 51 +++++++++++++++++++ 9 files changed, 125 insertions(+), 25 deletions(-) create mode 100644 modules/timer/Makefile rename modules/{eventx/cron_timer => timer}/ccronexpr.cpp (100%) rename modules/{eventx/cron_timer => timer}/ccronexpr.h (100%) rename modules/{eventx/cron_timer => timer}/cron_timer.cpp (90%) rename modules/{eventx/cron_timer => timer}/cron_timer.h (79%) create mode 100644 modules/timer/weekly_timer.cpp create mode 100644 modules/timer/weekly_timer.h diff --git a/config.mk b/config.mk index 38a137d..ca0485d 100644 --- a/config.mk +++ b/config.mk @@ -9,3 +9,4 @@ MODULES += coroutine MODULES += mqtt MODULES += terminal MODULES += main +MODULES += timer diff --git a/modules/eventx/Makefile b/modules/eventx/Makefile index e14c410..fd537b4 100644 --- a/modules/eventx/Makefile +++ b/modules/eventx/Makefile @@ -8,13 +8,11 @@ HEAD_FILES = \ timer_pool.h \ timeout_monitor.h \ request_pool.hpp \ - cron_timer/cron_timer.h CPP_SRC_FILES = \ thread_pool.cpp \ timer_pool.cpp \ timeout_monitor.cpp \ - cron_timer/cron_timer.cpp CXXFLAGS := -DLOG_MODULE_ID='"eventx"' $(CXXFLAGS) @@ -24,7 +22,7 @@ TEST_CPP_SRC_FILES = \ timeout_monitor_test.cpp \ request_pool_test.cpp \ -TEST_LDFLAGS := $(LDFLAGS) -ltbox_event -ltbox_base -levent_core -lev +TEST_LDFLAGS := $(LDFLAGS) -ltbox_event -ltbox_base ENABLE_SHARED_LIB = no include ../../tools/lib_common.mk diff --git a/modules/timer/Makefile b/modules/timer/Makefile new file mode 100644 index 0000000..04422bb --- /dev/null +++ b/modules/timer/Makefile @@ -0,0 +1,22 @@ +LIB_NAME = timer +LIB_VERSION_X = 0 +LIB_VERSION_Y = 0 +LIB_VERSION_Z = 1 + +HEAD_FILES = \ + weekly_timer.h \ + #cron_timer.h \ + +CPP_SRC_FILES = \ + weekly_timer.cpp \ + #cron_timer.cpp \ + ccronexpr.cpp \ + +CXXFLAGS := -DLOG_MODULE_ID='"timer"' $(CXXFLAGS) + +TEST_CPP_SRC_FILES = + +TEST_LDFLAGS := $(LDFLAGS) -ltbox_event -ltbox_base +ENABLE_SHARED_LIB = no + +include ../../tools/lib_common.mk diff --git a/modules/eventx/cron_timer/ccronexpr.cpp b/modules/timer/ccronexpr.cpp similarity index 100% rename from modules/eventx/cron_timer/ccronexpr.cpp rename to modules/timer/ccronexpr.cpp diff --git a/modules/eventx/cron_timer/ccronexpr.h b/modules/timer/ccronexpr.h similarity index 100% rename from modules/eventx/cron_timer/ccronexpr.h rename to modules/timer/ccronexpr.h diff --git a/modules/eventx/cron_timer/cron_timer.cpp b/modules/timer/cron_timer.cpp similarity index 90% rename from modules/eventx/cron_timer/cron_timer.cpp rename to modules/timer/cron_timer.cpp index c7e9fda..999388b 100644 --- a/modules/eventx/cron_timer/cron_timer.cpp +++ b/modules/timer/cron_timer.cpp @@ -1,20 +1,19 @@ +#include "cron_timer.h" + #include #include #include #include #include -#include "cron_timer.h" -#include "tbox/event/timer_event.h" -#include "tbox/event/loop.h" +#include +#include #include "ccronexpr.h" -namespace tbox -{ -namespace event -{ +namespace tbox { +namespace timer { -CrontabTimer::CrontabTimer(Loop *loop) : +CrontabTimer::CrontabTimer(event::Loop *loop) : wp_loop_(loop), sp_timer_ev_(loop->newTimerEvent()) { @@ -89,7 +88,7 @@ bool CrontabTimer::setNextAlarm() auto duration_s = next_ts - now_tv.tv_sec; auto duration_ms = duration_s * 1000 - now_tv.tv_usec / 1000; - sp_timer_ev_->initialize(std::chrono::milliseconds(duration_ms), Mode::kOneshot); + sp_timer_ev_->initialize(std::chrono::milliseconds(duration_ms), event::Event::Mode::kOneshot); return sp_timer_ev_->enable(); } diff --git a/modules/eventx/cron_timer/cron_timer.h b/modules/timer/cron_timer.h similarity index 79% rename from modules/eventx/cron_timer/cron_timer.h rename to modules/timer/cron_timer.h index 99da292..820f468 100644 --- a/modules/eventx/cron_timer/cron_timer.h +++ b/modules/timer/cron_timer.h @@ -1,13 +1,13 @@ -#ifndef TBOX_CRON_TIMER_H -#define TBOX_CRON_TIMER_H +#ifndef TBOX_TIMER_CRON_TIMER_H +#define TBOX_TIMER_CRON_TIMER_H #include + #include -#include "tbox/event/event.h" -#include "tbox/event/forward.h" +#include namespace tbox { -namespace event { +namespace timer { /* * @brief The linux crontab timer. * @@ -33,18 +33,18 @@ namespace event { * loop->start(); */ -class CrontabTimer : public Event +class CrontabTimer { public: - CrontabTimer(Loop *wp_loop); + CrontabTimer(event::Loop *wp_loop); virtual ~CrontabTimer(); using Callback = std::function; bool initialize(const std::string &crontab_str, Callback cb); - virtual bool isEnabled() const override; - virtual bool enable() override; - virtual bool disable() override; + bool isEnabled() const; + bool enable(); + bool disable(); /* * @brief refresh the internal timer. @@ -61,8 +61,8 @@ class CrontabTimer : public Event bool setNextAlarm(); private: - Loop *wp_loop_; - TimerEvent *sp_timer_ev_; + event::Loop *wp_loop_; + event::TimerEvent *sp_timer_ev_; Callback cb_; void *sp_cron_expr_; }; @@ -70,4 +70,4 @@ class CrontabTimer : public Event } } -#endif +#endif //TBOX_TIMER_CRON_TIMER_H diff --git a/modules/timer/weekly_timer.cpp b/modules/timer/weekly_timer.cpp new file mode 100644 index 0000000..275ef88 --- /dev/null +++ b/modules/timer/weekly_timer.cpp @@ -0,0 +1,29 @@ +#include "weekly_timer.h" + +namespace tbox { +namespace timer { + +WeeklyTimer::WeeklyTimer(event::Loop *wp_loop) { } + +WeeklyTimer::~WeeklyTimer() { } + +bool WeeklyTimer::initialize(const std::string &week_mask, uint32_t seconds_of_day, int timezone_offset_minutes) { + return false; +} + +bool WeeklyTimer::isEnabled() const { + return false; +} + +bool WeeklyTimer::enable() { + return false; +} + +bool WeeklyTimer::disable() { + return false; +} + +void WeeklyTimer::cleanup() { } + +} +} diff --git a/modules/timer/weekly_timer.h b/modules/timer/weekly_timer.h new file mode 100644 index 0000000..0dafbda --- /dev/null +++ b/modules/timer/weekly_timer.h @@ -0,0 +1,51 @@ +#ifndef TBOX_TIMER_WEEKLY_TIMER_H_20221019 +#define TBOX_TIMER_WEEKLY_TIMER_H_20221019 + +#include + +#include +#include + +namespace tbox { +namespace timer { +/* + * @brief The linux crontab timer. + * + * WeeklyTimer allow the user to make plans by linux crontab expression. + * + * code example: + * + * Loop *loop = Loop::New(); + * WeeklyTimer *tm = new WeeklyTimer(loop); + * tm->initialize("0111110", (8 * 60 * 60), 0); // every day at 08:00 at 0 timezone + * tm->enable(); + * loop->start(); + */ + +class WeeklyTimer +{ + public: + explicit WeeklyTimer(event::Loop *wp_loop); + virtual ~WeeklyTimer(); + + bool initialize(const std::string &week_mask, uint32_t seconds_of_day, int timezone_offset_minutes = 0); + + using Callback = std::function; + void setCallback(const Callback &cb) { cb_ = cb; } + + bool isEnabled() const; + bool enable(); + bool disable(); + + void cleanup(); + + private: + event::Loop *wp_loop_; + event::TimerEvent *sp_timer_ev_; + Callback cb_; +}; + +} +} + +#endif //TBOX_TIMER_WEEKLY_TIMER_H_20221019 -- Gitee From 63661bfaffb39b0531c7b21d2533183e5a32a4a1 Mon Sep 17 00:00:00 2001 From: Hevake Date: Wed, 19 Oct 2022 09:00:10 +0800 Subject: [PATCH 03/11] fix: build problem --- modules/timer/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/timer/Makefile b/modules/timer/Makefile index 04422bb..14892ca 100644 --- a/modules/timer/Makefile +++ b/modules/timer/Makefile @@ -1,15 +1,15 @@ -LIB_NAME = timer +LIB_NAME = timer LIB_VERSION_X = 0 LIB_VERSION_Y = 0 LIB_VERSION_Z = 1 HEAD_FILES = \ weekly_timer.h \ - #cron_timer.h \ + cron_timer.h \ CPP_SRC_FILES = \ weekly_timer.cpp \ - #cron_timer.cpp \ + cron_timer.cpp \ ccronexpr.cpp \ CXXFLAGS := -DLOG_MODULE_ID='"timer"' $(CXXFLAGS) -- Gitee From a20faada2fad8a8eb228d979e2c68d0534f169c7 Mon Sep 17 00:00:00 2001 From: Hevake Date: Wed, 19 Oct 2022 09:06:31 +0800 Subject: [PATCH 04/11] tidy --- modules/timer/cron_timer.h | 10 +++++----- modules/timer/weekly_timer.h | 18 ++++++++++++------ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/modules/timer/cron_timer.h b/modules/timer/cron_timer.h index 820f468..c65aed4 100644 --- a/modules/timer/cron_timer.h +++ b/modules/timer/cron_timer.h @@ -11,7 +11,7 @@ namespace timer { /* * @brief The linux crontab timer. * - * CrontabTimer allow the user to make plains by linux crontab expression. + * CrontabTimer allow the user to make plans by linux crontab expression. * Linux-cron tab expression * * * * * * - - - - - - @@ -27,10 +27,10 @@ namespace timer { * code example: * * Loop *loop = Loop::New(); - * CrontabTimer *tm = new CrontabTimer(loop); - * tm->initialize("18 28 14 * * *", [] { std::cout << "timeout" << std::endl; }); // every day at 14:28:18 - * tm->enable(); - * loop->start(); + * CrontabTimer *tmr = new CrontabTimer(loop); + * tmr->initialize("18 28 14 * * *", [] { std::cout << "timeout" << std::endl; }); // every day at 14:28:18 + * tmr->enable(); + * loop->runLoop(); */ class CrontabTimer diff --git a/modules/timer/weekly_timer.h b/modules/timer/weekly_timer.h index 0dafbda..7b7497f 100644 --- a/modules/timer/weekly_timer.h +++ b/modules/timer/weekly_timer.h @@ -9,17 +9,18 @@ namespace tbox { namespace timer { /* - * @brief The linux crontab timer. + * @brief The linux weekly timer. * - * WeeklyTimer allow the user to make plans by linux crontab expression. + * WeeklyTimer allow the user to make plans weekly * * code example: * * Loop *loop = Loop::New(); - * WeeklyTimer *tm = new WeeklyTimer(loop); - * tm->initialize("0111110", (8 * 60 * 60), 0); // every day at 08:00 at 0 timezone - * tm->enable(); - * loop->start(); + * WeeklyTimer *tmr = new WeeklyTimer(loop); + * tmr->initialize("0111110", (8 * 60 * 60), 0); // every day at 08:00 at 0 timezone + * tmr->setCallback([] { std::cout << "time is up" << endl;}); + * tmr->enable(); + * loop->runLoop(); */ class WeeklyTimer @@ -42,6 +43,11 @@ class WeeklyTimer private: event::Loop *wp_loop_; event::TimerEvent *sp_timer_ev_; + + std::string week_mask_; + uint32_t seconds_of_day_; + int timezone_offset_minutes_; + Callback cb_; }; -- Gitee From cea9db58ae459be47931b58742fefadcba40229d Mon Sep 17 00:00:00 2001 From: Hevake Date: Wed, 19 Oct 2022 23:15:04 +0800 Subject: [PATCH 05/11] tidy:rename cron_timer.* to crontab_timer.* --- modules/timer/Makefile | 4 +- modules/timer/crontab_timer.cpp | 96 +++++++++++++++++++++++++++++++++ modules/timer/crontab_timer.h | 73 +++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 modules/timer/crontab_timer.cpp create mode 100644 modules/timer/crontab_timer.h diff --git a/modules/timer/Makefile b/modules/timer/Makefile index 14892ca..e93e0e6 100644 --- a/modules/timer/Makefile +++ b/modules/timer/Makefile @@ -5,11 +5,11 @@ LIB_VERSION_Z = 1 HEAD_FILES = \ weekly_timer.h \ - cron_timer.h \ + crontab_timer.h \ CPP_SRC_FILES = \ weekly_timer.cpp \ - cron_timer.cpp \ + crontab_timer.cpp \ ccronexpr.cpp \ CXXFLAGS := -DLOG_MODULE_ID='"timer"' $(CXXFLAGS) diff --git a/modules/timer/crontab_timer.cpp b/modules/timer/crontab_timer.cpp new file mode 100644 index 0000000..999388b --- /dev/null +++ b/modules/timer/crontab_timer.cpp @@ -0,0 +1,96 @@ +#include "cron_timer.h" + +#include +#include +#include +#include +#include + +#include +#include +#include "ccronexpr.h" + +namespace tbox { +namespace timer { + +CrontabTimer::CrontabTimer(event::Loop *loop) : + wp_loop_(loop), + sp_timer_ev_(loop->newTimerEvent()) +{ + assert(sp_timer_ev_ != nullptr); + sp_cron_expr_ = malloc(sizeof(cron_expr)); + assert(sp_cron_expr_ != nullptr); + memset(sp_cron_expr_, 0, sizeof(cron_expr)); +} + +CrontabTimer::~CrontabTimer() +{ + free(sp_cron_expr_); + delete sp_timer_ev_; +} + +bool CrontabTimer::isEnabled() const +{ + return sp_timer_ev_->isEnabled(); +} + +bool CrontabTimer::enable() +{ + return setNextAlarm(); +} + +bool CrontabTimer::disable() +{ + return sp_timer_ev_->disable(); +} + +bool CrontabTimer::initialize(const std::string &crontab_str, Callback cb) +{ + const char *error_str = nullptr; + memset(sp_cron_expr_, 0, sizeof(cron_expr)); + + // check validity of crontab str + cron_parse_expr(crontab_str.c_str(), static_cast(sp_cron_expr_), &error_str); + if (error_str != nullptr) { // Invalid expression. + return false; + } + + cb_ = cb; + sp_timer_ev_->setCallback([this] { this->onTimeExpired(); }); + + return true; +} + +void CrontabTimer::cleanup() +{ + cb_ = nullptr; +} + +void CrontabTimer::refresh() +{ + if (sp_timer_ev_->isEnabled()) + setNextAlarm(); +} + +void CrontabTimer::onTimeExpired() +{ + setNextAlarm(); + + if (cb_) + cb_(); +} + +bool CrontabTimer::setNextAlarm() +{ + struct timeval now_tv; + gettimeofday(&now_tv, nullptr); + auto next_ts = cron_next(static_cast(sp_cron_expr_), now_tv.tv_sec); + auto duration_s = next_ts - now_tv.tv_sec; + auto duration_ms = duration_s * 1000 - now_tv.tv_usec / 1000; + + sp_timer_ev_->initialize(std::chrono::milliseconds(duration_ms), event::Event::Mode::kOneshot); + return sp_timer_ev_->enable(); +} + +} +} diff --git a/modules/timer/crontab_timer.h b/modules/timer/crontab_timer.h new file mode 100644 index 0000000..c65aed4 --- /dev/null +++ b/modules/timer/crontab_timer.h @@ -0,0 +1,73 @@ +#ifndef TBOX_TIMER_CRON_TIMER_H +#define TBOX_TIMER_CRON_TIMER_H + +#include + +#include +#include + +namespace tbox { +namespace timer { +/* + * @brief The linux crontab timer. + * + * CrontabTimer allow the user to make plans by linux crontab expression. + * Linux-cron tab expression + * * * * * * + - - - - - - + | | | | | | + | | | | | +----- day of week (0 - 7) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat + | | | | +---------- month (1 - 12) OR jan,feb,mar,apr ... + | | | +--------------- day of month (1 - 31) + | | +-------------------- hour (0 - 23) + | +------------------------- minute (0 - 59) + +------------------------------ second (0 - 59) + + * + * code example: + * + * Loop *loop = Loop::New(); + * CrontabTimer *tmr = new CrontabTimer(loop); + * tmr->initialize("18 28 14 * * *", [] { std::cout << "timeout" << std::endl; }); // every day at 14:28:18 + * tmr->enable(); + * loop->runLoop(); + */ + +class CrontabTimer +{ + public: + CrontabTimer(event::Loop *wp_loop); + virtual ~CrontabTimer(); + + using Callback = std::function; + bool initialize(const std::string &crontab_str, Callback cb); + + bool isEnabled() const; + bool enable(); + bool disable(); + + /* + * @brief refresh the internal timer. + * + * In order not to affect the accuracy of the timer, + * you need to call this function to refresh the internal timer + * when the system clock was changed. + */ + void refresh(); + void cleanup(); + + private: + void onTimeExpired(); + bool setNextAlarm(); + + private: + event::Loop *wp_loop_; + event::TimerEvent *sp_timer_ev_; + Callback cb_; + void *sp_cron_expr_; +}; + +} +} + +#endif //TBOX_TIMER_CRON_TIMER_H -- Gitee From bb3f19c968f7506388d57988097570ec9ffb6245 Mon Sep 17 00:00:00 2001 From: Hevake Date: Thu, 20 Oct 2022 00:37:13 +0800 Subject: [PATCH 06/11] finish WeeklyTimer code, but did not test --- modules/timer/weekly_timer.cpp | 92 +++++++++++++++++++++++++++++++--- modules/timer/weekly_timer.h | 10 ++-- 2 files changed, 92 insertions(+), 10 deletions(-) diff --git a/modules/timer/weekly_timer.cpp b/modules/timer/weekly_timer.cpp index 275ef88..0013236 100644 --- a/modules/timer/weekly_timer.cpp +++ b/modules/timer/weekly_timer.cpp @@ -1,29 +1,107 @@ #include "weekly_timer.h" +#include + +#include +#include +#include + namespace tbox { namespace timer { -WeeklyTimer::WeeklyTimer(event::Loop *wp_loop) { } +WeeklyTimer::WeeklyTimer(event::Loop *wp_loop) : + wp_loop_(wp_loop), + sp_timer_ev_(wp_loop->newTimerEvent()) +{ + sp_timer_ev_->setCallback( + [this] { + activeTimer(); + if (cb_) + cb_(); + } + ); +} + +WeeklyTimer::~WeeklyTimer() { + delete sp_timer_ev_; +} + +bool WeeklyTimer::initialize(int seconds_of_day, const std::string &week_mask, int timezone_offset_minutes) { + if (seconds_of_day < 0) + return false; -WeeklyTimer::~WeeklyTimer() { } + if (week_mask_.size() != 7 && !week_mask_.empty()) + return false; -bool WeeklyTimer::initialize(const std::string &week_mask, uint32_t seconds_of_day, int timezone_offset_minutes) { + week_mask_ = week_mask; + seconds_of_day_ = seconds_of_day; + timezone_offset_minutes_ = timezone_offset_minutes; return false; } bool WeeklyTimer::isEnabled() const { - return false; + return sp_timer_ev_->isEnabled(); } bool WeeklyTimer::enable() { - return false; + return activeTimer(); } bool WeeklyTimer::disable() { - return false; + return sp_timer_ev_->disable(); +} + +void WeeklyTimer::cleanup() { + week_mask_.clear(); + seconds_of_day_ = 0; + timezone_offset_minutes_ = 0; + cb_ = nullptr; } -void WeeklyTimer::cleanup() { } +namespace { +constexpr auto kSecondsOfDay = 60 * 60 * 24; +constexpr auto kSecondsOfWeek = kSecondsOfDay * 7; +uint32_t GetCurrentUTCSeconds() { + struct timeval tv; + gettimeofday(&tv, nullptr); + return tv.tv_sec; +} +} + +int WeeklyTimer::calculateWaitSeconds() { + uint32_t curr_utc_ts = GetCurrentUTCSeconds(); + uint32_t curr_local_ts = curr_utc_ts + timezone_offset_minutes_; + + int curr_week = (((curr_local_ts % kSecondsOfWeek) / kSecondsOfDay) + 4) % 7; + int curr_seconds = curr_local_ts % kSecondsOfDay; + + LogTrace("curr_week:%d, curr_seconds:%d", curr_week, curr_seconds); + + int wait_seconds = static_cast(seconds_of_day_) - curr_seconds; + if (week_mask_.empty()) { + if (wait_seconds <= 0) + wait_seconds += kSecondsOfDay; + return wait_seconds; + } else { + for (int i = 0; i < 7; ++i) { + if (wait_seconds > 0 && week_mask_.at((i + curr_week) % 7) == '1') + return wait_seconds; + wait_seconds += kSecondsOfDay; + } + return -1; + } +} + +bool WeeklyTimer::activeTimer() { + auto wait_seconds = calculateWaitSeconds(); + LogTrace("wait_seconds:%d", wait_seconds); + if (wait_seconds < 0) + return false; + + sp_timer_ev_->initialize(std::chrono::seconds(wait_seconds), event::Event::Mode::kOneshot); + sp_timer_ev_->enable(); + return true; +} } } diff --git a/modules/timer/weekly_timer.h b/modules/timer/weekly_timer.h index 7b7497f..62d50c3 100644 --- a/modules/timer/weekly_timer.h +++ b/modules/timer/weekly_timer.h @@ -17,7 +17,7 @@ namespace timer { * * Loop *loop = Loop::New(); * WeeklyTimer *tmr = new WeeklyTimer(loop); - * tmr->initialize("0111110", (8 * 60 * 60), 0); // every day at 08:00 at 0 timezone + * tmr->initialize((8 * 60 * 60), "0111110", 0); // every day at 08:00 at 0 timezone * tmr->setCallback([] { std::cout << "time is up" << endl;}); * tmr->enable(); * loop->runLoop(); @@ -29,7 +29,7 @@ class WeeklyTimer explicit WeeklyTimer(event::Loop *wp_loop); virtual ~WeeklyTimer(); - bool initialize(const std::string &week_mask, uint32_t seconds_of_day, int timezone_offset_minutes = 0); + bool initialize(int seconds_of_day, const std::string &week_mask, int timezone_offset_minutes = 0); using Callback = std::function; void setCallback(const Callback &cb) { cb_ = cb; } @@ -40,12 +40,16 @@ class WeeklyTimer void cleanup(); + protected: + int calculateWaitSeconds(); + bool activeTimer(); + private: event::Loop *wp_loop_; event::TimerEvent *sp_timer_ev_; + int seconds_of_day_; std::string week_mask_; - uint32_t seconds_of_day_; int timezone_offset_minutes_; Callback cb_; -- Gitee From 0ec9ca2109ea832bf781090b402d148a44a2b3ac Mon Sep 17 00:00:00 2001 From: Hevake Date: Thu, 20 Oct 2022 07:48:33 +0800 Subject: [PATCH 07/11] add example for WeeklyTimer and fix bugs --- examples/timer/Makefile | 6 ++++ examples/timer/weekly_timer/Makefile | 11 ++++++ examples/timer/weekly_timer/main.cpp | 54 ++++++++++++++++++++++++++++ modules/timer/weekly_timer.cpp | 52 +++++++++++++++++++++------ modules/timer/weekly_timer.h | 9 ++++- 5 files changed, 121 insertions(+), 11 deletions(-) create mode 100644 examples/timer/Makefile create mode 100644 examples/timer/weekly_timer/Makefile create mode 100644 examples/timer/weekly_timer/main.cpp diff --git a/examples/timer/Makefile b/examples/timer/Makefile new file mode 100644 index 0000000..31374b4 --- /dev/null +++ b/examples/timer/Makefile @@ -0,0 +1,6 @@ +all test clean distclean: + @for i in $(shell ls) ; do \ + if [ -d $$i ]; then \ + $(MAKE) -C $$i $@ || exit $$? ; \ + fi \ + done diff --git a/examples/timer/weekly_timer/Makefile b/examples/timer/weekly_timer/Makefile new file mode 100644 index 0000000..caed9d7 --- /dev/null +++ b/examples/timer/weekly_timer/Makefile @@ -0,0 +1,11 @@ +EXE_NAME := example/timer/weekly_timer + +CPP_SRC_FILES := main.cpp + +CXXFLAGS := -DLOG_MODULE_ID='"$(EXE_NAME)"' $(CXXFLAGS) +LDFLAGS += \ + -ltbox_timer \ + -ltbox_event \ + -ltbox_base \ + +include ${TOP_DIR}/tools/exe_common.mk diff --git a/examples/timer/weekly_timer/main.cpp b/examples/timer/weekly_timer/main.cpp new file mode 100644 index 0000000..9d4fdfc --- /dev/null +++ b/examples/timer/weekly_timer/main.cpp @@ -0,0 +1,54 @@ +#include + +#include +#include +#include + +#include +#include +#include + +using namespace std; +using namespace tbox; +using namespace tbox::event; + +void PrintUsage(const char *process_name) { + cout << "Usage:" << process_name << " seconds_from_00 week_mask timezone_offset_minutes" << endl; +} + +int main(int argc, char *argv[]) { + if (argc < 4) { + PrintUsage(argv[0]); + return 0; + } + + int seconds_from_00 = 0; + std::string week_mask; + int timezone_offset_minutes = 0; + + try { + seconds_from_00 = std::stoi(argv[1]); + week_mask = argv[2]; + timezone_offset_minutes = std::stoi(argv[3]); + } catch (const std::exception &e) { + PrintUsage(argv[0]); + return 0; + } + + LogOutput_Initialize(argv[0]); + LogInfo("second:%d, week_mask:%s, timezone:%d", seconds_from_00, + week_mask.c_str(), timezone_offset_minutes); + + Loop* sp_loop = Loop::New(); + SetScopeExitAction([sp_loop] { delete sp_loop; }); + + timer::WeeklyTimer tmr(sp_loop); + tmr.initialize(seconds_from_00, week_mask, timezone_offset_minutes); + tmr.setCallback([]{ LogInfo("time is up"); }); + tmr.enable(); + + sp_loop->runLoop(Loop::Mode::kForever); + + LogOutput_Cleanup(); + return 0; +} diff --git a/modules/timer/weekly_timer.cpp b/modules/timer/weekly_timer.cpp index 0013236..478665a 100644 --- a/modules/timer/weekly_timer.cpp +++ b/modules/timer/weekly_timer.cpp @@ -15,7 +15,10 @@ WeeklyTimer::WeeklyTimer(event::Loop *wp_loop) : { sp_timer_ev_->setCallback( [this] { - activeTimer(); + state_ = State::kInited; + if (!week_mask_.empty()) + activeTimer(); + if (cb_) cb_(); } @@ -23,39 +26,65 @@ WeeklyTimer::WeeklyTimer(event::Loop *wp_loop) : } WeeklyTimer::~WeeklyTimer() { + cleanup(); delete sp_timer_ev_; } bool WeeklyTimer::initialize(int seconds_of_day, const std::string &week_mask, int timezone_offset_minutes) { - if (seconds_of_day < 0) + if (state_ == State::kRunning) { + LogWarn("timer is running state, disable first"); return false; + } - if (week_mask_.size() != 7 && !week_mask_.empty()) + if (seconds_of_day < 0) { + LogWarn("seconds_of_day:%d, < 0", seconds_of_day); return false; + } + + if (week_mask_.size() != 7 && !week_mask_.empty()) { + LogWarn("week_mask:%s, invalid", week_mask.c_str()); + return false; + } week_mask_ = week_mask; seconds_of_day_ = seconds_of_day; - timezone_offset_minutes_ = timezone_offset_minutes; - return false; + timezone_offset_seconds_ = timezone_offset_minutes * 60; + state_ = State::kInited; + + return true; } bool WeeklyTimer::isEnabled() const { - return sp_timer_ev_->isEnabled(); + return state_ == State::kRunning; } bool WeeklyTimer::enable() { - return activeTimer(); + if (state_ == State::kInited) + return activeTimer(); + + LogWarn("need initialize first"); + return false; } bool WeeklyTimer::disable() { - return sp_timer_ev_->disable(); + if (state_ == State::kRunning) { + state_ = State::kInited; + return sp_timer_ev_->disable(); + } + return false; } void WeeklyTimer::cleanup() { + if (state_ < State::kInited) + return; + + disable(); + week_mask_.clear(); seconds_of_day_ = 0; - timezone_offset_minutes_ = 0; + timezone_offset_seconds_ = 0; cb_ = nullptr; + state_ = State::kNone; } namespace { @@ -71,7 +100,7 @@ uint32_t GetCurrentUTCSeconds() { int WeeklyTimer::calculateWaitSeconds() { uint32_t curr_utc_ts = GetCurrentUTCSeconds(); - uint32_t curr_local_ts = curr_utc_ts + timezone_offset_minutes_; + uint32_t curr_local_ts = curr_utc_ts + timezone_offset_seconds_; int curr_week = (((curr_local_ts % kSecondsOfWeek) / kSecondsOfDay) + 4) % 7; int curr_seconds = curr_local_ts % kSecondsOfDay; @@ -101,7 +130,10 @@ bool WeeklyTimer::activeTimer() { sp_timer_ev_->initialize(std::chrono::seconds(wait_seconds), event::Event::Mode::kOneshot); sp_timer_ev_->enable(); + + state_ = State::kRunning; return true; } + } } diff --git a/modules/timer/weekly_timer.h b/modules/timer/weekly_timer.h index 62d50c3..33e96c7 100644 --- a/modules/timer/weekly_timer.h +++ b/modules/timer/weekly_timer.h @@ -50,9 +50,16 @@ class WeeklyTimer int seconds_of_day_; std::string week_mask_; - int timezone_offset_minutes_; + int timezone_offset_seconds_; Callback cb_; + + enum class State { + kNone, + kInited, + kRunning + }; + State state_ = State::kNone; }; } -- Gitee From 5a13b5f715192a0d15cd03247287f68ed41c2e35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=B7=E5=8D=AB=E5=93=A5?= Date: Thu, 20 Oct 2022 03:02:42 +0000 Subject: [PATCH 08/11] =?UTF-8?q?update=20modules/timer/weekly=5Ftimer.h.?= =?UTF-8?q?=20=E6=B7=BB=E5=8A=A0=E5=AF=B9=E6=88=90=E5=91=98=E5=8F=98?= =?UTF-8?q?=E9=87=8F=E7=9A=84=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 海卫哥 --- modules/timer/weekly_timer.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/timer/weekly_timer.h b/modules/timer/weekly_timer.h index 33e96c7..93e173e 100644 --- a/modules/timer/weekly_timer.h +++ b/modules/timer/weekly_timer.h @@ -17,7 +17,7 @@ namespace timer { * * Loop *loop = Loop::New(); * WeeklyTimer *tmr = new WeeklyTimer(loop); - * tmr->initialize((8 * 60 * 60), "0111110", 0); // every day at 08:00 at 0 timezone + * tmr->initialize((8 * 60 * 60), "1111111", 0); // everyday at 08:00 in 0 timezone * tmr->setCallback([] { std::cout << "time is up" << endl;}); * tmr->enable(); * loop->runLoop(); @@ -48,9 +48,9 @@ class WeeklyTimer event::Loop *wp_loop_; event::TimerEvent *sp_timer_ev_; - int seconds_of_day_; + int seconds_of_day_ = 0; std::string week_mask_; - int timezone_offset_seconds_; + int timezone_offset_seconds_ = 0; Callback cb_; -- Gitee From 2c186eb4631620d0f8e5a00a5809b722c9b0ff4c Mon Sep 17 00:00:00 2001 From: Hevake Date: Fri, 21 Oct 2022 00:24:48 +0800 Subject: [PATCH 09/11] fix(WeeklyTimer) --- modules/timer/weekly_timer.cpp | 52 ++++++++++++++++++++-------------- modules/timer/weekly_timer.h | 3 +- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/modules/timer/weekly_timer.cpp b/modules/timer/weekly_timer.cpp index 478665a..ec97fd1 100644 --- a/modules/timer/weekly_timer.cpp +++ b/modules/timer/weekly_timer.cpp @@ -1,6 +1,7 @@ #include "weekly_timer.h" #include +#include #include #include @@ -9,6 +10,12 @@ namespace tbox { namespace timer { +namespace { +constexpr auto kSecondsOfDay = 60 * 60 * 24; +constexpr auto kSecondsOfWeek = kSecondsOfDay * 7; +constexpr auto kMaxTimezoneOffsetMinutes = 60 * 12; +} + WeeklyTimer::WeeklyTimer(event::Loop *wp_loop) : wp_loop_(wp_loop), sp_timer_ev_(wp_loop->newTimerEvent()) @@ -19,13 +26,16 @@ WeeklyTimer::WeeklyTimer(event::Loop *wp_loop) : if (!week_mask_.empty()) activeTimer(); + ++cb_level_; if (cb_) cb_(); + --cb_level_; } ); } WeeklyTimer::~WeeklyTimer() { + assert(cb_level_ == 0); cleanup(); delete sp_timer_ev_; } @@ -36,13 +46,13 @@ bool WeeklyTimer::initialize(int seconds_of_day, const std::string &week_mask, i return false; } - if (seconds_of_day < 0) { - LogWarn("seconds_of_day:%d, < 0", seconds_of_day); + if (seconds_of_day < 0 || seconds_of_day >= kSecondsOfDay) { + LogWarn("seconds_of_day:%d, out of range: [0,%d)", seconds_of_day, kSecondsOfDay); return false; } - if (week_mask_.size() != 7 && !week_mask_.empty()) { - LogWarn("week_mask:%s, invalid", week_mask.c_str()); + if (week_mask.size() != 7 && !week_mask.empty()) { + LogWarn("week_mask:%s, length either 0 or 7", week_mask.c_str()); return false; } @@ -62,7 +72,7 @@ bool WeeklyTimer::enable() { if (state_ == State::kInited) return activeTimer(); - LogWarn("need initialize first"); + LogWarn("should initialize first"); return false; } @@ -87,27 +97,17 @@ void WeeklyTimer::cleanup() { state_ = State::kNone; } -namespace { -constexpr auto kSecondsOfDay = 60 * 60 * 24; -constexpr auto kSecondsOfWeek = kSecondsOfDay * 7; - -uint32_t GetCurrentUTCSeconds() { - struct timeval tv; - gettimeofday(&tv, nullptr); - return tv.tv_sec; -} -} - -int WeeklyTimer::calculateWaitSeconds() { - uint32_t curr_utc_ts = GetCurrentUTCSeconds(); +int WeeklyTimer::calculateWaitSeconds(uint32_t curr_utc_ts) { uint32_t curr_local_ts = curr_utc_ts + timezone_offset_seconds_; int curr_week = (((curr_local_ts % kSecondsOfWeek) / kSecondsOfDay) + 4) % 7; int curr_seconds = curr_local_ts % kSecondsOfDay; +#if 0 LogTrace("curr_week:%d, curr_seconds:%d", curr_week, curr_seconds); +#endif - int wait_seconds = static_cast(seconds_of_day_) - curr_seconds; + int wait_seconds = seconds_of_day_ - curr_seconds; if (week_mask_.empty()) { if (wait_seconds <= 0) wait_seconds += kSecondsOfDay; @@ -123,12 +123,22 @@ int WeeklyTimer::calculateWaitSeconds() { } bool WeeklyTimer::activeTimer() { - auto wait_seconds = calculateWaitSeconds(); + struct timeval tv; + int ret = gettimeofday(&tv, nullptr); + if (ret != 0) { + LogWarn("gettimeofday() fail, ret:%d", ret); + return false; + } + + auto wait_seconds = calculateWaitSeconds(tv.tv_sec); +#if 0 LogTrace("wait_seconds:%d", wait_seconds); +#endif if (wait_seconds < 0) return false; - sp_timer_ev_->initialize(std::chrono::seconds(wait_seconds), event::Event::Mode::kOneshot); + auto wait_milliseconds = wait_seconds * 1000 - tv.tv_usec / 1000; + sp_timer_ev_->initialize(std::chrono::milliseconds(wait_milliseconds), event::Event::Mode::kOneshot); sp_timer_ev_->enable(); state_ = State::kRunning; diff --git a/modules/timer/weekly_timer.h b/modules/timer/weekly_timer.h index 93e173e..0d11a69 100644 --- a/modules/timer/weekly_timer.h +++ b/modules/timer/weekly_timer.h @@ -41,7 +41,7 @@ class WeeklyTimer void cleanup(); protected: - int calculateWaitSeconds(); + int calculateWaitSeconds(uint32_t curr_utc_ts); bool activeTimer(); private: @@ -52,6 +52,7 @@ class WeeklyTimer std::string week_mask_; int timezone_offset_seconds_ = 0; + int cb_level_ = 0; Callback cb_; enum class State { -- Gitee From 265f726cdb59be5a6a9492df647707340217d498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=B7=E5=8D=AB=E5=93=A5?= Date: Fri, 21 Oct 2022 02:50:46 +0000 Subject: [PATCH 10/11] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20mo?= =?UTF-8?q?dules/timer/cron=5Ftimer.h?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/timer/cron_timer.h | 73 -------------------------------------- 1 file changed, 73 deletions(-) delete mode 100644 modules/timer/cron_timer.h diff --git a/modules/timer/cron_timer.h b/modules/timer/cron_timer.h deleted file mode 100644 index c65aed4..0000000 --- a/modules/timer/cron_timer.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef TBOX_TIMER_CRON_TIMER_H -#define TBOX_TIMER_CRON_TIMER_H - -#include - -#include -#include - -namespace tbox { -namespace timer { -/* - * @brief The linux crontab timer. - * - * CrontabTimer allow the user to make plans by linux crontab expression. - * Linux-cron tab expression - * * * * * * - - - - - - - - | | | | | | - | | | | | +----- day of week (0 - 7) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat - | | | | +---------- month (1 - 12) OR jan,feb,mar,apr ... - | | | +--------------- day of month (1 - 31) - | | +-------------------- hour (0 - 23) - | +------------------------- minute (0 - 59) - +------------------------------ second (0 - 59) - - * - * code example: - * - * Loop *loop = Loop::New(); - * CrontabTimer *tmr = new CrontabTimer(loop); - * tmr->initialize("18 28 14 * * *", [] { std::cout << "timeout" << std::endl; }); // every day at 14:28:18 - * tmr->enable(); - * loop->runLoop(); - */ - -class CrontabTimer -{ - public: - CrontabTimer(event::Loop *wp_loop); - virtual ~CrontabTimer(); - - using Callback = std::function; - bool initialize(const std::string &crontab_str, Callback cb); - - bool isEnabled() const; - bool enable(); - bool disable(); - - /* - * @brief refresh the internal timer. - * - * In order not to affect the accuracy of the timer, - * you need to call this function to refresh the internal timer - * when the system clock was changed. - */ - void refresh(); - void cleanup(); - - private: - void onTimeExpired(); - bool setNextAlarm(); - - private: - event::Loop *wp_loop_; - event::TimerEvent *sp_timer_ev_; - Callback cb_; - void *sp_cron_expr_; -}; - -} -} - -#endif //TBOX_TIMER_CRON_TIMER_H -- Gitee From 7c0d8e42d78cd4be6be769f1e61e800319400ede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=B7=E5=8D=AB=E5=93=A5?= Date: Fri, 21 Oct 2022 02:50:57 +0000 Subject: [PATCH 11/11] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20mo?= =?UTF-8?q?dules/timer/cron=5Ftimer.cpp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/timer/cron_timer.cpp | 96 ------------------------------------ 1 file changed, 96 deletions(-) delete mode 100644 modules/timer/cron_timer.cpp diff --git a/modules/timer/cron_timer.cpp b/modules/timer/cron_timer.cpp deleted file mode 100644 index 999388b..0000000 --- a/modules/timer/cron_timer.cpp +++ /dev/null @@ -1,96 +0,0 @@ -#include "cron_timer.h" - -#include -#include -#include -#include -#include - -#include -#include -#include "ccronexpr.h" - -namespace tbox { -namespace timer { - -CrontabTimer::CrontabTimer(event::Loop *loop) : - wp_loop_(loop), - sp_timer_ev_(loop->newTimerEvent()) -{ - assert(sp_timer_ev_ != nullptr); - sp_cron_expr_ = malloc(sizeof(cron_expr)); - assert(sp_cron_expr_ != nullptr); - memset(sp_cron_expr_, 0, sizeof(cron_expr)); -} - -CrontabTimer::~CrontabTimer() -{ - free(sp_cron_expr_); - delete sp_timer_ev_; -} - -bool CrontabTimer::isEnabled() const -{ - return sp_timer_ev_->isEnabled(); -} - -bool CrontabTimer::enable() -{ - return setNextAlarm(); -} - -bool CrontabTimer::disable() -{ - return sp_timer_ev_->disable(); -} - -bool CrontabTimer::initialize(const std::string &crontab_str, Callback cb) -{ - const char *error_str = nullptr; - memset(sp_cron_expr_, 0, sizeof(cron_expr)); - - // check validity of crontab str - cron_parse_expr(crontab_str.c_str(), static_cast(sp_cron_expr_), &error_str); - if (error_str != nullptr) { // Invalid expression. - return false; - } - - cb_ = cb; - sp_timer_ev_->setCallback([this] { this->onTimeExpired(); }); - - return true; -} - -void CrontabTimer::cleanup() -{ - cb_ = nullptr; -} - -void CrontabTimer::refresh() -{ - if (sp_timer_ev_->isEnabled()) - setNextAlarm(); -} - -void CrontabTimer::onTimeExpired() -{ - setNextAlarm(); - - if (cb_) - cb_(); -} - -bool CrontabTimer::setNextAlarm() -{ - struct timeval now_tv; - gettimeofday(&now_tv, nullptr); - auto next_ts = cron_next(static_cast(sp_cron_expr_), now_tv.tv_sec); - auto duration_s = next_ts - now_tv.tv_sec; - auto duration_ms = duration_s * 1000 - now_tv.tv_usec / 1000; - - sp_timer_ev_->initialize(std::chrono::milliseconds(duration_ms), event::Event::Mode::kOneshot); - return sp_timer_ev_->enable(); -} - -} -} -- Gitee