commit dc30e93184466c41c694d2a6d0c6a65b890f0b77
parent 7c07002cc892dd88e1cb7cdaec54589fe15dcded
Author: Jacob R. Edwards <jacob@jacobedwards.org>
Date: Mon, 11 Mar 2024 17:31:01 -0700
Implement a stimesheet struct
Instead of just using a time_t array, use a struct to make things
more simple. The struct has a time_t array with four times (associated
with a time_field value) as well as the set member which is a bitmask
of time_flag values to determine which time values are set.
The struct is also a doubly linked list to make things easier.
Diffstat:
M | pages/export.c | | | 27 | ++++++++++++--------------- |
M | pages/main.c | | | 115 | ++++++++++++++++++++++++++++++++++++++++--------------------------------------- |
M | stmt.c | | | 3 | +-- |
M | times.c | | | 116 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------- |
M | times.h | | | 22 | +++++++++++++++++++++- |
5 files changed, 180 insertions(+), 103 deletions(-)
diff --git a/pages/export.c b/pages/export.c
@@ -27,10 +27,8 @@ enum kcgi_err
pageexport(struct pagedata *pd)
{
enum kcgi_err status;
- time_t *times;
- size_t len;
- size_t a, i;
- int b;
+ struct timesheet *times, *ts;
+ int n;
char *tmp;
enum kcgi_err (*serialize)(struct pagedata *, time_t) = serialize_ustr;
@@ -48,7 +46,7 @@ pageexport(struct pagedata *pd)
serialize = serialize_epoch;
}
- times = gettimes(pd, pd->user->hash, &len);
+ times = gettimes(pd, pd->user->hash);
if (!times)
return errorpage(pd, KHTTP_500);
@@ -63,28 +61,27 @@ pageexport(struct pagedata *pd)
return status;
}
- for (a = 0; a < len / 4; ++a) {
- for (b = 0; b < 4; ++b) {
- if (b && (status = khttp_putc(&pd->req, '\t')) != KCGI_OK) {
- free(times);
+ for (ts = times; ts; ts = ts->next) {
+ for (n = 0; n < 4; ++n) {
+ if (n && (status = khttp_putc(&pd->req, '\t')) != KCGI_OK) {
+ freetimesheet(times);
return status;
}
- i = (a * 4) + b;
- if (times[i]) {
- status = serialize(pd, times[i]);
+ if (ts->set & timeflagmap[n]) {
+ status = serialize(pd, ts->times[n]);
if (status != KCGI_OK) {
- free(times);
+ freetimesheet(times);
return status;
}
}
}
if ((status = khttp_putc(&pd->req, '\n')) != KCGI_OK) {
- free(times);
+ freetimesheet(times);
return status;
}
}
- free(times);
+ freetimesheet(times);
return KCGI_OK;
}
diff --git a/pages/main.c b/pages/main.c
@@ -25,28 +25,16 @@
static char *datetime_fmt = "%FT%T+0000";
enum kcgi_err
-printhours(struct pagedata *pd, time_t *times, int len)
+printhours(struct pagedata *pd, struct timesheet *ts)
{
enum kcgi_err status;
time_t elapsed;
time_t hr, mn, sc;
struct tm *tm;
char datetime[13]; /* allows up to four digits for hours */
- time_t to, from, breaktime;
+ int final;
- assert(len > 0);
- from = times[StartTime];
- assert(from);
- to = ((len - 1) >= EndTime) ? times[EndTime] : 0;
- breaktime = 0;
-
- if ((len - 1) == BreakStartTime && times[BreakStartTime])
- to = times[BreakStartTime];
- else if ((len - 1) >= BreakEndTime &&
- times[BreakStartTime] && times[BreakEndTime])
- breaktime = times[BreakEndTime] - times[BreakStartTime];
-
- elapsed = ((to ? to : time(NULL)) - from) - breaktime;
+ elapsed = getduration(ts);
assert(elapsed >= 0);
mn = elapsed / 60;
@@ -54,7 +42,8 @@ printhours(struct pagedata *pd, time_t *times, int len)
hr = mn / 60;
mn = mn % 60;
- tm = gmtime(&from);
+ assert(ts->set & StartTimeFlag);
+ tm = gmtime(&ts->times[0]);
if (!tm)
err(1, "gmtime");
@@ -63,7 +52,10 @@ printhours(struct pagedata *pd, time_t *times, int len)
err(1, "snprintf (%lldh %lldm %llds)", hr, mn, sc);
}
- if (!to)
+ final = (ts->set & EndTimeFlag ||
+ (ts->set & BreakStartTimeFlag && !(ts->set & BreakEndTimeFlag)));
+
+ if (final)
status = khtml_attr(&pd->html, KELEM_TIME,
KATTR_ID, "counter", KATTR_DATETIME, datetime, KATTR__MAX);
else
@@ -75,7 +67,7 @@ printhours(struct pagedata *pd, time_t *times, int len)
if ((status = khtml_printf(&pd->html, "%.2lld:%.2lld:%.2lld", hr, mn, sc)) != KCGI_OK)
return status;
- if (!to) {
+ if (!final) {
if ((status = khtml_elem(&pd->html, KELEM_NOSCRIPT)) != KCGI_OK ||
(status = khtml_attr(&pd->html, KELEM_A,
KATTR_HREF, pd->pages[PageMain], KATTR__MAX)) != KCGI_OK ||
@@ -117,7 +109,7 @@ printtimebutton(struct pagedata *pd, enum time_field tf)
/* prints one row in times table */
enum kcgi_err
-printtime(struct pagedata *pd, time_t *times, unsigned int len)
+printtime(struct pagedata *pd, struct timesheet *ts)
{
unsigned int i;
struct tm *tm;
@@ -126,11 +118,8 @@ printtime(struct pagedata *pd, time_t *times, unsigned int len)
char time[6]; /* %R */
enum kcgi_err status;
- if (len > 4)
- return KCGI_SYSTEM;
-
- if (len > 0) {
- tm = gmtime(×[0]);
+ if (ts->set & StartTimeFlag) {
+ tm = gmtime(&ts->times[0]);
if (!tm)
return KCGI_SYSTEM;
if (strftime(date, sizeof(date), "%F", tm) >= sizeof(date))
@@ -155,55 +144,60 @@ printtime(struct pagedata *pd, time_t *times, unsigned int len)
* with no break end time but a start break time mean the
* break end time is the end time.
*/
- if ((len - 1) == EndTime && times[BreakStartTime] && !times[BreakEndTime])
- times[BreakEndTime] = times[EndTime];
+ if (ts->set & BreakStartTimeFlag &&
+ !(ts->set & BreakEndTimeFlag) &&
+ ts->set & EndTimeFlag)
+ timesheet_set(ts, BreakEndTime, ts->times[EndTime]);
for (i = 0; i < 4; ++i) {
- if (i >= len || !times[i]) {
+ if (!(ts->set & timeflagmap[i])) {
if ((status = khtml_elem(&pd->html, KELEM_TD)) != KCGI_OK)
return status;
- if (len == StartTime && i == StartTime)
+ /* This does not validate input */
+ if (i == StartTime)
status = printtimebutton(pd, StartTime);
- else if (len == BreakStartTime && (i == BreakStartTime || i == EndTime))
+ else if (ts->set & StartTimeFlag &&
+ i == BreakStartTime && !(ts->set & EndTimeFlag))
status = printtimebutton(pd, i);
- else if (len > BreakStartTime && ((times[BreakStartTime] && i == BreakEndTime) || i == EndTime))
+ else if (i == BreakEndTime && (ts->set & BreakStartTimeFlag))
+ status = printtimebutton(pd, i);
+ else if (ts->set & StartTimeFlag && i == EndTime)
status = printtimebutton(pd, i);
else
status = khtml_putc(&pd->html, '-');
if (status != KCGI_OK || (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK)
return status;
- continue;
- }
-
- if (i) {
- tm = gmtime(×[i]);
- if (!tm)
+ } else {
+ if (i) {
+ tm = gmtime(&ts->times[i]);
+ if (!tm)
+ return KCGI_SYSTEM;
+ }
+
+ if (strftime(datetime, sizeof(datetime), datetime_fmt, tm) >= sizeof(datetime) ||
+ strftime(time, sizeof(time), "%R", tm) >= sizeof(time))
return KCGI_SYSTEM;
- }
- if (strftime(datetime, sizeof(datetime), datetime_fmt, tm) >= sizeof(datetime) ||
- strftime(time, sizeof(time), "%R", tm) >= sizeof(time))
- return KCGI_SYSTEM;
-
- if ((status = khtml_elem(&pd->html, KELEM_TD)) != KCGI_OK ||
- (status = khtml_attr(&pd->html, KELEM_TIME,
- KATTR_DATETIME, datetime, KATTR__MAX)) != KCGI_OK ||
- (status = khtml_puts(&pd->html, time)) != KCGI_OK ||
- (status = khtml_closeelem(&pd->html, 2)) != KCGI_OK)
- return status;
+ if ((status = khtml_elem(&pd->html, KELEM_TD)) != KCGI_OK ||
+ (status = khtml_attr(&pd->html, KELEM_TIME,
+ KATTR_DATETIME, datetime, KATTR__MAX)) != KCGI_OK ||
+ (status = khtml_puts(&pd->html, time)) != KCGI_OK ||
+ (status = khtml_closeelem(&pd->html, 2)) != KCGI_OK)
+ return status;
+ }
}
if ((status = khtml_elem(&pd->html, KELEM_TD)) != KCGI_OK ||
- (len && (status = printhours(pd, times, len)) != KCGI_OK) ||
+ (ts->set & StartTimeFlag && (status = printhours(pd, ts)) != KCGI_OK) ||
(status = khtml_closeelem(&pd->html, 1)) != KCGI_OK)
return status;
return KCGI_OK;
}
enum kcgi_err
-printtimes(struct pagedata *pd, time_t *times, size_t len)
+printtimes(struct pagedata *pd, struct timesheet *times)
{
unsigned int i;
enum kcgi_err status;
@@ -231,9 +225,9 @@ printtimes(struct pagedata *pd, time_t *times, size_t len)
(status = khtml_elem(&pd->html, KELEM_TBODY)) != KCGI_OK)
return status;
- for (i = 0; i <= len; i += 4) {
+ for (; times; times = times->next) {
if ((status = khtml_elem(&pd->html, KELEM_TR)) != KCGI_OK ||
- (status = printtime(pd, ×[i], Min(len - i, 4))) != KCGI_OK ||
+ (status = printtime(pd, times)) != KCGI_OK ||
(status = khtml_closeelem(&pd->html, 1)) != KCGI_OK)
return status;
}
@@ -258,8 +252,7 @@ pagemain(struct pagedata *pd)
{
enum kcgi_err status;
enum time_field tf;
- time_t *times;
- size_t ntimes;
+ struct timesheet *times, *end;
if (!pd->user)
return errorpage(pd, KHTTP_401);
@@ -278,19 +271,27 @@ pagemain(struct pagedata *pd)
if (status != KCGI_OK)
return status;
- times = gettimes(pd, pd->user->hash, &ntimes);
+ times = gettimes(pd, pd->user->hash);
if (!times)
err(1, "gettimes");
+ for (end = times; end->next; end = end->next)
+ ;
+ if (end->set & EndTimeFlag) {
+ end->next = newtimesheet();
+ if (!end->next)
+ freetimesheet(times);
+ }
+
if ((status = htmlwithin(pd, KELEM_H1, "Timekeeper")) != KCGI_OK ||
- (status = printtimes(pd, times, ntimes)) != KCGI_OK ||
+ (status = printtimes(pd, times)) != KCGI_OK ||
(status = khtml_attr(&pd->html, KELEM_SCRIPT,
KATTR_SRC, "scripts/localize.js", KATTR__MAX)) != KCGI_OK ||
(status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) {
- free(times);
+ freetimesheet(times);
return status;
}
- free(times);
+ freetimesheet(times);
if ((status = htmlwithin(pd, KELEM_H2, "Export")) != KCGI_OK ||
(status = khtml_attr(&pd->html, KELEM_FORM,
diff --git a/stmt.c b/stmt.c
@@ -26,6 +26,5 @@ struct sqlbox_pstmt pstmts[] = {
*/
[StmtSetTime] = { .stmt = "UPDATE userdata SET times =\n"
"json_replace(times, '$[#-1][' || ? || ']', ?) WHERE id IS ? AND json_extract(times, '$[#-1][' || ? || ']') IS null" },
- [StmtGetTimes] = { .stmt = "SELECT json_array_length(times) * 4 FROM userdata UNION ALL\n"
- "SELECT value FROM userdata, json_tree(times) WHERE userdata.id IS ? AND type IS NOT 'array'" }
+ [StmtGetTimes] = { .stmt = "SELECT value FROM userdata, json_tree(times) WHERE userdata.id IS ? AND type IS NOT 'array'" }
};
diff --git a/times.c b/times.c
@@ -3,10 +3,11 @@
#define const
#include <sys/types.h>
+#include <err.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdlib.h>
-#include <err.h>
+#include <time.h>
#include <kcgi.h>
#include <kcgihtml.h>
@@ -25,6 +26,13 @@ char *timefields[] = {
[EndTime] = "end"
};
+enum time_flag timeflagmap[] = {
+ StartTimeFlag,
+ BreakStartTimeFlag,
+ BreakEndTimeFlag,
+ EndTimeFlag
+};
+
enum sqlbox_code
newrow(struct pagedata *pd, char *hash)
{
@@ -52,47 +60,73 @@ settime(struct pagedata *pd, char *hash, enum time_field f, time_t time)
Len(ps), ps, 0) != SQLBOX_CODE_OK;
}
-time_t *
-gettimes(struct pagedata *pd, char *hash, size_t *relem)
+void
+freetimesheet(struct timesheet *ts)
+{
+ if (!ts)
+ return;
+
+ freetimesheet(ts->next);
+ free(ts);
+}
+
+struct timesheet *
+newtimesheet(void)
+{
+ return calloc(1, sizeof(struct timesheet));
+}
+void
+timesheet_set(struct timesheet *ts, enum time_field f, time_t v)
+{
+ assert(f >= StartTime && f <= EndTime);
+ ts->times[f] = v;
+ ts->set |= timeflagmap[f];
+}
+
+struct timesheet *
+gettimes(struct pagedata *pd, char *hash)
{
size_t stmtid;
struct sqlbox_parm p = {
.sparm = hash, .type = SQLBOX_PARM_STRING
};
struct sqlbox_parmset *r;
- time_t *times;
- size_t i;
-
- assert(relem);
+ struct timesheet *times, *oldtime, *time;
+ unsigned int n;
stmtid = sqlbox_prepare_bind(pd->db, pd->dbid, StmtGetTimes, 1, &p, 0);
if (stmtid == 0)
err(1, "prepare bind");
- r = sqlbox_step(pd->db, stmtid);
- if (!r || r->code != SQLBOX_CODE_OK)
- err(1, "step");
-
- assert(r->psz == 1 && r->ps[0].type == SQLBOX_PARM_INT);
- assert(r->ps[0].iparm >= 0);
+ times = oldtime = NULL;
+ for (n = 0; (r = sqlbox_step(pd->db, stmtid)) &&
+ !(!r->psz && r->code == SQLBOX_CODE_OK); n = (n + 1) % 4) {
+ if (n % 4 == 0) {
+ oldtime = time;
+ time = newtimesheet();
+ if (!time) {
+ freetimesheet(times);
+ return NULL;
+ }
+ if (!times)
+ times = time;
+ else if (oldtime) {
+ oldtime->next = time;
+ time->prev = oldtime;
+ }
+ }
- *relem = r->ps[0].iparm;
- kutil_info(NULL,NULL,"Number of times: %zu", *relem);
- times = calloc(*relem, sizeof(*times));
- if (!times)
- err(1, "calloc");
-
- for (i = 0; i < *relem && (r = sqlbox_step(pd->db, stmtid)) &&
- !(!r->psz && r->code == SQLBOX_CODE_OK); ++i) {
assert(r->psz == 1);
- assert(r->ps[0].type == SQLBOX_PARM_INT ||
- r->ps[0].type == SQLBOX_PARM_NULL);
- times[i] = (r->ps[0].type == SQLBOX_PARM_NULL) ? 0 : r->ps[0].iparm;
+ switch (r->ps[0].type) {
+ case SQLBOX_PARM_NULL:
+ break;
+ case SQLBOX_PARM_INT:
+ timesheet_set(time, n, r->ps[0].iparm);
+ break;
+ default:
+ err(1, "This is a stmt.c bug");
+ }
}
- assert(r);
-
- while (*relem > 0 && !times[*relem - 1])
- --*relem;
if (!sqlbox_finalise(pd->db, stmtid)) {
free(times);
@@ -101,3 +135,29 @@ gettimes(struct pagedata *pd, char *hash, size_t *relem)
return times;
}
+
+time_t
+getduration(struct timesheet *ts)
+{
+ time_t s, e, o;
+
+ assert(ts->set & StartTimeFlag);
+
+ o = 0;
+ s = ts->times[StartTime];
+ if (ts->set & EndTimeFlag)
+ e = ts->times[EndTime];
+ else
+ e = time(NULL);
+
+ if (ts->set & BreakStartTimeFlag) {
+ if (ts->set & BreakEndTimeFlag) {
+ o = ts->times[BreakEndTime] - ts->times[BreakStartTime];
+ } else {
+ assert(!(ts->set & EndTimeFlag));
+ e = ts->times[BreakStartTime];
+ }
+ }
+
+ return e - s - o;
+}
diff --git a/times.h b/times.h
@@ -5,8 +5,28 @@ enum time_field {
EndTime
};
+enum time_flag {
+ StartTimeFlag = 1,
+ BreakStartTimeFlag = 2,
+ BreakEndTimeFlag = 4,
+ EndTimeFlag = 8
+};
+
+struct timesheet;
+
+struct timesheet {
+ time_t times[4]; /* raw time data */
+ enum time_flag set; /* which times are set */
+ struct timesheet *prev, *next; /* previous and next entry */
+};
+
extern char *timefields[];
+extern enum time_flag timeflagmap[];
enum sqlbox_code newrow(struct pagedata *pd, char *hash);
enum sqlbox_code settime(struct pagedata *pd, char *hash, enum time_field f, time_t time);
-time_t *gettimes(struct pagedata *pd, char *hash, size_t *relem);
+void freetimesheet(struct timesheet *ts);
+struct timesheet *newtimesheet(void);
+void timesheet_set(struct timesheet *ts, enum time_field f, time_t v);
+struct timesheet *gettimes(struct pagedata *pd, char *hash);
+time_t getduration(struct timesheet *ts);