home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1992 #26 / NN_1992_26.iso / spool / comp / sources / misc / 4081 < prev    next >
Encoding:
Text File  |  1992-11-12  |  48.7 KB  |  1,759 lines

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