diff options
Diffstat (limited to 'src/io.c')
-rw-r--r-- | src/io.c | 494 |
1 files changed, 256 insertions, 238 deletions
@@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2017 calcurse Development Team <misc@calcurse.org> + * Copyright (c) 2004-2023 calcurse Development Team <misc@calcurse.org> * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,8 +50,8 @@ struct ht_keybindings_s { const char *label; - enum key key; - HTABLE_ENTRY(ht_keybindings_s); + enum vkey key; + HTABLE_ENTRY(ht_keybindings_s); }; static void load_keys_ht_getkey(struct ht_keybindings_s *, const char **, @@ -136,24 +136,52 @@ unsigned io_fprintln(const char *fname, const char *fmt, ...) /* * Initialization of data paths. The cfile argument is the variable * which contains the calendar file. If none is given, then the default - * one (~/.calcurse/apts) is taken. If the one given does not exist, it - * is created. + * one (~/.local/share/calcurse/apts) is taken. If the one given does not exist, + * it is created. * The datadir argument can be used to specify an alternative data root dir. * The confdir argument can be used to specify an alternative configuration dir. + * If ~/.calcurse exists, it will be used instead for backward compatibility. */ void io_init(const char *cfile, const char *datadir, const char *confdir) { - if (!datadir) { - if (!(datadir = getenv("HOME"))) - datadir = "."; - asprintf(&path_ddir, "%s%s", datadir, "/" DIR_NAME); - } else - asprintf(&path_ddir, "%s%s", datadir, "/"); + char* home_dir = getenv("HOME"); + char* legacy_dir = NULL; + + if (home_dir) { + asprintf(&legacy_dir, "%s%s", home_dir, "/" DIR_NAME_LEGACY); + if (!io_dir_exists(legacy_dir)) { + mem_free(legacy_dir); + legacy_dir = NULL; + } + } - if (!confdir) - asprintf(&path_cdir, "%s%s", path_ddir, "/"); + if (datadir) + asprintf(&path_ddir, "%s%s", datadir, "/"); + else if (legacy_dir) + path_ddir = mem_strdup(legacy_dir); + else if ((path_ddir = getenv("XDG_DATA_HOME"))) + asprintf(&path_ddir, "%s%s", path_ddir, "/" DIR_NAME); + else if (home_dir) + asprintf(&path_ddir, "%s%s", home_dir, "/.local/share/" DIR_NAME); else + path_ddir = mem_strdup("./." DIR_NAME); + + + if (confdir) asprintf(&path_cdir, "%s%s", confdir, "/"); + else if (datadir) + path_cdir = mem_strdup(path_ddir); + else if (legacy_dir) + path_cdir = mem_strdup(legacy_dir); + else if ((path_cdir = getenv("XDG_CONFIG_HOME"))) + asprintf(&path_cdir, "%s%s", path_cdir, "/" DIR_NAME); + else if (home_dir) + asprintf(&path_cdir, "%s%s", home_dir, "/.config/" DIR_NAME); + else + path_cdir = mem_strdup("./." DIR_NAME); + + if (legacy_dir) + mem_free(legacy_dir); /* Data files */ if (cfile) { @@ -209,25 +237,25 @@ void io_dump_apts(const char *fmt_apt, const char *fmt_rapt, LLIST_FOREACH(&recur_elist, i) { struct recur_event *rev = LLIST_GET_DATA(i); - time_t day = update_time_in_date(rev->day, 0, 0); + time_t day = DAY(rev->day); print_recur_event(fmt_rev, day, rev); } LLIST_TS_FOREACH(&recur_alist_p, i) { struct recur_apoint *rapt = LLIST_GET_DATA(i); - time_t day = update_time_in_date(rapt->start, 0, 0); + time_t day = DAY(rapt->start); print_recur_apoint(fmt_rapt, day, rapt->start, rapt); } LLIST_TS_FOREACH(&alist_p, i) { struct apoint *apt = LLIST_TS_GET_DATA(i); - time_t day = update_time_in_date(apt->start, 0, 0); + time_t day = DAY(apt->start); print_apoint(fmt_apt, day, apt); } LLIST_FOREACH(&eventlist, i) { struct event *ev = LLIST_TS_GET_DATA(i); - time_t day = update_time_in_date(ev->day, 0, 0); + time_t day = DAY(ev->day); print_event(fmt_ev, day, ev); } } @@ -525,13 +553,13 @@ void io_load_app(struct item_filter *filter) FILE *data_file; int c, is_appointment, is_event, is_recursive; struct tm start, end, until, lt; - llist_t exc; + struct rpt rpt; time_t t; int id = 0; - int freq; char type, state = 0L; char note[MAX_NOTESIZ + 1], *notep; unsigned line = 0; + char *scan_error; t = time(NULL); localtime_r(&t, <); @@ -544,9 +572,10 @@ void io_load_app(struct item_filter *filter) rewind(data_file); for (;;) { - LLIST_INIT(&exc); is_appointment = is_event = is_recursive = 0; line++; + scan_error = NULL; + c = getc(data_file); if (c == EOF) break; @@ -602,102 +631,113 @@ void io_load_app(struct item_filter *filter) if (c == '{') { is_recursive = 1; - if (fscanf(data_file, " %d%c ", &freq, &type) != 2) + if (fscanf(data_file, " %d%c ", &rpt.freq, &type) != 2) io_load_error(path_apts, line, _("syntax error in item repetition")); - + else + rpt.type = recur_char2def(type); c = getc(data_file); - if (c == '}') { /* endless recurrent item */ - until.tm_year = 0; - while ((c = getc(data_file)) == ' ') ; - ungetc(c, data_file); - } else if (c == '-' && getc(data_file) == '>') { + /* Optional until date */ + if (c == '-' && getc(data_file) == '>') { if (fscanf (data_file, " %d / %d / %d ", &until.tm_mon, &until.tm_mday, &until.tm_year) != 3) io_load_error(path_apts, line, - _("syntax error in item repetition")); + _("syntax error in until date")); + if (!check_date(until.tm_year, until.tm_mon, + until.tm_mday)) + io_load_error(path_apts, line, + _("until date error")); + until.tm_hour = 0; + until.tm_min = 0; + until.tm_sec = 0; + until.tm_isdst = -1; + until.tm_year -= 1900; + until.tm_mon--; + rpt.until = mktime(&until); c = getc(data_file); - if (c == '!') { - ungetc(c, data_file); - recur_exc_scan(&exc, data_file); - while ((c = - getc(data_file)) == ' ') ; - ungetc(c, data_file); - } else if (c == '}') { - while ((c = - getc(data_file)) == ' ') ; - ungetc(c, data_file); - } else { + } else + rpt.until = 0; + /* Optional bymonthday list */ + if (c == 'd') { + if (rpt.type == RECUR_WEEKLY) io_load_error(path_apts, line, - _("syntax error in item repetition")); - } - } else if (c == '!') { /* endless item with exceptions */ + _("BYMONTHDAY illegal with WEEKLY")); ungetc(c, data_file); - recur_exc_scan(&exc, data_file); - while ((c = getc(data_file)) == ' ') ; + recur_bymonthday(&rpt.bymonthday, data_file); + c = getc(data_file); + } else + LLIST_INIT(&rpt.bymonthday); + /* Optional bywday list */ + if (c == 'w') { ungetc(c, data_file); - until.tm_year = 0; - } else { + recur_bywday(rpt.type, &rpt.bywday, data_file); + c = getc(data_file); + } else + LLIST_INIT(&rpt.bywday); + /* Optional bymonth list */ + if (c == 'm') { + ungetc(c, data_file); + recur_bymonth(&rpt.bymonth, data_file); + c = getc(data_file); + } else + LLIST_INIT(&rpt.bymonth); + /* Optional exception dates */ + if (c == '!') { + ungetc(c, data_file); + recur_exc_scan(&rpt.exc, data_file); + c = getc(data_file); + } else + LLIST_INIT(&rpt.exc); + /* End of recurrence rule */ + if (c != '}') io_load_error(path_apts, line, - _("wrong format in the appointment or event")); - /* NOTREACHED */ - } - } else { - ungetc(c, data_file); + _("missing end of recurrence")); + while ((c = getc(data_file)) == ' ') ; } /* Check if a note is attached to the item. */ - c = getc(data_file); if (c == '>') { note_read(note, data_file); + c = getc(data_file); notep = note; - } else { + } else notep = NULL; - ungetc(c, data_file); - } /* * Last: read the item description and load it into its * corresponding linked list, depending on the item type. */ if (is_appointment) { - c = getc(data_file); - if (c == '!') { + if (c == '!') state |= APOINT_NOTIFY; - while ((c = getc(data_file)) == ' ') ; - ungetc(c, data_file); - } else if (c == '|') { + else if (c == '|') state = 0L; - while ((c = getc(data_file)) == ' ') ; - ungetc(c, data_file); - } else { + else io_load_error(path_apts, line, - _("syntax error in item repetition")); - } - if (is_recursive) { - recur_apoint_scan(data_file, start, end, - type, freq, until, notep, - &exc, state, filter); - } else { - apoint_scan(data_file, start, end, state, + _("syntax error in item state")); + + if (is_recursive) + scan_error = recur_apoint_scan(data_file, start, end, state, + notep, filter, &rpt); + else + scan_error = apoint_scan(data_file, start, end, state, notep, filter); - } } else if (is_event) { - if (is_recursive) { - recur_event_scan(data_file, start, id, - type, freq, until, notep, - &exc, filter); - } else { - event_scan(data_file, start, id, notep, - filter); - } + ungetc(c, data_file); + if (is_recursive) + scan_error = recur_event_scan(data_file, start, id, notep, + filter, &rpt); + else + scan_error = event_scan(data_file, start, id, notep, filter); } else { io_load_error(path_apts, line, _("wrong format in the appointment or event")); /* NOTREACHED */ } + if (scan_error) + io_load_error(path_apts, line, scan_error); } file_close(data_file, __FILE_POS__); } @@ -900,16 +940,6 @@ load_keys_ht_compare(struct ht_keybindings_s *data1, } /* - * isblank(3) is protected by the __BSD_VISIBLE macro and this fails to be - * visible in some specific cases. Thus replace it by the following is_blank() - * function. - */ -static int is_blank(int c) -{ - return c == ' ' || c == '\t'; -} - -/* * Load user-definable keys from file. * A hash table is used to speed up loading process in avoiding string * comparisons. @@ -918,21 +948,21 @@ static int is_blank(int c) */ void io_load_keys(const char *pager) { - struct ht_keybindings_s keys[NBKEYS]; + struct ht_keybindings_s virt_keys[NBVKEYS], *ht_elm, ht_entry; FILE *keyfp; - char buf[BUFSIZ]; + char buf[BUFSIZ], key_label[BUFSIZ], key_str[BUFSIZ]; + char *p, *msg; struct io_file *log; - int i, skipped, loaded, line; - const int MAX_ERRORS = 5; + int i, n, skipped, loaded, line, assigned, undefined, key; keys_init(); struct ht_keybindings ht_keys = HTABLE_INITIALIZER(&ht_keys); - for (i = 0; i < NBKEYS; i++) { - keys[i].key = (enum key)i; - keys[i].label = keys_get_label((enum key)i); - HTABLE_INSERT(ht_keybindings, &ht_keys, &keys[i]); + for (i = 0; i < NBVKEYS; i++) { + virt_keys[i].key = (enum vkey)i; + virt_keys[i].label = keys_get_label((enum vkey)i); + HTABLE_INSERT(ht_keybindings, &ht_keys, &virt_keys[i]); } keyfp = fopen(path_keys, "r"); @@ -941,111 +971,97 @@ void io_load_keys(const char *pager) log = io_log_init(); skipped = loaded = line = 0; while (fgets(buf, BUFSIZ, keyfp) != NULL) { - char key_label[BUFSIZ], *p; - struct ht_keybindings_s *ht_elm, ht_entry; - const int AWAITED = 1; - int assigned; - line++; - if (skipped > MAX_ERRORS) { - const char *too_many = - _("\nToo many errors while reading configuration file!\n" - "Please backup your keys file, remove it from directory, " - "and launch calcurse again.\n"); - - io_log_print(log, line, too_many); - break; - } - for (p = buf; is_blank((int)*p); p++) ; - if (p != buf) - memmove(buf, p, strlen(p)); - if (buf[0] == '#' || buf[0] == '\n') + p = buf; + while (*p == ' ' || *p == '\t') p++; + if (*p == '#' || *p == '\n') continue; - if (sscanf(buf, "%s", key_label) != AWAITED) { + /* Find the virtual key by key label. */ + if (sscanf(p, "%s", key_label) != 1) { skipped++; io_log_print(log, line, _("Could not read key label")); continue; } - - /* Skip legacy entries. */ - if (strcmp(key_label, "generic-cut") == 0) - continue; - + p += strlen(key_label); ht_entry.label = key_label; - p = buf + strlen(key_label) + 1; - ht_elm = - HTABLE_LOOKUP(ht_keybindings, &ht_keys, &ht_entry); + ht_elm = HTABLE_LOOKUP(ht_keybindings, &ht_keys, &ht_entry); if (!ht_elm) { skipped++; - io_log_print(log, line, - _("Key label not recognized")); + asprintf(&msg, + _("Key label not recognized: \"%s\""), + key_label); + io_log_print(log, line, msg); + mem_free(msg); continue; } - assigned = 0; - for (;;) { - char key_ch[BUFSIZ], tmpbuf[BUFSIZ]; - - while (*p == ' ') - p++; - (void)strncpy(tmpbuf, p, BUFSIZ); - tmpbuf[BUFSIZ - 1] = '\0'; - if (sscanf(tmpbuf, "%s", key_ch) == AWAITED) { - int ch; - - if ((ch = keys_str2int(key_ch)) < 0) { - char *unknown_key; + /* Assign keyboard keys to the virtual key. */ + assigned = undefined = 0; + for (;;) { + if (sscanf(p, "%s%n", key_str, &n) != 1) { + if (assigned || undefined) + loaded++; + else { skipped++; - asprintf(&unknown_key, - _("Error reading key: \"%s\""), - key_ch); - io_log_print(log, line, unknown_key); - mem_free(unknown_key); - } else { - int used; - - used = - keys_assign_binding(ch, - ht_elm-> - key); - if (used) { - char *already_assigned; - - skipped++; - asprintf(&already_assigned, - _("\"%s\" assigned multiple times!"), - key_ch); - io_log_print(log, line, - already_assigned); - mem_free(already_assigned); - } else { - assigned++; - } + asprintf(&msg, + _("No keys assigned to " + "\"%s\"."), + key_label); + io_log_print(log, line, msg); + mem_free(msg); } - p += strlen(key_ch) + 1; - } else { - if (assigned) - loaded++; break; } + p += n; + if (!strcmp(key_str, "UNDEFINED")) { + undefined++; + keys_assign_binding(-1, ht_elm->key); + } else if ((key = keys_str2int(key_str)) < 0) { + skipped++; + asprintf(&msg, + _("Keyname not recognized: \"%s\""), + key_str); + io_log_print(log, line, msg); + mem_free(msg); + } else if (keys_assign_binding(key, ht_elm->key)) { + skipped++; + asprintf(&msg, + _("\"%s\" assigned twice: \"%s\"."), + key_str, key_label); + io_log_print(log, line, msg); + mem_free(msg); + } else + assigned++; } } file_close(keyfp, __FILE_POS__); + if (loaded < NBVKEYS && (i = keys_fill_missing()) < 1) { + skipped++; + strcpy(key_label, keys_get_label((enum vkey)(-i))); + strcpy(key_str, keys_get_binding((enum vkey)(-i))); + asprintf(&msg, _("Action \"%s\" absent, but default key \"%s\" " + "assigned to another action."), + key_label, key_str); + io_log_print(log, line, msg); + mem_free(msg); + } file_close(log->fd, __FILE_POS__); if (skipped > 0) { - const char *view_log = - _("There were some errors when loading keys file."); - io_log_display(log, view_log, pager); + msg = _("Errors in the keys file."); + io_log_display(log, msg, pager); + WARN_MSG(_("Remove offending line(s) from the keys file, " + "aborting...")); + exit_calcurse(EXIT_FAILURE); } io_log_free(log); - EXIT_IF(skipped > MAX_ERRORS, - _("Too many errors while reading keys file, aborting...")); - if (loaded < NBKEYS) - keys_fill_missing(); - if (keys_check_missing_bindings()) - WARN_MSG(_("Some actions do not have any associated key bindings!")); + /* Default keys were inserted. */ + if (loaded < NBVKEYS) + io_save_keys(); + /* Should never occur. */ + EXIT_IF(keys_check_missing(), + _("Some actions do not have any associated key bindings!")); } int io_check_dir(const char *dir) @@ -1053,19 +1069,42 @@ int io_check_dir(const char *dir) if (read_only) return -1; + char *path = mem_strdup(dir); + char *index; + + int existed = 1, failed = 0; errno = 0; - if (mkdir(dir, 0700) != 0) { - if (errno != EEXIST) { - fprintf(stderr, - _("FATAL ERROR: could not create %s: %s\n"), - dir, strerror(errno)); - exit_calcurse(EXIT_FAILURE); - } else { - return 1; + for (index = path + 1; *index; index++) { + if (*index == '/') { + *index = '\0'; + if (mkdir(path, 0700) != 0) { + if (errno != EEXIST) { + failed = 1; + break; + } + } else { + existed = 0; + } + *index = '/'; } + } + + if (!failed && mkdir(path, 0700) != 0) { + if (errno != EEXIST) + failed = 1; } else { - return 0; + existed = 0; } + + if(failed) { + fprintf(stderr, + _("FATAL ERROR: could not create %s: %s\n"), + path, strerror(errno)); + exit_calcurse(EXIT_FAILURE); + } + + mem_free(path); + return existed; } unsigned io_dir_exists(const char *path) @@ -1114,11 +1153,15 @@ int io_check_file(const char *file) * Checks if data files exist. If not, create them. * The following structure has to be created: * - * <datadir> <configdir> (default for both: $HOME/.calcurse/) + * <datadir> <configdir> * | | * |__ apts |___ conf * |__ todo |___ keys * |__ notes/ |___ hooks/ + * + * Defaults: + * - datadir: $XDG_DATA_HOME/calcurse (~/.local/share/calcurse) + * - configdir: $XDG_CONFIG_HOME/calcurse (~/.config/calcurse) */ int io_check_data_files(void) { @@ -1140,21 +1183,6 @@ int io_check_data_files(void) return missing; } -/* Draw the startup screen */ -void io_startup_screen(int no_data_file) -{ - const char *enter = _("Press [ENTER] to continue"); - - if (no_data_file) - status_mesg(_("Welcome to Calcurse. Missing data files were created."), - enter); - else - status_mesg(_("Data files found. Data will be loaded now."), - enter); - - keys_wait_for_any_key(win[KEY].p); -} - /* Export calcurse data. */ void io_export_data(enum export_type type, int export_uid) { @@ -1185,38 +1213,36 @@ void io_export_data(enum export_type type, int export_uid) else if (type == IO_EXPORT_PCAL) pcal_export_data(stream); - if (show_dialogs() && ui_mode == UI_CURSES) { + if (!quiet && ui_mode == UI_CURSES) { fclose(stream); status_mesg(success, enter); keys_wait_for_any_key(win[KEY].p); } } -static FILE *get_import_stream(enum import_type type) +static FILE *get_import_stream(enum import_type type, char **stream_name) { FILE *stream = NULL; - char *stream_name; const char *ask_fname = _("Enter the file name to import data from:"); const char *wrong_file = _("The file cannot be accessed, please enter another file name."); const char *press_enter = _("Press [ENTER] to continue."); - stream_name = mem_malloc(BUFSIZ); - memset(stream_name, 0, BUFSIZ); + *stream_name = mem_malloc(BUFSIZ); + memset(*stream_name, 0, BUFSIZ); while (stream == NULL) { status_mesg(ask_fname, ""); - if (updatestring(win[STA].p, &stream_name, 0, 1)) { - mem_free(stream_name); + if (updatestring(win[STA].p, stream_name, 0, 1)) { + mem_free(*stream_name); return NULL; } - stream = fopen(stream_name, "r"); + stream = fopen(*stream_name, "r"); if (stream == NULL) { status_mesg(wrong_file, press_enter); keys_wait_for_any_key(win[KEY].p); } } - mem_free(stream_name); return stream; } @@ -1227,7 +1253,7 @@ static FILE *get_import_stream(enum import_type type) * A temporary log file is created in /tmp to store the import process report, * and is cleared at the end. */ -void io_import_data(enum import_type type, const char *stream_name, +int io_import_data(enum import_type type, char *stream_name, const char *fmt_ev, const char *fmt_rev, const char *fmt_apt, const char *fmt_rapt, const char *fmt_todo) @@ -1254,7 +1280,7 @@ void io_import_data(enum import_type type, const char *stream_name, "Aborting...")); break; case UI_CURSES: - stream = get_import_stream(type); + stream = get_import_stream(type, &stream_name); break; default: EXIT(_("FATAL ERROR: wrong import mode")); @@ -1262,7 +1288,7 @@ void io_import_data(enum import_type type, const char *stream_name, } if (stream == NULL) - return; + return 0; memset(&stats, 0, sizeof stats); @@ -1270,11 +1296,11 @@ void io_import_data(enum import_type type, const char *stream_name, if (log == NULL) { if (stream != stdin) file_close(stream, __FILE_POS__); - return; + return 0; } if (type == IO_IMPORT_ICAL) - ical_import_data(stream, log->fd, &stats.events, + ical_import_data(stream_name, stream, log->fd, &stats.events, &stats.apoints, &stats.todos, &stats.lines, &stats.skipped, fmt_ev, fmt_rev, fmt_apt, fmt_rapt, fmt_todo); @@ -1295,7 +1321,7 @@ void io_import_data(enum import_type type, const char *stream_name, stats.todos); asprintf(&stats_str[3], _("%d skipped"), stats.skipped); - if (ui_mode == UI_CURSES && show_dialogs()) { + if (ui_mode == UI_CURSES && !quiet) { char *read, *stat; asprintf(&read, proc_report, stats.lines); @@ -1306,7 +1332,7 @@ void io_import_data(enum import_type type, const char *stream_name, mem_free(read); mem_free(stat); keys_wait_for_any_key(win[KEY].p); - } else if (ui_mode == UI_CMDLINE && show_dialogs()) { + } else if (ui_mode == UI_CMDLINE && !quiet) { printf(proc_report, stats.lines); printf("\n%s / %s / %s / %s\n", stats_str[0], stats_str[1], stats_str[2], stats_str[3]); @@ -1325,7 +1351,13 @@ void io_import_data(enum import_type type, const char *stream_name, mem_free(stats_str[1]); mem_free(stats_str[2]); mem_free(stats_str[3]); - io_log_free(log); + if (ui_mode == UI_CURSES) + mem_free(stream_name); + if (!stats.skipped) { + io_log_free(log); + return 1; + } else + return 0; } struct io_file *io_log_init(void) @@ -1343,7 +1375,7 @@ struct io_file *io_log_init(void) ERROR_MSG(_("Warning: could not create temporary log file, Aborting...")); goto error; } - strncpy(log->name, logname, sizeof(log->name)); + log->name = mem_strdup(logname); log->fd = fopen(log->name, "w"); if (log->fd == NULL) { ERROR_MSG(_("Warning: could not open temporary log file, Aborting...")); @@ -1393,6 +1425,7 @@ void io_log_free(struct io_file *log) EXIT_IF(unlink(log->name) != 0, _("Warning: could not erase temporary log file %s, Aborting..."), log->name); + mem_free(log->name); mem_free(log); } @@ -1401,7 +1434,8 @@ static void *io_psave_thread(void *arg) { int delay = conf.periodic_save; EXIT_IF(delay < 0, _("Invalid delay")); - char *mesg = _("Periodic save: data files have changed. Save cancelled."); + char *mesg = _("Periodic save cancelled. Data files have changed. " + "Save and merge interactively"); pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); for (;;) { @@ -1521,22 +1555,6 @@ unsigned io_get_pid(char *file) } /* - * Check whether a file is empty. - */ -int io_file_is_empty(char *file) -{ - FILE *fp; - int ret = -1; - - if (file && (fp = fopen(file, "r"))) { - ret = (fgetc(fp) == '\n' && fgetc(fp) == EOF) || feof(fp); - fclose(fp); - } - - return ret; -} - -/* * Check whether two files are equal. */ int io_files_equal(const char *file1, const char *file2) |