diary

Text-based journaling program
git clone https://git.in0rdr.ch/diary.git
Log | Files | Refs | Pull requests |Archive | README | LICENSE

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 };