home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1994 March / Source_Code_CD-ROM_Walnut_Creek_March_1994.iso / compsrcs / misc / volume33 / problem1 / part06 < prev    next >
Encoding:
Text File  |  1992-11-12  |  48.4 KB  |  1,752 lines

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