diff --git a/CHANGELOG.md b/CHANGELOG.md index be5f445..6b1e583 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Search in list of targets using API & CLI * Allow filtering targets by arch value +### Changed + +* Rewrote cron expression logic in C + ## [0.5.0](https://git.rustybever.be/vieter-v/vieter/src/tag/0.5.0) ### Added diff --git a/src/build/queue.v b/src/build/queue.v index e87024b..122180e 100644 --- a/src/build/queue.v +++ b/src/build/queue.v @@ -1,7 +1,7 @@ module build import models { BuildConfig, Target } -import cron.expression { CronExpression, parse_expression } +import cron import time import datatypes { MinHeap } import util @@ -13,7 +13,7 @@ pub mut: // Next timestamp from which point this job is allowed to be executed timestamp time.Time // Required for calculating next timestamp after having pop'ed a job - ce &CronExpression = unsafe { nil } + ce &cron.Expression = unsafe { nil } // Actual build config sent to the agent config BuildConfig // Whether this is a one-time job @@ -30,7 +30,7 @@ fn (r1 BuildJob) < (r2 BuildJob) bool { // for each architecture. Agents receive jobs from this queue. pub struct BuildJobQueue { // Schedule to use for targets without explicitely defined cron expression - default_schedule &CronExpression + default_schedule &cron.Expression // Base image to use for targets without defined base image default_base_image string mut: @@ -44,7 +44,7 @@ mut: } // new_job_queue initializes a new job queue -pub fn new_job_queue(default_schedule &CronExpression, default_base_image string) BuildJobQueue { +pub fn new_job_queue(default_schedule &cron.Expression, default_base_image string) BuildJobQueue { return BuildJobQueue{ default_schedule: unsafe { default_schedule } default_base_image: default_base_image @@ -85,7 +85,7 @@ pub fn (mut q BuildJobQueue) insert(input InsertConfig) ! { if !input.now { ce := if input.target.schedule != '' { - parse_expression(input.target.schedule) or { + cron.parse_expression(input.target.schedule) or { return error("Error while parsing cron expression '$input.target.schedule' (id $input.target.id): $err.msg()") } } else { diff --git a/src/console/schedule/schedule.v b/src/console/schedule/schedule.v index 7ce0516..40b300f 100644 --- a/src/console/schedule/schedule.v +++ b/src/console/schedule/schedule.v @@ -1,7 +1,7 @@ module schedule import cli -import cron.expression { parse_expression } +import cron import time // cmd returns the cli submodule for previewing a cron schedule. @@ -19,7 +19,7 @@ pub fn cmd() cli.Command { }, ] execute: fn (cmd cli.Command) ! { - ce := parse_expression(cmd.args.join(' '))! + ce := cron.parse_expression(cmd.args.join(' '))! count := cmd.flags.get_int('count')! for t in ce.next_n(time.now(), count)! { diff --git a/src/console/targets/targets.v b/src/console/targets/targets.v index 6152a53..709c196 100644 --- a/src/console/targets/targets.v +++ b/src/console/targets/targets.v @@ -2,7 +2,7 @@ module targets import cli import conf as vconf -import cron.expression { parse_expression } +import cron import client { NewTarget } import console import models { TargetFilter } @@ -295,7 +295,7 @@ fn patch(conf Config, id string, params map[string]string) ! { // We check the cron expression first because it's useless to send an // invalid one to the server. if 'schedule' in params && params['schedule'] != '' { - parse_expression(params['schedule']) or { + cron.parse_expression(params['schedule']) or { return error('Invalid cron expression: $err.msg()') } } diff --git a/src/cron/expression/c/expression.c b/src/cron/c/expression.c similarity index 91% rename from src/cron/expression/c/expression.c rename to src/cron/c/expression.c index f15e359..ed0306f 100644 --- a/src/cron/expression/c/expression.c +++ b/src/cron/c/expression.c @@ -3,11 +3,11 @@ const uint8_t month_days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; -struct cron_expression *ce_init() { - return malloc(sizeof(struct cron_expression)); +cron_expression *ce_init() { + return malloc(sizeof(cron_expression)); } -void ce_free(struct cron_expression *ce) { +void ce_free(cron_expression *ce) { free(ce->months); free(ce->days); free(ce->hours); @@ -15,7 +15,7 @@ void ce_free(struct cron_expression *ce) { free(ce); } -int ce_next(struct cron_simple_time *out, struct cron_expression *ce, struct cron_simple_time *ref) { +int ce_next(cron_simple_time *out, cron_expression *ce, cron_simple_time *ref) { // For all of these values, the rule is the following: if their value is // the length of their respective array in the CronExpression object, that // means we've looped back around. This means that the "bigger" value has @@ -103,7 +103,7 @@ int ce_next(struct cron_simple_time *out, struct cron_expression *ce, struct cro return 0; } -int ce_next_from_now(struct cron_simple_time *out, struct cron_expression *ce) { +int ce_next_from_now(cron_simple_time *out, cron_expression *ce) { time_t t = time(NULL); struct tm gm; gmtime_r(&t, &gm); diff --git a/src/cron/c/expression.h b/src/cron/c/expression.h new file mode 100644 index 0000000..c9441f6 --- /dev/null +++ b/src/cron/c/expression.h @@ -0,0 +1,40 @@ +#include +#include +#include +#include + +typedef enum cron_parse_error { + cron_parse_ok = 0, + cron_parse_invalid_expression = 1, + cron_parse_invalid_number = 2, + cron_parse_out_of_range = 3 +} cron_parse_error; + +typedef struct cron_expression { + uint8_t *minutes; + uint8_t *hours; + uint8_t *days; + uint8_t *months; + uint8_t minute_count; + uint8_t hour_count; + uint8_t day_count; + uint8_t month_count; +} cron_expression; + +typedef struct cron_simple_time { + int year; + int month; + int day; + int hour; + int minute; +} cron_simple_time; + +cron_expression *ce_init(); + +void cron_ce_free(cron_expression *ce); + +int cron_ce_next(cron_simple_time *out, cron_expression *ce, cron_simple_time *ref); + +int cron_ce_next_from_now(cron_simple_time *out, cron_expression *ce); + +enum cron_parse_error cron_ce_parse_expression(cron_expression *out, char *s); diff --git a/src/cron/expression/c/parse.c b/src/cron/c/parse.c similarity index 92% rename from src/cron/expression/c/parse.c rename to src/cron/c/parse.c index cd23458..a577c6d 100644 --- a/src/cron/expression/c/parse.c +++ b/src/cron/c/parse.c @@ -9,10 +9,10 @@ const uint8_t max[4] = {59, 23, 31, 12}; #define SAFE_ATOI(v,s,min,max) \ int _##v = atoi(s); \ if ((_##v) == 0 && strcmp((s), "0") != 0) { \ - return CPEParseInvalidNumber; \ + return cron_parse_invalid_number; \ } \ if (v < (min) || v > (max)) { \ - return CPEParseOutOfRange; \ + return cron_parse_out_of_range; \ } \ v = (uint8_t) (_##v); @@ -37,12 +37,12 @@ enum cron_parse_error ce_parse_range(uint64_t *out, char *s, uint8_t min, uint8_ if (s[0] == '*') { // A '*' is only valid on its own if (s[1] != '\0') { - return CPEParseInvalidExpression; + return cron_parse_invalid_expression; } *out = ~0; - return CPEParseOk; + return cron_parse_ok; } size_t slash_index = 0; @@ -90,7 +90,7 @@ enum cron_parse_error ce_parse_range(uint64_t *out, char *s, uint8_t min, uint8_ } } - return CPEParseOk; + return cron_parse_ok; } /* @@ -108,7 +108,7 @@ enum cron_parse_error ce_parse_part(uint64_t *out, char *s, uint8_t min, uint8_t next[0] = '\0'; res = ce_parse_range(out, s, min, max); - if (res != CPEParseOk) { + if (res != cron_parse_ok) { return res; } @@ -170,7 +170,7 @@ uint8_t bf_to_nums(uint8_t **out, uint64_t bf, uint8_t min, uint8_t max) { /* * Parse a cron expression string into a cron_expression struct. */ -enum cron_parse_error ce_parse_expression(struct cron_expression *out, char *s) { +enum cron_parse_error ce_parse_expression(cron_expression *out, char *s) { // The parsing functions modify the input string in-place s = strdup(s); char *orig_s = s; @@ -178,7 +178,7 @@ enum cron_parse_error ce_parse_expression(struct cron_expression *out, char *s) uint8_t part_count = 0; char *next; - enum cron_parse_error res = CPEParseOk; + enum cron_parse_error res = cron_parse_ok; uint64_t bfs[4]; // Skip leading spaces @@ -190,7 +190,7 @@ enum cron_parse_error ce_parse_expression(struct cron_expression *out, char *s) next[0] = '\0'; res = ce_parse_part(&bfs[part_count], s, min[part_count], max[part_count]); - if (res != CPEParseOk) { + if (res != cron_parse_ok) { goto end; } @@ -209,7 +209,7 @@ enum cron_parse_error ce_parse_expression(struct cron_expression *out, char *s) if (part_count < 4 && s[0] != '\0') { res = ce_parse_part(&bfs[part_count], s, min[part_count], max[part_count]); - if (res != CPEParseOk) { + if (res != cron_parse_ok) { goto end; } @@ -218,7 +218,7 @@ enum cron_parse_error ce_parse_expression(struct cron_expression *out, char *s) // At least two parts need to be provided if (part_count < 2) { - res = CPEParseInvalidExpression; + res = cron_parse_invalid_expression; goto end; } diff --git a/src/cron/expression/expression.c.v b/src/cron/expression.c.v similarity index 88% rename from src/cron/expression/expression.c.v rename to src/cron/expression.c.v index fc97176..08af25c 100644 --- a/src/cron/expression/expression.c.v +++ b/src/cron/expression.c.v @@ -1,4 +1,4 @@ -module expression +module cron #flag -I @VMODROOT/c #flag @VMODROOT/c/parse.o @@ -16,7 +16,7 @@ pub struct C.cron_expression { month_count u8 } -pub type CronExpression = C.cron_expression +pub type Expression = C.cron_expression struct C.cron_simple_time { year int @@ -26,6 +26,8 @@ struct C.cron_simple_time { minute int } +type SimpleTime = C.cron_simple_time + fn C.ce_init() &C.cron_expression fn C.ce_free(ce &C.cron_expression) diff --git a/src/cron/expression/expression.v b/src/cron/expression.v similarity index 69% rename from src/cron/expression/expression.v rename to src/cron/expression.v index a51f562..0429b93 100644 --- a/src/cron/expression/expression.v +++ b/src/cron/expression.v @@ -1,8 +1,8 @@ -module expression +module cron import time -pub fn parse_expression(exp string) !&CronExpression { +pub fn parse_expression(exp string) !&Expression { out := C.ce_init() res := C.ce_parse_expression(out, exp.str) @@ -13,12 +13,12 @@ pub fn parse_expression(exp string) !&CronExpression { return out } -pub fn (ce &CronExpression) free() { +pub fn (ce &Expression) free() { C.ce_free(ce) } -pub fn (ce &CronExpression) next(ref time.Time) !time.Time { - st := C.cron_simple_time{ +pub fn (ce &Expression) next(ref time.Time) !time.Time { + st := SimpleTime{ year: ref.year month: ref.month day: ref.day @@ -26,7 +26,7 @@ pub fn (ce &CronExpression) next(ref time.Time) !time.Time { minute: ref.minute } - out := C.cron_simple_time{} + out := SimpleTime{} res := C.ce_next(&out, ce, &st) if res != 0 { @@ -42,8 +42,8 @@ pub fn (ce &CronExpression) next(ref time.Time) !time.Time { }) } -pub fn (ce &CronExpression) next_from_now() !time.Time { - out := C.cron_simple_time{} +pub fn (ce &Expression) next_from_now() !time.Time { + out := SimpleTime{} res := C.ce_next_from_now(&out, ce) if res != 0 { @@ -61,7 +61,7 @@ pub fn (ce &CronExpression) next_from_now() !time.Time { // next_n returns the n next occurences of the expression, given a starting // time. -pub fn (ce &CronExpression) next_n(ref time.Time, n int) ![]time.Time { +pub fn (ce &Expression) next_n(ref time.Time, n int) ![]time.Time { mut times := []time.Time{cap: n} times << ce.next(ref)! diff --git a/src/cron/expression/c/expression.h b/src/cron/expression/c/expression.h deleted file mode 100644 index f6d8826..0000000 --- a/src/cron/expression/c/expression.h +++ /dev/null @@ -1,43 +0,0 @@ -#include -#include -#include -#include - -enum cron_parse_error { - CPEParseOk = 0, - CPEParseInvalidExpression = 1, - CPEParseInvalidNumber = 2, - CPEParseOutOfRange = 3 -}; - -struct cron_expression { - uint8_t *minutes; - uint8_t *hours; - uint8_t *days; - uint8_t *months; - uint8_t minute_count; - uint8_t hour_count; - uint8_t day_count; - uint8_t month_count; -}; - -struct cron_simple_time { - int year; - int month; - int day; - int hour; - int minute; -}; - -struct cron_expression *ce_init(); - -void cron_ce_free(struct cron_expression *ce); - -/** - * Given a - */ -int cron_ce_next(struct cron_simple_time *out, struct cron_expression *ce, struct ce_simple_time *ref); - -int cron_ce_next_from_now(struct simple_time *out, struct cron_expression *ce); - -enum cron_parse_error cron_ce_parse_expression(struct cron_expression *out, char *s); diff --git a/src/cron/expression/expression_parse_test.v b/src/cron/expression_parse_test.v similarity index 99% rename from src/cron/expression/expression_parse_test.v rename to src/cron/expression_parse_test.v index 92e8291..0b0b605 100644 --- a/src/cron/expression/expression_parse_test.v +++ b/src/cron/expression_parse_test.v @@ -1,4 +1,4 @@ -module expression +module cron // parse_range_error returns the returned error message. If the result is '', // that means the function didn't error. diff --git a/src/cron/expression/expression_test.v b/src/cron/expression_test.v similarity index 83% rename from src/cron/expression/expression_test.v rename to src/cron/expression_test.v index 448927a..e1a9849 100644 --- a/src/cron/expression/expression_test.v +++ b/src/cron/expression_test.v @@ -1,4 +1,4 @@ -module expression +module cron import time { parse } @@ -19,7 +19,7 @@ fn util_test_time(exp string, t1_str string, t2_str string) ! { fn test_next_simple() ! { // Very simple - /* util_test_time('0 3', '2002-01-01 00:00:00', '2002-01-01 03:00:00')! */ + // util_test_time('0 3', '2002-01-01 00:00:00', '2002-01-01 03:00:00')! // Overlap to next day mut exp := '0 3 ' @@ -28,9 +28,9 @@ fn test_next_simple() ! { util_test_time('0 3/4', '2002-01-01 04:00:00', '2002-01-01 07:00:00')! - /* // Overlap to next month */ + //// Overlap to next month util_test_time('0 3', '2002-11-31 04:00:00', '2002-12-01 03:00:00')! - /* // Overlap to next year */ + //// Overlap to next year util_test_time('0 3', '2002-12-31 04:00:00', '2003-01-01 03:00:00')! } diff --git a/src/cron/expression/v.mod b/src/cron/v.mod similarity index 100% rename from src/cron/expression/v.mod rename to src/cron/v.mod diff --git a/src/server/log_removal.v b/src/server/log_removal.v index 8e1a8c2..98cba93 100644 --- a/src/server/log_removal.v +++ b/src/server/log_removal.v @@ -3,12 +3,12 @@ module server import time import models { BuildLog } import os -import cron.expression { CronExpression } +import cron const fallback_log_removal_frequency = 24 * time.hour // log_removal_daemon removes old build logs every `log_removal_frequency`. -fn (mut app App) log_removal_daemon(schedule CronExpression) { +fn (mut app App) log_removal_daemon(schedule cron.Expression) { mut start_time := time.Time{} for { diff --git a/src/server/server.v b/src/server/server.v index 5dd1a20..ae086f5 100644 --- a/src/server/server.v +++ b/src/server/server.v @@ -7,7 +7,7 @@ import repo import util import db import build { BuildJobQueue } -import cron.expression +import cron import metrics const ( @@ -43,11 +43,11 @@ pub fn server(conf Config) ! { util.exit_with_message(1, "'any' is not allowed as the value for default_arch.") } - global_ce := expression.parse_expression(conf.global_schedule) or { + global_ce := cron.parse_expression(conf.global_schedule) or { util.exit_with_message(1, 'Invalid global cron expression: $err.msg()') } - log_removal_ce := expression.parse_expression(conf.log_removal_schedule) or { + log_removal_ce := cron.parse_expression(conf.log_removal_schedule) or { util.exit_with_message(1, 'Invalid log removal cron expression: $err.msg()') }