home *** CD-ROM | disk | FTP | other *** search
/ DP Tool Club 19 / CD_ASCQ_19_010295.iso / vrac / odoors50.zip / EX_VOTE3.C < prev    next >
C/C++ Source or Header  |  1994-09-24  |  44KB  |  1,174 lines

  1. /* EX_VOTE3.C - The files ex_vote1.c thru ex_vote5.c demonstrate the         */
  2. /*              possible steps in the development of a door program using    */
  3. /*              OpenDoors. The EZVote door program allows users to create    */
  4. /*              questions or surveys for other users to respond to. Users    */
  5. /*              are also able to view the results of voting on each topic    */
  6. /*              after having voted on the topic themselves.                  */
  7. /*                                                                           */
  8. /*              To recompile this program, follow the instructions in the    */
  9. /*              OpenDoors manual. Be sure to set your compiler to use the    */
  10. /*              large memory model, and add the ODOORL.LIB file to your      */
  11. /*              project/makefile.                                            */
  12. /*                                                                           */
  13. /*              Each of the ex_vote?.c files shows a further incremental     */
  14. /*              step in the construction of the EZVote door program, as      */
  15. /*              follows:                                                     */
  16. /*                                                                           */
  17. /*              EX_VOTE1.C - Demonstrates the basic elements of any door     */
  18. /*                           program. Contains the code to handle display    */
  19. /*                           of main menu, responding to the user's choice   */
  20. /*                           from the main menu, and common commands such    */
  21. /*                           as returning to the BBS and paging the system   */
  22. /*                           operator. Demonstrates basics of displaying     */
  23. /*                           text and retrieving input from user.            */
  24. /*                                                                           */
  25. /*              EX_VOTE2.C - Adds the user interface code to handle the main */
  26. /*                           menu commands specific to EZVote, such as       */
  27. /*                           answering questions, viewing the results of     */
  28. /*                           questions, and adding new questions.            */
  29. /*                           Demonstrates the use of OpenDoors functions     */
  30. /*                           such as od_input_str() for allowing the user to */
  31. /*                           input a sring of characters, and od_get_key()   */
  32. /*                           for inputting any character from the user. Also */
  33. /*                           introduces the od_control structure for         */
  34. /*                           obtaining information about the user and the    */
  35. /*                           system that the door program is running on.     */
  36. /*                                                                           */
  37. /*              EX_VOTE3.C - Adds the underlying file access functionality   */
  38. /*                           that is specific to EZVote. EZVote uses a       */
  39. /*                           relatively complex file structure in order to   */
  40. /*                           track which questions each user has voted on,   */
  41. /*                           and in order to allow a large number (200)      */
  42. /*                           question records to be stored in the file.      */
  43. /*                                                                           */
  44. /*              EX_VOTE4.C - Adds color to display and demonstrates the use  */
  45. /*                           of ANSI/AVATAR/RIP specific features.           */
  46. /*                                                                           */
  47. /*              EX_VOTE5.C - Adds support for the OpenDoors configuration    */
  48. /*                           file system, which provides automatic support   */
  49. /*                           for a wide variety of configurable options.     */
  50. /*                           EZVote adds its own configuration options to    */
  51. /*                           control program characteristics such as whether */
  52. /*                           or not the user is premitted to create their    */
  53. /*                           own questions. Also adds support for the        */
  54. /*                           OpenDoors log file system which records major   */
  55. /*                           actions taken by the user. In addition, this    */
  56. /*                           step enables the OpenDoors multiple-personality */
  57. /*                           system and adds other finishing touches.        */
  58.  
  59.  
  60. /* Include standard C header files required by EZVote. */
  61. #include <string.h>
  62. #include <stdio.h>
  63. #include <stdlib.h>
  64. #include <time.h>
  65. #include <errno.h>
  66. #include <ctype.h>
  67.  
  68. /* Include the OpenDoors header file. This line must be done in any program */
  69. /* using OpenDoors.                                                         */
  70. #include "opendoor.h"
  71.  
  72.  
  73. /* Manifest constants used by EZVote */
  74. #define NO_QUESTION              -1
  75. #define NEW_ANSWER               -1
  76.  
  77. #define QUESTIONS_VOTED_ON        0x0001
  78. #define QUESTIONS_NOT_VOTED_ON    0x0002
  79.  
  80. #define MAX_QUESTIONS             200
  81. #define MAX_USERS                 30000
  82. #define MAX_ANSWERS               15
  83. #define QUESTION_STR_SIZE         71
  84. #define ANSWER_STR_SIZE           31
  85.  
  86. #define USER_FILENAME             "EZVOTE.USR"
  87. #define QUESTION_FILENAME         "EZVOTE.QST"
  88.  
  89. #define FILE_ACCESS_MAX_WAIT      20
  90.  
  91. #define QUESTION_PAGE_SIZE        17
  92.  
  93.  
  94. /* Structure of records stored in the EZVOTE.USR file */
  95. typedef struct
  96. {
  97.    char szUserName[36];
  98.    char bVotedOnQuestion[MAX_QUESTIONS];
  99. } tUserRecord;
  100.               
  101. tUserRecord CurrentUserRecord;
  102. int nCurrentUserNumber;
  103.  
  104.  
  105. /* Structure of records stored in the EZVOTE.QST file */
  106. typedef struct
  107. {
  108.    char szQuestion[QUESTION_STR_SIZE];
  109.    char aszAnswer[MAX_ANSWERS][ANSWER_STR_SIZE];
  110.    int nTotalAnswers;
  111.    unsigned int auVotesForAnswer[MAX_ANSWERS];
  112.    unsigned int uTotalVotes;
  113.    char bCanAddAnswers;
  114.    char bDeleted;
  115.    char szCreatorName[36];
  116.    time_t lCreationTime;
  117. } tQuestionRecord;
  118.  
  119.  
  120. /* Prototypes for functions that form EZVote */
  121. void VoteOnQuestion(void);
  122. void ViewResults(void);
  123. int GetQuestion(int nQuestion, tQuestionRecord *pQuestionRecord);
  124. void AddQuestion(void);
  125. void DeleteQuestion(void);
  126. int ChooseQuestion(int nFromWhichQuestions, char *pszTitle, int *nLocation);
  127. void DisplayQuestionResult(tQuestionRecord *pQuestionRecord);
  128. int ReadOrAddCurrentUser(void);
  129. void WriteCurrentUser(void);
  130. FILE *ExculsiveFileOpen(char *pszFileName, char *pszMode);
  131. void WaitForEnter(void);
  132.  
  133.  
  134. /* main() function - Program execution begins here. */
  135. main()
  136. {
  137.    /* Variable to store user's choice from the menu */
  138.    char chMenuChoice;
  139.    char chYesOrNo;
  140.  
  141.    /* Initialize OpenDoors. This function call is optional, and can be used */
  142.    /* to force OpenDoors to read the door informtion file and begin door    */
  143.    /* operations. If a call to od_init() is not included in your program,   */
  144.    /* OpenDoors initialization will be performed at the time of your first  */
  145.    /* call to any OpenDoors function. */
  146.    od_init();
  147.  
  148.    /* Call the EZVote function ReadOrAddCurrentUser() to read the current */
  149.    /* user's record from the EZVote user file, or to add the user to the  */
  150.    /* file if this is the first time that they have used EZVote.          */
  151.    if(!ReadOrAddCurrentUser())
  152.    {
  153.       /* If unable to obtain a user record for the current user, then exit */
  154.       /* the door after displaying an error message.                       */
  155.       od_printf("Unable to access user file. File may be locked or full.\n\r");
  156.       WaitForEnter();
  157.       od_exit(1, FALSE);
  158.    }   
  159.  
  160.    /* Loop until the user choses to exit the door. For each iteration of  */
  161.    /* this loop, we display the main menu, get the user's choice from the */
  162.    /* menu, and perform the appropriate action for their choice.          */
  163.  
  164.    for(;;)
  165.    {
  166.       /* Clear the screen */
  167.       od_clr_scr();
  168.  
  169.       /* Display main menu */
  170.       od_printf("                    EZVote - OpenDoors 5.00 demonstration Door\n\r");
  171.       od_printf("-------------------------------------------------------------------------------\n\r\n\r\n\r");
  172.       od_printf("                        [V] Vote on a question\n\r\n\r");
  173.       od_printf("                        [R] View the results of question\n\r\n\r");
  174.       od_printf("                        [A] Add a new question\n\r\n\r");
  175.       
  176.       /* If current user is the system operator, add a D function to permit */
  177.       /* deletion of unwanted questions.                                    */
  178.       if(strcmp(od_control.sysop_name, od_control.user_name) == 0)
  179.       {
  180.          od_printf("                        [D] Delete a question\n\r\n\r");
  181.       }
  182.       od_printf("                        [P] Page system operator for chat\n\r\n\r");
  183.       od_printf("                        [E] Exit door and return to the BBS\n\r\n\r");
  184.       od_printf("                        [H] End call (hangup)\n\r\n\r\n\r");
  185.       od_printf("Press the key corresponding to the option of your choice.\n\r");
  186.  
  187.       /* Get the user's choice from the main menu. This choice may only be */
  188.       /* V, R, A, D, P, E or H.                                            */
  189.       chMenuChoice = od_get_answer("VRADPEH");
  190.  
  191.       /* Perform the appropriate action based on the user's choice */
  192.       switch(chMenuChoice)
  193.       {
  194.          case 'V':
  195.             /* Call EZVote's function to vote on question */
  196.             VoteOnQuestion();
  197.             break;
  198.             
  199.          case 'R':
  200.             /* Call EZVote's function to view the results of voting */
  201.             ViewResults();
  202.             break;
  203.             
  204.          case 'A':
  205.             /* Call EZVote's function to add a new question */
  206.             AddQuestion();
  207.             break;
  208.  
  209.          case 'D':
  210.             /* Call EZVote's funciton to delete an existing question */
  211.             DeleteQuestion();
  212.             break;
  213.             
  214.          case 'P':
  215.             /* If the user pressed P, allow them page the system operator. */
  216.             od_page();
  217.             break;
  218.  
  219.          case 'E':
  220.             /* If the user pressed E, exit door and return to BBS. */
  221.             od_exit(0, FALSE);
  222.             break;
  223.  
  224.          case 'H':
  225.             /* If the user pressed H, ask whether they wish to hangup. */
  226.             od_printf("\n\rAre you sure you wish to hangup? (Y/N) ");
  227.  
  228.             /* Get user's response */
  229.             chYesOrNo = od_get_answer("YN");
  230.  
  231.             /* If user answered yes, exit door and hangup */
  232.             if(chYesOrNo == 'Y')
  233.             {
  234.                od_exit(0, TRUE);
  235.             }
  236.             break;
  237.       }
  238.    }
  239.  
  240.    return(0);
  241. }
  242.  
  243.  
  244. /* EZVote calls the VoteOnQuestion() function when the user chooses the   */
  245. /* vote command from the main menu. This function displays a list of      */
  246. /* available topics, asks for the user's answer to the topic they select, */
  247. /* and display's the results of voting on that topic.                     */
  248. void VoteOnQuestion(void)
  249. {
  250.    int nQuestion;
  251.    int nAnswer;
  252.    tQuestionRecord QuestionRecord;
  253.    char szNewAnswer[ANSWER_STR_SIZE];
  254.    char szUserInput[3];
  255.    FILE *fpFile;
  256.    int nPageLocation = 0;
  257.    
  258.    /* Loop until the user chooses to return to the main menu, or until */
  259.    /* there are no more questions to vote on.                          */
  260.    for(;;)   
  261.    {
  262.       /* Allow the user to choose a question from the list of questions */
  263.       /* that they have not voted on.                                   */
  264.       nQuestion = ChooseQuestion(QUESTIONS_NOT_VOTED_ON,
  265.          "                              Vote On A Question\n\r",
  266.          &nPageLocation);
  267.  
  268.  
  269.       /* If the user did not choose a question, return to main menu. */
  270.       if(nQuestion == NO_QUESTION)
  271.       {
  272.          return;
  273.       }
  274.  
  275.       /* Read the question chosen by the user. */
  276.       if(!GetQuestion(nQuestion, &QuestionRecord))
  277.       {
  278.          /* If unable to access file, return to main menu. */
  279.          return;
  280.       }
  281.  
  282.       /* Don't allow addition of new answers if maximum number of answers */
  283.       /* have already been added.                                         */
  284.       if(QuestionRecord.nTotalAnswers >= MAX_ANSWERS)
  285.       {
  286.          QuestionRecord.bCanAddAnswers = FALSE;
  287.       }
  288.  
  289.       /* Loop until user makes a valid respose. */
  290.       for(;;)
  291.       {
  292.          /* Display question to user. */
  293.  
  294.          /* Clear the screen. */
  295.          od_clr_scr();
  296.  
  297.          /* Display question itself. */
  298.          od_printf("%s\n\r\n\r", QuestionRecord.szQuestion);
  299.  
  300.          /* Loop for each answer to the question. */
  301.          for(nAnswer = 0; nAnswer < QuestionRecord.nTotalAnswers; ++nAnswer)
  302.          {
  303.             /* Display answer number and answer. */
  304.             od_printf("%d. %s\n\r",
  305.                nAnswer + 1,
  306.                QuestionRecord.aszAnswer[nAnswer]);
  307.          }
  308.  
  309.          /* Display prompt to user. */
  310.          od_printf("\n\rEnter answer number, ");
  311.          if(QuestionRecord.bCanAddAnswers)
  312.          {
  313.             od_printf("[A] to add your own response, ");
  314.          }
  315.          od_printf("[Q] to quit: ");
  316.  
  317.          /* Get response from user. */
  318.          od_input_str(szUserInput, 2, ' ', 255);
  319.          /* Add a blank line. */
  320.          od_printf("\n\r");
  321.  
  322.          /* If user entered Q, return to main menu. */
  323.          if(stricmp(szUserInput, "Q") == 0)
  324.          {
  325.             return;
  326.          }
  327.  
  328.          /* If user enetered A, and adding answers is premitted ... */
  329.          else if (stricmp(szUserInput, "A") == 0
  330.             && QuestionRecord.bCanAddAnswers)
  331.          {
  332.             /* ... Prompt for answer from user. */
  333.             od_printf("Please enter your new answer:\n\r");
  334.             od_printf("[------------------------------]\n\r ");
  335.  
  336.             /* Get string from user. */
  337.             od_input_str(szNewAnswer, ANSWER_STR_SIZE - 1, ' ', 255);
  338.  
  339.             /* Record that user entered a new answer answer. */
  340.             nAnswer = NEW_ANSWER;
  341.  
  342.             /* If user entered a valid answer, then exit loop. */
  343.             if(strlen(szNewAnswer) > 0)
  344.             {
  345.                break;
  346.             }
  347.          }
  348.  
  349.          /* Otherwise, attempt to get answer number from user. */
  350.          nAnswer = atoi(szUserInput) - 1;
  351.  
  352.          /* If user input is not a valid answer. */
  353.          if(nAnswer < 0 || nAnswer >= QuestionRecord.nTotalAnswers)
  354.          {
  355.             /* Display message. */
  356.             od_printf("That is not a valid response.\n\r");
  357.             WaitForEnter();
  358.          }
  359.          else
  360.          {
  361.             /* Otherwise, exit loop. */
  362.             break;
  363.          }
  364.       }
  365.  
  366.       /* Add user's vote to question. */
  367.  
  368.       /* Open question file for exclusive access by this node. */
  369.       fpFile = ExculsiveFileOpen(QUESTION_FILENAME, "r+b");
  370.       if(fpFile == NULL)
  371.       {
  372.          /* If unable to access file, display error and return. */
  373.          od_printf("Unable to access the question file.\n\r");
  374.          WaitForEnter();
  375.          return;
  376.       }
  377.  
  378.       /* Read the answer record from disk, because it may have been changed. */
  379.       /* by another node. */
  380.       fseek(fpFile, (long)nQuestion * sizeof(tQuestionRecord), SEEK_SET);
  381.       if(fread(&QuestionRecord, sizeof(tQuestionRecord), 1, fpFile) != 1)
  382.       {
  383.          /* If unable to access file, display error and return. */
  384.          fclose(fpFile);
  385.          od_printf("Umable to read from question file.\n\r");
  386.          WaitForEnter();
  387.          return;
  388.       }
  389.  
  390.       /* If user entered their own answer, try to add it to the question. */
  391.       if(nAnswer == NEW_ANSWER)
  392.       {
  393.          /* Check that there is still room for another answer. */
  394.          if(QuestionRecord.nTotalAnswers >= MAX_ANSWERS)
  395.          {
  396.             fclose(fpFile);
  397.             od_printf("Sorry, this question already has the maximum number of answers.\n\r");
  398.             WaitForEnter();
  399.             return;
  400.          }
  401.  
  402.          /* Set answer number to number of new answer. */
  403.          nAnswer = QuestionRecord.nTotalAnswers;
  404.  
  405.          /* Add 1 to total number of answers. */
  406.          ++QuestionRecord.nTotalAnswers;
  407.  
  408.          /* Initialize new answer string and count. */
  409.          strcpy(QuestionRecord.aszAnswer[nAnswer], szNewAnswer);
  410.          QuestionRecord.auVotesForAnswer[nAnswer] = 0;
  411.       }
  412.  
  413.       /* Add user's vote to question. */
  414.       ++QuestionRecord.auVotesForAnswer[nAnswer];
  415.       ++QuestionRecord.uTotalVotes;
  416.  
  417.       /* Write the question record back to the file. */
  418.       fseek(fpFile, (long)nQuestion * sizeof(tQuestionRecord), SEEK_SET);
  419.       if(fwrite(&QuestionRecord, sizeof(tQuestionRecord), 1, fpFile) != 1)
  420.       {
  421.          /* If unable to access file, display error and return. */
  422.          fclose(fpFile);
  423.          od_printf("Umable to write question to file.\n\r");
  424.          WaitForEnter();
  425.          return;
  426.       }
  427.  
  428.       /* Close the question file to allow access by other nodes. */
  429.       fclose(fpFile);
  430.  
  431.       /* Record that user has voted on this question. */
  432.       CurrentUserRecord.bVotedOnQuestion[nQuestion] = TRUE;
  433.  
  434.       /* Open user file for exclusive access by this node. */
  435.       fpFile = ExculsiveFileOpen(USER_FILENAME, "r+b");
  436.       if(fpFile == NULL)
  437.       {
  438.          /* If unable to access file, display error and return. */
  439.          od_printf("Unable to access the user file.\n\r");
  440.          WaitForEnter();
  441.          return;
  442.       }
  443.  
  444.       /* Update the user's record in the user file. */
  445.       fseek(fpFile, nCurrentUserNumber * sizeof(tUserRecord), SEEK_SET);
  446.       if(fwrite(&CurrentUserRecord, sizeof(tUserRecord), 1, fpFile) != 1)
  447.       {
  448.          /* If unable to access file, display error and return. */
  449.          fclose(fpFile);
  450.          od_printf("Unable to write to user file.\n\r");
  451.          WaitForEnter();
  452.          return;
  453.       }
  454.  
  455.       /* Close the user file to allow access by other nodes. */
  456.       fclose(fpFile);
  457.  
  458.       /* Display the result of voting on this question to the user. */
  459.       DisplayQuestionResult(&QuestionRecord);
  460.    }
  461. }
  462.  
  463.  
  464. /* The ViewResults function is called when the user chooses the "view    */
  465. /* results" command from the main menu. This function alows the user to  */
  466. /* choose a question from the list of questions, and then displays the   */
  467. /* results of voting on that question.                                   */
  468. void ViewResults(void)
  469. {
  470.    int nChoice;
  471.    tQuestionRecord QuestionRecord;
  472.    int nPageLocation = 0;
  473.  
  474.    /* Loop until user chooses to return to main menu. */
  475.    for(;;)
  476.    {   
  477.       /* Allow the user to choose a question from the list of questions that */
  478.       /* they have already voted on.                                         */
  479.       nChoice = ChooseQuestion(QUESTIONS_VOTED_ON,
  480.          "                                 View Results\n\r", &nPageLocation);
  481.  
  482.       /* If the user did not choose a question, return to main menu. */
  483.       if(nChoice == NO_QUESTION)
  484.       {
  485.          return;
  486.       }
  487.  
  488.       /* Read the specified question number from the question file. */
  489.       if(!GetQuestion(nChoice, &QuestionRecord))
  490.       {
  491.          return;
  492.       }
  493.  
  494.       /* Display the results for the selected question. */
  495.       DisplayQuestionResult(&QuestionRecord);
  496.    }
  497. }
  498.  
  499.  
  500. /* The GetQuestion function read the record for the specified question */
  501. /* number from the question file.                                      */
  502. int GetQuestion(int nQuestion, tQuestionRecord *pQuestionRecord)
  503. {
  504.    FILE *fpQuestionFile;
  505.  
  506.    /* Open the question file for exculsive access by this node. */
  507.    fpQuestionFile = ExculsiveFileOpen(QUESTION_FILENAME, "r+b");
  508.    if(fpQuestionFile == NULL)
  509.    {
  510.       /* If unable to access file, display error and return. */
  511.       od_printf("Unable to access the question file.\n\r");
  512.       WaitForEnter();
  513.       return(FALSE);
  514.    }
  515.    
  516.    /* Move to location of question in file. */
  517.    fseek(fpQuestionFile, (long)nQuestion * sizeof(tQuestionRecord), SEEK_SET);
  518.    
  519.    /* Read the question from the file. */
  520.    if(fread(pQuestionRecord, sizeof(tQuestionRecord), 1, fpQuestionFile) != 1)
  521.    {
  522.       /* If unable to access file, display error and return. */
  523.       fclose(fpQuestionFile);
  524.       od_printf("Umable to read from question file.\n\r");
  525.       WaitForEnter();
  526.       return(FALSE);;
  527.    }
  528.    
  529.    /* Close the question file to allow access by other nodes. */
  530.    fclose(fpQuestionFile);
  531.  
  532.    /* Return with success. */
  533.    return(TRUE);
  534. }
  535.  
  536.  
  537. /* The AddQuestion() function is called when the user chooses the "add    */
  538. /* question" option from the main menu. This function allows the user     */
  539. /* to enter a new question, possible responses, and save the question for */
  540. /* other users to vote on.                                                */
  541. void AddQuestion(void)
  542. {
  543.    tQuestionRecord QuestionRecord;
  544.    FILE *fpQuestionFile;
  545.  
  546.    /* Clear the screen. */
  547.    od_clr_scr();
  548.    
  549.    /* Display screen header. */
  550.    od_printf("                                Add A Question\n\r");
  551.    od_printf("-------------------------------------------------------------------------------\n\r\n\r");
  552.    
  553.    /* Obtain quesiton text from the user. */
  554.    od_printf("Enter Your Question (blank line cancels)\n\r");
  555.    od_printf("[----------------------------------------------------------------------]\n\r ");
  556.    od_input_str(QuestionRecord.szQuestion, QUESTION_STR_SIZE - 1, ' ', 255);
  557.    
  558.    /* If question was empty, then return to main menu. */
  559.    if(strlen(QuestionRecord.szQuestion) == 0)
  560.    {
  561.       return;
  562.    }
  563.    
  564.    /* Display prompt for answers. */
  565.    od_printf("\n\rEnter Possible Answers (blank line when done)\n\r");
  566.    od_printf("   [------------------------------]\n\r");
  567.    
  568.    /* Loop, getting answers from user. */
  569.    for(QuestionRecord.nTotalAnswers = 0;
  570.        QuestionRecord.nTotalAnswers < MAX_ANSWERS;
  571.        QuestionRecord.nTotalAnswers++)
  572.    {
  573.       /* Display prompt with answer number. */
  574.       od_printf("%2d: ", QuestionRecord.nTotalAnswers + 1);
  575.       
  576.       /* Get string from user. */
  577.       od_input_str(QuestionRecord.aszAnswer[QuestionRecord.nTotalAnswers],
  578.          ANSWER_STR_SIZE - 1, ' ', 255);
  579.          
  580.       /* If string was empty, then exit loop. */
  581.       if(strlen(QuestionRecord.aszAnswer[QuestionRecord.nTotalAnswers]) == 0)
  582.       {
  583.          break;
  584.       }
  585.       
  586.       /* Reset count of votes for this answer to zero. */
  587.       QuestionRecord.auVotesForAnswer[QuestionRecord.nTotalAnswers] = 0;
  588.    }
  589.    
  590.    /* If no answers were supplied, then cancel, returning to main menu. */
  591.    if(QuestionRecord.nTotalAnswers == 0)
  592.    {
  593.       return;
  594.    }
  595.  
  596.    /* Ask whether users should be able to add their own answers. */
  597.    od_printf("\n\rShould voters be able to add their own options? (Y/N) ");
  598.    
  599.    /* Get answer from user. */
  600.    if(od_get_answer("YN") == 'Y')
  601.    {
  602.       /* If user pressed the 'Y' key. */
  603.       od_printf("Yes\n\r\n\r");
  604.       
  605.       /* Record user's response. */
  606.       QuestionRecord.bCanAddAnswers = TRUE;
  607.    }
  608.    else
  609.    {
  610.       /* If user pressed the 'N' key. */
  611.       od_printf("No\n\r\n\r");
  612.  
  613.       /* Record user's response. */
  614.       QuestionRecord.bCanAddAnswers = FALSE;
  615.    }
  616.    
  617.    /* Confirm save of new question. */
  618.    od_printf("Do you wish to save this new question? (Y/N) ");
  619.    
  620.    /* If user does not want to save the question, return to main menu now. */
  621.    if(od_get_answer("YN") == 'N')
  622.    {
  623.       return;
  624.    }
  625.  
  626.    /* Set total number of votes for this question to 0. */   
  627.    QuestionRecord.uTotalVotes = 0;
  628.    
  629.    /* Set creator name and creation time for this question. */
  630.    strcpy(QuestionRecord.szCreatorName, od_control.user_name);
  631.    QuestionRecord.lCreationTime = time(NULL);
  632.    QuestionRecord.bDeleted = FALSE;
  633.    
  634.    /* Open question file for exclusive access by this node. */
  635.    fpQuestionFile = ExculsiveFileOpen(QUESTION_FILENAME, "a+b");
  636.    if(fpQuestionFile == NULL)
  637.    {
  638.       od_printf("Unable to access the question file.\n\r");
  639.       WaitForEnter();
  640.       return;
  641.    }
  642.    
  643.    /* Determine number of records in question file. */
  644.    fseek(fpQuestionFile, 0, SEEK_END);
  645.    
  646.    /* If question file is full, display message and return to main menu */
  647.    /* after closing file.                                               */
  648.    if(ftell(fpQuestionFile) / sizeof(tQuestionRecord) >= MAX_QUESTIONS)
  649.    {
  650.       fclose(fpQuestionFile);
  651.       od_printf("Cannot add another question, EZVote is limisted to %d questions.\n\r", MAX_QUESTIONS);
  652.       WaitForEnter();
  653.       return;
  654.    }
  655.    
  656.    /* Add new question to file. */
  657.    if(fwrite(&QuestionRecord, sizeof(QuestionRecord), 1, fpQuestionFile) != 1)
  658.    {
  659.       fclose(fpQuestionFile);
  660.       od_printf("Umable to write to question file.\n\r");
  661.       WaitForEnter();
  662.       return;
  663.    }
  664.    
  665.    /* Close question file, allowing other nodes to access file. */
  666.    fclose(fpQuestionFile);
  667. }
  668.  
  669.  
  670. /* The DeleteQuestion() function is called when the sysop chooses the   */
  671. /* "delete question" option from the main menu. This function displays  */
  672. /* a list of all questions, allowing the sysop to choose a question for */
  673. /* deletion.                                                            */
  674. void DeleteQuestion(void)
  675. {
  676.    int nQuestion;
  677.    tQuestionRecord QuestionRecord;
  678.    FILE *fpFile;
  679.    int nPageLocation = 0;
  680.  
  681.    /* Check that user is system operator. */
  682.    if(strcmp(od_control.user_name, od_control.sysop_name) != 0)
  683.    {
  684.       return;
  685.    }
  686.  
  687.    /* Allow the user to choose a question from the list of all questions. */
  688.    nQuestion = ChooseQuestion(QUESTIONS_NOT_VOTED_ON | QUESTIONS_VOTED_ON,
  689.       "                              Delete A Question\n\r", &nPageLocation);
  690.  
  691.    /* If the user did not choose a question, return to main menu. */   
  692.    if(nQuestion == NO_QUESTION)
  693.    {
  694.       return;
  695.    }
  696.  
  697.    /* Read the question chosen by the user. */
  698.    if(!GetQuestion(nQuestion, &QuestionRecord))
  699.    {
  700.       /* If unable to access file, return to main menu. */
  701.       return;
  702.    }
  703.  
  704.    /* Confirm deletion of this question. */
  705.    od_printf("\n\rAre you sure you want to delete the question:\n\r   %s\n\r",
  706.       QuestionRecord.szQuestion);
  707.    od_printf("[Y]es or [N]o?\n\r");
  708.  
  709.    /* If user canceled deletion, return now. */   
  710.    if(od_get_answer("YN") == 'N')
  711.    {
  712.       return;
  713.    }
  714.  
  715.    /* Mark the question as being deleted. */
  716.    QuestionRecord.bDeleted = TRUE;
  717.    
  718.    /* Open question file for exclusive access by this node. */
  719.    fpFile = ExculsiveFileOpen(QUESTION_FILENAME, "r+b");
  720.    if(fpFile == NULL)
  721.    {
  722.       /* If unable to access file, display error and return. */
  723.       od_printf("Unable to access the question file.\n\r");
  724.       WaitForEnter();
  725.       return;
  726.    }
  727.  
  728.    /* Write the question record back to the file. */
  729.    fseek(fpFile, (long)nQuestion * sizeof(tQuestionRecord), SEEK_SET);
  730.    if(fwrite(&QuestionRecord, sizeof(tQuestionRecord), 1, fpFile) != 1)
  731.    {
  732.       /* If unable to access file, display error and return. */
  733.       fclose(fpFile);
  734.       od_printf("Umable to write question to file.\n\r");
  735.       WaitForEnter();
  736.       return;
  737.    }
  738.    
  739.    /* Close the question file to allow access by other nodes. */
  740.    fclose(fpFile);
  741. }
  742.  
  743.  
  744. /* The ChooseQuestion() function provides a list of questions and allows   */
  745. /* the user to choose a particular question, cancel back to the main menu, */ 
  746. /* and page up and down in the list of questions. Depending upon the value */
  747. /* of the nFromWhichQuestions parameter, this function will present a list */
  748. /* of questions that the user has voted on, a list of questions that the   */
  749. /* user has not voted on, or a list of all questions.                      */
  750. int ChooseQuestion(int nFromWhichQuestions, char *pszTitle, int *nLocation)
  751. {
  752.    int nCurrent;
  753.    int nFileQuestion = 0;
  754.    int nPagedToQuestion = *nLocation;
  755.    int nDisplayedQuestion = 0;
  756.    char bVotedOnQuestion;
  757.    char chCurrent;
  758.    tQuestionRecord QuestionRecord;
  759.    FILE *fpQuestionFile;
  760.    static char szQuestionName[MAX_QUESTIONS][QUESTION_STR_SIZE];
  761.    static int nQuestionNumber[MAX_QUESTIONS];
  762.    
  763.    /* Attempt to open question file. */
  764.    fpQuestionFile = ExculsiveFileOpen(QUESTION_FILENAME, "r+b");
  765.  
  766.    /* If unable to open question file, assume that no questions have been */
  767.    /* created.                                                            */
  768.    if(fpQuestionFile == NULL)
  769.    {
  770.       /* Display "no questions yet" message. */
  771.       od_printf("\n\rNo questions have been created so far.\n\r");
  772.       
  773.       /* Wait for user to press enter. */
  774.       WaitForEnter();
  775.       
  776.       /* Indicate that no question has been chosen. */
  777.       return(NO_QUESTION);
  778.    }
  779.    
  780.    /* Loop for every question record in the file. */
  781.    while(fread(&QuestionRecord, sizeof(QuestionRecord), 1, fpQuestionFile) == 1)
  782.    {
  783.       /* Determine whether or not the user has voted on this question. */
  784.       bVotedOnQuestion = CurrentUserRecord.bVotedOnQuestion[nFileQuestion];
  785.       
  786.       /* If this is the kind of question that the user is choosing from */
  787.       /* right now.                                                     */
  788.       if((bVotedOnQuestion && (nFromWhichQuestions & QUESTIONS_VOTED_ON)) ||
  789.          (!bVotedOnQuestion && (nFromWhichQuestions & QUESTIONS_NOT_VOTED_ON)))
  790.       {
  791.          /* If question is not deleted. */
  792.          if(!QuestionRecord.bDeleted)
  793.          {
  794.             /* Add this question to list to be displayed. */
  795.             strcpy(szQuestionName[nDisplayedQuestion],
  796.                QuestionRecord.szQuestion);
  797.             nQuestionNumber[nDisplayedQuestion] = nFileQuestion;
  798.          
  799.             /* Add one to number of questions to be displayed in list. */
  800.             nDisplayedQuestion++;
  801.          }
  802.       }
  803.       
  804.       /* Move to next question in file. */
  805.       ++nFileQuestion;
  806.    }   
  807.    
  808.    /* Close question file to allow other nodes to access the file. */
  809.    fclose(fpQuestionFile);
  810.  
  811.    /* If there are no questions for the user to choose, display an */
  812.    /* appropriate message and return. */
  813.    if(nDisplayedQuestion == 0)
  814.    {
  815.       /* If we were to list all questions. */
  816.       if((nFromWhichQuestions & QUESTIONS_VOTED_ON)
  817.          && (nFromWhichQuestions & QUESTIONS_NOT_VOTED_ON))
  818.       {
  819.          od_printf("\n\rThere are no questions.\n\r");
  820.       }
  821.       /* If we were to list questions that the user has voted on. */
  822.       else if(nFromWhichQuestions & QUESTIONS_VOTED_ON)
  823.       {
  824.          od_printf("\n\rThere are no questions that you have voted on.\n\r");
  825.       }
  826.       /* Otherwise, we were to list questions that use has not voted on. */
  827.       else
  828.       {
  829.          od_printf("\n\rYou have voted on all the questions.\n\r");
  830.       }
  831.       
  832.       /* Wait for user to press enter key. */
  833.       WaitForEnter();
  834.       
  835.       /* Return, indicating that no question was chosen. */
  836.       return(NO_QUESTION);
  837.    }
  838.  
  839.    /* Loop, displaying current page of questions, until the user makes a */
  840.    /* choice.                                                            */
  841.    for(;;)
  842.    {
  843.       /* Clear the screen. */
  844.       od_clr_scr();
  845.  
  846.       /* Display header. */
  847.       od_printf(pszTitle);
  848.       od_printf("-------------------------------------------------------------------------------\n\r");
  849.    
  850.       /* Display list of questions on this page. */
  851.       for(nCurrent = 0;
  852.          nCurrent < QUESTION_PAGE_SIZE
  853.          && nCurrent < (nDisplayedQuestion - nPagedToQuestion);
  854.          ++nCurrent)
  855.       {
  856.          /* Determine character to display for current line. */
  857.          if(nCurrent < 9)
  858.          {
  859.             chCurrent = (char)('1' + nCurrent);
  860.          }
  861.          else
  862.          {
  863.             chCurrent = (char)('A' + (nCurrent - 9));
  864.          }
  865.       
  866.          /* Display this question's title. */
  867.          od_printf("%c. %s\n\r", chCurrent,
  868.             szQuestionName[nCurrent + nPagedToQuestion]);
  869.       }
  870.  
  871.       /* Display prompt for input. */
  872.       od_printf("\n\r[Page %d]  Choose a question or",
  873.          (nPagedToQuestion / QUESTION_PAGE_SIZE) + 1);
  874.       if(nPagedToQuestion < nDisplayedQuestion - QUESTION_PAGE_SIZE)
  875.       {
  876.          od_printf(" [N]ext page,");
  877.       }
  878.       if(nPagedToQuestion > 0)
  879.       {
  880.          od_printf(" [P]revious page,");
  881.       }
  882.       od_printf(" [Q]uit.\n\r");
  883.       
  884.       /* Loop until the user makes a valid choice. */
  885.       for(;;)
  886.       {      
  887.          /* Get input from user */
  888.          chCurrent = (char)od_get_key(TRUE);
  889.          chCurrent = (char)toupper(chCurrent);
  890.       
  891.          /* Respond to user's input. */
  892.       
  893.          /* If user pressed Q key. */
  894.          if(chCurrent == 'Q')
  895.          {
  896.             /* Return without a choosing a question. */
  897.             return(NO_QUESTION);
  898.          }
  899.       
  900.          /* If user pressed P key. */
  901.          else if(chCurrent == 'P')
  902.          {
  903.             /* If we are not at the first page. */
  904.             if(nPagedToQuestion > 0)
  905.             {
  906.                /* Move paged to location up one page. */
  907.                nPagedToQuestion -= QUESTION_PAGE_SIZE;
  908.                
  909.                /* Exit user input loop to display next page. */
  910.                break;
  911.             }
  912.          }
  913.       
  914.          /* If user pressed N key. */
  915.          else if(chCurrent == 'N')
  916.          {
  917.             /* If there is more questions after this page. */
  918.             if(nPagedToQuestion < nDisplayedQuestion - QUESTION_PAGE_SIZE)
  919.             {
  920.                /* Move paged.to location down one page. */
  921.                nPagedToQuestion += QUESTION_PAGE_SIZE;
  922.  
  923.                /* Exit user input loop to display next page. */
  924.                break;
  925.             }
  926.          }
  927.       
  928.          /* Otherwise, check whether the user chose a valid question. */
  929.          else if ((chCurrent >= '1' && chCurrent <= '9')
  930.             || (chCurrent >= 'A' && chCurrent <= 'H'))
  931.          {
  932.             /* Get question number from key pressed. */
  933.             if(chCurrent >= '1' && chCurrent <= '9')
  934.             {
  935.                nCurrent = chCurrent - '1';
  936.             }
  937.             else
  938.             {
  939.                nCurrent = (chCurrent - 'A') + 9;
  940.             }
  941.          
  942.             /* Add current paged to position to user's choice. */
  943.             nCurrent += nPagedToQuestion;
  944.  
  945.             /* If this is valid question number. */            
  946.             if(nCurrent < nDisplayedQuestion)
  947.             {
  948.                /* Set caller's current question number. */
  949.                *nLocation = nPagedToQuestion;
  950.             
  951.                /* Return actual question number in file. */
  952.                return(nQuestionNumber[nCurrent]);
  953.             }
  954.          }
  955.       }
  956.    }
  957. }
  958.  
  959.  
  960. /* The DisplayQuestionResult() function is called to display the results */
  961. /* of voting on a paricular question, and is passed the question record  */
  962. /* of the question. This function is called when the user selects a      */
  963. /* question using the "view results" option, and is also called after    */
  964. /* the user has voted on a question, to display the results of voting on */
  965. /* that question.                                                        */
  966. void DisplayQuestionResult(tQuestionRecord *pQuestionRecord)
  967. {
  968.    int nAnswer;
  969.    int uPercent;
  970.  
  971.    /* Clear the screen. */
  972.    od_clr_scr();
  973.  
  974.    /* Check that there have been votes on this question. */
  975.    if(pQuestionRecord->uTotalVotes == 0)
  976.    {
  977.       /* If there have been no votes for this question, display a message */
  978.       /* and return.                                                      */
  979.       od_printf("Nobody has voted on this question yet.\n\r");
  980.       WaitForEnter();
  981.       return;
  982.    }
  983.  
  984.    /* Display question itself. */
  985.    od_printf("%s\n\r", pQuestionRecord->szQuestion);
  986.  
  987.    /* Display author's name. */
  988.    od_printf("Question created by %s on %s\n\r", pQuestionRecord->szCreatorName,
  989.       ctime(&pQuestionRecord->lCreationTime));
  990.    
  991.    /* Display heading for responses. */
  992.    od_printf("Response                        Votes  Percent  Graph\n\r");
  993.    od_printf("-------------------------------------------------------------------------------\n\r");
  994.  
  995.    /* Loop for each answer to the question. */   
  996.    for(nAnswer = 0; nAnswer < pQuestionRecord->nTotalAnswers; ++nAnswer)
  997.    {
  998.       /* Determine percent of users who voted for this answer. */
  999.       uPercent = (pQuestionRecord->auVotesForAnswer[nAnswer] * 100)
  1000.          / pQuestionRecord->uTotalVotes;
  1001.       
  1002.       /* Display answer, total votes and percentage of votes. */
  1003.       od_printf("%-30.30s  %-5u  %3u%%     ",
  1004.          pQuestionRecord->aszAnswer[nAnswer],
  1005.          pQuestionRecord->auVotesForAnswer[nAnswer],
  1006.          uPercent);
  1007.  
  1008.       /* Display a bar graph corresponding to percent of users who voted */
  1009.       /* for this answer.                                                */      
  1010.       od_repeat('=', (unsigned char)((uPercent * 31) / 100));
  1011.  
  1012.       /* Move to next line. */
  1013.       od_printf("\n\r");
  1014.    }
  1015.    
  1016.    /* Display footer. */
  1017.    od_printf("-------------------------------------------------------------------------------\n\r");
  1018.    od_printf("                         TOTAL: %u\n\r\n\r",
  1019.       pQuestionRecord->uTotalVotes);
  1020.    
  1021.    /* Wait for user to press enter. */
  1022.    WaitForEnter();
  1023. }
  1024.  
  1025.  
  1026. /* The ReadOrAddCurrentUser() function is used by EZVote to search the    */
  1027. /* EZVote user file for the record containing information on the user who */
  1028. /* is currently using the door. If this is the first time that the user   */
  1029. /* has used this door, then their record will not exist in the user file. */
  1030. /* In this case, this function will add a new record for the current      */
  1031. /* user. This function returns TRUE on success, or FALSE on failure.      */
  1032. int ReadOrAddCurrentUser(void)
  1033. {
  1034.    FILE *fpUserFile;
  1035.    int bGotUser = FALSE;
  1036.    int nQuestion;
  1037.  
  1038.    /* Attempt to open the user file for exclusize access by this node.     */
  1039.    /* This function will wait up to the pre-set amount of time (as defined */   
  1040.    /* near the beginning of this file) for access to the user file.        */
  1041.    fpUserFile = ExculsiveFileOpen(USER_FILENAME, "a+b");
  1042.  
  1043.    /* If unable to open user file, return with failure. */   
  1044.    if(fpUserFile == NULL)
  1045.    {
  1046.       return(FALSE);
  1047.    }
  1048.  
  1049.    /* Begin with the current user record number set to 0. */
  1050.    nCurrentUserNumber = 0;
  1051.  
  1052.    /* Loop for each record in the file */
  1053.    while(fread(&CurrentUserRecord, sizeof(tUserRecord), 1, fpUserFile) == 1)
  1054.    {
  1055.       /* If name in record matches the current user name ... */
  1056.       if(strcmp(CurrentUserRecord.szUserName, od_control.user_name) == 0)
  1057.       {
  1058.          /* ... then record that we have found the user's record, */
  1059.          bGotUser = TRUE;
  1060.          
  1061.          /* and exit the loop. */
  1062.          break;
  1063.       }
  1064.  
  1065.       /* Move user record number to next user record. */      
  1066.       nCurrentUserNumber++;
  1067.    }
  1068.  
  1069.    /* If the user was not found in the file, attempt to add them as a */
  1070.    /* new user if the user file is not already full.                  */
  1071.    if(!bGotUser && nCurrentUserNumber < MAX_USERS)
  1072.    {
  1073.       /* Place the user's name in the current user record. */
  1074.       strcpy(CurrentUserRecord.szUserName, od_control.user_name);
  1075.       
  1076.       /* Record that user hasn't voted on any of the questions. */
  1077.       for(nQuestion = 0; nQuestion < MAX_QUESTIONS; ++nQuestion)
  1078.       {
  1079.          CurrentUserRecord.bVotedOnQuestion[nQuestion] = FALSE;
  1080.       }
  1081.       
  1082.       /* Write the new record to the file. */
  1083.       if(fwrite(&CurrentUserRecord, sizeof(tUserRecord), 1, fpUserFile) == 1)
  1084.       {
  1085.          /* If write succeeded, record that we now have a valid user record. */
  1086.          bGotUser = TRUE;
  1087.       }
  1088.    }
  1089.  
  1090.    /* Close the user file to allow other nodes to access it. */
  1091.    fclose(fpUserFile);
  1092.  
  1093.    /* Return, indciating whether or not a valid user record now exists for */
  1094.    /* the user that is currently online.                                   */   
  1095.    return(bGotUser);
  1096. }
  1097.  
  1098.  
  1099. /* The WriteCurrentUser() function is called to save the information on the */
  1100. /* user who is currently using the door, to the EZVOTE.USR file.            */
  1101. void WriteCurrentUser(void)
  1102. {
  1103.    FILE *fpUserFile;
  1104.  
  1105.    /* Attempt to open the user file for exclusize access by this node.     */
  1106.    /* This function will wait up to the pre-set amount of time (as defined */   
  1107.    /* near the beginning of this file) for access to the user file.        */
  1108.    fpUserFile = ExculsiveFileOpen(USER_FILENAME, "r+b");
  1109.  
  1110.    /* If unable to access the user file, display an error message and */
  1111.    /* return.                                                         */
  1112.    if(fpUserFile == NULL)
  1113.    {
  1114.       od_printf("Unable to access the user file.\n\r");
  1115.       WaitForEnter();
  1116.       return;
  1117.    }
  1118.    
  1119.    /* Move to appropriate location in user file for the current user's */
  1120.    /* record. */
  1121.    fseek(fpUserFile, (long)nCurrentUserNumber * sizeof(tUserRecord), SEEK_SET);
  1122.  
  1123.    /* Write the new record to the file. */
  1124.    if(fwrite(&CurrentUserRecord, sizeof(tUserRecord), 1, fpUserFile) == 1)
  1125.    {
  1126.       /* If unable to write the record, display an error message. */
  1127.       fclose(fpUserFile);
  1128.       od_printf("Unable to update your user record file.\n\r");
  1129.       WaitForEnter();
  1130.       return;
  1131.    }
  1132.    
  1133.    /* Close the user file to allow other nodes to access it again. */
  1134.    fclose(fpUserFile);
  1135. }
  1136.  
  1137.  
  1138. /* This function opens the specified file in the specified mode for         */
  1139. /* exculsive access by this node; while the file is open, other nodes will  */
  1140. /* be unable to open the file. This function will wait for up to the number */
  1141. /* of seconds set by FILE_ACCESS_MAX_WAIT, which is defined near the        */
  1142. /* beginning of this file.                                                  */
  1143. FILE *ExculsiveFileOpen(char *pszFileName, char *pszMode)
  1144. {
  1145.    FILE *fpFile = NULL;
  1146.    time_t StartTime = time(NULL);
  1147.  
  1148.    /* Attempt to open the file while there is still time remaining. */    
  1149.    while((fpFile = fopen(pszFileName, pszMode)) == NULL
  1150.       && errno == EACCES
  1151.       && difftime(time(NULL), StartTime) < FILE_ACCESS_MAX_WAIT)
  1152.    {
  1153.       /* If we were unable to open the file, call od_kernal, so that    */
  1154.       /* OpenDoors can continue to respond to sysop function keys, loss */
  1155.       /* of connection, etc.                                            */
  1156.       od_kernal();
  1157.    }
  1158.  
  1159.    /* Return FILE pointer for opened file, if any. */   
  1160.    return(fpFile);
  1161. }
  1162.  
  1163.  
  1164. /* The WaitForEnter() function is used by EZVote to create its custom */
  1165. /* "Press [ENTER] to continue." prompt.                               */
  1166. void WaitForEnter(void)
  1167. {
  1168.    /* Display prompt. */
  1169.    od_printf("Press [ENTER] to continue.\n\r");
  1170.    
  1171.    /* Wait for a Carriage Return or Line Feed character from the user. */
  1172.    od_get_answer("\n\r");
  1173. }
  1174.