Lukas Fleischer c2dc3109ae Do not use filters in GC or import mode
Filtering items when running garbage collection or when importing data
might result in data loss, so simply ignore filters here.

Signed-off-by: Lukas Fleischer <calcurse@cryptocrack.de>
2014-08-06 12:08:14 +02:00

703 lines
20 KiB
C

/*
* Calcurse - text-based organizer
*
* Copyright (c) 2004-2013 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 <ctype.h>
#include <sys/types.h>
#include <limits.h>
#include <getopt.h>
#include <time.h>
#include "calcurse.h"
/* Long options */
enum {
OPT_FILTER_TYPE = 1000,
OPT_FILTER_PATTERN,
OPT_FILTER_START_FROM,
OPT_FILTER_START_TO,
OPT_FILTER_START_AFTER,
OPT_FILTER_START_BEFORE,
OPT_FILTER_END_FROM,
OPT_FILTER_END_TO,
OPT_FILTER_END_AFTER,
OPT_FILTER_END_BEFORE,
OPT_FILTER_PRIORITY,
OPT_FILTER_COMPLETED,
OPT_FILTER_UNCOMPLETED,
OPT_FROM,
OPT_TO,
OPT_DAYS,
OPT_FMT_APT,
OPT_FMT_RAPT,
OPT_FMT_EV,
OPT_FMT_REV,
OPT_FMT_TODO,
OPT_READ_ONLY,
OPT_STATUS
};
/*
* Print Calcurse usage and exit.
*/
static void usage(void)
{
const char *arg_usage =
_("Usage: calcurse [-g|-h|-v] [-an] [-t[num]] [-i<file>] [-x[format]]\n"
" [-d <date>|<num>] [-s[date]] [-r[range]]\n"
" [-c<file>] [-D<dir>] [-S<regex>] [--status]\n"
" [--read-only]\n");
fputs(arg_usage, stdout);
}
static void usage_try(void)
{
const char *arg_usage_try =
_("Try 'calcurse -h' for more information.\n");
fputs(arg_usage_try, stdout);
}
/*
* Print Calcurse version with a short copyright text and exit.
*/
static void version_arg(void)
{
const char *vtext =
_("\nCopyright (c) 2004-2013 calcurse Development Team.\n"
"This is free software; see the source for copying conditions.\n");
fprintf(stdout, _("Calcurse %s - text-based organizer\n"),
VERSION);
fputs(vtext, stdout);
}
static void more_info(void)
{
fputs(_("\nFor more information, type '?' from within Calcurse, "
"or read the manpage.\n"), stdout);
fputs(_("Mail feature requests and suggestions to <misc@calcurse.org>.\n"),
stdout);
fputs(_("Mail bug reports to <bugs@calcurse.org>.\n"), stdout);
}
/*
* Print the command line options and exit.
*/
static void help_arg(void)
{
const char *htext =
_("\nMiscellaneous:\n"
" -h, --help\n"
" print this help and exit.\n"
"\n -v, --version\n"
" print calcurse version and exit.\n"
"\n --status\n"
" display the status of running instances of calcurse.\n"
"\n --read-only\n"
" Don't save configuration nor appointments/todos. Use with care.\n"
"\nFiles:\n"
" -c <file>, --calendar <file>\n"
" specify the calendar <file> to use (has precedence over '-D').\n"
"\n -D <dir>, --directory <dir>\n"
" specify the data directory to use.\n"
"\tIf not specified, the default directory is ~/.calcurse\n"
"\nNon-interactive:\n"
" -a, --appointment\n"
" print events and appointments for current day and exit.\n"
"\n -d <date|num>, --day <date|num>\n"
" print events and appointments for <date> or <num> upcoming days and"
"\n\texit. To specify both a starting date and a range, use the\n"
"\t'--startday' and the '--range' option.\n"
"\n -g, --gc\n"
" run the garbage collector for note files and exit. \n"
"\n -i <file>, --import <file>\n"
" import the icalendar data contained in <file>. \n"
"\n -n, --next\n"
" print next appointment within upcoming 24 hours "
"and exit. Also given\n\tis the remaining time before this "
"next appointment.\n"
"\n -l <num>, --limit <num>\n"
" only print information regarding the next <num> items. \n"
"\n -r[num], --range[=num]\n"
" print events and appointments for the [num] number of days"
"\n\tand exit. If no [num] is given, a range of 1 day is considered.\n"
"\n -s[date], --startday[=date]\n"
" print events and appointments from [date] and exit.\n"
"\tIf no [date] is given, the current day is considered.\n"
"\n -S<regex>, --search=<regex>\n"
" search for the given regular expression within events, appointments,\n"
"\tand todos description.\n"
"\n -t[num], --todo[=num]\n"
" print todo list and exit. If the optional number [num] is given,\n"
"\tthen only todos having a priority equal to [num] will be returned.\n"
"\tThe priority number must be between 1 (highest) and 9 (lowest).\n"
"\tIt is also possible to specify '0' for the priority, in which case\n"
"\tonly completed tasks will be shown.\n"
"\n -x[format], --export[=format]\n"
" export user data to the specified format. Events, appointments and\n"
"\ttodos are converted and echoed to stdout.\n"
"\tTwo possible formats are available: 'ical' and 'pcal'.\n"
"\tIf the optional argument format is not given, ical format is\n"
"\tselected by default.\n"
"\tnote: redirect standard output to export data to a file,\n"
"\tby issuing a command such as: calcurse --export > calcurse.dat\n");
fprintf(stdout, _("Calcurse %s - text-based organizer\n"),
VERSION);
usage();
fputs(htext, stdout);
more_info();
}
/*
* Used to display the status of running instances of calcurse.
* The displayed message will look like one of the following ones:
*
* calcurse is running (pid #)
* calcurse is running in background (pid #)
* calcurse is not running
*
* The status is obtained by looking at pid files in user data directory
* (.calcurse.pid and .daemon.pid).
*/
static void status_arg(void)
{
int cpid, dpid;
cpid = io_get_pid(path_cpid);
dpid = io_get_pid(path_dpid);
EXIT_IF(cpid && dpid,
_("Error: both calcurse (pid: %d) and its daemon (pid: %d)\n"
"seem to be running at the same time!\n"
"Please check manually and restart calcurse.\n"), cpid,
dpid);
if (cpid)
fprintf(stdout, _("calcurse is running (pid %d)\n"), cpid);
else if (dpid)
fprintf(stdout,
_("calcurse is running in background (pid %d)\n"),
dpid);
else
puts(_("calcurse is not running\n"));
}
/* Print TODO list and exit. */
static void todo_arg(const char *format, int *limit,
struct item_filter *filter)
{
const char *titlestr =
filter->completed ? _("completed tasks:\n") : _("to do:\n");
int title = 1;
llist_item_t *i;
LLIST_FOREACH(&todolist, i) {
if (*limit == 0)
return;
struct todo *todo = LLIST_TS_GET_DATA(i);
if (title) {
fputs(titlestr, stdout);
title = 0;
}
print_todo(format, todo);
(*limit)--;
}
}
/* Print the next appointment within the upcoming 24 hours. */
static void next_arg(void)
{
struct notify_app next_app;
const long current_time = now();
int time_left, hours_left, min_left;
next_app.time = current_time + DAYINSEC;
next_app.got_app = 0;
next_app.txt = NULL;
next_app = *recur_apoint_check_next(&next_app, current_time,
get_today());
next_app = *apoint_check_next(&next_app, current_time);
if (next_app.got_app) {
time_left = next_app.time - current_time;
hours_left = (time_left / HOURINSEC);
min_left = (time_left - hours_left * HOURINSEC) / MININSEC;
fputs(_("next appointment:\n"), stdout);
fprintf(stdout, " [%02d:%02d] %s\n", hours_left,
min_left, next_app.txt);
mem_free(next_app.txt);
}
}
/*
* Print the date on stdout.
*/
static void arg_print_date(long date)
{
char date_str[BUFSIZ];
struct tm lt;
localtime_r((time_t *) & date, &lt);
strftime(date_str, BUFSIZ, conf.output_datefmt, &lt);
fputs(date_str, stdout);
fputs(":\n", stdout);
}
/*
* Print appointments inside the given query range.
* If no start day is given (-1), today is considered.
* If no end date is given (-1), a range of 1 day is considered.
*/
static void
date_arg_from_to(long from, long to, const char *fmt_apt, const char *fmt_rapt,
const char *fmt_ev, const char *fmt_rev, int *limit)
{
long date;
int add_line = 0;
for (date = from; date < to; date += DAYINSEC) {
day_store_items(date, 0);
if (day_item_count(0) == 0)
continue;
if (add_line)
fputs("\n", stdout);
arg_print_date(date);
day_write_stdout(date, fmt_apt, fmt_rapt, fmt_ev,
fmt_rev, limit);
add_line = 1;
}
}
static int parse_datearg(const char *str)
{
struct date day;
if (parse_date(str, DATEFMT_YYYYMMDD, (int *)&day.yyyy,
(int *)&day.mm, (int *)&day.dd, NULL))
return date2sec(day, 0, 0);
if (parse_date(str, DATEFMT_MMDDYYYY, (int *)&day.yyyy,
(int *)&day.mm, (int *)&day.dd, NULL))
return date2sec(day, 0, 0);
if (parse_date(str, DATEFMT_ISO, (int *)&day.yyyy,
(int *)&day.mm, (int *)&day.dd, NULL))
return date2sec(day, 0, 0);
return -1;
}
static int parse_type_mask(const char *str)
{
char *buf = mem_strdup(str), *p;
int mask = 0;
for (p = strtok(buf, ","); p; p = strtok(NULL, ",")) {
if (!strcmp(p, "event")) {
mask |= TYPE_MASK_EVNT;
} else if (!strcmp(p, "apt")) {
mask |= TYPE_MASK_APPT;
} else if (!strcmp(p, "recur-event")) {
mask |= TYPE_MASK_RECUR_EVNT;
} else if (!strcmp(p, "recur-apt")) {
mask |= TYPE_MASK_RECUR_APPT;
} else if (!strcmp(p, "recur")) {
mask |= TYPE_MASK_RECUR;
} else if (!strcmp(p, "cal")) {
mask |= TYPE_MASK_CAL;
} else if (!strcmp(p, "todo")) {
mask |= TYPE_MASK_TODO;
} else {
mask = 0;
goto cleanup;
}
}
cleanup:
mem_free(buf);
return mask;
}
/*
* Parse the command-line arguments and call the appropriate
* routines to handle those arguments. Also initialize the data paths.
*/
int parse_args(int argc, char **argv)
{
/* Command-line flags */
int status = 0, query = 0, next = 0, gc = 0, import = 0, export = 0;
/* Query ranges */
long from = -1, range = -1, to = -1;
int limit = INT_MAX;
/* Filters */
struct item_filter filter = { 0, NULL, -1, -1, -1, -1, 0, 0, 0 };
/* Format strings */
const char *fmt_apt = " - %S -> %E\n\t%m\n";
const char *fmt_rapt = " - %S -> %E\n\t%m\n";
const char *fmt_ev = " * %m\n";
const char *fmt_rev = " * %m\n";
const char *fmt_todo = "%p. %m\n";
/* Import and export parameters */
int xfmt = IO_EXPORT_ICAL;
/* Data file locations */
const char *cfile = NULL, *datadir = NULL, *ifile = NULL;
int non_interactive = 1;
int ch;
regex_t reg;
static const char *optstr = "ghvnNax::t::d:c:r::s::S:D:i:l:Q";
struct option longopts[] = {
{"appointment", no_argument, NULL, 'a'},
{"calendar", required_argument, NULL, 'c'},
{"day", required_argument, NULL, 'd'},
{"directory", required_argument, NULL, 'D'},
{"gc", no_argument, NULL, 'g'},
{"help", no_argument, NULL, 'h'},
{"import", required_argument, NULL, 'i'},
{"limit", required_argument, NULL, 'l'},
{"next", no_argument, NULL, 'n'},
{"note", no_argument, NULL, 'N'},
{"range", optional_argument, NULL, 'r'},
{"startday", optional_argument, NULL, 's'},
{"search", required_argument, NULL, 'S'},
{"todo", optional_argument, NULL, 't'},
{"version", no_argument, NULL, 'v'},
{"export", optional_argument, NULL, 'x'},
{"query", optional_argument, NULL, 'Q'},
{"filter-type", required_argument, NULL, OPT_FILTER_TYPE},
{"filter-pattern", required_argument, NULL, OPT_FILTER_PATTERN},
{"filter-start-from", required_argument, NULL, OPT_FILTER_START_FROM},
{"filter-start-to", required_argument, NULL, OPT_FILTER_START_TO},
{"filter-start-after", required_argument, NULL, OPT_FILTER_START_AFTER},
{"filter-start-before", required_argument, NULL, OPT_FILTER_START_BEFORE},
{"filter-end-from", required_argument, NULL, OPT_FILTER_END_FROM},
{"filter-end-to", required_argument, NULL, OPT_FILTER_END_TO},
{"filter-end-after", required_argument, NULL, OPT_FILTER_END_AFTER},
{"filter-end-before", required_argument, NULL, OPT_FILTER_END_BEFORE},
{"filter-priority", required_argument, NULL, OPT_FILTER_PRIORITY},
{"filter-completed", no_argument, NULL, OPT_FILTER_COMPLETED},
{"filter-uncompleted", no_argument, NULL, OPT_FILTER_UNCOMPLETED},
{"from", required_argument, NULL, OPT_FROM},
{"to", required_argument, NULL, OPT_TO},
{"days", required_argument, NULL, OPT_DAYS},
{"format-apt", required_argument, NULL, OPT_FMT_APT},
{"format-recur-apt", required_argument, NULL, OPT_FMT_RAPT},
{"format-event", required_argument, NULL, OPT_FMT_EV},
{"format-recur-event", required_argument, NULL, OPT_FMT_REV},
{"format-todo", required_argument, NULL, OPT_FMT_TODO},
{"read-only", no_argument, NULL, OPT_READ_ONLY},
{"status", no_argument, NULL, OPT_STATUS},
{NULL, no_argument, NULL, 0}
};
while ((ch = getopt_long(argc, argv, optstr, longopts, NULL)) != -1) {
switch (ch) {
case 'a':
filter.type_mask |= TYPE_MASK_CAL;
query = 1;
break;
case 'c':
cfile = optarg;
break;
case 'd':
if (is_all_digit(optarg)) {
range = atoi(optarg);
} else {
from = parse_datearg(optarg);
EXIT_IF(from == -1, _("invalid date: %s"),
optarg);
}
filter.type_mask |= TYPE_MASK_CAL;
query = 1;
break;
case 'D':
datadir = optarg;
break;
case 'h':
help_arg();
goto cleanup;
case 'g':
gc = 1;
break;
case 'i':
import = 1;
ifile = optarg;
break;
case 'l':
limit = atoi(optarg);
break;
case 'n':
next = 1;
break;
case 'r':
if (optarg)
range = atoi(optarg);
else
range = 1;
EXIT_IF(range == 0, _("invalid range: %s"), optarg);
filter.type_mask |= TYPE_MASK_CAL;
query = 1;
break;
case 's':
from = parse_datearg(optarg);
EXIT_IF(from == -1, _("invalid date: %s"), optarg);
filter.type_mask |= TYPE_MASK_CAL;
query = 1;
break;
case 't':
if (optarg) {
EXIT_IF(!is_all_digit(optarg),
_("invalid priority: %s"), optarg);
filter.priority = atoi(optarg);
if (filter.priority == 0)
filter.completed = 1;
EXIT_IF(filter.priority > 9,
_("invalid priority: %s"), optarg);
} else {
filter.uncompleted = 1;
}
filter.type_mask |= TYPE_MASK_TODO;
query = 1;
break;
case 'v':
version_arg();
goto cleanup;
case 'x':
export = 1;
if (optarg) {
if (!strcmp(optarg, "ical"))
xfmt = IO_EXPORT_ICAL;
else if (!strcmp(optarg, "pcal"))
xfmt = IO_EXPORT_PCAL;
else
EXIT(_("invalid export format: %s"),
optarg);
}
break;
case 'Q':
query = 1;
break;
case OPT_FILTER_TYPE:
filter.type_mask = parse_type_mask(optarg);
EXIT_IF(filter.type_mask == 0,
_("invalid filter mask"));
break;
case 'S':
case OPT_FILTER_PATTERN:
EXIT_IF(filter.regex,
_("Can not handle more than one regular expression."));
if (regcomp(&reg, optarg, REG_EXTENDED))
EXIT(_("Could not compile regular expression."));
filter.regex = &reg;
break;
case OPT_FILTER_START_FROM:
filter.start_from = parse_datearg(optarg);
EXIT_IF(filter.start_from == -1,
_("invalid filter start date"));
break;
case OPT_FILTER_START_TO:
filter.start_to = parse_datearg(optarg);
EXIT_IF(filter.start_to == -1,
_("invalid filter end date"));
break;
case OPT_FILTER_START_AFTER:
filter.start_from = parse_datearg(optarg) + 1;
EXIT_IF(filter.start_from == -1,
_("invalid filter start date"));
break;
case OPT_FILTER_START_BEFORE:
filter.start_to = parse_datearg(optarg) - 1;
EXIT_IF(filter.start_to == -1,
_("invalid filter end date"));
break;
case OPT_FILTER_END_FROM:
filter.end_from = parse_datearg(optarg);
EXIT_IF(filter.end_from == -1,
_("invalid filter start date"));
break;
case OPT_FILTER_END_TO:
filter.end_to = parse_datearg(optarg);
EXIT_IF(filter.end_to == -1,
_("invalid filter end date"));
break;
case OPT_FILTER_END_AFTER:
filter.end_from = parse_datearg(optarg) + 1;
EXIT_IF(filter.end_from == -1,
_("invalid filter start date"));
break;
case OPT_FILTER_END_BEFORE:
filter.end_to = parse_datearg(optarg) - 1;
EXIT_IF(filter.end_to == -1,
_("invalid filter end date"));
break;
case OPT_FILTER_PRIORITY:
filter.priority = atoi(optarg);
EXIT_IF(filter.priority < 1 || filter.priority > 9,
_("invalid priority"));
break;
case OPT_FILTER_COMPLETED:
filter.completed = 1;
break;
case OPT_FILTER_UNCOMPLETED:
filter.uncompleted = 1;
break;
case OPT_FROM:
from = parse_datearg(optarg);
EXIT_IF(from == -1, _("invalid start date"));
break;
case OPT_TO:
to = parse_datearg(optarg);
EXIT_IF(to == -1, _("invalid end date"));
break;
case OPT_DAYS:
range = atoi(optarg);
EXIT_IF(range == 0, _("invalid range"));
break;
case OPT_FMT_APT:
fmt_apt = optarg;
break;
case OPT_FMT_RAPT:
fmt_rapt = optarg;
break;
case OPT_FMT_EV:
fmt_ev = optarg;
break;
case OPT_FMT_REV:
fmt_rev = optarg;
break;
case OPT_FMT_TODO:
fmt_todo = optarg;
break;
case OPT_READ_ONLY:
read_only = 1;
break;
case OPT_STATUS:
status = 1;
break;
default:
usage();
usage_try();
goto cleanup;
}
}
argc -= optind;
if (filter.type_mask == 0)
filter.type_mask = TYPE_MASK_ALL;
if (status + query + next + gc + import + export > 1) {
ERROR_MSG(_("invalid argument combination"));
usage();
usage_try();
goto cleanup;
}
if (from == -1) {
struct date day = { 0, 0, 0 };
from = get_sec_date(day);
}
if (to == -1 && range == -1)
to = from + DAYINSEC;
else if (to == -1 && range >= 0)
to = from + range * DAYINSEC;
else if (to >= 0 && range >= 0)
EXIT_IF(to >= 0, _("cannot specify a range and an end date"));
io_init(cfile, datadir);
io_check_dir(path_dir);
io_check_dir(path_notes);
if (status) {
status_arg();
} else if (query) {
io_check_file(path_apts);
io_check_file(path_todo);
io_check_file(path_conf);
vars_init();
config_load(); /* To get output date format. */
io_load_app(&filter);
io_load_todo(&filter);
date_arg_from_to(from, to, fmt_apt, fmt_rapt, fmt_ev, fmt_rev,
&limit);
todo_arg(fmt_todo, &limit, &filter);
} else if (next) {
io_check_file(path_apts);
io_load_app(&filter);
next_arg();
} else if (gc) {
io_check_file(path_apts);
io_check_file(path_todo);
io_load_app(NULL);
io_load_todo(NULL);
note_gc();
} else if (import) {
io_check_file(path_apts);
io_check_file(path_todo);
/* Get default pager in case we need to show a log file. */
vars_init();
io_load_app(NULL);
io_load_todo(NULL);
io_import_data(IO_IMPORT_ICAL, ifile);
io_save_apts(path_apts);
io_save_todo(path_todo);
} else if (export) {
io_check_file(path_apts);
io_check_file(path_todo);
io_load_app(&filter);
io_load_todo(&filter);
io_export_data(xfmt);
} else {
/* interactive mode */
non_interactive = 0;
}
cleanup:
/* Free filter parameters. */
if (filter.regex)
regfree(filter.regex);
return non_interactive;
}