utils.c (12593B)
1 #include "utils.h" 2 3 /* Update the header with the cursor date */ 4 void update_date(WINDOW* header, struct tm* curs_date) { 5 // TODO: dstr for strlen(CONFIG.format) > 16 ? 6 char dstr[16]; 7 mktime(curs_date); 8 strftime(dstr, sizeof dstr, CONFIG.fmt, curs_date); 9 10 wclear(header); 11 mvwaddstr(header, 0, 0, dstr); 12 wrefresh(header); 13 } 14 15 bool date_has_entry(const char* dir, size_t dir_size, const struct tm* i) { 16 char epath[100]; 17 char* pepath = epath; 18 19 // get entry path and check for existence 20 fpath(dir, dir_size, i, &pepath, sizeof epath); 21 22 if (pepath == NULL) { 23 tracepoint(diary, error_date, "Cannot get file path for entry", i); 24 return false; 25 } 26 27 return (access(epath, F_OK) != -1); 28 } 29 30 char* fold(const char* str) { 31 // work on a copy of the str 32 char* strcp = (char *) malloc(strlen(str) * sizeof(char) + 1); 33 if (strcp == NULL) { 34 perror("malloc failed"); 35 return NULL; 36 } 37 strcpy(strcp, str); 38 39 // create buffer for escaped result TEXT 40 char* buf = calloc(1, sizeof(char)); 41 if (buf == NULL) { 42 perror("calloc failed"); 43 return NULL; 44 } 45 46 void* newbuf; 47 // bufl is the current buffer size incl. \0 48 int bufl = 1; 49 // i is the iterator in strcp 50 char* i = strcp; 51 // escch is the char to be escaped, 52 // only written when esc=true 53 char escch = '\0'; 54 bool esc = false; 55 56 while(*i != '\0' || esc) { 57 newbuf = realloc(buf, ++bufl); 58 if (newbuf == NULL) { 59 perror("realloc failed"); 60 free(buf); 61 return NULL; 62 } 63 buf = (char*) newbuf; 64 65 // break lines after 75 chars 66 if ((bufl > 2) && ((bufl % 77) == 0)) { 67 // make room for the cr (\r) below 68 newbuf = realloc(buf, ++bufl); 69 if (newbuf == NULL) { 70 perror("realloc failed"); 71 free(buf); 72 return NULL; 73 } 74 buf = (char*) newbuf; 75 76 // split between any two characters by inserting a CRLF 77 // immediately followed by a white space character 78 buf[bufl-3] = '\r'; 79 buf[bufl-2] = '\n'; 80 escch = ' '; 81 esc = true; 82 continue; 83 } 84 85 if (esc) { 86 // only escape char, do not advance iterator i 87 buf[bufl-2] = escch; 88 esc = false; 89 } else { 90 // escape characters 91 // https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.11 92 switch (*i) { 93 case 0x5c: // backslash 94 buf[bufl-2] = 0x5c; 95 escch = 0x5c; 96 esc = true; 97 break; 98 case ';': 99 buf[bufl-2] = 0x5c; 100 escch = ';'; 101 esc = true; 102 break; 103 case ',': 104 buf[bufl-2] = 0x5c; 105 escch = ','; 106 esc = true; 107 break; 108 case '\n': 109 buf[bufl-2] = 0x5c; 110 escch = 'n'; 111 esc = true; 112 break; 113 default: 114 // write regular character from strcp 115 buf[bufl-2] = *i; 116 break; 117 } 118 i++; 119 } 120 121 // terminate the char string in any case (esc or not) 122 buf[bufl-1] = '\0'; 123 } 124 125 free(strcp); 126 return buf; 127 } 128 129 char* unfold(const char* str) { 130 // work on a copy of the str 131 char* strcp = (char *) malloc(strlen(str) * sizeof(char) + 1); 132 if (strcp == NULL) { 133 perror("malloc failed"); 134 return NULL; 135 } 136 strcpy(strcp, str); 137 138 char* res = strtok(strcp, "\r\n"); 139 140 if (res == NULL) { 141 tracepoint(diary, debug, "No more lines in multiline string, stop unfolding"); 142 free(strcp); 143 return NULL; 144 } 145 146 char* buf = malloc(strlen(res) + 1); 147 if (buf == NULL) { 148 perror("malloc failed"); 149 return NULL; 150 } 151 strcpy(buf, res); 152 153 char* newbuf; 154 regex_t re; 155 regmatch_t pm[1]; 156 157 if (regcomp(&re, "^[^ \t]", 0) != 0) { 158 perror("Failed to compile regex"); 159 return NULL; 160 } 161 162 while (res != NULL) { 163 res = strtok(NULL, "\n"); 164 165 if (res == NULL) { 166 // Stop unfolding if no more lines to unfold 167 break; 168 } 169 170 if (regexec(&re, res, 1, pm, 0) == 0) { 171 // Stop unfolding if line does not start with white space/tab: 172 // https://datatracker.ietf.org/doc/html/rfc2445#section-4.1 173 break; 174 } 175 176 newbuf = realloc(buf, strlen(buf) + strlen(res)); 177 if (newbuf == NULL) { 178 perror("realloc failed"); 179 break; 180 } else { 181 buf = newbuf; 182 strcat(buf, res + 1); 183 } 184 } 185 186 regfree(&re); 187 free(strcp); 188 return buf; 189 } 190 191 /* Find ical key in string. The value of 'start_pos' is set to the start position 192 of the value (match) after the colon (':'). 193 */ 194 char* extract_ical_field(const char* ics, char* key, long* start_pos, bool multiline) { 195 regex_t re; 196 regmatch_t pm[1]; 197 char key_regex[strlen(key) + 2]; 198 sprintf(key_regex, "^%s", key); 199 200 if (regcomp(&re, key_regex, 0) != 0) { 201 perror("Failed to compile regex"); 202 return NULL; 203 } 204 205 // work on a copy of the ical xml response 206 char* icscp = (char *) malloc(strlen(ics) + 1 * sizeof(char)); 207 if (icscp == NULL) { 208 perror("malloc failed"); 209 return NULL; 210 } 211 strcpy(icscp, ics); 212 213 // tokenize ical by newlines 214 char* buf = NULL; 215 char* res = strtok(icscp, "\n"); 216 while (res != NULL) { 217 if (regexec(&re, res, 1, pm, 0) == 0) { 218 // found the key in line 'res' 219 res = strstr(res, ":"); // value 220 res++; // strip the ":" 221 222 *start_pos = res - icscp; 223 224 if (strlen(res) != 0) { 225 // non-empty remote value 226 if (multiline) { 227 buf = unfold(ics + *start_pos); 228 } else { 229 buf = malloc(strlen(res) + 1); 230 if (buf == NULL) { 231 perror("malloc failed"); 232 return NULL; 233 } 234 strcpy(buf, res); 235 } 236 } 237 break; 238 } 239 res = strtok(NULL, "\n"); 240 } 241 242 regfree(&re); 243 free(icscp); 244 return buf; 245 } 246 247 /* Extract xml node content using XPath expression, xmlsoft.org/examples */ 248 char* extract_xml_content(const char* xml, const char* xpathExpression, WINDOW* w, pthread_t* p) { 249 xmlDocPtr doc = NULL; 250 xmlXPathContextPtr xpathCtx = NULL; 251 xmlXPathObjectPtr xpathObj = NULL; 252 253 // Macro to check that the libxml version in use is compatible with the 254 // version the software has been compiled against 255 LIBXML_TEST_VERSION; 256 257 doc = xmlReadMemory(xml, strlen(xml), NULL, NULL, 0); 258 if (doc == NULL) { 259 tracepoint(diary, debug, "Failed to parse xml"); 260 show_info(w, "Failed to parse xml", p); 261 return NULL; 262 } 263 264 xpathCtx = xmlXPathNewContext(doc); 265 if (xpathCtx == NULL) { 266 tracepoint(diary, debug, "Failed to create xpath context"); 267 show_info(w, "Failed to create xpath context", p); 268 xmlFreeDoc(doc); 269 return NULL; 270 } 271 272 xmlChar* xpath = (unsigned char *) xpathExpression; 273 xpathObj = xmlXPathEvalExpression(xpath, xpathCtx); 274 if(xpathObj == NULL) { 275 tracepoint(diary, debug, "Failed to evaluate xpath expression"); 276 show_info(w, "Failed to evaluate xpath expression", p); 277 xmlXPathFreeContext(xpathCtx); 278 xmlFreeDoc(doc); 279 return NULL; 280 } 281 282 char* nodeContent = (char *) xmlNodeGetContent(xpathObj->nodesetval->nodeTab[0]); 283 tracepoint(diary, debug_string, "XML node contents", nodeContent); 284 285 xmlFreeDoc(doc); 286 287 return nodeContent; 288 } 289 290 // Return expanded file path 291 char* expand_path(const char* str) { 292 char* res = ""; 293 wordexp_t str_wordexp; 294 if ( wordexp( str, &str_wordexp, 0 ) == 0) { 295 res = (char *) calloc(strlen(str_wordexp.we_wordv[0]) + 1, sizeof(char)); 296 strcpy(res, str_wordexp.we_wordv[0]); 297 } 298 wordfree(&str_wordexp); 299 return res; 300 } 301 302 // Get last occurence of string in string 303 // https://stackoverflow.com/questions/20213799/finding-last-occurence-of-string 304 char* strrstr(char *haystack, char *needle) { 305 char *i; 306 int nlen = strlen(needle); 307 for (i = haystack + strlen(haystack) - nlen; i >= haystack; i--) { 308 if (strncmp(i, needle, nlen) == 0) { 309 return i; 310 } 311 } 312 return NULL; 313 } 314 315 /* Writes file path for 'date' entry to 'rpath'. '*rpath' is NULL on error. */ 316 void fpath(const char* dir, size_t dir_size, const struct tm* date, char** rpath, size_t rpath_size) 317 { 318 // check size of result path 319 if (dir_size + 1 > rpath_size) { 320 tracepoint(diary, error, "Directory path too long"); 321 *rpath = NULL; 322 return; 323 } 324 325 // add path of the diary dir to result path 326 strcpy(*rpath, dir); 327 328 // check for terminating '/' in path 329 if (dir[dir_size - 1] != '/') { 330 // check size again to accommodate '/' 331 if (dir_size + 1 > rpath_size) { 332 tracepoint(diary, error, "Directory path too long"); 333 *rpath = NULL; 334 return; 335 } 336 strcat(*rpath, "/"); 337 } 338 339 char dstr[16]; 340 strftime(dstr, sizeof dstr, CONFIG.fmt, date); 341 342 // append date to the result path 343 if (strlen(*rpath) + strlen(dstr) > rpath_size) { 344 tracepoint(diary, error, "File path too long"); 345 *rpath = NULL; 346 return; 347 } 348 strcat(*rpath, dstr); 349 } 350 351 bool go_to(WINDOW* calendar, WINDOW* aside, time_t date, int* cur_pad_pos, struct tm* curs_date, struct tm* cal_start, struct tm* cal_end) { 352 if (date < mktime(cal_start) || date > mktime(cal_end)) { 353 return false; 354 } 355 356 int diff_seconds = date - mktime(cal_start); 357 int diff_days = diff_seconds / 60 / 60 / 24; 358 int diff_weeks = diff_days / 7; 359 int diff_wdays = diff_days % 7; 360 361 localtime_r(&date, curs_date); 362 mktime(curs_date); 363 364 int cy, cx; 365 getyx(calendar, cy, cx); 366 367 // remove the STANDOUT attribute from the day we are leaving 368 chtype current_attrs = mvwinch(calendar, cy, cx) & A_ATTRIBUTES; 369 // leave every attr as is, but turn off STANDOUT 370 current_attrs &= ~A_STANDOUT; 371 mvwchgat(calendar, cy, cx, 2, current_attrs, 0, NULL); 372 373 // add the STANDOUT attribute to the day we are entering 374 chtype new_attrs = mvwinch(calendar, diff_weeks, diff_wdays * 3) & A_ATTRIBUTES; 375 new_attrs |= A_STANDOUT; 376 mvwchgat(calendar, diff_weeks, diff_wdays * 3, 2, new_attrs, 0, NULL); 377 378 if (diff_weeks < *cur_pad_pos) 379 *cur_pad_pos = diff_weeks; 380 if (diff_weeks > *cur_pad_pos + LINES - 2) 381 *cur_pad_pos = diff_weeks - LINES + 2; 382 prefresh(aside, *cur_pad_pos, 0, 1, 0, LINES - 1, ASIDE_WIDTH); 383 prefresh(calendar, *cur_pad_pos, 0, 1, ASIDE_WIDTH, LINES - 1, ASIDE_WIDTH + CAL_WIDTH); 384 385 return true; 386 } 387 388 void* show_progress(void* vargp){ 389 struct timespec timer = { 0, 70000000 }; 390 WINDOW* header = (WINDOW*) vargp; 391 mvwprintw(header, 0, COLS - CAL_WIDTH - ASIDE_WIDTH - 11, " syncing "); 392 for(;;) { 393 mvwprintw(header, 0, COLS - CAL_WIDTH - ASIDE_WIDTH - 10, "|"); 394 wrefresh(header); 395 nanosleep(&timer, &timer); 396 mvwprintw(header, 0, COLS - CAL_WIDTH - ASIDE_WIDTH - 10, "/"); 397 wrefresh(header); 398 nanosleep(&timer, &timer); 399 mvwprintw(header, 0, COLS - CAL_WIDTH - ASIDE_WIDTH - 10, "-"); 400 wrefresh(header); 401 nanosleep(&timer, &timer); 402 mvwprintw(header, 0, COLS - CAL_WIDTH - ASIDE_WIDTH - 10, "\\"); 403 wrefresh(header); 404 nanosleep(&timer, &timer); 405 } 406 } 407 408 void show_info(WINDOW* w, char* msg, pthread_t* p) { 409 if (p != NULL) { 410 // cancel any progress spinner 411 pthread_cancel(*p); 412 } 413 414 wclear(w); 415 wresize(w, LINES, getmaxx(w)); 416 mvwaddstr(w, 0, 0, msg); 417 wrefresh(w); 418 419 // accept any input to proceed 420 noecho(); 421 wgetch(w); 422 echo(); 423 wclear(w); 424 } 425 426 config CONFIG = { 427 .dir = NULL, 428 .range = 1, 429 .weekday = 1, 430 .fmt = "%Y-%m-%d", 431 .fmt_cmd = "", 432 .no_pty = false, 433 .no_mouse = false, 434 .editor = "", 435 .caldav_calendar = "", 436 .caldav_server = "", 437 .caldav_username = "", 438 .caldav_password = "", 439 .oauth_eval_cmd = "" 440 };