commit 6db6e57653f8a3dcd14a1d605b86fb3d8708ed5e
parent 6272882e0dbfc4e20e72855fd7ac842bddf7ef1d
Author: Andreas Gruhler <agruhl@gmx.ch>
Date: Mon, 27 Dec 2021 08:18:14 +0100
add export feature
Diffstat:
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);