* "WAIT_MYPGRP" isn't POSIX'ish. Relying on this caused compilation issues in certain environments (e.g. under Cygwin). As a workaround, define "WAIT_MYPGRP" explicitly if it's undefined. * "P_tmpdir" is an XSI extension. Don't try use it if it isn't available. Signed-off-by: Lukas Fleischer <calcurse@cryptocrack.de>
1224 lines
27 KiB
C
1224 lines
27 KiB
C
/*
|
|
* 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 <time.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <limits.h>
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
#include <sys/types.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include "calcurse.h"
|
|
|
|
#define ISLEAP(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0)
|
|
|
|
/* General routine to exit calcurse properly. */
|
|
void
|
|
exit_calcurse (int status)
|
|
{
|
|
int was_interactive;
|
|
|
|
if (ui_mode == UI_CURSES)
|
|
{
|
|
notify_stop_main_thread ();
|
|
clear ();
|
|
wins_refresh ();
|
|
endwin ();
|
|
ui_mode = UI_CMDLINE;
|
|
was_interactive = 1;
|
|
}
|
|
else
|
|
was_interactive = 0;
|
|
|
|
calendar_stop_date_thread ();
|
|
io_stop_psave_thread ();
|
|
free_user_data ();
|
|
keys_free ();
|
|
mem_stats ();
|
|
if (was_interactive)
|
|
{
|
|
if (unlink (path_cpid) != 0)
|
|
EXIT (_("Could not remove calcurse lock file: %s\n"),
|
|
strerror (errno));
|
|
if (dmon.enable)
|
|
dmon_start (status);
|
|
}
|
|
|
|
exit (status);
|
|
}
|
|
|
|
void
|
|
free_user_data (void)
|
|
{
|
|
day_free_list ();
|
|
event_llist_free ();
|
|
event_free_bkp ();
|
|
apoint_llist_free ();
|
|
apoint_free_bkp ();
|
|
recur_apoint_llist_free ();
|
|
recur_event_llist_free ();
|
|
recur_apoint_free_bkp ();
|
|
recur_event_free_bkp ();
|
|
todo_free_list ();
|
|
notify_free_app ();
|
|
}
|
|
|
|
/* Function to exit on internal error. */
|
|
void
|
|
fatalbox (const char *errmsg)
|
|
{
|
|
WINDOW *errwin;
|
|
char *label = _("/!\\ INTERNAL ERROR /!\\");
|
|
char *reportmsg = _("Please report the following bug:");
|
|
const int WINROW = 10;
|
|
const int WINCOL = col - 2;
|
|
const int MSGLEN = WINCOL - 2;
|
|
char msg[MSGLEN];
|
|
|
|
if (errmsg == NULL)
|
|
return;
|
|
|
|
strncpy (msg, errmsg, MSGLEN);
|
|
errwin = newwin (WINROW, WINCOL, (row - WINROW) / 2, (col - WINCOL) / 2);
|
|
custom_apply_attr (errwin, ATTR_HIGHEST);
|
|
box (errwin, 0, 0);
|
|
wins_show (errwin, label);
|
|
mvwprintw (errwin, 3, 1, reportmsg);
|
|
mvwprintw (errwin, 5, (WINCOL - strlen (msg)) / 2, "%s", msg);
|
|
custom_remove_attr (errwin, ATTR_HIGHEST);
|
|
wins_wrefresh (errwin);
|
|
wgetch (errwin);
|
|
delwin (errwin);
|
|
wins_doupdate ();
|
|
}
|
|
|
|
void
|
|
warnbox (const char *msg)
|
|
{
|
|
WINDOW *warnwin;
|
|
char *label = "/!\\";
|
|
const int WINROW = 10;
|
|
const int WINCOL = col - 2;
|
|
const int MSGLEN = WINCOL - 2;
|
|
char displmsg[MSGLEN];
|
|
|
|
if (msg == NULL)
|
|
return;
|
|
|
|
strncpy (displmsg, msg, MSGLEN);
|
|
warnwin = newwin (WINROW, WINCOL, (row - WINROW) / 2, (col - WINCOL) / 2);
|
|
custom_apply_attr (warnwin, ATTR_HIGHEST);
|
|
box (warnwin, 0, 0);
|
|
wins_show (warnwin, label);
|
|
mvwprintw (warnwin, 5, (WINCOL - strlen (displmsg)) / 2, "%s", displmsg);
|
|
custom_remove_attr (warnwin, ATTR_HIGHEST);
|
|
wins_wrefresh (warnwin);
|
|
wgetch (warnwin);
|
|
delwin (warnwin);
|
|
wins_doupdate ();
|
|
}
|
|
|
|
/*
|
|
* Print a message in the status bar.
|
|
* Message texts for first line and second line are to be provided.
|
|
*/
|
|
void
|
|
status_mesg (char *mesg_line1, char *mesg_line2)
|
|
{
|
|
wins_erase_status_bar ();
|
|
custom_apply_attr (win[STA].p, ATTR_HIGHEST);
|
|
mvwprintw (win[STA].p, 0, 0, mesg_line1);
|
|
mvwprintw (win[STA].p, 1, 0, mesg_line2);
|
|
custom_remove_attr (win[STA].p, ATTR_HIGHEST);
|
|
}
|
|
|
|
/* Erase part of a window. */
|
|
void
|
|
erase_window_part (WINDOW *win, int first_col, int first_row, int last_col,
|
|
int last_row)
|
|
{
|
|
int c, r;
|
|
|
|
for (r = first_row; r <= last_row; r++)
|
|
{
|
|
for (c = first_col; c <= last_col; c++)
|
|
mvwprintw (win, r, c, " ");
|
|
}
|
|
|
|
wnoutrefresh (win);
|
|
}
|
|
|
|
/* draws a popup window */
|
|
WINDOW *
|
|
popup (int pop_row, int pop_col, int pop_y, int pop_x, char *title, char *msg,
|
|
int hint)
|
|
{
|
|
char *any_key = _("Press any key to continue...");
|
|
char label[BUFSIZ];
|
|
WINDOW *popup_win;
|
|
const int MSGXPOS = 5;
|
|
|
|
popup_win = newwin (pop_row, pop_col, pop_y, pop_x);
|
|
keypad (popup_win, TRUE);
|
|
if (msg)
|
|
mvwprintw (popup_win, MSGXPOS, (pop_col - strlen (msg)) / 2, "%s", msg);
|
|
custom_apply_attr (popup_win, ATTR_HIGHEST);
|
|
box (popup_win, 0, 0);
|
|
snprintf (label, BUFSIZ, "%s", title);
|
|
wins_show (popup_win, label);
|
|
if (hint)
|
|
mvwprintw (popup_win, pop_row - 2, pop_col - (strlen (any_key) + 1), "%s",
|
|
any_key);
|
|
custom_remove_attr (popup_win, ATTR_HIGHEST);
|
|
wins_wrefresh (popup_win);
|
|
|
|
return popup_win;
|
|
}
|
|
|
|
/* prints in middle of a panel */
|
|
void
|
|
print_in_middle (WINDOW *win, int starty, int startx, int width, char *string)
|
|
{
|
|
int len = strlen (string);
|
|
int x, y;
|
|
|
|
win = win ? win : stdscr;
|
|
getyx (win, y, x);
|
|
x = startx ? startx : x;
|
|
y = starty ? starty : y;
|
|
width = width ? width : 80;
|
|
|
|
x += (width - len) / 2;
|
|
|
|
custom_apply_attr (win, ATTR_HIGHEST);
|
|
mvwprintw (win, y, x, "%s", string);
|
|
custom_remove_attr (win, ATTR_HIGHEST);
|
|
}
|
|
|
|
/* checks if a string is only made of digits */
|
|
int
|
|
is_all_digit (const char *string)
|
|
{
|
|
for (; *string; string++)
|
|
{
|
|
if (!isdigit ((int)*string))
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Given an item date expressed in seconds, return its start time in seconds. */
|
|
long
|
|
get_item_time (long date)
|
|
{
|
|
return (long)(get_item_hour(date) * HOURINSEC +
|
|
get_item_min(date) * MININSEC);
|
|
}
|
|
|
|
int
|
|
get_item_hour (long date)
|
|
{
|
|
return (localtime ((time_t *)&date))->tm_hour;
|
|
}
|
|
|
|
int
|
|
get_item_min (long date)
|
|
{
|
|
return (localtime ((time_t *)&date))->tm_min;
|
|
}
|
|
|
|
long
|
|
date2sec (struct date day, unsigned hour, unsigned min)
|
|
{
|
|
time_t t = now ();
|
|
struct tm start = *(localtime (&t));
|
|
|
|
start.tm_mon = day.mm - 1;
|
|
start.tm_mday = day.dd;
|
|
start.tm_year = day.yyyy - 1900;
|
|
start.tm_hour = hour;
|
|
start.tm_min = min;
|
|
start.tm_sec = 0;
|
|
start.tm_isdst = -1;
|
|
|
|
t = mktime (&start);
|
|
EXIT_IF (t == -1, _("failure in mktime"));
|
|
|
|
return t;
|
|
}
|
|
|
|
/* Return a string containing the date, given a date in seconds. */
|
|
char *
|
|
date_sec2date_str (long sec, char *datefmt)
|
|
{
|
|
struct tm *lt;
|
|
char *datestr = (char *) mem_calloc (BUFSIZ, sizeof (char));
|
|
|
|
if (sec == 0)
|
|
strncpy (datestr, "0", BUFSIZ);
|
|
else
|
|
{
|
|
lt = localtime ((time_t *)&sec);
|
|
strftime (datestr, BUFSIZ, datefmt, lt);
|
|
}
|
|
|
|
return datestr;
|
|
}
|
|
|
|
/* Generic function to format date. */
|
|
void
|
|
date_sec2date_fmt (long sec, const char *fmt, char *datef)
|
|
{
|
|
struct tm *lt = localtime ((time_t *)&sec);
|
|
strftime (datef, BUFSIZ, fmt, lt);
|
|
}
|
|
|
|
/*
|
|
* Used to change date by adding a certain amount of days or weeks.
|
|
*/
|
|
long
|
|
date_sec_change (long date, int delta_month, int delta_day)
|
|
{
|
|
struct tm *lt;
|
|
time_t t;
|
|
|
|
t = date;
|
|
lt = localtime (&t);
|
|
lt->tm_mon += delta_month;
|
|
lt->tm_mday += delta_day;
|
|
lt->tm_isdst = -1;
|
|
t = mktime (lt);
|
|
EXIT_IF (t == -1, _("failure in mktime"));
|
|
|
|
return t;
|
|
}
|
|
|
|
/*
|
|
* Return a long containing the date which is updated taking into account
|
|
* the new time and date entered by the user.
|
|
*/
|
|
long
|
|
update_time_in_date (long date, unsigned hr, unsigned mn)
|
|
{
|
|
struct tm *lt;
|
|
time_t t, new_date;
|
|
|
|
t = date;
|
|
lt = localtime (&t);
|
|
lt->tm_hour = hr;
|
|
lt->tm_min = mn;
|
|
new_date = mktime (lt);
|
|
EXIT_IF (new_date == -1, _("error in mktime"));
|
|
|
|
return new_date;
|
|
}
|
|
|
|
/*
|
|
* Returns the date in seconds from year 1900.
|
|
* If no date is entered, current date is chosen.
|
|
*/
|
|
long
|
|
get_sec_date (struct date date)
|
|
{
|
|
struct tm *ptrtime;
|
|
time_t timer;
|
|
long long_date;
|
|
char current_day[] = "dd ";
|
|
char current_month[] = "mm ";
|
|
char current_year[] = "yyyy ";
|
|
|
|
if (date.yyyy == 0 && date.mm == 0 && date.dd == 0)
|
|
{
|
|
timer = time (NULL);
|
|
ptrtime = localtime (&timer);
|
|
strftime (current_day, strlen (current_day), "%d", ptrtime);
|
|
strftime (current_month, strlen (current_month), "%m", ptrtime);
|
|
strftime (current_year, strlen (current_year), "%Y", ptrtime);
|
|
date.mm = atoi (current_month);
|
|
date.dd = atoi (current_day);
|
|
date.yyyy = atoi (current_year);
|
|
}
|
|
long_date = date2sec (date, 0, 0);
|
|
return long_date;
|
|
}
|
|
|
|
long
|
|
min2sec (unsigned minutes)
|
|
{
|
|
return minutes * MININSEC;
|
|
}
|
|
|
|
/*
|
|
* Display a scroll bar when there are so many items that they
|
|
* can not be displayed inside the corresponding panel.
|
|
*/
|
|
void
|
|
draw_scrollbar (WINDOW *win, int y, int x, int length,
|
|
int bar_top, int bar_bottom, unsigned hilt)
|
|
{
|
|
mvwvline (win, bar_top, x, ACS_VLINE, bar_bottom - bar_top);
|
|
if (hilt)
|
|
custom_apply_attr (win, ATTR_HIGHEST);
|
|
wattron (win, A_REVERSE);
|
|
mvwvline (win, y, x, ' ', length);
|
|
wattroff (win, A_REVERSE);
|
|
if (hilt)
|
|
custom_remove_attr (win, ATTR_HIGHEST);
|
|
}
|
|
|
|
/*
|
|
* Print an item (either an appointment, event, or todo) in a
|
|
* popup window. This is useful if an item description is too
|
|
* long to fit in its corresponding panel window.
|
|
*/
|
|
void
|
|
item_in_popup (char *saved_a_start, char *saved_a_end, char *msg,
|
|
char *pop_title)
|
|
{
|
|
WINDOW *popup_win, *pad;
|
|
const int margin_left = 4, margin_top = 4;
|
|
const int winl = row - 5, winw = col - margin_left;
|
|
const int padl = winl - 2, padw = winw - margin_left;
|
|
|
|
pad = newpad (padl, padw);
|
|
popup_win = popup (winl, winw, 1, 2, pop_title, NULL, 1);
|
|
if (strncmp (pop_title, _("Appointment"), 11) == 0)
|
|
{
|
|
mvwprintw (popup_win, margin_top, margin_left, "- %s -> %s",
|
|
saved_a_start, saved_a_end);
|
|
}
|
|
mvwprintw (pad, 0, margin_left, "%s", msg);
|
|
wmove (win[STA].p, 0, 0);
|
|
pnoutrefresh (pad, 0, 0, margin_top + 2, margin_left, padl, winw);
|
|
wins_doupdate ();
|
|
wgetch (popup_win);
|
|
delwin (pad);
|
|
delwin (popup_win);
|
|
}
|
|
|
|
/* Returns the beginning of current day in seconds from 1900. */
|
|
long
|
|
get_today (void)
|
|
{
|
|
struct tm *lt;
|
|
time_t current_time;
|
|
long current_day;
|
|
struct date day;
|
|
|
|
current_time = time (NULL);
|
|
lt = localtime (¤t_time);
|
|
day.mm = lt->tm_mon + 1;
|
|
day.dd = lt->tm_mday;
|
|
day.yyyy = lt->tm_year + 1900;
|
|
current_day = date2sec (day, 0, 0);
|
|
|
|
return current_day;
|
|
}
|
|
|
|
/* Returns the current time in seconds. */
|
|
long
|
|
now (void)
|
|
{
|
|
return (long)time (NULL);
|
|
}
|
|
|
|
char *
|
|
nowstr (void)
|
|
{
|
|
static char buf[BUFSIZ];
|
|
time_t t = now ();
|
|
|
|
strftime (buf, sizeof buf, "%a %b %d %T %Y", localtime (&t));
|
|
|
|
return buf;
|
|
}
|
|
|
|
long
|
|
mystrtol (const char *str)
|
|
{
|
|
char *ep;
|
|
long lval;
|
|
|
|
errno = 0;
|
|
lval = strtol (str, &ep, 10);
|
|
if (str[0] == '\0' || *ep != '\0')
|
|
EXIT (_("could not convert string"));
|
|
if (errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN))
|
|
EXIT (_("out of range"));
|
|
|
|
return lval;
|
|
}
|
|
|
|
/* Print the given option value with appropriate color. */
|
|
void
|
|
print_bool_option_incolor (WINDOW *win, unsigned option, int pos_y, int pos_x)
|
|
{
|
|
int color = 0;
|
|
char option_value[BUFSIZ] = "";
|
|
|
|
if (option == 1)
|
|
{
|
|
color = ATTR_TRUE;
|
|
strncpy (option_value, _("yes"), BUFSIZ);
|
|
}
|
|
else if (option == 0)
|
|
{
|
|
color = ATTR_FALSE;
|
|
strncpy (option_value, _("no"), BUFSIZ);
|
|
}
|
|
else
|
|
EXIT (_("option not defined"));
|
|
|
|
custom_apply_attr (win, color);
|
|
mvwprintw (win, pos_y, pos_x, "%s", option_value);
|
|
custom_remove_attr (win, color);
|
|
wnoutrefresh (win);
|
|
wins_doupdate ();
|
|
}
|
|
|
|
|
|
/*
|
|
* Get the name of the default directory for temporary files.
|
|
*/
|
|
const char *
|
|
get_tempdir (void)
|
|
{
|
|
if (getenv ("TMPDIR"))
|
|
return getenv ("TMPDIR");
|
|
#ifdef P_tmpdir
|
|
else if (P_tmpdir)
|
|
return P_tmpdir;
|
|
#endif
|
|
else
|
|
return "/tmp";
|
|
}
|
|
|
|
/*
|
|
* Create a new unique file, and return a newly allocated string which contains
|
|
* the random part of the file name.
|
|
*/
|
|
char *
|
|
new_tempfile (const char *prefix, int trailing_len)
|
|
{
|
|
char fullname[BUFSIZ];
|
|
int prefix_len, fd;
|
|
FILE *file;
|
|
|
|
if (prefix == NULL)
|
|
return NULL;
|
|
|
|
prefix_len = strlen (prefix);
|
|
if (prefix_len + trailing_len >= BUFSIZ)
|
|
return NULL;
|
|
memcpy (fullname, prefix, prefix_len);
|
|
memset (fullname + prefix_len, 'X', trailing_len);
|
|
fullname[prefix_len + trailing_len] = '\0';
|
|
if ((fd = mkstemp (fullname)) == -1 || (file = fdopen (fd, "w+")) == NULL)
|
|
{
|
|
if (fd != -1)
|
|
{
|
|
unlink (fullname);
|
|
close (fd);
|
|
}
|
|
ERROR_MSG (_("temporary file \"%s\" could not be created"), fullname);
|
|
return NULL;
|
|
}
|
|
fclose (file);
|
|
|
|
return mem_strdup (fullname + prefix_len);
|
|
}
|
|
|
|
/*
|
|
* Convert a string containing a date into three integers containing the year,
|
|
* month and day.
|
|
*
|
|
* If a pointer to a date structure containing the current date is passed as
|
|
* last parameter ("slctd_date"), the function will accept several short forms,
|
|
* e.g. "26" for the 26th of the current month/year or "3/1" for Mar 01 (or Jan
|
|
* 03, depending on the date format) of the current year. If a null pointer is
|
|
* passed, short forms won't be accepted at all.
|
|
*
|
|
* Returns 1 if sucessfully converted or 0 if the string is an invalid date.
|
|
*/
|
|
int
|
|
parse_date (const char *date_string, enum datefmt datefmt, int *year,
|
|
int *month, int *day, struct date *slctd_date)
|
|
{
|
|
const char sep = (datefmt == DATEFMT_ISO) ? '-' : '/';
|
|
const char *p;
|
|
int in[3] = {0, 0, 0}, n = 0;
|
|
int d, m, y;
|
|
|
|
if (!date_string)
|
|
return 0;
|
|
|
|
/* parse string into in[], read up to three integers */
|
|
for (p = date_string; *p; p++)
|
|
{
|
|
if (*p == sep)
|
|
{
|
|
if ((++n) > 2)
|
|
return 0;
|
|
}
|
|
else if ((*p >= '0') && (*p <= '9'))
|
|
in[n] = in[n] * 10 + (int)(*p - '0');
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
if ((!slctd_date && n < 2) || in[n] == 0)
|
|
return 0;
|
|
|
|
/* convert into day, month and year, depending on the date format */
|
|
switch (datefmt)
|
|
{
|
|
case DATEFMT_MMDDYYYY:
|
|
m = (n >= 1) ? in[0] : 0;
|
|
d = (n >= 1) ? in[1] : in[0];
|
|
y = in[2];
|
|
break;
|
|
case DATEFMT_DDMMYYYY:
|
|
d = in[0];
|
|
m = in[1];
|
|
y = in[2];
|
|
break;
|
|
case DATEFMT_YYYYMMDD:
|
|
case DATEFMT_ISO:
|
|
y = (n >= 2) ? in[n - 2] : 0;
|
|
m = (n >= 1) ? in[n - 1] : 0;
|
|
d = in[n];
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
if (slctd_date)
|
|
{
|
|
if (y > 0 && y < 100)
|
|
{
|
|
/* convert "YY" format into "YYYY" */
|
|
y += slctd_date->yyyy - slctd_date->yyyy % 100;
|
|
}
|
|
else if (n < 2)
|
|
{
|
|
/* set year and, optionally, month if short from is used */
|
|
y = slctd_date->yyyy;
|
|
if (n < 1) m = slctd_date->mm;
|
|
}
|
|
}
|
|
|
|
/* check if date is valid, take leap years into account */
|
|
if (y < 1902 || y > 2037 || m < 1 || m > 12 || d < 1 ||
|
|
d > days[m - 1] + (m == 2 && ISLEAP (y)) ? 1 : 0)
|
|
return 0;
|
|
|
|
if (year)
|
|
*year = y;
|
|
if (month)
|
|
*month = m;
|
|
if (day)
|
|
*day = d;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Converts a time string into hours and minutes. Short forms like "23:"
|
|
* (23:00) or ":45" (0:45) are allowed.
|
|
*
|
|
* Returns 1 on success and 0 on failure.
|
|
*/
|
|
int
|
|
parse_time (const char *string, unsigned *hour, unsigned *minute)
|
|
{
|
|
const char *p;
|
|
unsigned in[2] = {0, 0}, n = 0;
|
|
|
|
if (!string)
|
|
return 0;
|
|
|
|
/* parse string into in[], read up to two integers */
|
|
for (p = string; *p; p++)
|
|
{
|
|
if (*p == ':')
|
|
{
|
|
if ((++n) > 1)
|
|
return 0;
|
|
}
|
|
else if ((*p >= '0') && (*p <= '9'))
|
|
in[n] = in[n] * 10 + (int)(*p - '0');
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
if (n != 1 || in[0] >= DAYINHOURS || in[1] >= HOURINMIN)
|
|
return 0;
|
|
|
|
*hour = in[0];
|
|
*minute = in[1];
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Converts a duration string into minutes.
|
|
*
|
|
* Allowed formats (noted as regular expressions):
|
|
*
|
|
* - \d*:\d*
|
|
* - (\d*m|\d*h(|\d*m)|\d*d(|\d*m|\d*h(|\d*m)))
|
|
* - \d+
|
|
*
|
|
* "\d" is used as a placeholder for "(0|1|2|3|4|5|6|7|8|9)".
|
|
*
|
|
* Note that this function performs an additional range check on each token to
|
|
* ensure we do not accept semantically invalid strings such as "42:23".
|
|
*
|
|
* Returns 1 on success and 0 on failure.
|
|
*/
|
|
int
|
|
parse_duration (const char *string, unsigned *duration)
|
|
{
|
|
enum {
|
|
STATE_INITIAL,
|
|
STATE_HHMM_MM,
|
|
STATE_DDHHMM_HH,
|
|
STATE_DDHHMM_MM,
|
|
STATE_DONE
|
|
} state = STATE_INITIAL;
|
|
|
|
const char *p;
|
|
unsigned in = 0;
|
|
unsigned dur = 0;
|
|
|
|
if (!string || *string == '\0')
|
|
return 0;
|
|
|
|
/* parse string using a simple state machine */
|
|
for (p = string; *p; p++)
|
|
{
|
|
if ((*p >= '0') && (*p <= '9'))
|
|
{
|
|
if (state == STATE_DONE)
|
|
return 0;
|
|
else
|
|
in = in * 10 + (int)(*p - '0');
|
|
}
|
|
else
|
|
{
|
|
switch (state)
|
|
{
|
|
case STATE_INITIAL:
|
|
if (*p == ':')
|
|
{
|
|
dur += in * HOURINMIN;
|
|
state = STATE_HHMM_MM;
|
|
}
|
|
else if (*p == 'd')
|
|
{
|
|
dur += in * DAYINMIN;
|
|
state = STATE_DDHHMM_HH;
|
|
}
|
|
else if (*p == 'h')
|
|
{
|
|
if (in >= DAYINHOURS)
|
|
return 0;
|
|
dur += in * HOURINMIN;
|
|
state = STATE_DDHHMM_MM;
|
|
}
|
|
else if (*p == 'm')
|
|
{
|
|
if (in >= HOURINMIN)
|
|
return 0;
|
|
dur += in;
|
|
state = STATE_DONE;
|
|
}
|
|
else
|
|
return 0;
|
|
break;
|
|
case STATE_DDHHMM_HH:
|
|
if (*p == 'h')
|
|
{
|
|
if (in >= DAYINHOURS)
|
|
return 0;
|
|
dur += in * HOURINMIN;
|
|
state = STATE_DDHHMM_MM;
|
|
}
|
|
else if (*p == 'm')
|
|
{
|
|
if (in >= HOURINMIN)
|
|
return 0;
|
|
dur += in;
|
|
state = STATE_DONE;
|
|
}
|
|
else
|
|
return 0;
|
|
break;
|
|
case STATE_DDHHMM_MM:
|
|
if (*p == 'm')
|
|
{
|
|
if (in >= HOURINMIN)
|
|
return 0;
|
|
dur += in;
|
|
state = STATE_DONE;
|
|
}
|
|
else
|
|
return 0;
|
|
break;
|
|
case STATE_HHMM_MM:
|
|
case STATE_DONE:
|
|
return 0;
|
|
break;
|
|
}
|
|
|
|
in = 0;
|
|
}
|
|
}
|
|
|
|
if ((state == STATE_HHMM_MM && in >= HOURINMIN) ||
|
|
((state == STATE_DDHHMM_HH || state == STATE_DDHHMM_MM) && in > 0))
|
|
return 0;
|
|
|
|
dur += in;
|
|
*duration = dur;
|
|
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
str_toupper (char *s)
|
|
{
|
|
if (!s)
|
|
return;
|
|
for (; *s; s++)
|
|
*s = toupper (*s);
|
|
}
|
|
|
|
void
|
|
file_close (FILE *f, const char *pos)
|
|
{
|
|
EXIT_IF ((fclose (f)) != 0, _("Error when closing file at %s"), pos);
|
|
}
|
|
|
|
/*
|
|
* Sleep the given number of seconds, but make it more 'precise' than sleep(3)
|
|
* (hence the 'p') in a way that even if a signal is caught during the sleep
|
|
* process, this function will return to sleep afterwards.
|
|
*/
|
|
void
|
|
psleep (unsigned secs)
|
|
{
|
|
unsigned unslept;
|
|
|
|
for (unslept = sleep (secs); unslept; unslept = sleep (unslept))
|
|
;
|
|
}
|
|
|
|
/*
|
|
* Fork and execute an external process.
|
|
*
|
|
* If pfdin and/or pfdout point to a valid address, a pipe is created and the
|
|
* appropriate file descriptors are written to pfdin/pfdout.
|
|
*/
|
|
int
|
|
fork_exec (int *pfdin, int *pfdout, const char *path, char *const *arg)
|
|
{
|
|
int pin[2], pout[2];
|
|
int pid;
|
|
|
|
if (pfdin && (pipe (pin) == -1))
|
|
return 0;
|
|
if (pfdout && (pipe (pout) == -1))
|
|
return 0;
|
|
|
|
if ((pid = fork ()) == 0)
|
|
{
|
|
if (pfdout)
|
|
{
|
|
if (dup2 (pout[0], STDIN_FILENO) < 0)
|
|
_exit (127);
|
|
close (pout[0]);
|
|
close (pout[1]);
|
|
}
|
|
|
|
if (pfdin)
|
|
{
|
|
if (dup2 (pin[1], STDOUT_FILENO) < 0)
|
|
_exit (127);
|
|
close (pin[0]);
|
|
close (pin[1]);
|
|
}
|
|
|
|
execvp (path, arg);
|
|
_exit (127);
|
|
}
|
|
else
|
|
{
|
|
if (pfdin)
|
|
close (pin[1]);
|
|
if (pfdout)
|
|
close (pout[0]);
|
|
|
|
if (pid > 0)
|
|
{
|
|
if (pfdin)
|
|
{
|
|
fcntl (pin[0], F_SETFD, FD_CLOEXEC);
|
|
*pfdin = pin[0];
|
|
}
|
|
if (pfdout)
|
|
{
|
|
fcntl (pout[1], F_SETFD, FD_CLOEXEC);
|
|
*pfdout = pout[1];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pfdin)
|
|
close (pin[0]);
|
|
if (pfdout)
|
|
close (pout[1]);
|
|
return 0;
|
|
}
|
|
}
|
|
return pid;
|
|
}
|
|
|
|
/* Execute an external program in a shell. */
|
|
int
|
|
shell_exec (int *pfdin, int *pfdout, char *cmd)
|
|
{
|
|
char *arg[] = { "/bin/sh", "-c", cmd, NULL };
|
|
return fork_exec (pfdin, pfdout, *arg, arg);
|
|
}
|
|
|
|
/* Wait for a child process to terminate. */
|
|
int
|
|
child_wait (int *pfdin, int *pfdout, int pid)
|
|
{
|
|
int stat;
|
|
|
|
if (pfdin)
|
|
close (*pfdin);
|
|
if (pfdout)
|
|
close (*pfdout);
|
|
|
|
waitpid (pid, &stat, 0);
|
|
return stat;
|
|
}
|
|
|
|
/* Display "Press any key to continue..." and wait for a key press. */
|
|
void
|
|
press_any_key (void)
|
|
{
|
|
fflush (stdout);
|
|
fputs (_("Press any key to continue..."), stdout);
|
|
fflush (stdout);
|
|
fgetc (stdin);
|
|
fflush (stdin);
|
|
fputs ("\r\n", stdout);
|
|
}
|
|
|
|
/*
|
|
* Display note contents if one is asociated with the currently displayed item
|
|
* (to be used together with the '-a' or '-t' flag in non-interactive mode).
|
|
* Each line begins with nbtab tabs.
|
|
* Print "No note file found", if the notefile does not exists.
|
|
*
|
|
* (patch submitted by Erik Saule).
|
|
*/
|
|
static void
|
|
print_notefile (FILE *out, char *filename, int nbtab)
|
|
{
|
|
char path_to_notefile[BUFSIZ];
|
|
FILE *notefile;
|
|
char linestarter[BUFSIZ];
|
|
char buffer[BUFSIZ];
|
|
int i;
|
|
int printlinestarter = 1;
|
|
|
|
if (nbtab < BUFSIZ)
|
|
{
|
|
for (i = 0; i < nbtab; i++)
|
|
linestarter[i] = '\t';
|
|
linestarter[nbtab] = '\0';
|
|
}
|
|
else
|
|
linestarter[0] = '\0';
|
|
|
|
snprintf (path_to_notefile, BUFSIZ, "%s/%s", path_notes, filename);
|
|
notefile = fopen (path_to_notefile, "r");
|
|
if (notefile)
|
|
{
|
|
while (fgets (buffer, BUFSIZ, notefile) != 0)
|
|
{
|
|
if (printlinestarter)
|
|
{
|
|
fputs (linestarter, out);
|
|
printlinestarter = 0;
|
|
}
|
|
fputs (buffer, out);
|
|
if (buffer[strlen (buffer) - 1] == '\n')
|
|
printlinestarter = 1;
|
|
}
|
|
fputs ("\n", out);
|
|
file_close (notefile, __FILE_POS__);
|
|
}
|
|
else
|
|
{
|
|
fputs (linestarter, out);
|
|
fputs (_("No note file found\n"), out);
|
|
}
|
|
}
|
|
|
|
/* Print an escape sequence and return its length. */
|
|
static int
|
|
print_escape (const char *s)
|
|
{
|
|
switch (*(s + 1))
|
|
{
|
|
case 'a':
|
|
putchar ('\a');
|
|
return 1;
|
|
case 'b':
|
|
putchar ('\b');
|
|
return 1;
|
|
case 'f':
|
|
putchar ('\f');
|
|
return 1;
|
|
case 'n':
|
|
putchar ('\n');
|
|
return 1;
|
|
case 'r':
|
|
putchar ('\r');
|
|
return 1;
|
|
case 't':
|
|
putchar ('\t');
|
|
return 1;
|
|
case 'v':
|
|
putchar ('\v');
|
|
return 1;
|
|
case '0':
|
|
putchar ('\0');
|
|
return 1;
|
|
case '\'':
|
|
putchar ('\'');
|
|
return 1;
|
|
case '"':
|
|
putchar ('"');
|
|
return 1;
|
|
case '\?':
|
|
putchar ('?');
|
|
return 1;
|
|
case '\\':
|
|
putchar ('\\');
|
|
return 1;
|
|
case '\0':
|
|
return 0;
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* Print a formatted appointment to stdout. */
|
|
void
|
|
print_apoint (const char *format, long day, struct apoint *apt)
|
|
{
|
|
const char *p;
|
|
char str_start[HRMIN_SIZE], str_end[HRMIN_SIZE];
|
|
|
|
apoint_sec2str (apt, day, str_start, str_end);
|
|
|
|
for (p = format; *p; p++)
|
|
{
|
|
if (*p == '%') {
|
|
p++;
|
|
switch (*p)
|
|
{
|
|
case 's':
|
|
printf ("%ld", apt->start);
|
|
break;
|
|
case 'S':
|
|
printf ("%s", str_start);
|
|
break;
|
|
case 'd':
|
|
printf ("%ld", apt->dur);
|
|
break;
|
|
case 'e':
|
|
printf ("%ld", apt->start + apt->dur);
|
|
break;
|
|
case 'E':
|
|
printf ("%s", str_end);
|
|
break;
|
|
case 'm':
|
|
printf ("%s", apt->mesg);
|
|
break;
|
|
case 'n':
|
|
printf ("%s", apt->note);
|
|
break;
|
|
case 'N':
|
|
print_notefile (stdout, apt->note, 1);
|
|
break;
|
|
case '%':
|
|
putchar ('%');
|
|
break;
|
|
case '\0':
|
|
return;
|
|
break;
|
|
default:
|
|
putchar ('?');
|
|
break;
|
|
}
|
|
}
|
|
else if (*p == '\\')
|
|
p += print_escape (p);
|
|
else
|
|
putchar (*p);
|
|
}
|
|
}
|
|
|
|
/* Print a formatted event to stdout. */
|
|
void
|
|
print_event (const char *format, long day, struct event *ev)
|
|
{
|
|
const char *p;
|
|
|
|
for (p = format; *p; p++)
|
|
{
|
|
if (*p == '%') {
|
|
p++;
|
|
switch (*p)
|
|
{
|
|
case 'm':
|
|
printf ("%s", ev->mesg);
|
|
break;
|
|
case 'n':
|
|
printf ("%s", ev->note);
|
|
break;
|
|
case 'N':
|
|
print_notefile (stdout, ev->note, 1);
|
|
break;
|
|
case '%':
|
|
putchar ('%');
|
|
break;
|
|
case '\0':
|
|
return;
|
|
break;
|
|
default:
|
|
putchar ('?');
|
|
break;
|
|
}
|
|
}
|
|
else if (*p == '\\')
|
|
p += print_escape (p);
|
|
else
|
|
putchar (*p);
|
|
}
|
|
}
|
|
|
|
/* Print a formatted recurrent appointment to stdout. */
|
|
void
|
|
print_recur_apoint (const char *format, long day, unsigned occurrence,
|
|
struct recur_apoint *rapt)
|
|
{
|
|
struct apoint apt;
|
|
|
|
apt.start = occurrence;
|
|
apt.dur = rapt->dur;
|
|
apt.mesg = rapt->mesg;
|
|
apt.note = rapt->note;
|
|
|
|
print_apoint (format, day, &apt);
|
|
}
|
|
|
|
/* Print a formatted recurrent event to stdout. */
|
|
void
|
|
print_recur_event (const char *format, long day, struct recur_event *rev)
|
|
{
|
|
struct event ev;
|
|
|
|
ev.mesg = rev->mesg;
|
|
ev.note = rev->note;
|
|
|
|
print_event (format, day, &ev);
|
|
}
|
|
|
|
/* Print a formatted todo item to stdout. */
|
|
void
|
|
print_todo (const char *format, struct todo *todo)
|
|
{
|
|
const char *p;
|
|
|
|
for (p = format; *p; p++)
|
|
{
|
|
if (*p == '%') {
|
|
p++;
|
|
switch (*p)
|
|
{
|
|
case 'p':
|
|
printf ("%d", abs (todo->id));
|
|
break;
|
|
case 'm':
|
|
printf ("%s", todo->mesg);
|
|
break;
|
|
case 'n':
|
|
printf ("%s", todo->note);
|
|
break;
|
|
case 'N':
|
|
print_notefile (stdout, todo->note, 1);
|
|
break;
|
|
case '%':
|
|
putchar ('%');
|
|
break;
|
|
case '\0':
|
|
return;
|
|
break;
|
|
default:
|
|
putchar ('?');
|
|
break;
|
|
}
|
|
}
|
|
else if (*p == '\\')
|
|
p += print_escape (p);
|
|
else
|
|
putchar (*p);
|
|
}
|
|
}
|