/*
* Calcurse - text-based organizer
*
* Copyright (c) 2004-2011 calcurse Development Team <misc@calcurse.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Send your feedback or comments to : misc@calcurse.org
* Calcurse home page : http://calcurse.org
*
*/
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <signal.h>
#include <time.h>
#include <math.h>
#include <unistd.h>
#include <errno.h>
#include "calcurse.h"
#define ICALDATEFMT "%Y%m%d"
#define ICALDATETIMEFMT "%Y%m%dT%H%M%S"
typedef enum {
PROGRESS_BAR_SAVE,
PROGRESS_BAR_LOAD,
PROGRESS_BAR_EXPORT
} progress_bar_t;
enum {
PROGRESS_BAR_CONF,
PROGRESS_BAR_TODO,
PROGRESS_BAR_APTS,
PROGRESS_BAR_KEYS
};
enum {
PROGRESS_BAR_EXPORT_EVENTS,
PROGRESS_BAR_EXPORT_APOINTS,
PROGRESS_BAR_EXPORT_TODO
};
typedef enum {
ICAL_VEVENT,
ICAL_VTODO,
ICAL_TYPES
} ical_types_e;
typedef enum {
UNDEFINED,
APPOINTMENT,
EVENT
} ical_vevent_e;
typedef struct {
enum recur_type type;
int freq;
long until;
unsigned count;
} ical_rpt_t;
struct ht_keybindings_s {
char *label;
enum key key;
HTABLE_ENTRY (ht_keybindings_s);
};
/* Type definition for callbacks to multiple-mode export functions. */
typedef void (*cb_export_t)(FILE *);
typedef void (*cb_dump_t)(FILE *, long, long, char *);
/* Static functions used to add export functionalities. */
static void ical_export_header (FILE *);
static void ical_export_recur_events (FILE *);
static void ical_export_events (FILE *);
static void ical_export_recur_apoints (FILE *);
static void ical_export_apoints (FILE *);
static void ical_export_todo (FILE *);
static void ical_export_footer (FILE *);
static void pcal_export_header (FILE *);
static void pcal_export_recur_events (FILE *);
static void pcal_export_events (FILE *);
static void pcal_export_recur_apoints (FILE *);
static void pcal_export_apoints (FILE *);
static void pcal_export_todo (FILE *);
static void pcal_export_footer (FILE *);
cb_export_t cb_export_header[IO_EXPORT_NBTYPES] =
{ical_export_header, pcal_export_header};
cb_export_t cb_export_recur_events[IO_EXPORT_NBTYPES] =
{ical_export_recur_events, pcal_export_recur_events};
cb_export_t cb_export_events[IO_EXPORT_NBTYPES] =
{ical_export_events, pcal_export_events};
cb_export_t cb_export_recur_apoints[IO_EXPORT_NBTYPES] =
{ical_export_recur_apoints, pcal_export_recur_apoints};
cb_export_t cb_export_apoints[IO_EXPORT_NBTYPES] =
{ical_export_apoints, pcal_export_apoints};
cb_export_t cb_export_todo[IO_EXPORT_NBTYPES] =
{ical_export_todo, pcal_export_todo};
cb_export_t cb_export_footer[IO_EXPORT_NBTYPES] =
{ical_export_footer, pcal_export_footer};
static char *ical_recur_type[RECUR_TYPES] =
{ "", "DAILY", "WEEKLY", "MONTHLY", "YEARLY" };
/* Draw a progress bar while saving, loading or exporting data. */
static void
progress_bar (progress_bar_t type, int progress)
{
#define NBFILES 4
#define NBEXPORTED 3
#define LABELENGTH 15
int i, step, steps;
char *mesg_sav = _("Saving...");
char *mesg_load = _("Loading...");
char *mesg_export = _("Exporting...");
char *error_msg = _("Internal error while displaying progress bar");
char *barchar = "|";
char *file[NBFILES] = {
"[ conf ]",
"[ todo ]",
"[ apts ]",
"[ keys ]"
};
char *data[NBEXPORTED] = {
"[ events ]",
"[appointments]",
"[ todo ]"
};
int ipos = LABELENGTH + 2;
int epos[NBFILES];
/* progress bar length init. */
ipos = LABELENGTH + 2;
steps = (type == PROGRESS_BAR_EXPORT) ? NBEXPORTED : NBFILES;
step = floor (col / (steps + 1));
for (i = 0; i < steps - 1; i++)
epos[i] = (i + 2) * step;
epos[steps - 1] = col - 2;
switch (type)
{
case PROGRESS_BAR_SAVE:
EXIT_IF (progress < 0 || progress > PROGRESS_BAR_KEYS, "%s", error_msg);
status_mesg (mesg_sav, file[progress]);
break;
case PROGRESS_BAR_LOAD:
EXIT_IF (progress < 0 || progress > PROGRESS_BAR_KEYS, "%s", error_msg);
status_mesg (mesg_load, file[progress]);
break;
case PROGRESS_BAR_EXPORT:
EXIT_IF (progress < 0
|| progress > PROGRESS_BAR_EXPORT_TODO, "%s", error_msg);
status_mesg (mesg_export, data[progress]);
break;
}
/* Draw the progress bar. */
mvwprintw (win[STA].p, 1, ipos, barchar);
mvwprintw (win[STA].p, 1, epos[steps - 1], barchar);
custom_apply_attr (win[STA].p, ATTR_HIGHEST);
for (i = ipos + 1; i < epos[progress]; i++)
mvwaddch (win[STA].p, 1, i, ' ' | A_REVERSE);
custom_remove_attr (win[STA].p, ATTR_HIGHEST);
wmove (win[STA].p, 0, 0);
wins_wrefresh (win[STA].p);
#undef NBFILES
#undef NBEXPORTED
#undef LABELENGTH
}
/* Ask user for a file name to export data to. */
static FILE *
get_export_stream (enum export_type type)
{
FILE *stream;
int cancel;
char *home, *stream_name;
char *question = _("Choose the file used to export calcurse data:");
char *wrong_name =
_("The file cannot be accessed, please enter another file name.");
char *press_enter = _("Press [ENTER] to continue.");
const char *file_ext[IO_EXPORT_NBTYPES] = {"ical", "txt"};
stream = NULL;
stream_name = (char *) mem_malloc (BUFSIZ);
if ((home = getenv ("HOME")) != NULL)
(void)snprintf (stream_name, BUFSIZ, "%s/calcurse.%s", home,
file_ext[type]);
else
(void)snprintf (stream_name, BUFSIZ, "%s/calcurse.%s", get_tempdir (),
file_ext[type]);
while (stream == NULL)
{
status_mesg (question, "");
cancel = updatestring (win[STA].p, &stream_name, 0, 1);
if (cancel)
{
mem_free (stream_name);
return (NULL);
}
stream = fopen (stream_name, "w");
if (stream == NULL)
{
status_mesg (wrong_name, press_enter);
(void)wgetch (win[STA].p);
}
}
mem_free (stream_name);
return (stream);
}
/*
* Travel through each occurence of an item, and execute the given callback
* (mainly used to export data).
*/
static void
foreach_date_dump (const long date_end, struct rpt *rpt, llist_t *exc,
long item_first_date, long item_dur, char *item_mesg,
cb_dump_t cb_dump, FILE *stream)
{
long date, item_time;
struct tm lt;
time_t t;
t = item_first_date;
lt = *localtime (&t);
lt.tm_hour = lt.tm_min = lt.tm_sec = 0;
lt.tm_isdst = -1;
date = mktime (<);
item_time = item_first_date - date;
while (date <= date_end && date <= rpt->until)
{
if (recur_item_inday (item_first_date, exc, rpt->type, rpt->freq,
rpt->until, date))
{
(*cb_dump)(stream, date + item_time, item_dur, item_mesg);
}
switch (rpt->type)
{
case RECUR_DAILY:
date = date_sec_change (date, 0, rpt->freq);
break;
case RECUR_WEEKLY:
date = date_sec_change (date, 0, rpt->freq * WEEKINDAYS);
break;
case RECUR_MONTHLY:
date = date_sec_change (date, rpt->freq, 0);
break;
case RECUR_YEARLY:
date = date_sec_change (date, rpt->freq * 12, 0);
break;
default:
EXIT (_("incoherent repetition type"));
/* NOTREACHED */
break;
}
}
}
/* iCal alarm notification. */
static void
ical_export_valarm (FILE *stream)
{
(void)fprintf (stream, "BEGIN:VALARM\n");
pthread_mutex_lock (&nbar.mutex);
(void)fprintf (stream, "TRIGGER:-P%dS\n", nbar.cntdwn);
pthread_mutex_unlock (&nbar.mutex);
(void)fprintf (stream, "ACTION:DISPLAY\n");
(void)fprintf (stream, "END:VALARM\n");
}
/* Export header. */
static void
ical_export_header (FILE *stream)
{
(void)fprintf (stream, "BEGIN:VCALENDAR\n");
(void)fprintf (stream, "PRODID:-//calcurse//NONSGML v%s//EN\n", VERSION);
(void)fprintf (stream, "VERSION:2.0\n");
}
static void
pcal_export_header (FILE *stream)
{
(void)fprintf (stream, "# calcurse pcal export\n");
(void)fprintf (stream, "\n# =======\n# options\n# =======\n");
(void)fprintf (stream, "opt -A -K -l -m -F %s\n",
calendar_week_begins_on_monday () ?
"Monday" : "Sunday");
(void)fprintf (stream, "# Display week number (i.e. 1-52) on every Monday\n");
(void)fprintf (stream, "all monday in all %s %%w\n", _("Week"));
(void)fprintf (stream, "\n");
}
/* Export footer. */
static void
ical_export_footer (FILE *stream)
{
(void)fprintf (stream, "END:VCALENDAR\n");
}
static void
pcal_export_footer (FILE *stream)
{
}
/* Export recurrent events. */
static void
ical_export_recur_events (FILE *stream)
{
llist_item_t *i, *j;
char ical_date[BUFSIZ];
LLIST_FOREACH (&recur_elist, i)
{
struct recur_event *rev = LLIST_GET_DATA (i);
date_sec2date_fmt (rev->day, ICALDATEFMT, ical_date);
(void)fprintf (stream, "BEGIN:VEVENT\n");
(void)fprintf (stream, "DTSTART:%s\n", ical_date);
(void)fprintf (stream, "RRULE:FREQ=%s;INTERVAL=%d",
ical_recur_type[rev->rpt->type], rev->rpt->freq);
if (rev->rpt->until != 0)
{
date_sec2date_fmt (rev->rpt->until, ICALDATEFMT, ical_date);
(void)fprintf (stream, ";UNTIL=%s\n", ical_date);
}
else
(void)fprintf (stream, "\n");
if (LLIST_FIRST (&rev->exc))
{
(void)fprintf (stream, "EXDATE:");
LLIST_FOREACH (&rev->exc, j)
{
struct excp *exc = LLIST_GET_DATA (j);
date_sec2date_fmt (exc->st, ICALDATEFMT, ical_date);
(void)fprintf (stream, "%s", ical_date);
if (LLIST_NEXT (j))
(void)fprintf (stream, ",");
else
(void)fprintf (stream, "\n");
}
}
(void)fprintf (stream, "SUMMARY:%s\n", rev->mesg);
(void)fprintf (stream, "END:VEVENT\n");
}
}
/* Format and dump event data to a pcal formatted file. */
static void
pcal_dump_event (FILE *stream, long event_date, long event_dur,
char *event_mesg)
{
char pcal_date[BUFSIZ];
date_sec2date_fmt (event_date, "%b %d", pcal_date);
(void)fprintf (stream, "%s %s\n", pcal_date, event_mesg);
}
/* Format and dump appointment data to a pcal formatted file. */
static void
pcal_dump_apoint (FILE *stream, long apoint_date, long apoint_dur,
char *apoint_mesg)
{
char pcal_date[BUFSIZ], pcal_beg[BUFSIZ], pcal_end[BUFSIZ];
date_sec2date_fmt (apoint_date, "%b %d", pcal_date);
date_sec2date_fmt (apoint_date, "%R", pcal_beg);
date_sec2date_fmt (apoint_date + apoint_dur, "%R", pcal_end);
(void)fprintf (stream, "%s ", pcal_date);
(void)fprintf (stream, "(%s -> %s) %s\n", pcal_beg, pcal_end, apoint_mesg);
}
static void
pcal_export_recur_events (FILE *stream)
{
llist_item_t *i;
char pcal_date[BUFSIZ];
(void)fprintf (stream, "\n# =============");
(void)fprintf (stream, "\n# Recur. Events");
(void)fprintf (stream, "\n# =============\n");
(void)fprintf (stream,
"# (pcal does not support from..until dates specification\n");
LLIST_FOREACH (&recur_elist, i)
{
struct recur_event *rev = LLIST_GET_DATA (i);
if (rev->rpt->until == 0 && rev->rpt->freq == 1)
{
switch (rev->rpt->type)
{
case RECUR_DAILY:
date_sec2date_fmt (rev->day, "%b %d", pcal_date);
(void)fprintf (stream, "all day on_or_after %s %s\n",
pcal_date, rev->mesg);
break;
case RECUR_WEEKLY:
date_sec2date_fmt (rev->day, "%a", pcal_date);
(void)fprintf (stream, "all %s on_or_after ", pcal_date);
date_sec2date_fmt (rev->day, "%b %d", pcal_date);
(void)fprintf (stream, "%s %s\n", pcal_date, rev->mesg);
break;
case RECUR_MONTHLY:
date_sec2date_fmt (rev->day, "%d", pcal_date);
(void)fprintf (stream, "day on all %s %s\n", pcal_date,
rev->mesg);
break;
case RECUR_YEARLY:
date_sec2date_fmt (rev->day, "%b %d", pcal_date);
(void)fprintf (stream, "%s %s\n", pcal_date, rev->mesg);
break;
default:
EXIT (_("incoherent repetition type"));
}
}
else
{
const long YEAR_START = calendar_start_of_year ();
const long YEAR_END = calendar_end_of_year ();
if (rev->day < YEAR_END && rev->day > YEAR_START)
foreach_date_dump (YEAR_END, rev->rpt, &rev->exc, rev->day, 0,
rev->mesg, (cb_dump_t) pcal_dump_event, stream);
}
}
}
/* Export events. */
static void
ical_export_events (FILE *stream)
{
llist_item_t *i;
char ical_date[BUFSIZ];
LLIST_FOREACH (&eventlist, i)
{
struct event *ev = LLIST_TS_GET_DATA (i);
date_sec2date_fmt (ev->day, ICALDATEFMT, ical_date);
(void)fprintf (stream, "BEGIN:VEVENT\n");
(void)fprintf (stream, "DTSTART:%s\n", ical_date);
(void)fprintf (stream, "SUMMARY:%s\n", ev->mesg);
(void)fprintf (stream, "END:VEVENT\n");
}
}
static void
pcal_export_events (FILE *stream)
{
llist_item_t *i;
(void)fprintf (stream, "\n# ======\n# Events\n# ======\n");
LLIST_FOREACH (&eventlist, i)
{
struct event *ev = LLIST_TS_GET_DATA (i);
pcal_dump_event (stream, ev->day, 0, ev->mesg);
}
(void)fprintf (stream, "\n");
}
/* Export recurrent appointments. */
static void
ical_export_recur_apoints (FILE *stream)
{
llist_item_t *i, *j;
char ical_datetime[BUFSIZ];
char ical_date[BUFSIZ];
LLIST_TS_LOCK (&recur_alist_p);
LLIST_TS_FOREACH (&recur_alist_p, i)
{
struct recur_apoint *rapt = LLIST_TS_GET_DATA (i);
date_sec2date_fmt (rapt->start, ICALDATETIMEFMT, ical_datetime);
(void)fprintf (stream, "BEGIN:VEVENT\n");
(void)fprintf (stream, "DTSTART:%s\n", ical_datetime);
(void)fprintf (stream, "DURATION:PT0H0M%ldS\n", rapt->dur);
(void)fprintf (stream, "RRULE:FREQ=%s;INTERVAL=%d",
ical_recur_type[rapt->rpt->type], rapt->rpt->freq);
if (rapt->rpt->until != 0)
{
date_sec2date_fmt (rapt->rpt->until + HOURINSEC, ICALDATEFMT,
ical_date);
(void)fprintf (stream, ";UNTIL=%s\n", ical_date);
}
else
(void)fprintf (stream, "\n");
if (LLIST_FIRST (&rapt->exc))
{
(void)fprintf (stream, "EXDATE:");
LLIST_FOREACH (&rapt->exc, j)
{
struct excp *exc = LLIST_GET_DATA (j);
date_sec2date_fmt (exc->st, ICALDATEFMT, ical_date);
(void)fprintf (stream, "%s", ical_date);
if (LLIST_NEXT (j))
(void)fprintf (stream, ",");
else
(void)fprintf (stream, "\n");
}
}
(void)fprintf (stream, "SUMMARY:%s\n", rapt->mesg);
if (rapt->state & APOINT_NOTIFY)
ical_export_valarm (stream);
(void)fprintf (stream, "END:VEVENT\n");
}
LLIST_TS_UNLOCK (&recur_alist_p);
}
static void
pcal_export_recur_apoints (FILE *stream)
{
llist_item_t *i;
char pcal_date[BUFSIZ], pcal_beg[BUFSIZ], pcal_end[BUFSIZ];
(void)fprintf (stream, "\n# ==============");
(void)fprintf (stream, "\n# Recur. Apoints");
(void)fprintf (stream, "\n# ==============\n");
(void)fprintf (stream,
"# (pcal does not support from..until dates specification\n");
LLIST_TS_FOREACH (&recur_alist_p, i)
{
struct recur_apoint *rapt = LLIST_TS_GET_DATA (i);
if (rapt->rpt->until == 0 && rapt->rpt->freq == 1)
{
date_sec2date_fmt (rapt->start, "%R", pcal_beg);
date_sec2date_fmt (rapt->start + rapt->dur, "%R", pcal_end);
switch (rapt->rpt->type)
{
case RECUR_DAILY:
date_sec2date_fmt (rapt->start, "%b %d", pcal_date);
(void)fprintf (stream, "all day on_or_after %s (%s -> %s) %s\n",
pcal_date, pcal_beg, pcal_end, rapt->mesg);
break;
case RECUR_WEEKLY:
date_sec2date_fmt (rapt->start, "%a", pcal_date);
(void)fprintf (stream, "all %s on_or_after ", pcal_date);
date_sec2date_fmt (rapt->start, "%b %d", pcal_date);
(void)fprintf (stream, "%s (%s -> %s) %s\n", pcal_date,
pcal_beg, pcal_end, rapt->mesg);
break;
case RECUR_MONTHLY:
date_sec2date_fmt (rapt->start, "%d", pcal_date);
(void)fprintf (stream, "day on all %s (%s -> %s) %s\n",
pcal_date, pcal_beg, pcal_end, rapt->mesg);
break;
case RECUR_YEARLY:
date_sec2date_fmt (rapt->start, "%b %d", pcal_date);
(void)fprintf (stream, "%s (%s -> %s) %s\n", pcal_date,
pcal_beg, pcal_end, rapt->mesg);
break;
default:
EXIT (_("incoherent repetition type"));
}
}
else
{
const long YEAR_START = calendar_start_of_year ();
const long YEAR_END = calendar_end_of_year ();
if (rapt->start < YEAR_END && rapt->start > YEAR_START)
foreach_date_dump (YEAR_END, rapt->rpt, &rapt->exc, rapt->start,
rapt->dur, rapt->mesg,
(cb_dump_t)pcal_dump_apoint, stream);
}
}
}
/* Export appointments. */
static void
ical_export_apoints (FILE *stream)
{
llist_item_t *i;
char ical_datetime[BUFSIZ];
LLIST_TS_LOCK (&alist_p);
LLIST_TS_FOREACH (&alist_p, i)
{
struct apoint *apt = LLIST_TS_GET_DATA (i);
date_sec2date_fmt (apt->start, ICALDATETIMEFMT, ical_datetime);
(void)fprintf (stream, "BEGIN:VEVENT\n");
(void)fprintf (stream, "DTSTART:%s\n", ical_datetime);
(void)fprintf (stream, "DURATION:PT0H0M%ldS\n", apt->dur);
(void)fprintf (stream, "SUMMARY:%s\n", apt->mesg);
if (apt->state & APOINT_NOTIFY)
ical_export_valarm (stream);
(void)fprintf (stream, "END:VEVENT\n");
}
LLIST_TS_UNLOCK (&alist_p);
}
static void
pcal_export_apoints (FILE *stream)
{
llist_item_t *i;
(void)fprintf (stream, "\n# ============\n# Appointments\n# ============\n");
LLIST_TS_LOCK (&alist_p);
LLIST_TS_FOREACH (&alist_p, i)
{
struct apoint *apt = LLIST_TS_GET_DATA (i);
pcal_dump_apoint (stream, apt->start, apt->dur, apt->mesg);
}
LLIST_TS_UNLOCK (&alist_p);
(void)fprintf (stream, "\n");
}
/* Export todo items. */
static void
ical_export_todo (FILE *stream)
{
llist_item_t *i;
LLIST_FOREACH (&todolist, i)
{
struct todo *todo = LLIST_TS_GET_DATA (i);
if (todo->id < 0) /* completed items */
continue;
(void)fprintf (stream, "BEGIN:VTODO\n");
(void)fprintf (stream, "PRIORITY:%d\n", todo->id);
(void)fprintf (stream, "SUMMARY:%s\n", todo->mesg);
(void)fprintf (stream, "END:VTODO\n");
}
}
static void
pcal_export_todo (FILE *stream)
{
llist_item_t *i;
(void)fprintf (stream, "#\n# Todos\n#\n");
LLIST_FOREACH (&todolist, i)
{
struct todo *todo = LLIST_TS_GET_DATA (i);
if (todo->id < 0) /* completed items */
continue;
(void)fprintf (stream, "note all ");
(void)fprintf (stream, "%d. %s\n", todo->id, todo->mesg);
}
(void)fprintf (stream, "\n");
}
/* Append a line to a file. */
unsigned
io_fprintln (const char *fname, const char *fmt, ...)
{
FILE *fp;
va_list ap;
char buf[BUFSIZ];
int ret;
fp = fopen (fname, "a");
RETVAL_IF (!fp, 0, _("Failed to open \"%s\", - %s\n"),
fname, strerror (errno));
va_start (ap, fmt);
ret = vsnprintf (buf, sizeof buf, fmt, ap);
RETVAL_IF (ret < 0, 0, _("Failed to build message\n"));
va_end (ap);
ret = fprintf (fp, "%s", buf);
RETVAL_IF (ret < 0, 0, _("Failed to print message \"%s\"\n"), buf);
ret = fclose (fp);
RETVAL_IF (ret != 0, 0, _("Failed to close \"%s\" - %s\n"),
fname, strerror (errno));
return 1;
}
/*
* 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.
* The datadir argument can be use to specify an alternative data root dir.
*/
void
io_init (char *cfile, char *datadir)
{
FILE *data_file;
char *home;
char apts_file[BUFSIZ] = "";
int ch;
if (datadir != NULL)
{
home = datadir;
(void)snprintf (path_dir, BUFSIZ, "%s", home);
(void)snprintf (path_todo, BUFSIZ, "%s/" TODO_PATH_NAME, home);
(void)snprintf (path_conf, BUFSIZ, "%s/" CONF_PATH_NAME, home);
(void)snprintf (path_notes, BUFSIZ, "%s/" NOTES_DIR_NAME, home);
(void)snprintf (path_apts, BUFSIZ, "%s/" APTS_PATH_NAME, home);
(void)snprintf (path_keys, BUFSIZ, "%s/" KEYS_PATH_NAME, home);
(void)snprintf (path_cpid, BUFSIZ, "%s/" CPID_PATH_NAME, home);
(void)snprintf (path_dpid, BUFSIZ, "%s/" DPID_PATH_NAME, home);
(void)snprintf (path_dmon_log, BUFSIZ, "%s/" DLOG_PATH_NAME, home);
}
else
{
home = getenv ("HOME");
if (home == NULL)
{
home = ".";
}
(void)snprintf (path_dir, BUFSIZ, "%s/" DIR_NAME, home);
(void)snprintf (path_todo, BUFSIZ, "%s/" TODO_PATH, home);
(void)snprintf (path_conf, BUFSIZ, "%s/" CONF_PATH, home);
(void)snprintf (path_keys, BUFSIZ, "%s/" KEYS_PATH, home);
(void)snprintf (path_cpid, BUFSIZ, "%s/" CPID_PATH, home);
(void)snprintf (path_dpid, BUFSIZ, "%s/" DPID_PATH, home);
(void)snprintf (path_dmon_log, BUFSIZ, "%s/" DLOG_PATH, home);
(void)snprintf (path_notes, BUFSIZ, "%s/" NOTES_DIR, home);
if (cfile == NULL)
{
(void)snprintf (path_apts, BUFSIZ, "%s/" APTS_PATH, home);
}
else
{
(void)snprintf (apts_file, BUFSIZ, "%s", cfile);
(void)strncpy (path_apts, apts_file, BUFSIZ);
/* check if the file exists, otherwise create it */
data_file = fopen (path_apts, "r");
if (data_file == NULL)
{
printf (_("%s does not exist, create it now [y or n] ? "),
path_apts);
ch = getchar ();
switch (ch)
{
case 'N':
case 'n':
printf (_("aborting...\n"));
exit_calcurse (EXIT_FAILURE);
break;
case 'Y':
case 'y':
data_file = fopen (path_apts, "w");
if (data_file == NULL)
{
perror (path_apts);
exit_calcurse (EXIT_FAILURE);
}
else
{
printf (_("%s successfully created\n"), path_apts);
printf (_("starting interactive mode...\n"));
}
break;
default:
printf (_("aborting...\n"));
exit_calcurse (EXIT_FAILURE);
break;
}
}
file_close (data_file, __FILE_POS__);
}
}
}
void
io_extract_data (char *dst_data, const char *org, int len)
{
int i;
for (; *org == ' ' || *org == '\t'; org++);
for (i = 0; i < len - 1; i++)
{
if (*org == '\n' || *org == '\0' || *org == '#')
break;
*dst_data++ = *org++;
}
*dst_data = '\0';
}
void
display_mark (void)
{
const int DISPLAY_TIME = 1;
WINDOW *mwin;
mwin = newwin (1, 2, 1, col - 3);
custom_apply_attr (mwin, ATTR_HIGHEST);
mvwprintw (mwin, 0, 0, "**");
wins_wrefresh (mwin);
sleep (DISPLAY_TIME);
mvwprintw (mwin, 0, 0, " ");
wins_wrefresh (mwin);
delwin (mwin);
wins_doupdate ();
}
static pthread_mutex_t io_save_mutex = PTHREAD_MUTEX_INITIALIZER;
/* Save the user configuration. */
unsigned
io_save_conf (struct conf *conf)
{
char *config_txt =
"#\n"
"# Calcurse configuration file\n#\n"
"# This file sets the configuration options used by Calcurse. These\n"
"# options are usually set from within Calcurse. A line beginning with \n"
"# a space or tab is considered to be a continuation of the previous "
"line.\n"
"# For a variable to be unset its value must be blank, followed by an\n"
"# empty line. To set a variable to the empty string its value should be "
"\"\".\n"
"# Lines beginning with \"#\" are comments, and ignored by Calcurse.\n";
char theme_name[BUFSIZ];
FILE *fp;
if ((fp = fopen (path_conf, "w")) == NULL)
return 0;
custom_color_theme_name (theme_name);
(void)fprintf (fp, "%s\n", config_txt);
(void)fprintf (fp, "# If this option is set to yes, "
"automatic save is done when quitting\n");
(void)fprintf (fp, "auto_save=");
(void)fprintf (fp, "%s\n", (conf->auto_save) ? "yes" : "no");
(void)fprintf (fp, "\n# If not null, perform automatic saves every "
"'periodic_save' minutes\n");
(void)fprintf (fp, "periodic_save=");
(void)fprintf (fp, "%d\n", conf->periodic_save);
(void)fprintf (fp, "\n# If this option is set to yes, "
"confirmation is required before quitting\n");
(void)fprintf (fp, "confirm_quit=");
(void)fprintf (fp, "%s\n", (conf->confirm_quit) ? "yes" : "no");
(void)fprintf (fp, "\n# If this option is set to yes, "
"confirmation is required before deleting an event\n");
(void)fprintf (fp, "confirm_delete=");
(void)fprintf (fp, "%s\n", (conf->confirm_delete) ? "yes" : "no");
(void)fprintf (fp, "\n# If this option is set to yes, "
"messages about loaded and saved data will not be displayed\n");
(void)fprintf (fp, "skip_system_dialogs=");
(void)fprintf (fp, "%s\n", (conf->skip_system_dialogs) ? "yes" : "no");
(void)fprintf (fp,
"\n# If this option is set to yes, progress bar appearing "
"when saving data will not be displayed\n");
(void)fprintf (fp, "skip_progress_bar=");
(void)fprintf (fp, "%s\n", (conf->skip_progress_bar) ? "yes" : "no");
(void)fprintf (fp, "\n# Default calendar view (0)monthly (1)weekly:\n");
(void)fprintf (fp, "calendar_default_view=");
(void)fprintf (fp, "%d\n", calendar_get_view ());
(void)fprintf (fp, "\n# If this option is set to yes, "
"monday is the first day of the week, else it is sunday\n");
(void)fprintf (fp, "week_begins_on_monday=");
(void)fprintf (fp, "%s\n",
(calendar_week_begins_on_monday ())? "yes" : "no");
(void)fprintf (fp, "\n# This is the color theme used for menus :\n");
(void)fprintf (fp, "color-theme=");
(void)fprintf (fp, "%s\n", theme_name);
(void)fprintf (fp, "\n# This is the layout of the calendar :\n");
(void)fprintf (fp, "layout=");
(void)fprintf (fp, "%d\n", wins_layout ());
(void)fprintf (fp, "\n# Width (in percentage, 0 being minimun width) "
"of the side bar :\n");
(void)fprintf (fp, "side-bar_width=");
(void)fprintf (fp, "%d\n", wins_sbar_wperc ());
if (ui_mode == UI_CURSES)
pthread_mutex_lock (&nbar.mutex);
(void)fprintf (fp,
"\n# If this option is set to yes, "
"notify-bar will be displayed :\n");
(void)fprintf (fp, "notify-bar_show=");
(void)fprintf (fp, "%s\n", (nbar.show) ? "yes" : "no");
(void)fprintf (fp,
"\n# Format of the date to be displayed inside notify-bar :\n");
(void)fprintf (fp, "notify-bar_date=");
(void)fprintf (fp, "%s\n", nbar.datefmt);
(void)fprintf (fp,
"\n# Format of the time to be displayed inside notify-bar :\n");
(void)fprintf (fp, "notify-bar_clock=");
(void)fprintf (fp, "%s\n", nbar.timefmt);
(void)fprintf (fp,
"\n# Warn user if he has an appointment within next "
"'notify-bar_warning' seconds :\n");
(void)fprintf (fp, "notify-bar_warning=");
(void)fprintf (fp, "%d\n", nbar.cntdwn);
(void)fprintf (fp, "\n# Command used to notify user of "
"an upcoming appointment :\n");
(void)fprintf (fp, "notify-bar_command=");
(void)fprintf (fp, "%s\n", nbar.cmd);
(void)fprintf (fp, "\n# Notify all appointments instead of flagged ones only\n");
(void)fprintf (fp, "notify-all=");
(void)fprintf (fp, "%s\n", (nbar.notify_all) ? "yes" : "no");
(void)fprintf (fp, "\n# Format of the date to be displayed "
"in non-interactive mode :\n");
(void)fprintf (fp, "output_datefmt=");
(void)fprintf (fp, "%s\n", conf->output_datefmt);
(void)fprintf (fp, "\n# Format to be used when entering a date "
"(1)mm/dd/yyyy (2)dd/mm/yyyy (3)yyyy/mm/dd) "
"(4)yyyy-mm-dd:\n");
(void)fprintf (fp, "input_datefmt=");
(void)fprintf (fp, "%d\n", conf->input_datefmt);
if (ui_mode == UI_CURSES)
pthread_mutex_unlock (&nbar.mutex);
(void)fprintf (fp, "\n# If this option is set to yes, "
"calcurse will run in background to get notifications "
"after exiting\n");
(void)fprintf (fp, "notify-daemon_enable=");
(void)fprintf (fp, "%s\n", dmon.enable ? "yes" : "no");
(void)fprintf (fp, "\n# If this option is set to yes, "
"activity will be logged when running in background\n");
(void)fprintf (fp, "notify-daemon_log=");
(void)fprintf (fp, "%s\n", dmon.log ? "yes" : "no");
file_close (fp, __FILE_POS__);
return 1;
}
/*
* Save the apts data file, which contains the
* appointments first, and then the events.
* Recursive items are written first.
*/
unsigned
io_save_apts (void)
{
llist_item_t *i;
FILE *fp;
if ((fp = fopen (path_apts, "w")) == NULL)
return 0;
recur_save_data (fp);
if (ui_mode == UI_CURSES)
LLIST_TS_LOCK (&alist_p);
LLIST_TS_FOREACH (&alist_p, i)
{
struct apoint *apt = LLIST_TS_GET_DATA (i);
apoint_write (apt, fp);
}
if (ui_mode == UI_CURSES)
LLIST_TS_UNLOCK (&alist_p);
LLIST_FOREACH (&eventlist, i)
{
struct event *ev = LLIST_TS_GET_DATA (i);
event_write (ev, fp);
}
file_close (fp, __FILE_POS__);
return 1;
}
/* Save the todo data file. */
unsigned
io_save_todo (void)
{
llist_item_t *i;
FILE *fp;
if ((fp = fopen (path_todo, "w")) == NULL)
return 0;
LLIST_FOREACH (&todolist, i)
{
struct todo *todo = LLIST_TS_GET_DATA (i);
todo_write (todo, fp);
}
file_close (fp, __FILE_POS__);
return 1;
}
/* Save user-defined keys */
unsigned
io_save_keys (void)
{
FILE *fp;
if ((fp = fopen (path_keys, "w")) == NULL)
return 0;
keys_save_bindings (fp);
file_close (fp, __FILE_POS__);
return 1;
}
/* Save the calendar data */
void
io_save_cal (struct conf *conf, enum save_display display)
{
char *access_pb = _("Problems accessing data file ...");
char *save_success = _("The data files were successfully saved");
char *enter = _("Press [ENTER] to continue");
int show_bar;
pthread_mutex_lock (&io_save_mutex);
show_bar = 0;
if (ui_mode == UI_CURSES && display == IO_SAVE_DISPLAY_BAR
&& !conf->skip_progress_bar)
show_bar = 1;
else if (ui_mode == UI_CURSES && display == IO_SAVE_DISPLAY_MARK)
display_mark ();
if (show_bar)
progress_bar (PROGRESS_BAR_SAVE, PROGRESS_BAR_CONF);
if (!io_save_conf (conf))
ERROR_MSG ("%s", access_pb);
if (show_bar)
progress_bar (PROGRESS_BAR_SAVE, PROGRESS_BAR_TODO);
if (!io_save_todo ())
ERROR_MSG ("%s", access_pb);
if (show_bar)
progress_bar (PROGRESS_BAR_SAVE, PROGRESS_BAR_APTS);
if (!io_save_apts ())
ERROR_MSG ("%s", access_pb);
if (show_bar)
progress_bar (PROGRESS_BAR_SAVE, PROGRESS_BAR_KEYS);
if (!io_save_keys ())
ERROR_MSG ("%s", access_pb);
/* Print a message telling data were saved */
if (ui_mode == UI_CURSES && !conf->skip_system_dialogs
&& display != IO_SAVE_DISPLAY_MARK)
{
status_mesg (save_success, enter);
(void)wgetch (win[STA].p);
}
pthread_mutex_unlock (&io_save_mutex);
}
/*
* Check what type of data is written in the appointment file,
* and then load either: a new appointment, a new event, or a new
* recursive item (which can also be either an event or an appointment).
*/
void
io_load_app (void)
{
FILE *data_file;
int c, is_appointment, is_event, is_recursive;
struct tm start, end, until, *lt;
llist_t exc;
time_t t;
int id = 0;
int freq;
char type, state = 0L;
char note[NOTESIZ + 1], *notep;
t = time (NULL);
lt = localtime (&t);
start = end = until = *lt;
data_file = fopen (path_apts, "r");
for (;;)
{
LLIST_INIT (&exc);
is_appointment = is_event = is_recursive = 0;
c = getc (data_file);
if (c == EOF)
break;
(void)ungetc (c, data_file);
/* Read the date first: it is common to both events
* and appointments.
*/
if (fscanf (data_file, "%u / %u / %u ",
&start.tm_mon, &start.tm_mday, &start.tm_year) != 3)
{
EXIT (_("syntax error in the item date"));
}
/* Read the next character : if it is an '@' then we have
* an appointment, else if it is an '[' we have en event.
*/
c = getc (data_file);
if (c == '@')
is_appointment = 1;
else if (c == '[')
is_event = 1;
else
{
EXIT (_("no event nor appointment found"));
}
(void)ungetc (c, data_file);
/* Read the remaining informations. */
if (is_appointment)
{
fscanf (data_file, "@ %u : %u -> %u / %u / %u @ %u : %u ",
&start.tm_hour, &start.tm_min,
&end.tm_mon, &end.tm_mday, &end.tm_year,
&end.tm_hour, &end.tm_min);
}
else if (is_event)
{
fscanf (data_file, "[%d] ", &id);
}
else
{
EXIT (_("wrong format in the appointment or event"));
/* NOTREACHED */
}
/* Check if we have a recursive item. */
c = getc (data_file);
if (c == '{')
{
(void)ungetc (c, data_file);
is_recursive = 1;
fscanf (data_file, "{ %d%c ", &freq, &type);
c = getc (data_file);
if (c == '}')
{ /* endless recurrent item */
(void)ungetc (c, data_file);
fscanf (data_file, "} ");
until.tm_year = 0;
}
else if (c == '-')
{
(void)ungetc (c, data_file);
fscanf (data_file, " -> %u / %u / %u ",
&until.tm_mon, &until.tm_mday, &until.tm_year);
c = getc (data_file);
if (c == '!')
{
(void)ungetc (c, data_file);
recur_exc_scan (&exc, data_file);
c = getc (data_file);
}
else
{
(void)ungetc (c, data_file);
fscanf (data_file, "} ");
}
}
else if (c == '!')
{ // endless item with exceptions
(void)ungetc (c, data_file);
recur_exc_scan (&exc, data_file);
c = getc (data_file);
until.tm_year = 0;
}
else
{
EXIT (_("wrong format in the appointment or event"));
/* NOTREACHED */
}
}
else
(void)ungetc (c, data_file);
/* Check if a note is attached to the item. */
c = getc (data_file);
if (c == '>')
{
(void)fgets (note, NOTESIZ + 1, data_file);
note[NOTESIZ] = '\0';
notep = note;
getc (data_file);
}
else
{
notep = NULL;
(void)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 == '!')
{
(void)ungetc (c, data_file);
fscanf (data_file, " ! ");
state |= APOINT_NOTIFY;
}
else
{
(void)ungetc (c, data_file);
fscanf (data_file, " | ");
state = 0L;
}
if (is_recursive)
{
recur_apoint_scan (data_file, start, end,
type, freq, until, notep, &exc, state);
}
else
{
apoint_scan (data_file, start, end, state, notep);
}
}
else if (is_event)
{
if (is_recursive)
{
recur_event_scan (data_file, start, id, type,
freq, until, notep, &exc);
}
else
{
event_scan (data_file, start, id, notep);
}
}
else
{
EXIT (_("wrong format in the appointment or event"));
/* NOTREACHED */
}
}
file_close (data_file, __FILE_POS__);
}
/* Load the todo data */
void
io_load_todo (void)
{
FILE *data_file;
char *mesg_line1 = _("Failed to open todo file");
char *mesg_line2 = _("Press [ENTER] to continue");
char *newline;
int nb_tod = 0;
int c, id;
char buf[BUFSIZ], e_todo[BUFSIZ], note[NOTESIZ + 1];
data_file = fopen (path_todo, "r");
if (data_file == NULL)
{
status_mesg (mesg_line1, mesg_line2);
(void)wgetch (win[STA].p);
}
for (;;)
{
c = getc (data_file);
if (c == EOF)
{
break;
}
else if (c == '[')
{ /* new style with id */
fscanf (data_file, "%d]", &id);
}
else
{
id = 9;
(void)ungetc (c, data_file);
}
/* Now read the attached note, if any. */
c = getc (data_file);
if (c == '>')
{
(void)fgets (note, NOTESIZ + 1, data_file);
note[NOTESIZ] = '\0';
getc (data_file);
}
else
note[0] = '\0';
/* Then read todo description. */
(void)fgets (buf, sizeof buf, data_file);
newline = strchr (buf, '\n');
if (newline)
*newline = '\0';
io_extract_data (e_todo, buf, sizeof buf);
todo_add (e_todo, id, note);
++nb_tod;
}
file_close (data_file, __FILE_POS__);
todo_set_nb (nb_tod);
}
static void
load_keys_ht_getkey (struct ht_keybindings_s *data, char **key, int *len)
{
*key = data->label;
*len = strlen (data->label);
}
static int
load_keys_ht_compare (struct ht_keybindings_s *data1,
struct ht_keybindings_s *data2)
{
const int KEYLEN = strlen (data1->label);
if (strlen (data2->label) == KEYLEN
&& !memcmp (data1->label, data2->label, KEYLEN))
return 0;
else
return 1;
}
/*
* 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.
* A log file is also built in case some errors were found in the key
* configuration file.
*/
void
io_load_keys (char *pager)
{
struct ht_keybindings_s keys[NBKEYS];
FILE *keyfp;
char buf[BUFSIZ];
struct io_file *log;
int i, skipped, loaded, line;
const int MAX_ERRORS = 5;
keys_init ();
#define HSIZE 256
HTABLE_HEAD (ht_keybindings, HSIZE, ht_keybindings_s) ht_keys =
HTABLE_INITIALIZER (&ht_keys);
HTABLE_GENERATE (ht_keybindings, ht_keybindings_s, load_keys_ht_getkey,
load_keys_ht_compare);
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]);
}
keyfp = fopen (path_keys, "r");
EXIT_IF (keyfp == NULL, _("could not find any key file."));
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)
{
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')
continue;
if (sscanf (buf, "%s", key_label) != AWAITED)
{
skipped++;
io_log_print (log, line, _("Could not read key label"));
continue;
}
ht_entry.label = key_label;
p = buf + strlen (key_label) + 1;
ht_elm = HTABLE_LOOKUP (ht_keybindings, &ht_keys, &ht_entry);
if (!ht_elm)
{
skipped++;
io_log_print (log, line, _("Key label not recognized"));
continue;
}
assigned = 0;
for (;;)
{
char key_ch[BUFSIZ], tmpbuf[BUFSIZ];
while (*p == ' ')
p++;
(void)strncpy (tmpbuf, p, BUFSIZ);
if (sscanf (tmpbuf, "%s", key_ch) == AWAITED)
{
int ch;
if ((ch = keys_str2int (key_ch)) < 0)
{
char unknown_key[BUFSIZ];
skipped++;
(void)snprintf (unknown_key, BUFSIZ,
_("Error reading key: \"%s\""), key_ch);
io_log_print (log, line, unknown_key);
}
else
{
int used;
used = keys_assign_binding (ch, ht_elm->key);
if (used)
{
char already_assigned[BUFSIZ];
skipped++;
(void)snprintf (already_assigned, BUFSIZ,
_("\"%s\" assigned multiple times!"), key_ch);
io_log_print (log, line, already_assigned);
}
else
assigned++;
}
p += strlen (key_ch) +