diary

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

commit 6db6e57653f8a3dcd14a1d605b86fb3d8708ed5e
parent 6272882e0dbfc4e20e72855fd7ac842bddf7ef1d
Author: Andreas Gruhler <agruhl@gmx.ch>
Date:   Mon, 27 Dec 2021 08:18:14 +0100

add export feature

Diffstat:
MMakefile | 2+-
MREADME.md | 7+++----
Mimg/diary-cheat-sheet.png | 0
Mman1/diary.1 | 1+
Mman1/diary.1.html | 4++++
Msrc/diary.c | 42++++++++++++++++++++----------------------
Msrc/diary.h | 3+--
Asrc/export.c | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/export.h | 12++++++++++++
Msrc/import.c | 2+-
Msrc/utils.c | 26++++++++++++++++++--------
Msrc/utils.h | 1+
12 files changed, 205 insertions(+), 38 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,6 +1,6 @@ TARGET = diary SRCDIR = src/ -_SRC = import.c utils.c caldav.c diary.c +_SRC = export.c import.c utils.c caldav.c diary.c SRC = $(addprefix $(SRCDIR), $(_SRC)) PREFIX ?= /usr/local BINDIR ?= $(DESTDIR)$(PREFIX)/bin diff --git a/README.md b/README.md @@ -34,6 +34,7 @@ This is a text-based diary, inspired by [khal](https://github.com/pimutils/khal) s sync current entry with CalDAV server (ALPHA) S sync all entries with CalDAV server (ALPHA) i import entries from .ics file (ALPHA) + E export entries to .ics file (ALPHA) t jump to today f jump to or find specific day @@ -176,12 +177,10 @@ The output processed on the pty (not with Ncurses, without `--no-pty` flag) incl Thus, formatters like `fmt` or any "cat like program" such as [`mdcat`](https://github.com/lunaryorn/mdcat) to process Markdown can be plugged in for `--fmt-cmd`. ## Import/Export (alpha) - -The import functionality can be triggered by pressing `i`. - > ⚠️ Alpha feature: Available only in `diary-nightly` (see [Installation Instructions](#Install)). Don't use this with "production" data. -The export functionality is not implemented yet, see [issues/78](https://github.com/in0rdr/diary/issues/78). +* The import functionality can be triggered by pressing `i`. +* The export functionality can be triggered by pressing `E`. ## CalDAV Sync (alpha) > ⚠️ Alpha feature: Available only in `diary-nightly` (see [Installation Instructions](#Install)). Don't use this with "production" data. diff --git a/img/diary-cheat-sheet.png b/img/diary-cheat-sheet.png Binary files differ. diff --git a/man1/diary.1 b/man1/diary.1 @@ -69,6 +69,7 @@ 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) +E | export entries to .ics file (ALPHA) t | jump to today f | jump to or find specific day diff --git a/man1/diary.1.html b/man1/diary.1.html @@ -126,6 +126,10 @@ Navigation is done using the following vim-inspired keyboard shortcuts: <td> import entries from .ics file (ALPHA)</td> </tr> <tr> + <td>E </td> + <td> export entries to .ics file (ALPHA)</td> + </tr> + <tr> <td></td> <td></td> </tr> diff --git a/src/diary.c b/src/diary.c @@ -259,21 +259,6 @@ void edit_cmd(const char* dir, size_t dir_size, const struct tm* date, char** rc strcat(*rcmd, path); } -bool date_has_entry(const char* dir, size_t dir_size, const struct tm* i) { - char epath[100]; - char* pepath = epath; - - // get entry path and check for existence - fpath(dir, dir_size, i, &pepath, sizeof epath); - - if (pepath == NULL) { - fprintf(stderr, "Error while retrieving file path for checking entry existence"); - return false; - } - - return (access(epath, F_OK) != -1); -} - /* * Finds the date with a diary entry closest to <current>. * Depending on <search_backwards>, it will find the most recent @@ -592,8 +577,8 @@ 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; + char ics_filepath[256]; + char* expanded_ics_filepath; struct tm new_date; int prev_width = COLS - ASIDE_WIDTH - CAL_WIDTH; int prev_height = LINES - 1; @@ -856,11 +841,24 @@ int main(int argc, char** argv) { 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); + if (wscanw(header, "%s", &ics_filepath) == 1) { + expanded_ics_filepath = expand_path(ics_filepath); + ics_import(expanded_ics_filepath, header, cal, aside, &pad_pos, &curs_date, &cal_start, &cal_end); + free(expanded_ics_filepath); + } + + curs_set(0); + echo(); + break; + // export to ics file + case 'E': + wclear(header); + curs_set(2); + mvwprintw(header, 0, 0, "Export to file: "); + if (wscanw(header, "%s", &ics_filepath) == 1) { + expanded_ics_filepath = expand_path(ics_filepath); + ics_export(expanded_ics_filepath, header, cal, aside, &pad_pos, &curs_date, &cal_start, &cal_end); + free(expanded_ics_filepath); } curs_set(0); diff --git a/src/diary.h b/src/diary.h @@ -23,6 +23,7 @@ #include "utils.h" #include "import.h" +#include "export.h" #include "caldav.h" #define XDG_CONFIG_HOME_FALLBACK "~/.config" @@ -38,8 +39,6 @@ void draw_calendar(WINDOW* number_pad, WINDOW* month_pad, const char* diary_dir, 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); -bool date_has_entry(const char* dir, size_t dir_size, const struct tm* i); - extern config CONFIG; #endif \ No newline at end of file diff --git a/src/export.c b/src/export.c @@ -0,0 +1,142 @@ +#include "export.h" + +/* Export journal entries to an ics file */ +void ics_export(const char* ics_filepath, 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; + int conf_ch; + + char* veventsr; + char* vevent_format = "BEGIN:VEVENT\n" + "UID:%s\n" + "DTSTART;VALUE=DATE:%s\n" + "SUMMARY:%s\n" + "%s\n" + "END:VEVENT\n"; + + size_t begins = strlen("BEGIN:VCALENDAR\n"); + char* vevents = calloc(begins + 1, sizeof(char)); + strcat(vevents, "BEGIN:VCALENDAR\n"); + + // prepare header for confirmation dialogue + curs_set(2); + noecho(); + wclear(header); + + // ask for confirmation + mvwprintw(header, 0, 0, "Export diary entries (and overwrite existing) file '%s'? [(Y)es/(n)o] ", ics_filepath); + + do { + conf_ch = wgetch(header); + + if (conf_ch == 'n') { + return; + } else if (conf_ch == 'y' || conf_ch == 'Y' || conf_ch == '\n') { + // open ics file for writing + FILE* pfile = fopen(ics_filepath, "wb"); + if (pfile == NULL) { + perror("Error opening ics file for writing"); + return; + } + + pthread_create(&progress_tid, NULL, show_progress, (void*)header); + pthread_detach(progress_tid); + + struct tm it = *cal_start; + time_t it_time = mktime(&it); + time_t end_time = mktime(cal_end); + size_t dirs = strlen(CONFIG.dir); + + for( ; it_time <= end_time; it_time = mktime(&it)) { + localtime_r(&it_time, &it); + + if (date_has_entry(CONFIG.dir, dirs, &it)) { + // get path of entry + char path[100]; + char* ppath = path; + char* descr; + long descr_bytes; + + fpath(CONFIG.dir, dirs, &it, &ppath, sizeof path); + + if (ppath == NULL) { + fprintf(stderr, "Error while retrieving file path for diary reading"); + return; + } + + FILE* fp = fopen(path, "r"); + if (fp == NULL) perror("Error opening entry path for reading"); + + fseek(fp, 0, SEEK_END); + descr_bytes = ftell(fp); + rewind(fp); + + size_t descr_labell = strlen("DESCRIPTION:"); + size_t descrl = descr_bytes + descr_labell + 1; + descr = malloc(descrl); + if (descr == NULL) { + perror("malloc failed"); + return; + } + + descr[0] = '\0'; + strcat(descr, "DESCRIPTION:"); + + // read description bytes from journal entry file and append to descr + int items_read = fread(descr + descr_labell, sizeof(char), descr_bytes, fp); + if (items_read != descr_bytes) { + fprintf(stderr, "Read %i items but expected %li, aborting.", items_read, descr_bytes); + return; + } + + descr[descrl - 1] = '\0'; + + char* folded_descr = fold(descr); + + char uid[9]; + strftime(uid, sizeof uid, "%Y%m%d", &it); + + char* vevent = calloc(strlen(vevent_format) + strlen(folded_descr) + 100, sizeof(char)); + sprintf(vevent, vevent_format, + uid, + uid, + uid, // todo: display first few chars of DESCRIPTION as SUMMARY + folded_descr); + + veventsr = realloc(vevents, strlen(vevents) + strlen(vevent) + 1); + if (veventsr == NULL) { + perror("failed to realloc vevents buffer"); + free(vevents); + break; + } else { + // vevents ptr already freed by realloc if moved + vevents = (char*) veventsr; + // append vevent to vevents buffer + strcat(vevents, vevent); + } + + free(folded_descr); + free(descr); + free(vevent); + } // end date_has_entry + + it.tm_mday++; + } // endfor each entry + + veventsr = realloc(vevents, strlen(vevents) + strlen("END:VCALENDAR") + 1); + if (veventsr == NULL) { + perror("failed to realloc vevents buffer"); + free(vevents); + break; + } else { + vevents = (char*) veventsr; + strcat(vevents, "END:VCALENDAR"); + } + + fputs(vevents, pfile); + fclose(pfile); + pthread_cancel(progress_tid); + } // end confirm to write + } while (!(conf_ch == 'q' || conf_ch == 'n' || conf_ch == 'y' || conf_ch == 'Y' || conf_ch == '\n')); + + free(vevents); +} +\ No newline at end of file diff --git a/src/export.h b/src/export.h @@ -0,0 +1,11 @@ +#ifndef DIARY_EXPORT_H +#define DIARY_EXPORT_H + +#include <stdio.h> +#include <pthread.h> +#include <time.h> +#include "utils.h" + +void ics_export(const char* ics_filepath, 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/import.c b/src/import.c @@ -66,7 +66,7 @@ void ics_import(const char* ics_input, WINDOW* header, WINDOW* cal, WINDOW* asid wclear(header); // ask for confirmation - mvwprintw(header, 0, 0, "Import entry '%s' and overwrite local file? [(y)es/(a)ll/(s)kip/(c)ancel] ", dstr); + mvwprintw(header, 0, 0, "Import entry '%s' and overwrite local file? [(Y)es/(a)ll/(s)kip/(c)ancel] ", dstr); conf_ch = wgetch(header); } diff --git a/src/utils.c b/src/utils.c @@ -12,6 +12,20 @@ void update_date(WINDOW* header, struct tm* curs_date) { wrefresh(header); } +bool date_has_entry(const char* dir, size_t dir_size, const struct tm* i) { + char epath[100]; + char* pepath = epath; + + // get entry path and check for existence + fpath(dir, dir_size, i, &pepath, sizeof epath); + + if (pepath == NULL) { + fprintf(stderr, "Error while retrieving file path for checking entry existence"); + return false; + } + + return (access(epath, F_OK) != -1); +} char* extract_json_value(const char* json, char* key, bool quoted) { // work on a copy of the json @@ -63,12 +77,11 @@ char* fold(const char* str) { strcpy(strcp, str); // create buffer for escaped result TEXT - char* buf = malloc(1); + char* buf = calloc(1, sizeof(char)); if (buf == NULL) { - perror("malloc failed"); + perror("calloc failed"); return NULL; } - buf[0] = '\0'; void* newbuf; // bufl is the current buffer size incl. \0 @@ -77,7 +90,7 @@ char* fold(const char* str) { char* i = strcp; // escch is the char to be escaped, // only written when esc=true - char escch; + char escch = '\0'; bool esc = false; while(*i != '\0' || esc) { @@ -85,7 +98,7 @@ char* fold(const char* str) { fprintf(stderr, "*i: %c\n", *i); fprintf(stderr, "escch: %c\n", escch); fprintf(stderr, "esc: %i\n", esc); - fprintf(stderr, "buffer: %s\n\n", buf); + // fprintf(stderr, "buffer: %s\n\n", buf); newbuf = realloc(buf, ++bufl); if (newbuf == NULL) { @@ -145,9 +158,6 @@ char* fold(const char* str) { buf[bufl-1] = '\0'; } - fprintf(stderr, "escch: %c\n", escch); - fprintf(stderr, "end: %c\n", buf[bufl]); - free(strcp); return buf; } diff --git a/src/utils.h b/src/utils.h @@ -24,6 +24,7 @@ #define MAX_MONTH_HEIGHT 6 void update_date(WINDOW* header, struct tm* curs_date); +bool date_has_entry(const char* dir, size_t dir_size, const struct tm* i); char* extract_json_value(const char* json, char* key, bool quoted); char* fold(const char* str); char* unfold(const char* str);