diary

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

commit 77592cdf2435b0b20728556a31936ae0d7c973e0
parent 4da97e90728f15da456179f8a25c5979b54ca791
Author: Andreas Gruhler <agruhl@gmx.ch>
Date:   Sun, 31 Oct 2021 00:37:48 +0200

Merge pull request #77 from in0rdr/feature/ics-import

Import ICS files
Diffstat:
M.obs/workflows.yml | 2+-
MMakefile | 5++++-
MREADME.md | 1+
MTESTING.md | 16++++++++++++++++
Mman1/diary.1 | 1+
Msrc/caldav.c | 46++++++++++++++++------------------------------
Msrc/caldav.h | 1-
Msrc/diary.c | 80+++++++++++++++++++++++++++++++++----------------------------------------------
Msrc/diary.h | 2+-
Asrc/import.c | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/import.h | 17+++++++++++++++++
Msrc/utils.c | 117++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Msrc/utils.h | 5++++-
13 files changed, 311 insertions(+), 101 deletions(-)

diff --git a/.obs/workflows.yml b/.obs/workflows.yml @@ -1,6 +1,6 @@ workflow: steps: - - branch_package: + - rebuild_package: source_project: home:in0rdr source_package: diary-nightly filters: diff --git a/Makefile b/Makefile @@ -1,6 +1,6 @@ TARGET = diary SRCDIR = src/ -_SRC = utils.c caldav.c diary.c +_SRC = import.c utils.c caldav.c diary.c SRC = $(addprefix $(SRCDIR), $(_SRC)) PREFIX ?= /usr/local BINDIR ?= $(DESTDIR)$(PREFIX)/bin @@ -32,6 +32,9 @@ default: $(TARGET) $(TARGET): $(SRC) $(CC) $(SRC) -o $(TARGET) $(CFLAGS) $(LIBS) +debug: $(SRC) + $(CC) $(SRC) -o $(TARGET) $(CFLAGS) -g $(LIBS) + clean: rm -f $(TARGET) diff --git a/README.md b/README.md @@ -33,6 +33,7 @@ This is a text-based diary, inspired by [khal](https://github.com/pimutils/khal) d, x delete current entry s sync current entry with CalDAV server (ALPHA) S sync all entries with CalDAV server (ALPHA) + i import entries from .ics file (ALPHA) t jump to today f jump to or find specific day diff --git a/TESTING.md b/TESTING.md @@ -2,6 +2,22 @@ This file holds notes for testing purposes. +## Valgrind + +Use Valgrind on a build with debug symbols to discover memory issues: +```bash +mkdir -p tmp-journal +make debug +valgrind --leak-check=full ./diary tmp-journal/ 2>log.txt +``` + +## Compile with Debug Symbols + +To make a build with debug symbols use the `debug` target: +```bash +make debug +``` + ## Send stderr to File Send stderr to a file for debugging: diff --git a/man1/diary.1 b/man1/diary.1 @@ -54,6 +54,7 @@ e, Enter | edit current entry d, x | delete current entry s | sync current entry with CalDAV server (ALPHA) S | sync all entries with CalDAV server (ALPHA) +i | import entries from .ics file (ALPHA) t | jump to today f | jump to or find specific day diff --git a/src/caldav.c b/src/caldav.c @@ -587,25 +587,6 @@ void put_event(struct tm* date, const char* dir, size_t dir_size, char* calendar } } -void* show_progress(void* vargp){ - WINDOW* header = (WINDOW*) vargp; - mvwprintw(header, 0, COLS - CAL_WIDTH - ASIDE_WIDTH - 11, " syncing "); - for(;;) { - mvwprintw(header, 0, COLS - CAL_WIDTH - ASIDE_WIDTH - 10, "|"); - wrefresh(header); - usleep(200000); - mvwprintw(header, 0, COLS - CAL_WIDTH - ASIDE_WIDTH - 10, "/"); - wrefresh(header); - usleep(200000); - mvwprintw(header, 0, COLS - CAL_WIDTH - ASIDE_WIDTH - 10, "-"); - wrefresh(header); - usleep(200000); - mvwprintw(header, 0, COLS - CAL_WIDTH - ASIDE_WIDTH - 10, "\\"); - wrefresh(header); - usleep(200000); - } -} - /* * Sync with CalDAV server. * Returns the answer char of the confirmation dialogue @@ -664,7 +645,7 @@ int caldav_sync(struct tm* date, char* tokenfile_path = expand_path(CONFIG.google_tokenfile); if (user_principal == NULL) { - fprintf(stderr, "Unable to fetch principal due to invalid tokenfile. Removing tokenfile '%s'.\n", CONFIG.google_tokenfile); + fprintf(stderr, "Unable to fetch principal. Offline or invalid tokenfile. Removing tokenfile '%s'.\n", CONFIG.google_tokenfile); wclear(header); mvwprintw(header, 0, 0, "Invalid Google OAuth2 credentials, removing tokenfile at '%s'. Please retry.", CONFIG.google_tokenfile); @@ -753,7 +734,9 @@ int caldav_sync(struct tm* date, } struct tm* localfile_time = gmtime(&attr.st_mtime); fprintf(stderr, "Local dst: %i\n", localfile_time->tm_isdst); - //local_time->tm_isdst = -1; + // set to negative value, so mktime uses timezone information and system databases + // to attempt to determine whether DST is in effect at the specified time + localfile_time->tm_isdst = -1; time_t localfile_date = mktime(localfile_time); fprintf(stderr, "Local dst: %i\n", localfile_time->tm_isdst); fprintf(stderr, "Local file last modified time: %s\n", ctime(&localfile_date)); @@ -761,21 +744,24 @@ int caldav_sync(struct tm* date, struct tm remote_datetime; time_t remote_date; + long search_pos = 0; // check remote LAST-MODIFIED:20210521T212441Z of remote event - char* remote_last_mod = extract_ical_field(event, "LAST-MODIFIED", false); - char* remote_uid = extract_ical_field(event, "UID", false); - fprintf(stderr, "Remote last modified: %s\n", remote_last_mod); - fprintf(stderr, "Remote UID: %s\n", remote_uid); - if (remote_last_mod == NULL) { + char* remote_last_mod = extract_ical_field(event, "LAST-MODIFIED", &search_pos, false); + char* remote_uid = extract_ical_field(event, "UID", &search_pos, false); + + if (remote_last_mod == NULL || remote_uid == NULL) { remote_file_exists = false; } else { + fprintf(stderr, "Remote last modified: %s\n", remote_last_mod); + fprintf(stderr, "Remote UID: %s\n", remote_uid); strptime(remote_last_mod, "%Y%m%dT%H%M%SZ", &remote_datetime); //remote_datetime.tm_isdst = -1; fprintf(stderr, "Remote dst: %i\n", remote_datetime.tm_isdst); remote_date = mktime(&remote_datetime); fprintf(stderr, "Remote dst: %i\n", remote_datetime.tm_isdst); fprintf(stderr, "Remote last modified: %s\n", ctime(&remote_date)); + free(remote_last_mod); } if (! (local_file_exists || remote_file_exists)) { @@ -788,7 +774,7 @@ int caldav_sync(struct tm* date, double timediff = difftime(localfile_date, remote_date); fprintf(stderr, "Time diff between local and remote mod time:%e\n", timediff); - if ((timediff > 0 && local_file_exists) || (local_file_exists && !remote_file_exists)) { + if (local_file_exists && (timediff > 0 || !remote_file_exists)) { // local time > remote time // if local file mod time more recent than LAST-MODIFIED @@ -811,8 +797,8 @@ int caldav_sync(struct tm* date, char* rmt_desc; char dstr[16]; int conf_ch; - if ((timediff < 0 && remote_file_exists) || (!local_file_exists && remote_file_exists)) { - rmt_desc = extract_ical_field(event, "DESCRIPTION", true); + if (remote_file_exists && (timediff < 0 || !local_file_exists)) { + rmt_desc = extract_ical_field(event, "DESCRIPTION", &search_pos, true); fprintf(stderr, "Remote event description:%s\n", rmt_desc); if (rmt_desc == NULL) { @@ -832,7 +818,7 @@ int caldav_sync(struct tm* date, // ask for confirmation strftime(dstr, sizeof dstr, CONFIG.fmt, date); - mvwprintw(header, 0, 0, "Remote event is more recent. Sync entry '%s' and overwrite local file? [(Y)es/(a)all/(n)o/(c)ancel] ", dstr); + mvwprintw(header, 0, 0, "Remote event is more recent. Sync entry '%s' and overwrite local file? [(Y)es/(a)ll/(n)o/(c)ancel] ", dstr); char* i; bool conf = false; while (!conf) { diff --git a/src/caldav.h b/src/caldav.h @@ -6,7 +6,6 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <unistd.h> #include <pthread.h> #include <time.h> #include <sys/stat.h> diff --git a/src/diary.c b/src/diary.c @@ -77,40 +77,6 @@ void draw_calendar(WINDOW* number_pad, WINDOW* month_pad, const char* diary_dir, } } -bool go_to(WINDOW* calendar, WINDOW* aside, time_t date, int* cur_pad_pos) { - if (date < mktime(&cal_start) || date > mktime(&cal_end)) - return false; - - int diff_seconds = date - mktime(&cal_start); - int diff_days = diff_seconds / 60 / 60 / 24; - int diff_weeks = diff_days / 7; - int diff_wdays = diff_days % 7; - - localtime_r(&date, &curs_date); - - getyx(calendar, cy, cx); - - // remove the STANDOUT attribute from the day we are leaving - chtype current_attrs = mvwinch(calendar, cy, cx) & A_ATTRIBUTES; - // leave every attr as is, but turn off STANDOUT - current_attrs &= ~A_STANDOUT; - mvwchgat(calendar, cy, cx, 2, current_attrs, 0, NULL); - - // add the STANDOUT attribute to the day we are entering - chtype new_attrs = mvwinch(calendar, diff_weeks, diff_wdays * 3) & A_ATTRIBUTES; - new_attrs |= A_STANDOUT; - mvwchgat(calendar, diff_weeks, diff_wdays * 3, 2, new_attrs, 0, NULL); - - if (diff_weeks < *cur_pad_pos) - *cur_pad_pos = diff_weeks; - if (diff_weeks > *cur_pad_pos + LINES - 2) - *cur_pad_pos = diff_weeks - LINES + 2; - prefresh(aside, *cur_pad_pos, 0, 1, 0, LINES - 1, ASIDE_WIDTH); - prefresh(calendar, *cur_pad_pos, 0, 1, ASIDE_WIDTH, LINES - 1, ASIDE_WIDTH + CAL_WIDTH); - - return true; -} - /* Update window 'win' with diary entry from date 'date' */ void display_entry(const char* dir, size_t dir_size, const struct tm* date, WINDOW* win, int width) { char path[100]; @@ -405,6 +371,7 @@ int main(int argc, char** argv) { return 0; break; case 'd': + free(CONFIG.dir); // set diary directory from option character CONFIG.dir = (char *) calloc(strlen(optarg) + 1, sizeof(char)); strcpy(CONFIG.dir, optarg); @@ -434,6 +401,7 @@ int main(int argc, char** argv) { } if (optind < argc) { + free(CONFIG.dir); // set diary directory from first non-option argv-element, // required for backwarad compatibility with diary <= 0.4 CONFIG.dir = (char *) calloc(strlen(argv[optind]) + 1, sizeof(char)); @@ -474,12 +442,14 @@ int main(int argc, char** argv) { int ch, conf_ch; int pad_pos = 0; int syear = 0, smonth = 0, sday = 0; + char ics_input_filepath[256]; + char* expanded_ics_input_filepath; struct tm new_date; int prev_width = COLS - ASIDE_WIDTH - CAL_WIDTH; int prev_height = LINES - 1; size_t diary_dir_size = strlen(CONFIG.dir); - bool mv_valid = go_to(cal, aside, raw_time, &pad_pos); + bool mv_valid = go_to(cal, aside, raw_time, &pad_pos, &curs_date, &cal_start, &cal_end); // mark current day atrs = winch(cal) & A_ATTRIBUTES; wchgat(cal, 2, atrs | A_UNDERLINE, 0, NULL); @@ -509,32 +479,32 @@ int main(int argc, char** argv) { case 'j': case KEY_DOWN: new_date.tm_mday += 7; - mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos); + mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos, &curs_date, &cal_start, &cal_end); break; case 'k': case KEY_UP: new_date.tm_mday -= 7; - mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos); + mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos, &curs_date, &cal_start, &cal_end); break; case 'l': case KEY_RIGHT: new_date.tm_mday++; - mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos); + mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos, &curs_date, &cal_start, &cal_end); break; case 'h': case KEY_LEFT: new_date.tm_mday--; - mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos); + mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos, &curs_date, &cal_start, &cal_end); break; // jump to top/bottom of page case 'g': new_date = find_closest_entry(cal_start, false, CONFIG.dir, diary_dir_size); - mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos); + mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos, &curs_date, &cal_start, &cal_end); break; case 'G': new_date = find_closest_entry(cal_end, true, CONFIG.dir, diary_dir_size); - mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos); + mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos, &curs_date, &cal_start, &cal_end); break; // jump backward/forward by a month @@ -542,12 +512,12 @@ int main(int argc, char** argv) { if (new_date.tm_mday == 1) new_date.tm_mon--; new_date.tm_mday = 1; - mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos); + mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos, &curs_date, &cal_start, &cal_end); break; case 'J': new_date.tm_mon++; new_date.tm_mday = 1; - mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos); + mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos, &curs_date, &cal_start, &cal_end); break; // find specific date @@ -561,14 +531,14 @@ int main(int argc, char** argv) { // struct tm.tm_mon in range [0, 11] new_date.tm_mon = smonth - 1; new_date.tm_mday = sday; - mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos); + mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos, &curs_date, &cal_start, &cal_end); } curs_set(0); break; // today shortcut case 't': new_date = today; - mv_valid = go_to(cal, aside, raw_time, &pad_pos); + mv_valid = go_to(cal, aside, raw_time, &pad_pos, &curs_date, &cal_start, &cal_end); break; // delete entry case 'd': @@ -635,12 +605,12 @@ int main(int argc, char** argv) { // Move to the previous diary entry case 'N': new_date = find_closest_entry(new_date, true, CONFIG.dir, diary_dir_size); - mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos); + mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos, &curs_date, &cal_start, &cal_end); break; // Move to the next diary entry case 'n': new_date = find_closest_entry(new_date, false, CONFIG.dir, diary_dir_size); - mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos); + mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos, &curs_date, &cal_start, &cal_end); break; // Sync entry with CalDAV server. // Show confirmation dialogue before overwriting local files @@ -666,6 +636,21 @@ int main(int argc, char** argv) { it.tm_mday++; } break; + // import from ics file + case 'i': + wclear(header); + curs_set(2); + mvwprintw(header, 0, 0, "Import from file: "); + if (wscanw(header, "%s", &ics_input_filepath) == 1) { + // fprintf(stderr, "ICS input file: %s\n", ics_input_filepath); + expanded_ics_input_filepath = expand_path(ics_input_filepath); + ics_import(expanded_ics_input_filepath, header, cal, aside, &pad_pos, &curs_date, &cal_start, &cal_end); + free(expanded_ics_input_filepath); + } + + curs_set(0); + echo(); + break; } if (mv_valid) { @@ -682,6 +667,7 @@ int main(int argc, char** argv) { } while (ch != 'q'); free(config_file_path); + free(CONFIG.dir); endwin(); system("clear"); return 0; diff --git a/src/diary.h b/src/diary.h @@ -17,6 +17,7 @@ #include <locale.h> #include <langinfo.h> #include "utils.h" +#include "import.h" #include "caldav.h" #define XDG_CONFIG_HOME_FALLBACK "~/.config" @@ -29,7 +30,6 @@ void setup_cal_timeframe(); void draw_wdays(WINDOW* head); void draw_calendar(WINDOW* number_pad, WINDOW* month_pad, const char* diary_dir, size_t diary_dir_size); -bool go_to(WINDOW* calendar, WINDOW* aside, time_t date, int* cur_pad_pos); void display_entry(const char* dir, size_t dir_size, const struct tm* date, WINDOW* win, int width); void edit_cmd(const char* dir, size_t dir_size, const struct tm* date, char** rcmd, size_t rcmd_size); diff --git a/src/import.c b/src/import.c @@ -0,0 +1,118 @@ +#include "import.h" + +/* Import journal entries from an ics file */ +void ics_import(const char* ics_input, WINDOW* header, WINDOW* cal, WINDOW* aside, int* pad_pos, struct tm* curs_date, struct tm* cal_start, struct tm* cal_end) { + pthread_t progress_tid; + + FILE* pfile = fopen(ics_input, "r"); + if (pfile == NULL) { + perror("Error opening file"); + return; + } + + fseek(pfile, 0, SEEK_END); + long ics_bytes = ftell(pfile); + rewind(pfile); + + char* ics = malloc(ics_bytes + 1); + fread(ics, 1, ics_bytes, pfile); + fclose(pfile); + + ics[ics_bytes] = 0; + // fprintf(stderr, "Import ICS file: %s\n", ics); + + int conf_ch = 0; + char dstr[16]; + struct tm date = {}; + + long search_pos = 0; + char* vevent; + char* vevent_date; + char* vevent_desc; + + // find all VEVENTs and write to files + char *i = ics; + while (i < ics + ics_bytes) { + vevent = extract_ical_field(i, "BEGIN:VEVENT", &search_pos, false); + vevent_date = extract_ical_field(i, "DTSTART", &search_pos, false); + vevent_desc = extract_ical_field(i, "DESCRIPTION", &search_pos, true); + if (vevent == NULL || vevent_desc == NULL) { + break; + } + + i += search_pos; + + // fprintf(stderr, "VEVENT DESCRIPTION: \n\n%s\n\n", vevent_desc); + + // parse date + strptime(vevent_date, "%Y%m%d", &date); + strftime(dstr, sizeof dstr, CONFIG.fmt, &date); + + // get path of entry + char path[100]; + char* ppath = path; + fpath(CONFIG.dir, strlen(CONFIG.dir), &date, &ppath, sizeof path); + fprintf(stderr, "Import date file path: %s\n", path); + + if (conf_ch == 'c') { + // cancel all + break; + } + + if (conf_ch != 'a') { + // prepare header for confirmation dialogue + curs_set(2); + noecho(); + wclear(header); + + // ask for confirmation + mvwprintw(header, 0, 0, "Import entry '%s' and overwrite local file? [(Y)es/(a)ll/(n)o/(c)ancel] ", dstr); + conf_ch = wgetch(header); + } + + if (conf_ch == 'y' || conf_ch == 'Y' || conf_ch == 'a' || conf_ch == '\n') { + pthread_create(&progress_tid, NULL, show_progress, (void*)header); + + // persist VEVENT to local file + FILE* cursordate_file = fopen(path, "wb"); + if (cursordate_file == NULL) { + perror("Failed to open import date file"); + } else { + for (char* j = vevent_desc; *j != '\0'; j++) { + if (vevent_desc[j-vevent_desc] == 0x5C) { // backslash + switch (*(j+1)) { + case 'n': + fputc('\n', cursordate_file); + j++; + break; + case 0x5c: // preserve real backslash + fputc(0x5c, cursordate_file); + j++; + break; + } + } else { + fputc(*j, cursordate_file); + } + } + fclose(cursordate_file); + + bool mv_valid = go_to(cal, aside, mktime(&date), pad_pos, curs_date, cal_start, cal_end); + if (mv_valid) { + // add new entry highlight + chtype atrs = winch(cal) & A_ATTRIBUTES; + wchgat(cal, 2, atrs | A_BOLD, 0, NULL); + prefresh(cal, *pad_pos, 0, 1, ASIDE_WIDTH, LINES - 1, ASIDE_WIDTH + CAL_WIDTH); + } + pthread_cancel(progress_tid); + } + } + + // fprintf(stderr, "Import DTSTART: %s\n", desc); + // fprintf(stderr, "Import DESCRIPTION: %s\n", desc); + fprintf(stderr, "* * * * * * * * * * * * * \n"); + } + free(vevent); + free(vevent_date); + free(vevent_desc); + free(ics); +} +\ No newline at end of file diff --git a/src/import.h b/src/import.h @@ -0,0 +1,16 @@ +#ifndef DIARY_IMPORT_H +#define DIARY_IMPORT_H + +#define __USE_XOPEN +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <regex.h> +#include <stdbool.h> +#include <time.h> +#include <pthread.h> +#include "utils.h" + +void ics_import(const char* ics_input, WINDOW* header, WINDOW* cal, WINDOW* aside, int* pad_pos, struct tm* curs_date, struct tm* cal_start, struct tm* cal_end); + +#endif +\ No newline at end of file diff --git a/src/utils.c b/src/utils.c @@ -154,10 +154,6 @@ char* fold(const char* str) { char* unfold(const char* str) { fprintf(stderr, "Before unfolding: %s\n", str); - //if (strcmp(str, "")) { - // fputs("Unfold string is empty.\n", stderr); - // return NULL; - //} // work on a copy of the str char* strcp = (char *) malloc(strlen(str) * sizeof(char) + 1); @@ -169,6 +165,12 @@ char* unfold(const char* str) { char* res = strtok(strcp, "\n"); + if (res == NULL) { + fprintf(stderr, "No more lines in multiline string, stop unfolding.\n"); + free(strcp); + return NULL; + } + char* buf = malloc(strlen(res) + 1); if (buf == NULL) { perror("malloc failed"); @@ -188,6 +190,11 @@ char* unfold(const char* str) { while (res != NULL) { res = strtok(NULL, "\n"); + if (res == NULL) { + fprintf(stderr, "No more lines in multiline string, stop unfolding.\n"); + break; + } + if (regexec(&re, res, 1, pm, 0) == 0) { // Stop unfolding if line does not start with white space/tab: // https://datatracker.ietf.org/doc/html/rfc2445#section-4.1 @@ -195,10 +202,9 @@ char* unfold(const char* str) { } newbuf = realloc(buf, strlen(buf) + strlen(res) + 1); - if (buf == NULL) { + if (newbuf == NULL) { perror("realloc failed"); - free(buf); - return NULL; + break; } else { buf = newbuf; strcat(buf, res + 1); @@ -210,7 +216,10 @@ char* unfold(const char* str) { return buf; } -char* extract_ical_field(const char* ics, char* key, bool multiline) { +/* Find ical key in string. The value of 'start_pos' is set to the start position + of the value (match) after the colon (':'). +*/ +char* extract_ical_field(const char* ics, char* key, long* start_pos, bool multiline) { regex_t re; regmatch_t pm[1]; char key_regex[strlen(key) + 1]; @@ -223,7 +232,7 @@ char* extract_ical_field(const char* ics, char* key, bool multiline) { } // work on a copy of the ical xml response - char* icscp = (char *) malloc(strlen(ics) * sizeof(char) + 1); + char* icscp = (char *) malloc(strlen(ics) + 1 * sizeof(char)); if (icscp == NULL) { perror("malloc failed"); return NULL; @@ -231,31 +240,43 @@ char* extract_ical_field(const char* ics, char* key, bool multiline) { strcpy(icscp, ics); // tokenize ical by newlines + char* buf = NULL; char* res = strtok(icscp, "\n"); - while (res != NULL) { if (regexec(&re, res, 1, pm, 0) == 0) { + // found the key in line 'res' res = strstr(res, ":"); // value res++; // strip the ":" fprintf(stderr, "Extracted ical result value: %s\n", res); fprintf(stderr, "Extracted ical result size: %li\n", strlen(res)); - if (strlen(res) == 0) { - // empty remote description - res = NULL; - } else if (multiline) { - res = unfold(ics + (res - icscp)); + fprintf(stderr, "Sizeof ics: %li\n", strlen(ics)); + fprintf(stderr, "Start pos: %li\n", *start_pos); + fprintf(stderr, "Res: %s\n", res); + *start_pos = res - icscp; + fprintf(stderr, "Start pos: %li\n", *start_pos); + + if (strlen(res) != 0) { + // non-empty remote value + if (multiline) { + buf = unfold(ics + *start_pos); + } else { + buf = malloc(strlen(res) + 1); + if (buf == NULL) { + perror("malloc failed"); + return NULL; + } + strcpy(buf, res); + } } - break; } - // key not in this line, advance line res = strtok(NULL, "\n"); } - fprintf(stderr, "Sizeof ics: %li\n", strlen(ics)); + regfree(&re); free(icscp); - return res; + return buf; } // Return expanded file path @@ -319,7 +340,65 @@ void fpath(const char* dir, size_t dir_size, const struct tm* date, char** rpath strcat(*rpath, dstr); } +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) { + if (date < mktime(cal_start) || date > mktime(cal_end)) { + fprintf(stderr, "Invalid cursor move, return from go_to\n"); + return false; + } + + int diff_seconds = date - mktime(cal_start); + int diff_days = diff_seconds / 60 / 60 / 24; + int diff_weeks = diff_days / 7; + int diff_wdays = diff_days % 7; + + localtime_r(&date, curs_date); + + int cy, cx; + getyx(calendar, cy, cx); + + // remove the STANDOUT attribute from the day we are leaving + chtype current_attrs = mvwinch(calendar, cy, cx) & A_ATTRIBUTES; + // leave every attr as is, but turn off STANDOUT + current_attrs &= ~A_STANDOUT; + mvwchgat(calendar, cy, cx, 2, current_attrs, 0, NULL); + + // add the STANDOUT attribute to the day we are entering + chtype new_attrs = mvwinch(calendar, diff_weeks, diff_wdays * 3) & A_ATTRIBUTES; + new_attrs |= A_STANDOUT; + mvwchgat(calendar, diff_weeks, diff_wdays * 3, 2, new_attrs, 0, NULL); + + if (diff_weeks < *cur_pad_pos) + *cur_pad_pos = diff_weeks; + if (diff_weeks > *cur_pad_pos + LINES - 2) + *cur_pad_pos = diff_weeks - LINES + 2; + prefresh(aside, *cur_pad_pos, 0, 1, 0, LINES - 1, ASIDE_WIDTH); + prefresh(calendar, *cur_pad_pos, 0, 1, ASIDE_WIDTH, LINES - 1, ASIDE_WIDTH + CAL_WIDTH); + + return true; +} + + +void* show_progress(void* vargp){ + WINDOW* header = (WINDOW*) vargp; + mvwprintw(header, 0, COLS - CAL_WIDTH - ASIDE_WIDTH - 11, " syncing "); + for(;;) { + mvwprintw(header, 0, COLS - CAL_WIDTH - ASIDE_WIDTH - 10, "|"); + wrefresh(header); + usleep(200000); + mvwprintw(header, 0, COLS - CAL_WIDTH - ASIDE_WIDTH - 10, "/"); + wrefresh(header); + usleep(200000); + mvwprintw(header, 0, COLS - CAL_WIDTH - ASIDE_WIDTH - 10, "-"); + wrefresh(header); + usleep(200000); + mvwprintw(header, 0, COLS - CAL_WIDTH - ASIDE_WIDTH - 10, "\\"); + wrefresh(header); + usleep(200000); + } +} + config CONFIG = { + .dir = NULL, .range = 1, .weekday = 1, .fmt = "%Y-%m-%d", diff --git a/src/utils.h b/src/utils.h @@ -3,6 +3,7 @@ #include <regex.h> #include <stdio.h> +#include <unistd.h> #include <stdlib.h> #include <time.h> #include <string.h> @@ -26,10 +27,12 @@ void update_date(WINDOW* header, struct tm* curs_date); char* extract_json_value(const char* json, char* key, bool quoted); char* fold(const char* str); char* unfold(const char* str); -char* extract_ical_field(const char* ical, char* key, bool multline); +char* extract_ical_field(const char* ical, char* key, long* start_pos, bool multline); char* expand_path(const char* str); char* strrstr(char *haystack, char *needle); void fpath(const char* dir, size_t dir_size, const struct tm* date, char** rpath, size_t rpath_size); +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); +void* show_progress(void* vargp); typedef struct {