main.c (12491B)
1 #include <assert.h> 2 3 #define const 4 5 #include <sys/types.h> 6 #include <stdarg.h> 7 #include <stdint.h> 8 #include <time.h> 9 #include <err.h> 10 #include <kcgi.h> 11 #include <stdio.h> 12 #include <stdlib.h> 13 #include <string.h> 14 15 #include <sqlbox.h> 16 17 #include "common.h" 18 #include "pages.h" 19 20 #include "../menu.h" 21 #include "../times.h" 22 #include "../util.h" 23 24 #define DateTimeSize 25 25 26 static char *datetime_fmt = "%FT%T+0000"; 27 28 enum kcgi_err 29 printhours(struct pagedata *pd, struct timesheet *ts) 30 { 31 int final; 32 enum kcgi_err status; 33 34 assert(ts->set & StartTimeFlag); 35 36 final = (ts->set & EndTimeFlag || 37 (ts->set & BreakStartTimeFlag && !(ts->set & BreakEndTimeFlag))); 38 39 status = htmlduration(pd, getduration(ts), final ? NULL : "counter", 1); 40 if (status != KCGI_OK) 41 return status; 42 43 if (!final) { 44 if ((status = khtml_elem(&pd->html, KELEM_NOSCRIPT)) != KCGI_OK || 45 (status = khtml_attr(&pd->html, KELEM_A, 46 KATTR_HREF, pd->pages[PageMain], KATTR__MAX)) != KCGI_OK || 47 (status = khtml_elem(&pd->html, KELEM_BUTTON)) != KCGI_OK || 48 (status = khtml_ncr(&pd->html, 0x21BB)) != KCGI_OK || 49 (status = khtml_closeelem(&pd->html, 3)) != KCGI_OK) 50 return status; 51 } 52 53 return KCGI_OK; 54 } 55 56 enum kcgi_err 57 printtotal(struct pagedata *pd, struct timesheet *ts) 58 { 59 enum kcgi_err status; 60 time_t t; 61 int i; 62 63 t = 0; 64 for (; ts; ts = ts->next) 65 if (ts->set & StartTimeFlag) 66 t += getduration(ts); 67 68 if ((status = khtml_attr(&pd->html, 69 KELEM_TH, KATTR_SCOPE, "row", KATTR__MAX)) != KCGI_OK || 70 (status = khtml_puts(&pd->html, "Total")) != KCGI_OK || 71 (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) 72 return status; 73 74 for (i = StartTime; i <= EndTime + 1; ++i) 75 if ((status = khtml_elem(&pd->html, KELEM_TD)) != KCGI_OK || 76 (i > EndTime && (status = htmlduration(pd, t, "total", 1)) != KCGI_OK) || 77 (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) 78 return status; 79 return KCGI_OK; 80 } 81 82 enum kcgi_err 83 printtimebutton(struct pagedata *pd, enum time_field tf) 84 { 85 enum kcgi_err status; 86 static char *buttons[] = { 87 [StartTime] = "Start", 88 [BreakStartTime] = "Start Break", 89 [BreakEndTime] = "End Break", 90 [EndTime] = "Stop Time" 91 }; 92 93 if ((status = khtml_elem(&pd->html, KELEM_FORM)) != KCGI_OK || 94 (status = khtml_attr(&pd->html, KELEM_INPUT, 95 KATTR_TYPE, "hidden", 96 KATTR_NAME, pd->keys[KeyTime].name, 97 KATTR_VALUE, timefields[tf], 98 KATTR__MAX)) != KCGI_OK || 99 (status = khtml_attr(&pd->html, KELEM_INPUT, 100 KATTR_TYPE, "submit", 101 KATTR_VALUE, buttons[tf], KATTR__MAX)) != KCGI_OK || 102 (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) 103 return status; 104 return KCGI_OK; 105 } 106 107 /* prints one row in times table */ 108 enum kcgi_err 109 printtime(struct pagedata *pd, struct timesheet *ts) 110 { 111 unsigned int i; 112 struct tm *tm; 113 char datetime[DateTimeSize]; 114 char date[11]; /* %F */ 115 char time[6]; /* %R */ 116 enum kcgi_err status; 117 118 if (ts->set & StartTimeFlag) { 119 tm = gmtime(&ts->times[0]); 120 if (!tm) 121 return KCGI_SYSTEM; 122 if (strftime(date, sizeof(date), "%F", tm) >= sizeof(date)) 123 return KCGI_SYSTEM; 124 } else { 125 date[0] = 0; 126 } 127 128 if ((status = khtml_attr(&pd->html, KELEM_TH, 129 KATTR_SCOPE, "row", KATTR__MAX)) != KCGI_OK || 130 (date[0] && ( 131 (status = khtml_elem(&pd->html, KELEM_TIME)) != KCGI_OK || 132 (status = khtml_puts(&pd->html, date)) != KCGI_OK || 133 (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK)) || 134 (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) 135 return status; 136 137 for (i = 0; i < 4; ++i) { 138 if ((status = khtml_elem(&pd->html, KELEM_TD)) != KCGI_OK) 139 return status; 140 if (!(ts->set & timeflagmap[i])) { 141 /* This does not validate input */ 142 if (i == StartTime) 143 status = printtimebutton(pd, StartTime); 144 else if (ts->set & StartTimeFlag && 145 i == BreakStartTime && !(ts->set & EndTimeFlag)) 146 status = printtimebutton(pd, i); 147 else if (i == BreakEndTime && (ts->set & BreakStartTimeFlag)) 148 status = printtimebutton(pd, i); 149 else if (ts->set & StartTimeFlag && i == EndTime) 150 status = printtimebutton(pd, i); 151 else 152 status = khtml_putc(&pd->html, '-'); 153 if (status != KCGI_OK) 154 return status; 155 } else { 156 if (i) { 157 tm = gmtime(&ts->times[i]); 158 if (!tm) 159 return KCGI_SYSTEM; 160 } 161 162 if (strftime(datetime, sizeof(datetime), datetime_fmt, tm) >= sizeof(datetime) || 163 strftime(time, sizeof(time), "%R", tm) >= sizeof(time)) 164 return KCGI_SYSTEM; 165 166 if ((status = khtml_attr(&pd->html, KELEM_TIME, 167 KATTR_DATETIME, datetime, KATTR__MAX)) != KCGI_OK || 168 (status = khtml_puts(&pd->html, time)) != KCGI_OK || 169 (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) 170 return status; 171 } 172 if ((status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) 173 return status; 174 } 175 176 if ((status = khtml_elem(&pd->html, KELEM_TD)) != KCGI_OK || 177 (ts->set & StartTimeFlag && (status = printhours(pd, ts)) != KCGI_OK) || 178 (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) 179 return status; 180 return KCGI_OK; 181 } 182 183 enum kcgi_err 184 printtimefunc(struct pagedata *pd, struct timesheet *ts, 185 char *key, char *name) 186 { 187 enum kcgi_err status; 188 189 if ((status = khtml_elem(&pd->html, KELEM_TD)) != KCGI_OK || 190 (status = khtml_elem(&pd->html, KELEM_FORM)) != KCGI_OK || 191 (status = khtml_attrx(&pd->html, KELEM_INPUT, 192 KATTR_TYPE, KATTRX_STRING, "hidden", 193 KATTR_NAME, KATTRX_STRING, key, 194 KATTR_VALUE, KATTRX_INT, (uint64_t)ts->entry, 195 KATTR__MAX)) != KCGI_OK || 196 (status = khtml_attrx(&pd->html, KELEM_INPUT, 197 KATTR_TYPE, KATTRX_STRING, "submit", 198 KATTR_VALUE, KATTRX_STRING, name, 199 KATTR__MAX)) != KCGI_OK || 200 (status = khtml_closeelem(&pd->html, 2)) != KCGI_OK) 201 return status; 202 return KCGI_OK; 203 } 204 205 enum kcgi_err 206 printtimefuncs(struct pagedata *pd, struct timesheet *ts) 207 { 208 enum kcgi_err status; 209 210 if (ts->set & StartTimeFlag && 211 (status = printtimefunc(pd, ts, "delete", "Delete")) != KCGI_OK) 212 return status; 213 return KCGI_OK; 214 } 215 216 int 217 iscurrent(struct pagedata *pd) 218 { 219 return (pd->req.fieldmap[KeyPeriod] ? 220 pd->req.fieldmap[KeyPeriod]->parsed.i : 0) == 0; 221 } 222 223 enum kcgi_err 224 printtimes(struct pagedata *pd, struct timesheet *times) 225 { 226 unsigned int i; 227 enum kcgi_err status; 228 char *headers[] = { 229 "Date", "Start time", "Break start", "Break end", "End time", "Hours", "" 230 }; 231 struct timesheet *p; 232 233 if ((status = khtml_attr(&pd->html, KELEM_TABLE, 234 KATTR_CLASS, "timetable", KATTR__MAX)) != KCGI_OK || 235 (status = htmlwithin(pd, KELEM_CAPTION, "Timesheets")) != KCGI_OK) 236 return status; 237 238 if ((status = khtml_elem(&pd->html, KELEM_THEAD)) != KCGI_OK || 239 (status = khtml_elem(&pd->html, KELEM_TR)) != KCGI_OK) 240 return status; 241 242 for (i = 0; i < Len(headers); ++i) { 243 if ((status = khtml_attr(&pd->html, KELEM_TH, 244 KATTR_SCOPE, "col", KATTR__MAX)) != KCGI_OK || 245 (status = khtml_puts(&pd->html, headers[i])) != KCGI_OK || 246 (i == 5 && ( 247 (status = khtml_attr(&pd->html, KELEM_A, 248 KATTR_HREF, "#total", KATTR__MAX)) != KCGI_OK || 249 (status = khtml_ncr(&pd->html, 0x2193)) != KCGI_OK || 250 (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK)) || 251 (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) 252 return status; 253 } 254 255 if ((status = khtml_closeelem(&pd->html, 2)) != KCGI_OK || 256 (status = khtml_elem(&pd->html, KELEM_TBODY)) != KCGI_OK) 257 return status; 258 259 for (p = times; p->next; p = p->next) 260 ; 261 for (; p; p = p->prev) { 262 if ((status = khtml_elem(&pd->html, KELEM_TR)) != KCGI_OK || 263 (status = printtime(pd, p)) != KCGI_OK || 264 (iscurrent(pd) && (status = printtimefuncs(pd, p)) != KCGI_OK) || 265 (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) 266 return status; 267 } 268 269 if ((status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) 270 return status; 271 272 if (times->set & EndTimeFlag && 273 (times->next && times->next->set & StartTimeFlag)) { 274 if ((status = khtml_elem(&pd->html, KELEM_TFOOT)) != KCGI_OK || 275 (status = khtml_elem(&pd->html, KELEM_TR)) != KCGI_OK || 276 (status = printtotal(pd, times)) != KCGI_OK || 277 (status = khtml_closeelem(&pd->html, 2)) != KCGI_OK) 278 return status; 279 } 280 281 return khtml_closeelem(&pd->html, 2); 282 } 283 284 int 285 gettf(char *tfs) 286 { 287 int i; 288 289 for (i = 0; i <= EndTime; ++i) { 290 if (strcmp(timefields[i], tfs) == 0) 291 return i; 292 } 293 return -1; 294 } 295 296 enum kcgi_err 297 exportform(struct pagedata *pd) 298 { 299 enum kcgi_err status; 300 301 if ((status = khtml_attr(&pd->html, KELEM_FORM, 302 KATTR_ACTION, pd->pages[PageExport], KATTR__MAX)) != KCGI_OK || 303 (status = khtml_elem(&pd->html, KELEM_LABEL)) != KCGI_OK || 304 ((pd->req.fieldmap[KeyPeriod] && 305 pd->req.fieldmap[KeyPeriod]->parsed.i > 0) && 306 (status = khtml_attrx(&pd->html, KELEM_INPUT, 307 KATTR_TYPE, KATTRX_STRING, "hidden", 308 KATTR_NAME, KATTRX_STRING, pd->keys[KeyPeriod].name, 309 KATTR_VALUE, KATTRX_INT, 310 (int64_t)pd->req.fieldmap[KeyPeriod]->parsed.i, 311 KATTR__MAX)) != KCGI_OK) || 312 (status = khtml_putc(&pd->html, ' ')) != KCGI_OK || 313 (status = khtml_attr(&pd->html, KELEM_INPUT, 314 KATTR_TYPE, "submit", 315 KATTR_VALUE, "Export", KATTR__MAX)) != KCGI_OK || 316 (status = khtml_attr(&pd->html, KELEM_INPUT, 317 KATTR_TYPE, "checkbox", 318 KATTR_NAME, keys[KeyFormat].name, 319 KATTR_VALUE, ".epoch", KATTR__MAX)) != KCGI_OK || 320 (status = khtml_puts(&pd->html, "Epoch times")) != KCGI_OK || 321 (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK || 322 (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) 323 return status; 324 return KCGI_OK; 325 } 326 327 enum kcgi_err 328 breakform(struct pagedata *pd) 329 { 330 enum kcgi_err status; 331 332 if ((status = khtml_elem(&pd->html, KELEM_FORM)) != KCGI_OK || 333 (status = khtml_putc(&pd->html, ' ')) != KCGI_OK || 334 (status = khtml_elem(&pd->html, KELEM_LABEL)) != KCGI_OK || 335 (status = khtml_attr(&pd->html, KELEM_INPUT, 336 KATTR_TYPE, "submit", 337 KATTR_VALUE, "End period", KATTR__MAX)) != KCGI_OK || 338 (status = khtml_attr(&pd->html, KELEM_INPUT, 339 KATTR_TYPE, "hidden", 340 KATTR_NAME, keys[KeyBreak].name, 341 KATTR_VALUE, "true", KATTR__MAX)) != KCGI_OK || 342 (status = khtml_closeelem(&pd->html, 2)) != KCGI_OK) 343 return status; 344 return KCGI_OK; 345 } 346 347 enum kcgi_err 348 pagemain(struct pagedata *pd) 349 { 350 static char *css[] = { 351 "css/main.css", "css/timesheet.css", "css/menu.css", NULL 352 }; 353 static char *scripts[] = { 354 "scripts/localize.js", 355 "scripts/counter.js", 356 NULL 357 }; 358 static struct pagetemplate template = { 359 "Timesheet", 360 .css = css, 361 .scripts = scripts 362 }; 363 364 enum kcgi_err status; 365 enum time_field tf; 366 struct timesheet *times, *end; 367 unsigned int period; 368 int current; 369 char heading[64]; 370 int h1len; 371 struct menuitem menuitems[] = { 372 { MenuFunc, { .func = breakform } }, 373 { MenuFunc, { .func = exportform } }, 374 { MenuURL, { .url = pd->pages[PageArchive] } } 375 }; 376 int menuoffset; 377 378 if (!pd->user) 379 return tk_prompt_login(pd); 380 381 period = pd->req.fieldmap[KeyPeriod] ? 382 pd->req.fieldmap[KeyPeriod]->parsed.i : 0; 383 current = period == 0; 384 385 if (current) { 386 if (pd->req.fieldmap[KeyBreak]) { 387 if (breaktime(pd, pd->user->hash)) 388 err(1, "Unable to break time"); 389 } 390 if (pd->req.fieldmap[KeyTime]) { 391 tf = gettf(pd->req.fieldmap[KeyTime]->parsed.s); 392 if (tf < 0) 393 err(1, "Invalid time field"); 394 if (settime(pd, pd->user->hash, tf, time(NULL))) 395 return KCGI_SYSTEM; 396 } 397 if (pd->req.fieldmap[KeyDelete]) { 398 if (!kvalid_uint(pd->req.fieldmap[KeyDelete])) 399 return KCGI_SYSTEM; 400 if (deletetime(pd, pd->user->hash, pd->req.fieldmap[KeyDelete]->parsed.i)) 401 return KCGI_SYSTEM; 402 } 403 } 404 405 status = tk_startpage(pd, &template, KHTTP_200); 406 if (status != KCGI_OK) 407 return status; 408 409 if (gettimes(pd, pd->user->hash, period, ×)) 410 err(1, "gettimes"); 411 if (current && !times && !(times = newtimesheet())) 412 err(1, "gettimes"); 413 414 for (end = times; end->next; end = end->next) 415 ; 416 if (current && end->set & EndTimeFlag && 417 !inserttimesheet(newtimesheet(), end)) { 418 freetimesheet(times); 419 return KCGI_SYSTEM; 420 } 421 422 if (current) 423 h1len = strlcpy(heading, "Timekeeper", sizeof(heading)); 424 else 425 h1len = snprintf(heading, sizeof(heading), "Timekeeper, Period %d", period); 426 if (h1len < 0 || (unsigned int)h1len >= sizeof(heading)) 427 return KCGI_SYSTEM; 428 429 menuoffset = current ? 0 : 1; 430 431 if ((status = htmlwithin(pd, KELEM_H1, heading)) != KCGI_OK || 432 (status = printtimes(pd, times)) != KCGI_OK || 433 (status = htmlmenu(pd, menuitems + menuoffset, Len(menuitems) - menuoffset)) != KCGI_OK) { 434 freetimesheet(times); 435 return status; 436 } 437 freetimesheet(times); 438 439 return endpage(pd, &template); 440 }