]> Joshua Wise's Git repositories - tdl.git/blame - main.c
Fall back on index for sort if the due date is the same, not just if both due dates...
[tdl.git] / main.c
CommitLineData
7024e37b
JW
1/*
2 $Header: /cvs/src/tdl/main.c,v 1.39.2.6 2004/02/03 22:17:22 richard Exp $
3
4 tdl - A console program for managing to-do lists
5 Copyright (C) 2001,2002,2003,2004,2005 Richard P. Curnow
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
20 */
21
22#include "tdl.h"
23#include "version.h"
24#include <assert.h>
25#include <string.h>
26#include <errno.h>
27#include <ctype.h>
28#include <fcntl.h>
29#include <sys/types.h>
30#include <sys/stat.h>
31#include <unistd.h>
32#include <signal.h>
33
34#ifdef USE_DOTLOCK
35#include <sys/utsname.h>
36#include <pwd.h>
37#endif
38
39/* The name of the database file (in whichever directory it may be) */
40#define DBNAME ".tdldb"
41
42/* Set if db doesn't exist in this directory */
43static char *current_database_path = NULL;
44
45/* The currently loaded database */
46struct links top;
47
48/* Flag for whether data is actually loaded yet */
49static int is_loaded = 0;
50
51#ifdef USE_DOTLOCK
52static char *lock_file_name = NULL;
53#endif
54
55/* Flag if currently loaded database has been changed and needs writing back to
56 * the filesystem */
57static int currently_dirty = 0;
58
59/* Flag indicating whether to load databases read only */
60static int read_only = 0;
61
62/* Whether to complain about problems with file operations */
63static int is_noisy = 1;
64
65/* Whether to forcibly unlock the database before the next lock attempt (e.g.
66 * if the program crashed for some reason before.) */
67static int forced_unlock = 0;
68
69static int is_interactive = 0;
70
71static void set_descendent_priority(struct node *x, enum Priority priority)/*{{{*/
72{
73 struct node *y;
74 for (y = x->kids.next; y != (struct node *) &x->kids; y = y->chain.next) {
75 y->priority = priority;
76 set_descendent_priority(y, priority);
77 }
78}
79/*}}}*/
80
81/* This will be variable eventually */
82static char default_database_path[] = "./" DBNAME;
83
84#ifdef USE_DOTLOCK
85static void unlock_database(void)/*{{{*/
86{
87 if (lock_file_name) unlink(lock_file_name);
88 return;
89}
90/*}}}*/
91static volatile void unlock_and_exit(int code)/*{{{*/
92{
93 unlock_database();
94 exit(code);
95}
96/*}}}*/
97static void lock_database(char *path)/*{{{*/
98{
99 struct utsname uu;
100 struct passwd *pw;
101 int pid;
102 int len;
103 char *tname;
104 struct stat sb;
105 FILE *out;
106
107 if (uname(&uu) < 0) {
108 perror("uname");
109 exit(1);
110 }
111 pw = getpwuid(getuid());
112 if (!pw) {
113 perror("getpwuid");
114 exit(1);
115 }
116 pid = getpid();
117 len = 1 + strlen(path) + 5;
118 lock_file_name = new_array(char, len);
119 sprintf(lock_file_name, "%s.lock", path);
120
121 if (forced_unlock) {
122 unlock_database();
123 forced_unlock = 0;
124 }
125
126 len += strlen(uu.nodename);
127 /* add on max width of pid field (allow up to 32 bit pid_t) + 2 '.' chars */
128 len += (10 + 2);
129 tname = new_array(char, len);
130 sprintf(tname, "%s.%d.%s", lock_file_name, pid, uu.nodename);
131 out = fopen(tname, "w");
132 if (!out) {
133 fprintf(stderr, "Cannot open lock file %s for writing\n", tname);
134 exit(1);
135 }
136 fprintf(out, "%d,%s,%s\n", pid, uu.nodename, pw->pw_name);
137 fclose(out);
138
139 if (link(tname, lock_file_name) < 0) {
140 /* check if link count==2 */
141 if (stat(tname, &sb) < 0) {
142 fprintf(stderr, "Could not stat the lock file\n");
143 unlink(tname);
144 exit(1);
145 } else {
146 if (sb.st_nlink != 2) {
147 FILE *in;
148 in = fopen(lock_file_name, "r");
149 if (in) {
150 char line[2048];
151 fgets(line, sizeof(line), in);
152 line[strlen(line)-1] = 0; /* strip trailing newline */
153 fprintf(stderr, "Database %s appears to be locked by (pid,node,user)=(%s)\n", path, line);
154 unlink(tname);
155 exit(1);
156 }
157 } else {
158 /* lock succeeded apparently */
159 }
160 }
161 } else {
162 /* lock succeeded apparently */
163 }
164 unlink(tname);
165 free(tname);
166 return;
167}
168/*}}}*/
169#else
170static volatile void unlock_and_exit(int code)/*{{{*/
171{
172 exit(code);
173}
174/*}}}*/
175#endif /* USE_DOTLOCK */
176
177static char *get_database_path(int traverse_up)/*{{{*/
178{
179 char *env_var;
180 env_var = getenv("TDL_DATABASE");
181 if (env_var) {
182 return env_var;
183 } else {
184 int at_root, orig_size, size, dbname_len, found, stat_result;
185 char *orig_cwd, *cwd, *result, *filename;
186 struct stat statbuf;
187
188 dbname_len = strlen(DBNAME);
189 size = 16;
190 orig_size = 16;
191 found = 0;
192 at_root = 0;
193 cwd = new_array(char, size);
194 orig_cwd = new_array(char, orig_size);
195 do {
196 result = getcwd(orig_cwd, orig_size);
197 if (!result) {
198 if (errno == ERANGE) {
199 orig_size <<= 1;
200 orig_cwd = grow_array(char, orig_size, orig_cwd);
201 } else {
202 fprintf(stderr, "Unexpected error reading current directory\n");
203 unlock_and_exit(1);
204 }
205 }
206 } while (!result);
207 filename = new_array(char, size + dbname_len + 2);
208 filename[0] = 0;
209 do {
210 result = getcwd(cwd, size);
211 if (!result && (errno == ERANGE)) {
212 size <<= 1;
213 cwd = grow_array(char, size, cwd);
214 filename = grow_array(char, size + dbname_len + 2, filename);
215 } else {
216 if (!strcmp(cwd, "/")) {
217 at_root = 1;
218 }
219 strcpy(filename, cwd);
220 strcat(filename, "/");
221 strcat(filename, DBNAME);
222 stat_result = stat(filename, &statbuf);
223 if ((stat_result >= 0) && (statbuf.st_mode & 0600)) {
224 found = 1;
225 break;
226 }
227
228 if (!traverse_up) break;
229
230 /* Otherwise, go up a level */
231 chdir ("..");
232 }
233 } while (!at_root);
234
235 free(cwd);
236
237 /* Reason for this : if using create in a subdirectory of a directory
238 * already containing a .tdldb, the cwd after the call here from main would
239 * get left pointing at the directory containing the .tdldb that already
240 * exists, making the call here from process_create() fail. So go back to
241 * the directory where we started. */
242 chdir(orig_cwd);
243 free(orig_cwd);
244
245 if (found) {
246 return filename;
247 } else {
248 return default_database_path;
249 }
250 }
251
252}
253/*}}}*/
254static void rename_database(char *path)/*{{{*/
255{
256 int len;
257 char *pathbak;
258
259 len = strlen(path);
260 pathbak = new_array(char, len + 5);
261 strcpy(pathbak, path);
262 strcat(pathbak, ".bak");
263 if (rename(path, pathbak) < 0) {
264 if (is_noisy) {
265 perror("warning, couldn't save backup database:");
266 }
267 }
268 free(pathbak);
269 return;
270}
271/*}}}*/
272static char *executable_name(char *argv0)/*{{{*/
273{
274 char *p;
275 for (p=argv0; *p; p++) ;
276 for (; p>=argv0; p--) {
277 if (*p == '/') return (p+1);
278 }
279 return argv0;
280}
281/*}}}*/
282static void load_database(char *path) /*{{{*/
283 /* Return 1 if successful, 0 if no database was found */
284{
285 FILE *in;
286 currently_dirty = 0;
287#ifdef USE_DOTLOCK
288 if (!read_only) {
289 lock_database(path);
290 }
291#endif
292 in = fopen(path, "rb");
293 if (in) {
294 /* Database may not exist, e.g. if the program has never been run before.
295 */
296 read_database(in, &top);
297 fclose(in);
298 is_loaded = 1;
299 } else {
300 if (is_noisy) {
301 fprintf(stderr, "warning: no database found above this directory\n");
302 }
303 }
304}
305/*}}}*/
306void load_database_if_not_loaded(void)/*{{{*/
307{
308 if (!is_loaded) {
309 load_database(current_database_path);
310 is_loaded = 1;
311 }
312}
313/*}}}*/
314static mode_t get_mode(const char *path)/*{{{*/
315{
316 mode_t result;
317 const mode_t default_result = 0600; /* access to user only. */
318 struct stat sb;
319 if (stat(path, &sb) < 0) {
320 result = default_result;
321 } else {
322 if (!S_ISREG(sb.st_mode)) {
323 fprintf(stderr, "Warning : existing database is not a regular file!\n");
324 result = default_result;
325 } else {
326 result = sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO);
327 }
328 }
329 return result;
330}
331/*}}}*/
332static void save_database(char *path)/*{{{*/
333{
334 FILE *out = NULL;
335 int out_fd;
336 mode_t database_mode;
337 if (read_only) {
338 fprintf(stderr, "Warning : database opened read-only. Not saving.\n");
339 return;
340 }
341 if (is_loaded && currently_dirty) {
342 database_mode = get_mode(path);
343
344 /* The next line only used to happen if the command wasn't 'create'.
345 * However, it should quietly fail for create, where the existing database
346 * doesn't exist */
347 rename_database(path);
348
349 /* Open database this way so that the permissions from the existing
350 database can be duplicated onto the new one in way free of race
351 conditions. */
352 out_fd = open(path, O_WRONLY | O_CREAT | O_EXCL, database_mode);
353 if (out_fd < 0) {
354 fprintf(stderr, "Could not open new database %s for writing : %s\n",
355 path, strerror(errno));
356 unlock_and_exit(1);
357 } else {
358 /* Normal case */
359 out = fdopen(out_fd, "wb");
360 }
361 if (!out) {
362 fprintf(stderr, "Cannot open database %s for writing\n", path);
363 unlock_and_exit(1);
364 }
365 write_database(out, &top);
366 fclose(out);
367 }
368 currently_dirty = 0;
369 return;
370}
371/*}}}*/
372void free_database(struct links *x)/*{{{*/
373{
374 struct node *y;
375 struct node *next;
376 for (y=x->next; y != (struct node *) x; y = next) {
377 free_database(&y->kids);
378 free(y->text);
379 next = y->chain.next;
380 free(y);
381 }
382 x->next = x->prev = (struct node *) x;
383}
384/*}}}*/
385
386/* {{{ One line descriptions of the subcommands */
387static char desc_above[] = "Move entries above (before) another entry";
388static char desc_add[] = "Add a new entry to the database";
389static char desc_after[] = "Move entries after (below) another entry";
390static char desc_before[] = "Move entries before (above) another entry";
391static char desc_below[] = "Move entries below (after) another entry";
392static char desc_clone[] = "Make deep copy of one or more entries";
393static char desc_copyto[] = "Insert deep copy of one or more entries under another entry";
394static char desc_create[] = "Create a new database in the current directory";
395static char desc_defer[] = "Put off starting some tasks until a given time";
396static char desc_delete[] = "Remove 1 or more entries from the database";
397static char desc_done[] = "Mark 1 or more entries as done";
398static char desc_edit[] = "Change the text of an entry";
399static char desc_exit[] = "Exit program, saving database";
400static char desc_export[] = "Export entries to another database";
401static char desc_help[] = "Display help information";
402static char desc_import[] = "Import entries from another database";
403static char desc_ignore[] = "Postpone or partially remove 1 or more entries";
404static char desc_into[] = "Move entries to end of new parent";
405static char desc_list[] = "List entries in database (default from top node)";
406static char desc_log[] = "Add a new entry to the database, mark it done as well";
407static char desc_moveto[] = "Move entries to end of new parent";
408static char desc_narrow[] = "Restrict actions to part of the database";
409static char desc_open[] = "Move one or more entries out of postponed/deferred state";
410static char desc_postpone[] = "Make one or more entries postponed indefinitely";
411static char desc_priority[] = "Change the priority of 1 or more entries";
412static char desc_purge[] = "Remove old done entries in subtrees";
413static char desc_quit[] = "Exit program, NOT saving database";
414static char desc_remove[] = "Remove 1 or more entries from the database";
415static char desc_report[] = "Report completed tasks in interval";
416static char desc_revert[] = "Discard changes and reload previous database from disc";
417static char desc_save[] = "Save the database back to disc and keep working";
418static char desc_undo[] = "Mark 1 or more entries as not done (cancel effect of 'done')";
419static char desc_usage[] = "Display help information";
420static char desc_version[] = "Display program version";
421static char desc_which[] = "Display filename of database being used";
422static char desc_widen[] = "Widen the part of the database to which actions apply";
423
424/* }}} */
425/* {{{ Synopsis of each subcommand */
426static char synop_above[] = "<index_to_insert_above> <index_to_move> ...";
427static char synop_add[] = "[@<datespec>] [<parent_index>] [<priority>] <entry_text>";
428static char synop_after[] = "<index_to_insert_below> <index_to_move> ...";
429static char synop_before[] = "<index_to_insert_above> <index_to_move> ...";
430static char synop_below[] = "<index_to_insert_below> <index_to_move> ...";
431static char synop_clone[] = "<index_to_clone> ...";
432static char synop_copyto[] = "<parent_index> <index_to_clone> ...";
433static char synop_create[] = "";
434static char synop_defer[] = "[@]<datespec> <entry_index>{...] ...";
435static char synop_delete[] = "<entry_index>[...] ...";
436static char synop_done[] = "[@<datespec>] <entry_index>[...] ...";
437static char synop_edit[] = "<entry_index> [<new_text>]";
438static char synop_exit[] = "";
439static char synop_export[] = "<filename> <entry_index> ...";
440static char synop_help[] = "[<command-name>]";
441static char synop_ignore[] = "<entry_index>[...] ...";
442static char synop_import[] = "<filename>";
443static char synop_into[] = "<new_parent_index> <index_to_move> ...";
444static char synop_list[] = "[-v] [-a] [-p] [-m] [-1..9] [<min-priority>] [<parent_index>|/<search_condition>...]\n"
445 "-v : verbose (show dates, priorities etc)\n"
446 "-a : show all entries, including 'done' ones\n"
447 "-p : show deferred and postponed entries\n"
448 "-m : don't use colours (monochrome)\n"
449 "-1,-2,..,-9 : summarise (and don't show) entries below this depth\n"
450 "<search_condition> : word to match on";
451static char synop_log[] = "[@<datespec>] [<parent_index>] [<priority>] <entry_text>";
452static char synop_moveto[] = "<new_parent_index> <index_to_move> ...";
453static char synop_narrow[] = "<entry_index>";
454static char synop_open[] = "<entry_index>[...] ...";
455static char synop_postpone[] = "<entry_index>[...] ...";
456static char synop_priority[] = "<new_priority> <entry_index>[...] ...";
457static char synop_purge[] = "<since_datespec> [<ancestor_index> ...]";
458static char synop_quit[] = "";
459static char synop_remove[] = "<entry_index>[...] ...";
460static char synop_report[] = "<start_datespec> [<end_datespec>]\n"
461 "(end defaults to now)";
462static char synop_revert[] = "";
463static char synop_save[] = "";
464static char synop_undo[] = "<entry_index>[...] ...";
465static char synop_usage[] = "[<command-name>]";
466static char synop_version[] = "";
467static char synop_which[] = "";
468static char synop_widen[] = "[<levels>]";
469/* }}} */
470
471static int process_create(char **x)/*{{{*/
472{
473 char *dbpath = get_database_path(0);
474 struct stat sb;
475 int result;
476
477 result = stat(dbpath, &sb);
478 if (result >= 0) {
479 fprintf(stderr, "Can't create database <%s>, it already exists!\n", dbpath);
480 return -1;
481 } else {
482 if (read_only) {
483 fprintf(stderr, "Can't create database <%s> in read-only mode!\n", dbpath);
484 return -1;
485 }
486 /* Should have an empty database, and the dirty flag will be set */
487 current_database_path = dbpath;
488 /* don't emit complaint about not being able to move database to its backup */
489 is_noisy = 0;
490 /* Force empty database to be written out */
491 is_loaded = currently_dirty = 1;
492 return 0;
493 }
494}
495/*}}}*/
496static int process_priority(char **x)/*{{{*/
497{
498 int error;
499 enum Priority priority;
500 struct node *n;
501 int do_descendents;
502
503 priority = parse_priority(*x, &error);
504 if (error < 0) {
505 fprintf(stderr, "usage: priority %s\n", synop_priority);
506 return error;
507 }
508
509 while (*++x) {
510 do_descendents = include_descendents(*x); /* May modify *x */
511 n = lookup_node(*x, 0, NULL);
512 if (!n) return -1;
513 n->priority = priority;
514 if (do_descendents) {
515 set_descendent_priority(n, priority);
516 }
517 }
518
519 return 0;
520}/*}}}*/
521static int process_which(char **argv)/*{{{*/
522{
523 printf("%s\n", current_database_path);
524 return 0;
525}
526/*}}}*/
527static int process_version(char **x)/*{{{*/
528{
529 fprintf(stderr, "tdl %s\n", PROGRAM_VERSION);
530 return 0;
531}
532/*}}}*/
533static int process_exit(char **x)/*{{{*/
534{
535 save_database(current_database_path);
536 free_database(&top);
537 unlock_and_exit(0);
538 return 0; /* moot */
539}
540/*}}}*/
541static int process_quit(char **x)/*{{{*/
542{
543 /* Just get out quick, don't write the database back */
544 char ans[4];
545 if (currently_dirty) {
546 printf(" WARNING: if you quit, all changes to database will be lost!\n"
547 " Use command 'exit' instead of 'quit' if you wish to save data.\n"
548 " Really quit [y/N]? ");
549 fgets (ans, 4, stdin);
550 if (strcasecmp(ans,"y\n") != 0) {
551 printf(" Quit canceled.\n");
552 return 0;
553 }
554 }
555 free_database(&top);
556 unlock_and_exit(0);
557 return 0; /* moot */
558}
559/*}}}*/
560static int process_save(char **x)/*{{{*/
561{
562 /* FIXME: I'm not sure whether the behaviour here should include renaming the
563 * existing disc database to become the backup file. I think the precedent
564 * would be how vi or emacs handle backup files when multiple saves are done
565 * within a session. */
566 save_database(current_database_path);
567 return 0;
568}
569/*}}}*/
570static int process_revert(char **x)/*{{{*/
571{
572 if (is_loaded) {
573 free_database(&top);
574 }
575 is_loaded = currently_dirty = 0;
576 return 0;
577}
578/*}}}*/
579/* Forward prototype */
580static int usage(char **x);
581
582struct command cmds[] = {/*{{{*/
583 {"--help", NULL, usage, desc_help, NULL, NULL, 0, 0, 3, 0, 1},
584 {"-h", NULL, usage, desc_help, NULL, NULL, 0, 0, 2, 0, 1},
585 {"-V", NULL, process_version, desc_version, NULL, NULL, 0, 0, 2, 0, 1},
586 {"above", NULL, process_above, desc_above, synop_above, NULL, 1, 1, 2, 1, 1},
587 {"add", "tdla", process_add, desc_add, synop_add, NULL, 1, 1, 2, 1, 1},
588 {"after", NULL, process_below, desc_after, synop_after, NULL, 1, 1, 2, 1, 1},
589 {"before", NULL, process_above, desc_before, synop_before, NULL, 1, 1, 3, 1, 1},
590 {"below", NULL, process_below, desc_below, synop_below, NULL, 1, 1, 3, 1, 1},
591 {"clone", NULL, process_clone, desc_clone, synop_clone, NULL, 1, 1, 2, 1, 1},
592 {"copyto", NULL, process_copyto, desc_copyto, synop_copyto, NULL, 1, 1, 2, 1, 1},
593 {"create", NULL, process_create, desc_create, synop_create, NULL, 1, 0, 2, 0, 1},
594 {"defer", NULL, process_defer, desc_defer, synop_defer, NULL, 1, 1, 3, 1, 1},
595 {"delete", NULL, process_remove, desc_delete, synop_delete, NULL, 1, 1, 3, 1, 1},
596 {"done", "tdld", process_done, desc_done, synop_done, complete_done, 1, 1, 2, 1, 1},
597 {"edit", NULL, process_edit, desc_edit, synop_edit, NULL, 1, 1, 2, 1, 1},
598 {"exit", NULL, process_exit, desc_exit, synop_exit, NULL, 0, 0, 3, 1, 0},
599 {"export", NULL, process_export, desc_export, synop_export, NULL, 0, 1, 3, 1, 1},
600 {"help", NULL, usage, desc_help, synop_help, complete_help, 0, 0, 1, 1, 1},
601 {"ignore", NULL, process_ignore, desc_ignore, synop_ignore, complete_done, 1, 1, 2, 1, 1},
602 {"import", NULL, process_import, desc_import, synop_import, NULL, 1, 1, 2, 1, 1},
603 {"into", NULL, process_into, desc_into, synop_into, NULL, 1, 1, 2, 1, 1},
604 {"list", "tdll", process_list, desc_list, synop_list, complete_list, 0, 1, 2, 1, 1},
605 {"ls", "tdls", process_list, desc_list, synop_list, complete_list, 0, 1, 2, 1, 1},
606 {"log", "tdlg", process_log, desc_log, synop_log, NULL, 1, 1, 2, 1, 1},
607 {"moveto", NULL, process_into, desc_moveto, synop_moveto, NULL, 1, 1, 1, 1, 1},
608 {"narrow", NULL, process_narrow, desc_narrow, synop_narrow, NULL, 0, 1, 1, 1, 0},
609 {"open", NULL, process_open, desc_open, synop_open, complete_open, 1, 1, 1, 1, 1},
610 {"postpone", NULL, process_postpone, desc_postpone,synop_postpone,complete_postpone, 1, 1, 2, 1, 1},
611 {"priority", NULL, process_priority, desc_priority,synop_priority,complete_priority, 1, 1, 2, 1, 1},
612 {"purge", NULL, process_purge, desc_purge, synop_purge, NULL, 1, 1, 2, 1, 1},
613 {"quit", NULL, process_quit, desc_quit, synop_quit, NULL, 0, 0, 1, 1, 0},
614 {"remove", NULL, process_remove, desc_remove, synop_remove, NULL, 1, 1, 3, 1, 1},
615 {"report", NULL, process_report, desc_report, synop_report, NULL, 0, 1, 3, 1, 1},
616 {"revert", NULL, process_revert, desc_revert, synop_revert, NULL, 0, 0, 3, 1, 0},
617 {"save", NULL, process_save, desc_save, synop_save, NULL, 0, 1, 1, 1, 0},
618 {"undo", NULL, process_undo, desc_undo, synop_undo, NULL, 1, 1, 2, 1, 1},
619 {"usage", NULL, usage, desc_usage, synop_usage, complete_help, 0, 0, 2, 1, 1},
620 {"version", NULL, process_version, desc_version, synop_version, NULL, 0, 0, 1, 1, 1},
621 {"which", NULL, process_which, desc_which, synop_which, NULL, 0, 0, 2, 1, 1},
622 {"widen", NULL, process_widen, desc_widen, synop_widen, NULL, 0, 1, 2, 1, 0}
623};/*}}}*/
624int n_cmds = 0;
625
626#define N(x) (sizeof(x) / sizeof(x[0]))
627
628static int is_processing = 0;
629static int signal_count = 0;
630
5f910b7c
JW
631static char *executable;
632static int is_tdl = 0;
633
7024e37b
JW
634static void handle_signal(int a)/*{{{*/
635{
636 signal_count++;
637 /* And close stdin, which should cause readline() in inter.c to return
638 * immediately if it was active when the signal arrived. */
639 close(0);
640
641 if (signal_count == 3) {
642 /* User is desperately hitting ^C to stop the program. Bail out without tidying up, and give a warning. */
643 static char msg[] = "About to force exit due to repeated termination signals.\n"
644 "Database changes since the last save will be LOST.\n";
645 write(2, msg, strlen(msg));
646 }
647 if (signal_count == 4) {
648 static char msg[] = "The database may be left locked.\n"
649 "You will need to run 'tdl -u' next time to unlock it.\n";
650 write(2, msg, strlen(msg));
651 exit(1);
652 }
653}
654/*}}}*/
655static void guarded_sigaction(int signum, struct sigaction *sa)/*{{{*/
656{
657 if (sigaction(signum, sa, NULL) < 0) {
658 perror("sigaction");
659 unlock_and_exit(1);
660 }
661}
662/*}}}*/
663static void setup_signals(void)/*{{{*/
664{
665 struct sigaction sa;
666 if (sigemptyset(&sa.sa_mask) < 0) {
667 perror("sigemptyset");
668 unlock_and_exit(1);
669 }
670 sa.sa_handler = handle_signal;
671 sa.sa_flags = 0;
672#if 0
673 guarded_sigaction(SIGHUP, &sa);
674 guarded_sigaction(SIGINT, &sa);
675 guarded_sigaction(SIGQUIT, &sa);
676 guarded_sigaction(SIGTERM, &sa);
677#endif
678
679 return;
680}
681/*}}}*/
682static void print_copyright(void)/*{{{*/
683{
684 fprintf(stderr,
685 "tdl %s, Copyright (C) 2001,2002,2003,2004,2005 Richard P. Curnow\n"
686 "tdl comes with ABSOLUTELY NO WARRANTY.\n"
687 "This is free software, and you are welcome to redistribute it\n"
688 "under certain conditions; see the GNU General Public License for details.\n\n",
689 PROGRAM_VERSION);
690}
691/*}}}*/
692void dispatch(char **argv) /* and other args *//*{{{*/
693{
694 int i, p_len, matchlen, index=-1;
7024e37b
JW
695 char **p, **pp;
696
697 if (signal_count > 0) {
698 save_database(current_database_path);
699 unlock_and_exit(0);
700 }
701
7024e37b 702 p = argv + 1;
5f910b7c
JW
703 if (is_tdl)
704 while (*p && *p[0] == '-') p++;
7024e37b
JW
705
706 /* Parse command line */
707 if (is_tdl && !*p) {
708 /* If no arguments, go into interactive mode, but only if we didn't come from there (!) */
709 if (!is_interactive) {
710 setup_signals();
711 print_copyright();
712 is_interactive = 1;
713 interactive();
714 }
715 return;
716 }
717
718 if (is_tdl) {
719 p_len = strlen(*p);
720 for (i=0; i<n_cmds; i++) {
721 matchlen = p_len < cmds[i].matchlen ? cmds[i].matchlen : p_len;
722 if ((is_interactive ? cmds[i].interactive_ok : cmds[i].non_interactive_ok) &&
723 !strncmp(cmds[i].name, *p, matchlen)) {
724 index = i;
725 break;
726 }
727 }
728 } else {
729 for (i=0; i<n_cmds; i++) {
730 if (cmds[i].shortcut && !strcmp(cmds[i].shortcut, executable)) {
731 index = i;
732 break;
733 }
734 }
735 }
736
737 /* Skip commands that dirty the database, if it was opened read-only. */
738 if (index >= 0 && cmds[index].dirty && read_only) {
739 fprintf(stderr, "Can't use command <%s> in read-only mode\n",
740 cmds[index].name);
741 if (!is_interactive) {
742 unlock_and_exit(-1);
743 }
744 } else if (index >= 0) {
745 int result;
746
747 is_processing = 1;
748
749 if (!is_loaded && cmds[index].load_db) {
750 load_database(current_database_path);
751 }
752
753 pp = is_tdl ? (p + 1) : p;
754 result = (cmds[index].func)(pp);
755
756 /* Check for failure */
757 if (result < 0) {
758 if (!is_interactive) {
759 unlock_and_exit(-result);
760 }
761
762 /* If interactive, the handling function has emitted its error message.
763 * Just 'abort' this command and go back to the prompt */
764
765 } else {
766
767 if (cmds[index].dirty) {
768 currently_dirty = 1;
769 }
770 }
771
772 is_processing = 0;
773 if (signal_count > 0) {
774 save_database(current_database_path);
775 unlock_and_exit(0);
776 }
777
778 } else {
779 if (is_tdl && *p) {
780 fprintf(stderr, "tdl: Unknown command <%s>\n", *p);
781 } else {
782 fprintf(stderr, "tdl: Unknown command\n");
783 }
784 if (!is_interactive) {
785 unlock_and_exit(1);
786 }
787 }
788
789}
790/*}}}*/
791static int usage(char **x)/*{{{*/
792{
793 int i, index;
794 char *cmd = *x;
795
796 if (cmd) {
797 /* Detailed help for the one command */
798 index = -1;
799 for (i=0; i<n_cmds; i++) {
800 if (!strncmp(cmds[i].name, cmd, 3) &&
801 (is_interactive ? cmds[i].interactive_ok : cmds[i].non_interactive_ok)) {
802 index = i;
803 break;
804 }
805 }
806 if (index >= 0) {
807 fprintf(stdout, "Description\n %s\n\n", cmds[i].descrip);
808 fprintf(stdout, "Synopsis\n");
809
810 if (is_interactive) {
811 fprintf(stdout, " %s %s\n", cmds[i].name, cmds[i].synopsis ? cmds[i].synopsis : "");
812 } else {
813 fprintf(stdout, " tdl [-qR] %s %s\n", cmds[i].name, cmds[i].synopsis ? cmds[i].synopsis : "");
814 if (cmds[i].shortcut) {
815 fprintf(stdout, " %s [-qR] %s\n", cmds[i].shortcut, cmds[i].synopsis ? cmds[i].synopsis : "");
816 }
817 }
818
819 fprintf(stdout,
820 "\n"
821 "General notes (where they apply to a command):\n"
822 "\n"
823 "<*_index> : 1, 1.1 etc (see output of 'tdl list')\n"
824 "<priority> : urgent|high|normal|low|verylow\n"
825 "<datespec> : [-|+][0-9]+[shdwmy][-hh[mm[ss]]] OR\n"
826 " [-|+](sun|mon|tue|wed|thu|fri|sat)[-hh[mm[ss]]] OR\n"
827 " [[[cc]yy]mm]dd[-hh[mm[ss]]]\n"
828 "<text> : Any text (you'll need to quote it if >1 word)\n"
829 );
830
831 } else {
832 fprintf(stderr, "Unrecognized command <%s>, no help available\n", cmd);
833 }
834
835 } else {
836 print_copyright();
837
838 if (!is_interactive) {
839 fprintf(stdout, "tdl [-qR] : Enter interactive mode\n");
840 }
841 for (i=0; i<n_cmds; i++) {
842 if (is_interactive) {
843 if (cmds[i].interactive_ok) {
844 fprintf(stdout, "%-8s : %s\n", cmds[i].name, cmds[i].descrip);
845 }
846 } else {
847 if (cmds[i].non_interactive_ok) {
848 fprintf(stdout, "tdl [-qR] %-8s : %s\n", cmds[i].name, cmds[i].descrip);
849 if (cmds[i].shortcut) {
850 fprintf(stdout, "%s [-qR] : %s\n", cmds[i].shortcut, cmds[i].descrip);
851 }
852 }
853 }
854 }
855 if (is_interactive) {
856 fprintf(stdout, "\nEnter 'help <command-name>' for more help on a particular command\n");
857 } else {
858 fprintf(stdout, "\nEnter 'tdl help <command-name>' for more help on a particular command\n");
859 }
860 }
861
862 fprintf(stdout, "\n");
863
864 return 0;
865
866}
867/*}}}*/
868
869/*{{{ int main (int argc, char **argv)*/
870int main (int argc, char **argv)
871{
872 int i = 0;
5f910b7c
JW
873
874 executable = executable_name(argv[0]);
875 is_tdl = (!strcmp(executable, "tdl"));
876
7024e37b
JW
877 n_cmds = N(cmds);
878 is_interactive = 0;
879
880 /* Initialise database */
881 top.prev = (struct node *) &top;
882 top.next = (struct node *) &top;
883
5f910b7c 884 if (argc > 1 && is_tdl) {
7024e37b
JW
885 for (i=1; i<argc && argv[i][0] == '-'; i++) {
886 if (strspn(argv[i]+1, "qRu")+1 != strlen(argv[i])) {
887 fprintf(stderr, "Unknown flag <%s>\n", argv[i]);
888 return 1;
889 }
890
891 if (strchr(argv[i], 'q')) {
892 is_noisy = 0;
893 }
894 if (strchr(argv[i], 'R')) {
895 read_only = 1;
896 }
897 if (strchr(argv[i], 'u')) {
898 forced_unlock = 1;
899 }
900 }
901 }
902
903 current_database_path = get_database_path(1);
904
905 dispatch(argv);
906
907 if (! read_only) {
908 save_database(current_database_path);
909 }
910 free_database(&top);
911 unlock_and_exit(0);
912 return 0; /* moot */
913}
914/*}}}*/
915
This page took 0.123663 seconds and 4 git commands to generate.