home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1994 March / Source_Code_CD-ROM_Walnut_Creek_March_1994.iso / compsrcs / misc / volume33 / problem / part06 < prev    next >
Encoding:
Text File  |  1992-10-18  |  44.8 KB  |  1,533 lines

  1. Newsgroups: comp.sources.misc
  2. From: lijewski@rosserv.gsfc.nasa.gov (Mike Lijewski)
  3. Subject:  v33i008:  problem - A Problem Database Manager, Part06/07
  4. Message-ID: <1992Oct19.165944.4359@sparky.imd.sterling.com>
  5. X-Md4-Signature: 172393e0f01b1b02f5c0e8788552dc2a
  6. Date: Mon, 19 Oct 1992 16:59:44 GMT
  7. Approved: kent@sparky.imd.sterling.com
  8.  
  9. Submitted-by: lijewski@rosserv.gsfc.nasa.gov (Mike Lijewski)
  10. Posting-number: Volume 33, Issue 8
  11. Archive-name: problem/part06
  12. Environment: UNIX, GDBM, C++, termcap
  13.  
  14. #! /bin/sh
  15. # This is a shell archive.  Remove anything before this line, then unpack
  16. # it by saving it into a file and typing "sh file".  To overwrite existing
  17. # files, type "sh file -c".  You can also feed this as standard input via
  18. # unshar, or by typing "sh <file", e.g..  If this archive is complete, you
  19. # will see the following message at the end:
  20. #        "End of archive 6 (of 7)."
  21. # Contents:  problem2.C
  22. # Wrapped by lijewski@xtesoc2 on Mon Oct 19 11:05:54 1992
  23. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  24. if test -f 'problem2.C' -a "${1}" != "-c" ; then 
  25.   echo shar: Will not clobber existing file \"'problem2.C'\"
  26. else
  27. echo shar: Extracting \"'problem2.C'\" \(42583 characters\)
  28. sed "s/^X//" >'problem2.C' <<'END_OF_FILE'
  29. X/*
  30. X**
  31. X** problem - a problem database manager
  32. X**
  33. X** Written in C++ using the termcap\(3\) library for screen management
  34. X** and GDBM as the database library.
  35. X**
  36. X** problem.C is made by cating together problem1.C and problem2.C
  37. X**
  38. X** problem2.C problem2.C 1.17   Delta\'d: 17:43:06 10/8/92   Mike Lijewski, CNSF
  39. X**
  40. X** Copyright \(c\) 1991, 1992 Cornell University
  41. X** All rights reserved.
  42. X**
  43. X** Redistribution and use in source and binary forms are permitted
  44. X** provided that: \(1\) source distributions retain this entire copyright
  45. X** notice and comment, and \(2\) distributions including binaries display
  46. X** the following acknowledgement:  ``This product includes software
  47. X** developed by Cornell University\'\' in the documentation or other
  48. X** materials provided with the distribution and in all advertising
  49. X** materials mentioning features or use of this software. Neither the
  50. X** name of the University nor the names of its contributors may be used
  51. X** to endorse or promote products derived from this software without
  52. X** specific prior written permission.
  53. X**
  54. X** THIS SOFTWARE IS PROVIDED ``AS IS\'\' AND WITHOUT ANY EXPRESS OR
  55. X** IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
  56. X** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  57. X*/
  58. X
  59. X/*
  60. X** commands_screen - display screen of problem commands. We assume
  61. X**                   that the caller will be setting the modeline.
  62. X**                   Needs at least five before anything useful is displayed.
  63. X*/
  64. X
  65. Xstatic void commands_screen()
  66. X{
  67. X    cursor_home();
  68. X    clear_to_end_of_line();
  69. X    enter_standout_mode();
  70. X    display_string("Commands");
  71. X    end_standout_mode();
  72. X    clear_to_end_of_line();
  73. X    cursor_wrap();
  74. X
  75. X    //
  76. X    // Display as many commands as will fit on screen starting in third row.
  77. X    //
  78. X    for (int i = 0; i < NCommands() && i < rows() - 4; i++)
  79. X    {
  80. X        clear_to_end_of_line();
  81. X        (void)fputs("  ", stdout);
  82. X        enter_standout_mode();
  83. X        putchar(Commands[i][0]);  // first char of command in bold
  84. X        end_standout_mode();
  85. X        display_string(&Commands[i][1], 0, 3);
  86. X    }
  87. X
  88. X    // clear any dirty lines
  89. X    for (; i < rows() - 4; i++) {
  90. X        clear_to_end_of_line();
  91. X        cursor_wrap();
  92. X    }
  93. X}
  94. X
  95. X/*
  96. X** redisplay_commands - redisplay the commands screen and modeline.
  97. X**                      This gets called on a SIGTSTP or SIGWINCH.
  98. X*/
  99. Xstatic void redisplay_commands() { commands_screen(); update_modeline(); }
  100. X    
  101. X/*
  102. X** update_existing_problem - update an existing problem entry.
  103. X*/
  104. X
  105. Xstatic void update_existing_problem(datum &data, datum &key, Modified how)
  106. X{
  107. X    message("Invoking your editor ...");
  108. X
  109. X    //
  110. X    // Build tmp file into which the data will be edited by user.
  111. X    //
  112. X    const char *file = temporary_file();
  113. X
  114. X    invoke_editor(file);
  115. X
  116. X    //
  117. X    // Merge old data with the new.
  118. X    //
  119. X    time_t t = time(0);
  120. X    char *updated = ctime(&t);
  121. X    char *fmt = "\n****** %s by `%s' on %s\n";
  122. X    char *separator = new char[strlen(fmt) + strlen(How[how]) +
  123. X                               strlen(username()) + strlen(updated) - 5];
  124. X
  125. X    (void)sprintf(separator, fmt, How[how], username(), updated);
  126. X    int fsize = filesize(file);
  127. X    datum newdata;
  128. X    newdata.dsize = int (strlen(separator) + data.dsize + fsize);
  129. X    newdata.dptr  = new char[newdata.dsize];
  130. X    (void)strcpy(newdata.dptr, data.dptr);  // the old data
  131. X    (void)strcat(newdata.dptr, separator);  // the separator
  132. X
  133. X    //
  134. X    // The new data.  Make sure this read only fails on a real
  135. X    // error -- block SIGTSTP and SIGWINCH.
  136. X    //
  137. X    block_tstp_and_winch();
  138. X
  139. X    int fd;
  140. X    if ((fd = open(file, O_RDONLY)) < 0) 
  141. X        error("file %s, line %d, open(%s, O_RDONLY) failed",
  142. X              __FILE__, __LINE__, file);
  143. X
  144. X    if (read(fd,&newdata.dptr[strlen(separator)+data.dsize-1],fsize) != fsize)
  145. X        error("file %s, line %d, read() failed", __FILE__, __LINE__);
  146. X
  147. X    unblock_tstp_and_winch();
  148. X    (void)close(fd);
  149. X    (void)unlink(file);
  150. X
  151. X    //
  152. X    // Always update the Updated field -- Fields\[4\].
  153. X    //
  154. X    char *head = newdata.dptr;
  155. X    for (int i = 0; i < 4; i++) 
  156. X    {
  157. X        // want to find head of fifth line
  158. X        head = strchr(head, '\n');
  159. X        head += 1; // step past the newline
  160. X    }
  161. X    int flen = max_field_length() + 1;
  162. X    head += flen;  // skip to the data in Fields\[4\]
  163. X    for (i = 0; i < 25; i++) head[i] = updated[i];
  164. X
  165. X    //
  166. X    // Update the Status field only on closes and reopens.
  167. X    //
  168. X    if (how == CLOSED || how == REOPENED)
  169. X    {
  170. X        char *field = (how == CLOSED ? "closed" : "open  ");
  171. X        for (i = 0; i < 3; i++)
  172. X        {
  173. X            // skip three more lines
  174. X            head = strchr(head, '\n');
  175. X            head += 1; // step past the newline
  176. X        }
  177. X        head += flen;  // step to the data in Fields\[7\]
  178. X        for (i = 0; i < 6; i++)  // StatDim == 6 in log_new_problem\(\)
  179. X            head[i] = field[i];
  180. X    }
  181. X
  182. X    fmt = "Really do the %s (y|n)?";
  183. X    char *str;
  184. X    switch (how)
  185. X    {
  186. X      case CLOSED:     str = "close" ; break;
  187. X      case REOPENED:   str = "reopen"; break;
  188. X      case APPENDED:   str = "append"; break;
  189. X      case KEYWORDMOD: str = "keyword modification"; break;
  190. X      default:         error("file %s, line %d, illegal case in switch()",
  191. X                             __FILE__, __LINE__);
  192. X    }
  193. X
  194. X    char *buf = new char[strlen(fmt) + strlen(str) - 1];
  195. X    (void)sprintf(buf, fmt, str);
  196. X    if (yes_or_no(buf, redisplay_commands, Yes, 1))
  197. X    {
  198. X        update_database(key, newdata, how);
  199. X        update_subscribers(newdata, key.dptr, how, (int)strlen(separator) +
  200. X                           data.dsize - 1);
  201. X    }
  202. X
  203. X    DELETE buf;
  204. X    DELETE separator;
  205. X    DELETE newdata.dptr;
  206. X}
  207. X
  208. X/*
  209. X** append_to_problem - append to an already existing problem.  Returns
  210. X**                     false if the problem doesn\'t exist.  This indicates
  211. X**                     that we don\'t need to redisplay the command list.
  212. X**                     Returns true if we need to redisplay the command
  213. X**                     list.  If `number\', which defaults to zero, is
  214. X**                     nonzero, we use that number instead of prompting.
  215. X*/
  216. X
  217. Xint append_to_problem(const char *number)
  218. X{
  219. X    datum key;
  220. X    key.dptr  = number ? (char *) number : prompt("Problem # --> ",
  221. X                                                  redisplay_commands);
  222. X    key.dsize = int (strlen(key.dptr) + 1);
  223. X    if (!database_exists())
  224. X    {
  225. X        ding();
  226. X        message("There is no database for problem area `%' ", CurrentArea());
  227. X        if (!number)
  228. X        {
  229. X            DELETE key.dptr;
  230. X            sleep(2);
  231. X        }
  232. X        return 0;
  233. X    }
  234. X
  235. X    open_database(GDBM_READER);
  236. X    datum data = gdbm_fetch(GdbmFile, key);
  237. X    gdbm_close(GdbmFile);
  238. X    if (!data.dptr)
  239. X    {
  240. X        ding();
  241. X        message("There is no problem # `%' ", key.dptr);
  242. X        if (!number)
  243. X        {
  244. X            DELETE key.dptr;
  245. X            sleep(2);
  246. X        }
  247. X        return 0;  // only the message area has been corrupted
  248. X    }
  249. X
  250. X    //
  251. X    // The problem exists.
  252. X    //
  253. X    update_existing_problem(data, key, APPENDED);
  254. X    update_modeline();  // redisplay the previous modeline
  255. X    free(data.dptr);
  256. X    if (!number) DELETE key.dptr;
  257. X
  258. X    return 1;  // must refresh the screen
  259. X}
  260. X
  261. X/*
  262. X** subscribe_to_area - put user on interested parties list for current area.
  263. X**                     Exits on error
  264. X*/
  265. X
  266. Xstatic void subscribe_to_area()
  267. X{
  268. X    int mailfd = open_maillist_file();
  269. X    const int chunksize = 20;
  270. X    const int linelen   = 10;
  271. X    char **users = new char*[chunksize];
  272. X    FILE *mailfp = fdopen(mailfd, "r+");
  273. X    if (!mailfp)
  274. X        error("file %s, line %d, fdopen() failed", __FILE__, __LINE__);
  275. X    int nusers = read_file(mailfp, users, chunksize, linelen);
  276. X    if (nusers < 0)
  277. X        error("file %s, line %d, error reading %s maillist",
  278. X              __FILE__, __LINE__, CurrentArea());
  279. X
  280. X    //
  281. X    // Is user already subscribed?
  282. X    //
  283. X    const char *user = username();
  284. X    for (int i = 0, subscribed = 0; i < nusers; i++)
  285. X        if (strcmp(users[i], user) == 0)
  286. X        {
  287. X            subscribed = 1; break;
  288. X        }
  289. X
  290. X    for (i = 0; i < nusers; i++) DELETE users[i];
  291. X    DELETE users;
  292. X
  293. X    if (subscribed)
  294. X    {
  295. X        (void)close(mailfd); (void)fclose(mailfp);
  296. X        ding();
  297. X        message("You already subscribe to the `%' area ", CurrentArea());
  298. X        sleep(2);
  299. X        return;
  300. X    }
  301. X
  302. X    //
  303. X    // Lock on sequence file when updating the maillist.
  304. X    //
  305. X    int seqfd = open(sequence_file(), O_RDWR);
  306. X    if (seqfd < 0) error("file %s, line %d, open() on `%s' failed",
  307. X                         __FILE__, __LINE__, sequence_file());
  308. X
  309. X    block_tstp_and_winch(); // block SIGTSTP and WINCH
  310. X    lock_file(seqfd);       // lock our sequence file
  311. X
  312. X    // seek to end of maillist file
  313. X    if (fseek(mailfp, 0, SEEK_END))
  314. X        error("file %s, line %d, fseek() failed", __FILE__, __LINE__);
  315. X    (void)fprintf(mailfp, "%s\n", user); // add\'em
  316. X
  317. X    (void)fclose(mailfp);
  318. X    (void) close(mailfd);
  319. X    unlock_file(seqfd);
  320. X    (void)close(seqfd);
  321. X
  322. X    unblock_tstp_and_winch();
  323. X
  324. X    message("You now subscribe to the `%' area ", CurrentArea());
  325. X    sleep(2);
  326. X}
  327. X
  328. X/*
  329. X** unsubscribe_from_area - unsubscribe from the current area.  Exits on error.
  330. X*/
  331. X
  332. Xstatic void unsubscribe_from_area()
  333. X{
  334. X    int mailfd   = open_maillist_file();
  335. X    FILE *mailfp = fdopen(mailfd, "r+");
  336. X    if (!mailfp)
  337. X        error("file %s, line %d, fdopen() failed", __FILE__, __LINE__);
  338. X
  339. X    const int chunksize = 20;
  340. X    const int linelen   = 10;
  341. X    char **users = new char*[chunksize];
  342. X    int nusers   = read_file(mailfp, users, chunksize, linelen);
  343. X    if (nusers < 0)
  344. X        error("file %s, line %d, error reading %s maillist",
  345. X              __FILE__, __LINE__, CurrentArea());
  346. X
  347. X    //
  348. X    // Are they already subscribed?
  349. X    //
  350. X    const char *user = username();
  351. X    for (int i = 0, subscribed = 0; i < nusers; i++)
  352. X        if (strcmp(users[i], user) == 0) { subscribed = 1; break; }
  353. X
  354. X    for (i = 0; i < nusers; i++) DELETE users[i];
  355. X    DELETE users;
  356. X
  357. X    if (!subscribed)
  358. X    {
  359. X        (void)fclose(mailfp);
  360. X        (void)close(mailfd);
  361. X        ding();
  362. X        message("You're not a subscribee of the `%' area ", CurrentArea());
  363. X        sleep(2);
  364. X        return;
  365. X    }
  366. X
  367. X    //
  368. X    // They subscribe - remove them.
  369. X    //
  370. X    if (fseek(mailfp, 0, SEEK_SET))  // seek to beginning of file
  371. X        error("file %s, line %d, fseek() failed", __FILE__, __LINE__);
  372. X
  373. X    //
  374. X    // First, build  a tmp file for new maillist.
  375. X    //
  376. X    char *tmpfile = new char[strlen(mail_list_prefix()) + 12];
  377. X    (void)sprintf(tmpfile, "%s.%d", mail_list_prefix(), getpid());
  378. X    int tmpfd = open(tmpfile, O_RDWR|O_CREAT, 0644);
  379. X    if (tmpfd < 0)
  380. X        error("file %s, line %d, open() failed", __FILE__, __LINE__);
  381. X    FILE *tmpfp = fdopen(tmpfd, "w+");
  382. X    if (!tmpfp)
  383. X        error("file %s, line %d, fdopen() failed", __FILE__, __LINE__);
  384. X
  385. X    for (char *line = fgetline(tmpfp, 10); line; line = fgetline(tmpfp, 10))
  386. X    {
  387. X        if (strcmp(line, user) == 0)
  388. X        {
  389. X            DELETE line;
  390. X            continue;
  391. X        }
  392. X        (void)fprintf(tmpfp, "%s\n", line);
  393. X        DELETE line;
  394. X    }
  395. X
  396. X    if (feof(tmpfp) && !ferror(tmpfp))
  397. X    {
  398. X        //
  399. X        // Alternate maillist correctly constructed.
  400. X        //
  401. X        (void)fclose(tmpfp); (void)fclose(mailfp);
  402. X        (void) close(tmpfd); (void) close(mailfd);
  403. X        //
  404. X        // Remove old maillist.
  405. X        //
  406. X        String maillist = String(mail_list_prefix()) + "." + CurrentArea();
  407. X        int seqfd = open(sequence_file(), O_RDWR);
  408. X        if (seqfd < 0)
  409. X            error("file %s, line %d, open() on `%s' failed",
  410. X                  __FILE__, __LINE__, sequence_file());
  411. X
  412. X        block_tstp_and_winch(); // block SIGTSTP and WINCH
  413. X        lock_file(seqfd);       // lock our sequence file
  414. X
  415. X        if (unlink((const char *)maillist) < 0)
  416. X            error("file %s, line %d, unlink(%s) failed",
  417. X                  __FILE__, __LINE__, (const char *)maillist);
  418. X        if (rename((const char *)tmpfile, (const char *)maillist) < 0)
  419. X            error("file %s, line %d, rename(%s, %s) failed", __FILE__,
  420. X                  __LINE__, (const char *)tmpfile, (const char *)maillist);
  421. X
  422. X        unlock_file(seqfd);
  423. X        (void)close(seqfd);
  424. X        unblock_tstp_and_winch();
  425. X
  426. X        DELETE tmpfile;
  427. X
  428. X        message("You've been unsubscribed from the `%' area ", CurrentArea());
  429. X    }
  430. X    else
  431. X    {
  432. X        (void)fclose(tmpfp);
  433. X        (void)fclose(mailfp);
  434. X        (void) close(tmpfd);
  435. X        (void) close(mailfd);
  436. X        ding();
  437. X        message("Problem updating maillist -- update not committed ");
  438. X    }
  439. X    sleep(2);
  440. X}
  441. X
  442. X/*
  443. X** examine_problem - pages through a problem in current area.  Returns one
  444. X**                   if the problem exists, otherwise zero.  If `number\' is
  445. X**                   nonzero \(it defaults to zero\), we use that number
  446. X**                   instead of prompting for one.
  447. X*/
  448. X
  449. Xint examine_problem(const char *number)
  450. X{
  451. X    if (!database_exists())
  452. X    {
  453. X        ding();
  454. X        message("There is no database for problem area `%' ", CurrentArea());
  455. X        if (!number) sleep(2);
  456. X        return 0;
  457. X    }
  458. X
  459. X    datum key;
  460. X    key.dptr = number ? (char *) number : prompt("Problem # --> ",
  461. X                                                 redisplay_commands); 
  462. X    key.dsize = int(strlen(key.dptr)) + 1;
  463. X    open_database(GDBM_READER);
  464. X    datum data = gdbm_fetch(GdbmFile, key);
  465. X    gdbm_close(GdbmFile);
  466. X
  467. X    if (!data.dptr)
  468. X    {
  469. X        ding();
  470. X        message("There is no problem # % ", key.dptr);
  471. X        if (!number)
  472. X        {
  473. X            DELETE key.dptr;
  474. X            sleep(2);
  475. X        }
  476. X        return 0;
  477. X    }
  478. X
  479. X    //
  480. X    // Build tmp file to pass to pager.
  481. X    //
  482. X    const char *file = temporary_file();
  483. X
  484. X    //
  485. X    // Write the problem to the file.
  486. X    //
  487. X    int fd;
  488. X    if ((fd = open(file, O_RDWR)) < 0) 
  489. X        error("file %s, line %d, open(%s, O_RDONLY) failed",
  490. X              __FILE__, __LINE__, file);
  491. X    if (write(fd, data.dptr, data.dsize - 1) != data.dsize - 1)
  492. X        error("file %s, line %d, write() failed", __FILE__, __LINE__);
  493. X
  494. X    const char *prefix = "-PProblem # ";
  495. X    const char *suffix = " -- line %lb of %L ?e(END) .-- q (quit) H (help)";
  496. X    String prompt = String(prefix) + key.dptr + suffix;
  497. X    const char *less = "less";
  498. X    const char *args[5];
  499. X    args[0] = less;   args[1] = "-d";
  500. X    args[2] = prompt; args[3] = file; args[4] = 0;
  501. X
  502. X    clear_display();
  503. X    synch_display();
  504. X
  505. X    if (!execute(less, args))
  506. X    {
  507. X        (void)unlink(file);
  508. X        error("file %s, line %d, problem running `less'", __FILE__, __LINE__);
  509. X    }
  510. X
  511. X    (void)close(fd);
  512. X    (void)unlink(file);
  513. X    update_modeline();  // redisplay previous modeline
  514. X    free(data.dptr);
  515. X    if (!number) DELETE key.dptr;
  516. X
  517. X    return 1;  // must update screen
  518. X}
  519. X
  520. X/*
  521. X** summary_info - returns a string in new\'d space of the form:
  522. X**
  523. X**                     prob# status severity last-activity-date summary
  524. X**
  525. X**                sort_by_date\(\) "knows" the format of the lines output
  526. X**                by this function, so if you change the format, you need
  527. X**                to change sort_by_date\(\) also.
  528. X*/
  529. X
  530. Xchar *summary_info(datum &data)
  531. X{
  532. X    const char *tail = data.dptr;
  533. X
  534. X    //
  535. X    // Updated is Fields\[4\]
  536. X    //
  537. X    for (int i = 0; i < 4; i++)
  538. X    {
  539. X        tail = strchr(tail, '\n');
  540. X        tail += 1; // step past the newline
  541. X    }
  542. X    tail += max_field_length() + 1; // this is the Updated line
  543. X    const char *updated = tail + 4; // don\'t output the day of the week
  544. X    tail = strchr(tail, '\n');      // end of Updated line
  545. X    int updatedLen = tail - updated;
  546. X
  547. X    //
  548. X    // Keywords is Fields\[5\] - just skip over it for now
  549. X    //
  550. X    tail += 1; // step past the newline
  551. X    tail = strchr(tail, '\n');      // end of Keywords line
  552. X
  553. X    //
  554. X    // Summary is Fields\[6\]
  555. X    //
  556. X    tail += 1; // step past the newline
  557. X    tail += max_field_length() + 1; // this is the Summary line
  558. X    const char *summary = tail;
  559. X    tail = strchr(tail, '\n');      // end of Summary line
  560. X    int summaryLen = tail - summary;
  561. X
  562. X    //
  563. X    // Status Field\[7\]
  564. X    //
  565. X    tail += 1; // step past the newline
  566. X    tail += max_field_length() + 1; // this is the Status line
  567. X    const char *status = tail;
  568. X    tail = strchr(tail, '\n');      // end of Status line
  569. X    int statusLen = tail - status;
  570. X
  571. X    //
  572. X    // Severity is Field\[9\]
  573. X    //
  574. X    tail += 1; // step past the newline
  575. X    tail = strchr(tail, '\n');      // end of Site line
  576. X    tail += 1;                      // step past the newline
  577. X    tail += max_field_length() + 1; // this is the Severity line
  578. X    const char severity = *tail;
  579. X    tail = strchr(tail, '\n');      // end of Severity line
  580. X
  581. X    //
  582. X    // Problem # is Fields\[10\]
  583. X    //
  584. X    tail += 1; // step past the newline
  585. X    tail += max_field_length() + 1; // this is the prob # line
  586. X    const char *number = tail;
  587. X    //
  588. X    // Find the end of the problem # field - probably contains a space.
  589. X    //
  590. X    tail = strchr(tail, '\n');
  591. X    while (*(tail - 1) == ' ') tail--; // strip off trailing spaces
  592. X    int numberLen = tail - number;
  593. X
  594. X    const int numberFieldLen = 5;  // min width of Prob # field in summary
  595. X    int len = numberLen > numberFieldLen ? numberLen : numberFieldLen;
  596. X    char *line = new char[updatedLen + summaryLen + statusLen + len + 4];
  597. X
  598. X
  599. X    *line = 0; // stringify
  600. X
  601. X    if (numberLen < numberFieldLen) 
  602. X    {
  603. X        //
  604. X        // Pad the length to numberFieldLen by prepending spaces.
  605. X        //
  606. X        int nSpaces = numberFieldLen - numberLen;
  607. X        for (int i = 0; i < nSpaces; i++)
  608. X            *(line + i) = ' ';
  609. X        *(line + nSpaces) = 0;  // restringify
  610. X    }
  611. X        
  612. X    (void)strncat(line, number, numberLen);
  613. X    line[len] = 0;
  614. X    (void)strcat(line, " ");
  615. X    (void)strncat(line, status, statusLen);
  616. X    line[len += statusLen + 1] = 0;
  617. X
  618. X    // if status == "open", output the severity also
  619. X    if (line[len - 1] == ' ') line[len - 1] = severity;
  620. X
  621. X    (void)strcat(line, " ");
  622. X    (void)strncat(line, updated, updatedLen);
  623. X    line[len += updatedLen + 1] = 0;
  624. X    (void)strcat(line, " ");
  625. X    (void)strncat(line, summary, summaryLen);
  626. X    line[len += summaryLen + 1] = 0;
  627. X
  628. X    return line;
  629. X}
  630. X
  631. X/*
  632. X** lsign - returns -1, 0 or 1 depending on the sign of the argument
  633. X*/
  634. X
  635. Xinline int lsign(long arg)
  636. X{
  637. X    return arg == 0 ? 0 : (arg > 0 ? 1 : -1);
  638. X}
  639. X
  640. X/*
  641. X** sort_by_date - the comparison function passed to qsort\(3\) used when
  642. X**                sorting listing lines by date.
  643. X*/
  644. X
  645. Xstatic int sort_by_date(const void *a, const void *b)
  646. X{
  647. X    char *tmpa = *(char **)a;
  648. X    char *tmpb = *(char **)b;
  649. X
  650. X    while (*tmpa == ' ') tmpa++;   // step over any spaces preceding Prob #
  651. X    while (*tmpb == ' ') tmpb++;   // ditto
  652. X
  653. X    tmpa = strchr(tmpa, ' ') + 1;  // step onto first character of Status
  654. X    tmpb = strchr(tmpb, ' ') + 1;  // ditto
  655. X
  656. X    if (strncmp(tmpa, tmpb, 4))
  657. X        return -strncmp(tmpa, tmpb, 4); // sort "open" before "closed"
  658. X    else if (strncmp(tmpa, tmpb, 6))
  659. X        return strncmp(tmpa, tmpb, 6);  // sort by severity
  660. X    else
  661. X    {
  662. X        // lastly, sort by most recent first
  663. X        tmpa += 7;
  664. X        tmpb += 7;
  665. X        return lsign(seconds_in_date(tmpb) - seconds_in_date(tmpa));
  666. X    }
  667. X}
  668. X
  669. X/*
  670. X** view_summary_lines - page through data from problem headers in current area.
  671. X**                      Returns one if the database exists, else zero.
  672. X*/
  673. X
  674. Xstatic int view_summary_lines()
  675. X{
  676. X    if (!database_exists())
  677. X    {
  678. X        ding();
  679. X        message("There is no database for problem area `%' ", CurrentArea());
  680. X        sleep(2);
  681. X        return 0;
  682. X    }
  683. X
  684. X    open_database(GDBM_READER);
  685. X    datum key = gdbm_firstkey(GdbmFile);
  686. X    if (!key.dptr)
  687. X    {
  688. X        const char *fmt = "Area `%s' appears to be empty ";
  689. X        char *msg = new char[strlen(fmt) + strlen(CurrentArea()) - 1];
  690. X        (void)sprintf(msg, fmt, CurrentArea());
  691. X        ding();
  692. X        message(msg);
  693. X        sleep(2);
  694. X        DELETE msg;
  695. X        gdbm_close(GdbmFile);
  696. X        return 0;
  697. X    }
  698. X
  699. X    DList summaryLines;  // listing of problem summaries
  700. X    datum data, tmp;
  701. X    const int chunksize = 100;
  702. X    int size = chunksize;
  703. X    int pos = 0;
  704. X    char **lines = new char*[size];
  705. X
  706. X    message("Reading problems ... ");
  707. X    while (1)
  708. X    {
  709. X        data = gdbm_fetch(GdbmFile, key);
  710. X        lines[pos++] = summary_info(data);
  711. X        if (pos == size)
  712. X        {
  713. X            // grow lines
  714. X            char **newspace = new char*[size += chunksize];
  715. X            for (int i = 0; i < pos; i++) newspace[i] = lines[i];
  716. X            DELETE lines;
  717. X            lines = newspace;
  718. X        }
  719. X        free(data.dptr);
  720. X        tmp = gdbm_nextkey(GdbmFile, key);
  721. X        free(key.dptr);
  722. X        key = tmp;
  723. X        if (!key.dptr) break;
  724. X    }
  725. X    gdbm_close(GdbmFile);
  726. X    message("Reading problems ... done");
  727. X
  728. X    //
  729. X    // Sort lines "most recently updated" first.
  730. X    //
  731. X    qsort(lines, pos, sizeof(char**), sort_by_date);
  732. X    for (int i = 0; i < pos; i++)
  733. X        summaryLines.add(new DLink((char **)&lines[i]));
  734. X
  735. X    DELETE lines;
  736. X
  737. X    initialize_lister(&summaryLines);
  738. X    initial_listing(&summaryLines);
  739. X
  740. X    const char *fmt = "%s ---- q (quit) H (help)";
  741. X    char *suffix = new char[strlen(fmt) + strlen(CurrentArea()) - 1];
  742. X    (void)sprintf(suffix, fmt, CurrentArea());
  743. X    update_modeline(ModelinePrefix, suffix);
  744. X
  745. X    DELETE suffix;
  746. X
  747. X    summaryLines.saveYXPos(0, goal_column(&summaryLines));
  748. X    if (summaryLines.currLine()->length() > columns())    
  749. X        leftshift_current_line(&summaryLines);
  750. X    else
  751. X        move_cursor(summaryLines.savedYPos(), summaryLines.savedXPos());
  752. X    synch_display();
  753. X
  754. X    lister_cmd_loop(&summaryLines);
  755. X
  756. X    return 1;
  757. X}
  758. X
  759. X/*
  760. X** close_problem - close an existing problem in current area.  Returns one if
  761. X**                 we did the close, zero otherwise.  Only a database
  762. X**                 administrator or the original logger can close a problem.
  763. X**                 `number\', which defaults to null, is used if not null,
  764. X**                 instead of prompting for the number.  Also, only an open
  765. X**                 problem can be closed.
  766. X*/
  767. X
  768. Xint close_problem(const char *number)
  769. X{
  770. X    if (!database_exists())
  771. X    {
  772. X        ding();
  773. X        message("There is no database for problem area `%' ", CurrentArea());
  774. X        if (!number) sleep(2);
  775. X        return 0;
  776. X    }
  777. X    datum key;
  778. X    key.dptr  = number ? (char *) number : prompt("Problem # --> ",
  779. X                                                  redisplay_commands);
  780. X    key.dsize = int(strlen(key.dptr)) + 1;
  781. X
  782. X    open_database(GDBM_READER);
  783. X    datum data = gdbm_fetch(GdbmFile, key);
  784. X    gdbm_close(GdbmFile);
  785. X
  786. X    if (!data.dptr)
  787. X    {
  788. X        ding();
  789. X        message("There is no problem # % ", key.dptr);
  790. X        if (!number)
  791. X        {
  792. X            DELETE key.dptr;
  793. X            sleep(2);
  794. X        }
  795. X        return 0;
  796. X    }
  797. X
  798. X    //
  799. X    // Are we authorized to close the problem?
  800. X    //
  801. X    char *tmp = strchr(data.dptr, '\n');  // logger is Fields\[1\]
  802. X    tmp += 1; // step past the newline
  803. X    tmp += max_field_length() + 1;        // the logger
  804. X    int llen  = strchr(tmp, '\n') - tmp;  // # of chars in logger
  805. X    const char *user = username();
  806. X
  807. X    if ((llen != strlen(user) || strncmp(user, tmp, llen)) &&
  808. X        getuid() != geteuid())
  809. X    {
  810. X        ding();
  811. X        message("You're not authorized to close problem # % ", key.dptr);
  812. X        if (!number)
  813. X        {
  814. X            DELETE key.dptr;
  815. X            sleep(2);
  816. X        }
  817. X        free(data.dptr);
  818. X        return 0;
  819. X    }
  820. X
  821. X    //
  822. X    // Is the problem open?
  823. X    //
  824. X    for (int i = 0; i < 6; i++)
  825. X    {
  826. X        // status is Fields\[7\]
  827. X        tmp = strchr(tmp, '\n');
  828. X        tmp += 1;  // step past newline
  829. X    }
  830. X    if (strncmp(tmp + max_field_length() + 1, "open", 4))
  831. X    {
  832. X        ding();
  833. X        message("Only open problems can be closed ");
  834. X        if (!number)
  835. X        {
  836. X            DELETE key.dptr;
  837. X            sleep(2);
  838. X        }
  839. X        free(data.dptr);
  840. X        return 0;
  841. X    }
  842. X
  843. X    update_existing_problem(data, key, CLOSED);
  844. X    update_modeline();  // redisplay previous modeline
  845. X    free(data.dptr);
  846. X    if (!number) DELETE key.dptr;
  847. X
  848. X    return 1;
  849. X}
  850. X
  851. X/*
  852. X** reopen_problem - reopen a closed problem in current area.  Returns one if
  853. X**                  we did the close, zero otherwise.  Only a database
  854. X**                  administrator or the original logger can reopen a problem.
  855. X**                  `number\', which defaults to null, is used if not null,
  856. X**                  instead of prompting for the number.
  857. X*/
  858. X
  859. Xint reopen_problem(const char *number)
  860. X{
  861. X    if (!database_exists())
  862. X    {
  863. X        ding();
  864. X        message("There is no database for problem area `%' ", CurrentArea());
  865. X        if (!number) sleep(2);
  866. X        return 0;
  867. X    }
  868. X
  869. X    datum key;
  870. X    key.dptr  = number ? (char *) number : prompt("Problem # --> ",
  871. X                                                  redisplay_commands);
  872. X    key.dsize = int(strlen(key.dptr)) + 1;
  873. X
  874. X    open_database(GDBM_READER);
  875. X    datum data = gdbm_fetch(GdbmFile, key);
  876. X    gdbm_close(GdbmFile);
  877. X
  878. X    if (!data.dptr)
  879. X    {
  880. X        ding();
  881. X        message("There is no problem # % ", key.dptr);
  882. X        if (!number)
  883. X        {
  884. X            DELETE key.dptr;
  885. X            sleep(2);
  886. X        }
  887. X        return 0;
  888. X    }
  889. X
  890. X    //
  891. X    // Are we authorized to reopen the problem?
  892. X    //
  893. X    char *tmp = strchr(data.dptr, '\n');  // logger is Fields\[1\]
  894. X    tmp += 1; // step past the newline
  895. X    tmp += max_field_length() + 1;        // the logger
  896. X    int llen  = strchr(tmp, '\n') - tmp;  // # of chars in logger
  897. X    const char *user = username();
  898. X
  899. X    if ((llen != strlen(user) || strncmp(user, tmp, llen)) &&
  900. X        getuid() != geteuid())
  901. X    {
  902. X        ding();
  903. X        message("You're not authorized to close problem # % ", key.dptr);
  904. X        if (!number)
  905. X        {
  906. X            DELETE key.dptr;
  907. X            sleep(2);
  908. X        }
  909. X        free(data.dptr);
  910. X        return 0;
  911. X    }
  912. X
  913. X    //
  914. X    // Only closed problems can be opened.
  915. X    //
  916. X    for (int i = 0; i < 6; i++)
  917. X    {
  918. X        // status is Fields\[7\]
  919. X        tmp = strchr(tmp, '\n');
  920. X        tmp += 1;  // step past newline
  921. X    }
  922. X    if (strncmp(tmp + max_field_length() + 1, "closed", 6))
  923. X    {
  924. X        ding();
  925. X        message("Only closed problems can be reopened ");
  926. X        if (!number)
  927. X        {
  928. X            DELETE key.dptr;
  929. X            sleep(2);
  930. X        }
  931. X        free(data.dptr);
  932. X        return 0;
  933. X    }
  934. X
  935. X    update_existing_problem(data, key, REOPENED);
  936. X    update_modeline();  // redisplay previous modeline
  937. X    free(data.dptr);
  938. X    if (!number) DELETE key.dptr;
  939. X
  940. X    return 1;
  941. X}
  942. X
  943. X/*
  944. X** delete_problem - delete the problem from current area.
  945. X**                  This is strictly for the database administrators.
  946. X**                  If number is non-null, use it instead of prompting
  947. X**                  for the problem number.  Returns 1 if the problem
  948. X**                  was deleted, 0 otherwise.
  949. X*/
  950. X
  951. Xint delete_problem(const char *number)
  952. X{
  953. X    int del = 0;  // was delete successful?
  954. X
  955. X    if (getuid() != geteuid())
  956. X    {
  957. X        ding();
  958. X        message("Only database administrators can to delete problems ");
  959. X        if (!number) sleep(2);
  960. X        return 0;
  961. X    }
  962. X
  963. X    if (!database_exists())
  964. X    {
  965. X        ding();
  966. X        message("There is no database for problem area `%' ", CurrentArea());
  967. X        if (!number) sleep(2);
  968. X        return 0;
  969. X    }
  970. X
  971. X    datum key;
  972. X    key.dptr  = number ? (char *) number : prompt("Problem # --> ",
  973. X                                                  redisplay_commands);
  974. X    key.dsize = int(strlen(key.dptr)) + 1;
  975. X
  976. X    open_database(GDBM_READER);
  977. X    datum data = gdbm_fetch(GdbmFile, key);
  978. X    gdbm_close(GdbmFile);
  979. X
  980. X    if (!data.dptr)
  981. X    {
  982. X        ding();
  983. X        message("There is no problem # % ", key.dptr);
  984. X        if (!number)
  985. X        {
  986. X            DELETE key.dptr;
  987. X            sleep(2);
  988. X        }
  989. X        return 0;
  990. X    }
  991. X
  992. X    //
  993. X    // The problem exists; delete it.
  994. X    //
  995. X    const char *msg = "Do you really want to delete this problem (n|y)? ";
  996. X    if (del = yes_or_no(msg, redisplay_commands, No, 0))
  997. X        update_database(key, data, DELETED);
  998. X    free(data.dptr);
  999. X    if (!number) DELETE key.dptr;
  1000. X
  1001. X    return del;
  1002. X}
  1003. X
  1004. X/*
  1005. X** reorganize_database - reorganize the database for current area
  1006. X*/
  1007. X
  1008. Xvoid reorganize_database(int dodelay)
  1009. X{
  1010. X    if (getuid() != geteuid())
  1011. X    {
  1012. X        ding();
  1013. X        message("Only database administrators can reorganize the database ");
  1014. X        if (dodelay) sleep(2);
  1015. X        return;
  1016. X    }
  1017. X    if (!database_exists())
  1018. X    {
  1019. X        ding();
  1020. X        message("There is no database for problem area `%' ", CurrentArea());
  1021. X        if (dodelay) sleep(2);
  1022. X        return;
  1023. X    }
  1024. X
  1025. X    const char *msg = "Do you really want to reorganize this database (n|y)? ";
  1026. X    if (yes_or_no(msg, redisplay_commands, No, 0))
  1027. X    {
  1028. X        datum key, data;  // just placeholders here
  1029. X        update_database(key, data, REORGANIZED);
  1030. X    }
  1031. X}
  1032. X
  1033. X/*
  1034. X** search - prompt for pattern \(regexp\) and display the matches.
  1035. X**          Returns zero if we only need to redisplay
  1036. X**          the prompt, else returns one.
  1037. X*/
  1038. X
  1039. X//
  1040. X// Ways to search -- over full problem text or just the header
  1041. X//
  1042. Xenum SearchMethod { FULLTEXT, HEADER };
  1043. X
  1044. Xstatic int search(const SearchMethod how)
  1045. X{
  1046. X    if (!database_exists())
  1047. X    {
  1048. X        ding();
  1049. X        message("There is no database for problem area `%' ", CurrentArea());
  1050. X        sleep(2);
  1051. X        return 0;
  1052. X    }
  1053. X
  1054. X    char *keywords = prompt("Search for --> ", redisplay_commands);
  1055. X    const char *separators = " ,\t";
  1056. X    const char **list = tokenize(keywords, separators);
  1057. X    DELETE keywords;
  1058. X
  1059. X    if (!list[0]) return 0;  // no words to search for
  1060. X
  1061. X    //
  1062. X    // Build a regular expression to search for.
  1063. X    // We want to build a pattern of the form:
  1064. X    //
  1065. X    //   word1|word2|word3|word4 ...
  1066. X    //
  1067. X    int len = 0;  // total length of all words in `list\'
  1068. X    for (int i = 0; list[i]; i++)
  1069. X        len += (int) strlen(list[i]) + 1; // plus one for the `|\'
  1070. X    char *line = new char[len + 1];
  1071. X    line[0] = 0;  // make it into a valid string
  1072. X
  1073. X    //
  1074. X    // Are each of the words individually a valid regexp?
  1075. X    //
  1076. X    regexp *regex;
  1077. X    for (i = 0; list[i]; i++)
  1078. X    {
  1079. X        if ((regex = regcomp(list[i])) == 0)
  1080. X        {
  1081. X            ding();
  1082. X            const char *fmt = "`%s' isn't a valid regex: %s , skipping ... ";
  1083. X            char *msg = new char[strlen(fmt) + strlen(list[i]) +
  1084. X                                 strlen(REerror) - 3];
  1085. X            (void)sprintf(msg, fmt, list[i], REerror);
  1086. X            message(msg);
  1087. X            sleep(2);
  1088. X            DELETE msg;
  1089. X            DELETE line;
  1090. X        }
  1091. X        else
  1092. X        {
  1093. X            (void)strcat(line, list[i]);
  1094. X            (void)strcat(line, "|");
  1095. X        }
  1096. X        DELETE (char *) regex;
  1097. X    }
  1098. X
  1099. X    //
  1100. X    // Remove any trailing `|\'s.
  1101. X    //
  1102. X    if (line[strlen(line) - 1] == '|') line[strlen(line) - 1] = 0;
  1103. X    if (strlen(line) == 0)
  1104. X    {
  1105. X        ding();
  1106. X        message("No valid regular expressions among your keywords ");
  1107. X        sleep(2);
  1108. X        DELETE line;
  1109. X        return 0;
  1110. X    }
  1111. X    if ((regex = regcomp(line)) == 0)
  1112. X    {
  1113. X        ding();
  1114. X        message("regcomp() failed: %s", REerror);
  1115. X        sleep(2);
  1116. X        DELETE line;
  1117. X        return 0;
  1118. X    }
  1119. X
  1120. X    open_database(GDBM_READER);
  1121. X    datum key = gdbm_firstkey(GdbmFile);
  1122. X    if (!key.dptr)
  1123. X    {
  1124. X        const char *fmt = "Area `%s' appears to be empty ";
  1125. X        char *msg = new char[strlen(fmt) + strlen(CurrentArea()) - 1];
  1126. X        (void)sprintf(msg, fmt, CurrentArea());
  1127. X        ding();
  1128. X        message(msg);
  1129. X        sleep(2);
  1130. X        DELETE msg;
  1131. X        DELETE line;
  1132. X        DELETE (char *) regex;
  1133. X        return 0;
  1134. X    }
  1135. X
  1136. X    DList summaryLines;  // listing of problem summaries
  1137. X    datum data, tmp;
  1138. X    const int chunksize = 100;
  1139. X    int size = chunksize, pos = 0;
  1140. X    char **lines = new char*[size];
  1141. X
  1142. X    message("Reading problems ... ");
  1143. X
  1144. X    while (1)
  1145. X    {
  1146. X        data = gdbm_fetch(GdbmFile, key);
  1147. X        switch (how)
  1148. X        {
  1149. X          case HEADER:
  1150. X          {
  1151. X              //
  1152. X              // Search only the problem header.
  1153. X              //
  1154. X              char *tail = data.dptr;
  1155. X              for (int i = 0; i < NFields(); i++)
  1156. X              {
  1157. X                  tail = strchr(tail, '\n');
  1158. X                  tail += 1; // step past the newline
  1159. X              }
  1160. X              *tail = 0;     // treat the header as one long string
  1161. X              if (regexec(regex, data.dptr)) lines[pos++] = summary_info(data);
  1162. X          }
  1163. X              break;
  1164. X          case FULLTEXT:
  1165. X              //
  1166. X              // Search over full problem text.
  1167. X              //
  1168. X              if (regexec(regex, data.dptr)) lines[pos++] = summary_info(data);
  1169. X              break;
  1170. X          default: error("file %s, line %d, illegal case in switch()",
  1171. X                           __FILE__, __LINE__);
  1172. X        }
  1173. X        if (pos == size)
  1174. X        {
  1175. X            // grow \'lines\'
  1176. X            char **newspace = new char*[size += chunksize];
  1177. X            for (int i = 0; i < pos; i++) newspace[i] = lines[i];
  1178. X            DELETE lines;
  1179. X            lines = newspace;
  1180. X        }
  1181. X        free(data.dptr);
  1182. X        tmp = gdbm_nextkey(GdbmFile, key);
  1183. X        free(key.dptr);
  1184. X        key = tmp;
  1185. X        if (!key.dptr) break;
  1186. X    }
  1187. X    gdbm_close(GdbmFile);
  1188. X    message("Reading problems ... done");
  1189. X    DELETE (char *) regex;
  1190. X
  1191. X    //
  1192. X    // sort lines "most recently updated" first
  1193. X    //
  1194. X    qsort(lines, pos, sizeof(char**), sort_by_date);
  1195. X    for (i = 0; i < pos; i++)
  1196. X        summaryLines.add(new DLink((char **)&lines[i]));
  1197. X    delete lines;
  1198. X
  1199. X    //
  1200. X    // Are there any problem summaries to peruse?
  1201. X    //
  1202. X    if (!summaryLines.nelems())
  1203. X    {
  1204. X         ding();
  1205. X         message("No matches for regex `%' ", line);
  1206. X         sleep(2);
  1207. X         delete line;
  1208. X         return 0;
  1209. X     }
  1210. X
  1211. X    initialize_lister(&summaryLines);
  1212. X    initial_listing(&summaryLines);
  1213. X
  1214. X    char *fmt    = "%s (regex: %s) ---- q (quit) H (help)";
  1215. X    char *suffix = new char[strlen(fmt) + strlen(CurrentArea()) +
  1216. X                            strlen(line) - 3];
  1217. X
  1218. X    (void)sprintf(suffix, fmt, CurrentArea(), line);
  1219. X    update_modeline(ModelinePrefix, suffix);
  1220. X    delete suffix;
  1221. X    delete line;
  1222. X    summaryLines.saveYXPos(0, goal_column(&summaryLines));
  1223. X    if (summaryLines.currLine()->length() > columns())    
  1224. X        leftshift_current_line(&summaryLines);
  1225. X    else
  1226. X        move_cursor(summaryLines.savedYPos(), summaryLines.savedXPos());
  1227. X    synch_display();
  1228. X    lister_cmd_loop(&summaryLines);
  1229. X
  1230. X    return 1;
  1231. X}
  1232. X
  1233. X//
  1234. X// the header data -- needs to be shared between display_header\(\) and 
  1235. X//                    modify_keywords\(\) so that we can call prompt\(\)
  1236. X//                    using display_header as its second argument.
  1237. Xstatic datum header_data;
  1238. X
  1239. X/*
  1240. X** display_header - put up the header of the problem in `data\'.
  1241. X*/
  1242. X
  1243. Xstatic void display_header()
  1244. X{
  1245. X    cursor_home();
  1246. X    enter_standout_mode();
  1247. X    for (int i = 0; i < NFields() && i < rows() - 2; i++)
  1248. X    {
  1249. X        clear_to_end_of_line();
  1250. X        display_string(Fields[i]);
  1251. X    }
  1252. X    end_standout_mode();
  1253. X
  1254. X    int flen   = max_field_length() + 1;
  1255. X    char *tmp1 = header_data.dptr, *tmp2;
  1256. X
  1257. X    for (i = 0; i < NFields() && i < rows() - 2; i++)
  1258. X    {
  1259. X        tmp2  = strchr(tmp1, '\n');
  1260. X        *tmp2 = 0;        // stringify
  1261. X        move_cursor(i, flen);
  1262. X        display_string(tmp1 + flen);
  1263. X        *tmp2 = '\n';     // un-stringify
  1264. X        tmp1  = tmp2 + 1; // step past newline to next Field
  1265. X    }
  1266. X
  1267. X    // clear any remaining potentially dirty lines
  1268. X    for (; i < rows() - 2; i++) { clear_to_end_of_line(); cursor_wrap(); }
  1269. X}
  1270. X
  1271. X/*
  1272. X** modify_keywords - allows the problem owner or the administrator
  1273. X**                   to change the Keyword field.
  1274. X*/
  1275. X
  1276. Xint modify_keywords(const char *number)
  1277. X{
  1278. X    if (!database_exists())
  1279. X    {
  1280. X        ding();
  1281. X        message("There is no database for problem area `%' ", CurrentArea());
  1282. X        if (!number) sleep(2);
  1283. X        return 0;
  1284. X    }
  1285. X    datum key;
  1286. X    key.dptr  = number ? (char *) number : prompt("Problem # --> ",
  1287. X                                                  redisplay_commands);
  1288. X    key.dsize = int(strlen(key.dptr)) + 1;
  1289. X
  1290. X    open_database(GDBM_READER);
  1291. X    datum data = gdbm_fetch(GdbmFile, key);
  1292. X    gdbm_close(GdbmFile);
  1293. X
  1294. X    if (!data.dptr)
  1295. X    {
  1296. X        ding();
  1297. X        message("There is no problem # % ", key.dptr);
  1298. X        if (!number) { delete key.dptr; sleep(2); }
  1299. X        return 0;
  1300. X    }
  1301. X    header_data = data;
  1302. X
  1303. X    //
  1304. X    // Are we authorized to modify the problem\'s keywords?
  1305. X    //
  1306. X    char *tmp = strchr(data.dptr, '\n');  // logger is Fields\[1\]
  1307. X    tmp += 1; // step past the newline
  1308. X    tmp += max_field_length() + 1;        // the logger
  1309. X    int llen  = strchr(tmp, '\n') - tmp;  // # of chars in logger
  1310. X    const char *user = username();
  1311. X
  1312. X    if ((llen != strlen(user) || strncmp(user, tmp, llen)) &&
  1313. X        getuid() != geteuid())
  1314. X    {
  1315. X        ding();
  1316. X        message("You're not authorized to modify problem # %'s keywords ",
  1317. X                key.dptr);
  1318. X        if (!number) { delete key.dptr; sleep(2); }
  1319. X        return 0;
  1320. X    }
  1321. X
  1322. X    //
  1323. X    // Put up problem header.
  1324. X    //
  1325. X    const char *fmt = "%s  # %s (modifying keywords)";
  1326. X    String suffix = String(CurrentArea()) + "  # " + key.dptr +
  1327. X                    " (modifying keywords)";
  1328. X    String old_modeline(current_modeline);
  1329. X
  1330. X    update_modeline(ModelinePrefix, suffix);
  1331. X    display_header();
  1332. X
  1333. X    char *newkeywords = prompt("New keyword field --> ", display_header);
  1334. X
  1335. X    //
  1336. X    // Make new data datum.
  1337. X    //
  1338. X    datum newdata;
  1339. X    tmp = data.dptr;
  1340. X
  1341. X    //
  1342. X    // Find old Keyword field -- Fields\[5\].
  1343. X    //
  1344. X    for (int i = 0; i < 5; i++)
  1345. X    {
  1346. X        tmp  = strchr(tmp, '\n');
  1347. X        tmp += 1;  // step past newline
  1348. X    }
  1349. X    tmp += max_field_length() + 1;  // the Keywords field data
  1350. X    newdata.dsize = data.dsize + (int)strlen(newkeywords) -
  1351. X                    (strchr(tmp, '\n') - tmp);
  1352. X    newdata.dptr = new char[newdata.dsize];
  1353. X    *tmp++ = 0;  // stringify data.dptr
  1354. X    (void)strcpy(newdata.dptr, data.dptr);    // data preceding Keywords field
  1355. X    (void)strcat(newdata.dptr, newkeywords);  // new keywords
  1356. X    (void)strcat(newdata.dptr, strchr(tmp, '\n'));  // following data
  1357. X    free(data.dptr);
  1358. X
  1359. X    update_existing_problem(newdata, key, KEYWORDMOD);
  1360. X    update_modeline(old_modeline);  // redisplay previous modeline
  1361. X    if (!number) delete key.dptr;
  1362. X    delete newdata.dptr;
  1363. X    return 1;
  1364. X}
  1365. X
  1366. X/*
  1367. X** problem - examine the `area\' in problem
  1368. X*/
  1369. X
  1370. Xstatic void problem(const char *area)
  1371. X{
  1372. X    const char *the_area = is_area(area) ? area : choose_problem_area();
  1373. X    const char *helpmsg  = "The Available Commands:";
  1374. X    setCurrentArea(the_area);
  1375. X    commands_screen();
  1376. X    String suffix = String(CurrentArea()) + " ---- q (quit) H (help)";
  1377. X
  1378. X    update_modeline(ModelinePrefix, suffix);
  1379. X    message("Your Choice --> ");
  1380. X
  1381. X    char key;
  1382. X    char refresh = 1; // need to redisplay command list?
  1383. X    char redomsg = 1; // need to redisplay the message?
  1384. X    while (1)
  1385. X    {
  1386. X        if (resumingAfterSuspension ||
  1387. X#ifdef SIGWINCH
  1388. X            windowSizeChanged       ||
  1389. X#endif
  1390. X            read(0, &key, 1) < 0    || // assume only fails when errno==EINTR 
  1391. X            key == KEY_CTL_L)
  1392. X        {
  1393. X#ifdef SIGWINCH
  1394. X            if (windowSizeChanged) { windowSizeChanged = 0; adjust_window(); }
  1395. X#endif
  1396. X            resumingAfterSuspension = 0;
  1397. X            redisplay_commands();
  1398. X            refresh = 0;
  1399. X        }
  1400. X        else
  1401. X            switch(key)
  1402. X            {
  1403. X              case KEY_l:
  1404. X                log_new_problem(); refresh = 1; break;
  1405. X              case KEY_e:
  1406. X                refresh = examine_problem(); break;
  1407. X              case KEY_v:
  1408. X                refresh = view_summary_lines(); break;
  1409. X              case KEY_a:
  1410. X                refresh = append_to_problem(); break;
  1411. X              case KEY_s:
  1412. X                subscribe_to_area(); refresh = 0; break;
  1413. X              case KEY_u:
  1414. X                unsubscribe_from_area(); refresh = 0; break;
  1415. X              case KEY_c:
  1416. X                refresh = close_problem(); break;
  1417. X              case KEY_k:
  1418. X                refresh = search(HEADER); break;
  1419. X              case KEY_K:
  1420. X                refresh = search(FULLTEXT); break;
  1421. X              case KEY_M:
  1422. X                refresh = modify_keywords(); break;
  1423. X              case KEY_R:
  1424. X                refresh = reopen_problem(); break;
  1425. X              case KEY_d:
  1426. X                (void)delete_problem(); refresh = 0; break;
  1427. X              case KEY_r:
  1428. X                reorganize_database(); refresh = 0; break;
  1429. X              case KEY_q:
  1430. X                return;
  1431. X              case KEY_H: case KEY_QM:
  1432. X                help((const char **)&Commands[0], NCommands(), helpmsg);
  1433. X                refresh = 1; break;
  1434. X              default:
  1435. X                ding(); redomsg = refresh = 0; break;
  1436. X            }
  1437. X        if (refresh)
  1438. X        {
  1439. X            commands_screen();
  1440. X            update_modeline(ModelinePrefix, suffix);
  1441. X        }
  1442. X        if (redomsg) message("Your Choice --> ");
  1443. X        redomsg = 1;
  1444. X    }
  1445. X}
  1446. X
  1447. Xmain(int argc, char *argv[])
  1448. X{
  1449. X    if (!isatty(0) || !isatty(1))
  1450. X    {
  1451. X        (void)fputs("stdin & stdout must be terminals\n", stderr);
  1452. X        exit(1);
  1453. X    }
  1454. X
  1455. X    //
  1456. X    // Usage: problem \[-v\] \[-d\] \[area1\] \[area2\] \[...\]
  1457. X    //
  1458. X    // We only accept the -d flag if it is the first argument.
  1459. X    // It directs us to use a different directory as our HomeBase.
  1460. X    // Regarding problem areas, we just ignore invalid ones.
  1461. X    // The \'-v\' option just prints out the version string and exits.
  1462. X    //
  1463. X    argc--; argv++;
  1464. X    if (argc && strcmp(*argv, "-v") == 0)
  1465. X    {
  1466. X        fputs(Version, stdout);
  1467. X        exit(0);
  1468. X    }
  1469. X    else if (argc && strcmp(*argv, "-d") == 0)
  1470. X    {
  1471. X        argc--; argv++;
  1472. X        if (!argc)
  1473. X        {
  1474. X            fputs("Ignoring `-d' flag - no corresponding directory.", stdout);
  1475. X            sleep(2);
  1476. X        }
  1477. X        else
  1478. X        {
  1479. X            HomeBase = *argv;
  1480. X            argc--; argv++;
  1481. X        }
  1482. X    }
  1483. X
  1484. X    //
  1485. X    // If you don\'t have SIGINTERRUPT then signals almost surely interrupt
  1486. X    // read\(2\).  If you do have it, you\'ll need this to ensure that signals
  1487. X    // interrupt slow system calls -- we\'re only interested in read.
  1488. X    //
  1489. X#ifdef SIGINTERRUPT
  1490. X    if (siginterrupt(SIGTSTP, 1) < 0 || siginterrupt(SIGWINCH, 1) < 0)
  1491. X    {
  1492. X        perror("siginterrupt()"); exit(1);
  1493. X    }
  1494. X#endif
  1495. X
  1496. X    if (umask(022) < 0) { perror("umask()"); exit(1); }
  1497. X
  1498. X    set_new_handler(free_store_exception);
  1499. X    init_display();
  1500. X    set_signals();
  1501. X
  1502. X    if (!database_directory_exists())
  1503. X        error("Database directory `%s' isn't accessible.", HomeBase);
  1504. X
  1505. X    while (1) problem(*argv ? *argv++ : choose_problem_area());
  1506. X}
  1507. END_OF_FILE
  1508. if test 42583 -ne `wc -c <'problem2.C'`; then
  1509.     echo shar: \"'problem2.C'\" unpacked with wrong size!
  1510. fi
  1511. # end of 'problem2.C'
  1512. fi
  1513. echo shar: End of archive 6 \(of 7\).
  1514. cp /dev/null ark6isdone
  1515. MISSING=""
  1516. for I in 1 2 3 4 5 6 7 ; do
  1517.     if test ! -f ark${I}isdone ; then
  1518.     MISSING="${MISSING} ${I}"
  1519.     fi
  1520. done
  1521. if test "${MISSING}" = "" ; then
  1522.     echo You have unpacked all 7 archives.
  1523.     rm -f ark[1-9]isdone
  1524. else
  1525.     echo You still need to unpack the following archives:
  1526.     echo "        " ${MISSING}
  1527. fi
  1528. ##  End of shell archive.
  1529. exit 0
  1530.  
  1531. exit 0 # Just in case...
  1532.