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

  1. /* EX_VOTE5.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. /* Global variables. */
  121. int nViewResultsFrom = QUESTIONS_VOTED_ON;
  122. char bAllowAdd = TRUE;
  123. int nQuestionsVotedOn = 0;
  124.  
  125.  
  126. /* Prototypes for functions that form EZVote */
  127. void CustomConfigFunction(char *pszKeyword, char *pszOptions);
  128. void BeforeExitFunction(void);
  129. void VoteOnQuestion(void);
  130. void ViewResults(void);
  131. int GetQuestion(int nQuestion, tQuestionRecord *pQuestionRecord);
  132. void AddQuestion(void);
  133. void DeleteQuestion(void);
  134. int ChooseQuestion(int nFromWhichQuestions, char *pszTitle, int *nLocation);
  135. void DisplayQuestionResult(tQuestionRecord *pQuestionRecord);
  136. int ReadOrAddCurrentUser(void);
  137. void WriteCurrentUser(void);
  138. FILE *ExculsiveFileOpen(char *pszFileName, char *pszMode);
  139. void WaitForEnter(void);
  140.  
  141.  
  142. /* main() function - Program execution begins here. */
  143. main()
  144. {
  145.    /* Variable to store user's choice from the menu */
  146.    char chMenuChoice;
  147.    char chYesOrNo;
  148.  
  149.    /* Enable use of OpenDoors configuration file system. */
  150.    od_control.od_config_file = INCLUDE_CONFIG_FILE;
  151.  
  152.    /* Set function to process custom configuration file lines. */
  153.    od_control.od_config_function = CustomConfigFunction;
  154.  
  155.    /* Include the OpenDoors multiple personality system, which allows    */
  156.    /* the system operator to set the sysop statusline / function key set */
  157.    /* to mimic the BBS software of their choice.                         */
  158.    od_control.od_mps = INCLUDE_MPS;
  159.    
  160.    /* Include the OpenDoors log file system, which will record when the */
  161.    /* door runs, and major activites that the user performs.            */ 
  162.    od_control.od_logfile = INCLUDE_LOGFILE;
  163.  
  164.    /* Set filename for log file. If not set, DOOR.LOG will be used by */
  165.    /* default.                                                        */
  166.    strcpy(od_control.od_logfile_name, "EZVOTE.LOG");
  167.    
  168.    /* Set program's name, to be written to the OpenDoors log file       */
  169.    strcpy(od_control.od_prog_name, "EZVote 5.00");
  170.  
  171.    /* Set function to be called before program exits. */
  172.    od_control.od_before_exit = BeforeExitFunction;
  173.  
  174.    /* Initialize OpenDoors. This function call is optional, and can be used */
  175.    /* to force OpenDoors to read the door informtion file and begin door    */
  176.    /* operations. If a call to od_init() is not included in your program,   */
  177.    /* OpenDoors initialization will be performed at the time of your first  */
  178.    /* call to any OpenDoors function. */
  179.    od_init();
  180.  
  181.    /* Call the EZVote function ReadOrAddCurrentUser() to read the current */
  182.    /* user's record from the EZVote user file, or to add the user to the  */
  183.    /* file if this is the first time that they have used EZVote.          */
  184.    if(!ReadOrAddCurrentUser())
  185.    {
  186.       /* If unable to obtain a user record for the current user, then exit */
  187.       /* the door after displaying an error message.                       */
  188.       od_printf("Unable to access user file. File may be locked or full.\n\r");
  189.       WaitForEnter();
  190.       od_exit(1, FALSE);
  191.    }   
  192.  
  193.    /* Loop until the user choses to exit the door. For each iteration of  */
  194.    /* this loop, we display the main menu, get the user's choice from the */
  195.    /* menu, and perform the appropriate action for their choice.          */
  196.  
  197.    for(;;)
  198.    {
  199.       /* Clear the screen */
  200.       od_clr_scr();
  201.  
  202.       /* Display main menu. */
  203.       
  204.       /* First, attempt to display menu from an EZVOTE.ASC/ANS/AVT/RIP file. */
  205.       if((chMenuChoice = od_hotkey_menu("EZVOTE", "VRADPEH", TRUE)) == 0)
  206.       {
  207.          /* If the EZVOTE file could not be displayed, display our own menu. */
  208.          od_printf("`bright red`                    EZVote - OpenDoors 5.00 demonstration Door\n\r");\
  209.          od_printf("`dark red`");
  210.          if(od_control.user_ansi || od_control.user_avatar)
  211.          {
  212.             od_repeat((unsigned char)196, 79);
  213.          }
  214.          else
  215.          {
  216.             od_repeat('-', 79);
  217.          }
  218.          od_printf("\n\r\n\r\n\r`dark green`");
  219.          od_printf("                        [`bright green`V`dark green`] Vote on a question\n\r\n\r");
  220.          od_printf("                        [`bright green`R`dark green`] View the results of question\n\r\n\r");
  221.  
  222.          /* Display Add New Question option if adding questions is enabled, */
  223.          /* or if the system operator is using the door.                    */
  224.          if(bAllowAdd ||
  225.             strcmp(od_control.sysop_name, od_control.user_name) == 0)
  226.          {
  227.             od_printf("                        [`bright green`A`dark green`] Add a new question\n\r\n\r");
  228.          }
  229.               
  230.          /* If current user is the system operator, add a D function to permit */
  231.          /* deletion of unwanted questions.                                    */
  232.          if(strcmp(od_control.sysop_name, od_control.user_name) == 0)
  233.          {
  234.             od_printf("                        [`bright green`D`dark green`] Delete a question\n\r\n\r");
  235.          }
  236.          od_printf("                        [`bright green`P`dark green`] Page system operator for chat\n\r\n\r");
  237.          od_printf("                        [`bright green`E`dark green`] Exit door and return to the BBS\n\r\n\r");
  238.          od_printf("                        [`bright green`H`dark green`] End call (hangup)\n\r\n\r\n\r");
  239.          od_printf("`bright white`Press the key corresponding to the option of your choice. (%d mins)\n\r`dark green`",
  240.             od_control.user_timelimit);
  241.                                                                                                        \
  242.          /* Get the user's choice from the main menu. This choice may only be */
  243.          /* V, R, A, D, P, E or H.                                            */
  244.          chMenuChoice = od_get_answer("VRADPEH");
  245.       }
  246.  
  247.       /* Perform the appropriate action based on the user's choice */
  248.       switch(chMenuChoice)
  249.       {
  250.          case 'V':
  251.             /* Call EZVote's function to vote on question */
  252.             VoteOnQuestion();
  253.             break;
  254.             
  255.          case 'R':
  256.             /* Call EZVote's function to view the results of voting */
  257.             ViewResults();
  258.             break;
  259.             
  260.          case 'A':
  261.             /* Call EZVote's function to add a new question if door is */
  262.             /* configured to allow the addition of question.           */
  263.             if(bAllowAdd ||
  264.                strcmp(od_control.sysop_name, od_control.user_name) == 0)
  265.             {
  266.                AddQuestion();
  267.             }
  268.             break;
  269.  
  270.          case 'D':                                                                                      \
  271.             /* Call EZVote's funciton to delete an existing question */
  272.             DeleteQuestion();
  273.             break;
  274.             
  275.          case 'P':
  276.             /* If the user pressed P, allow them page the system operator. */
  277.             od_page();
  278.             break;
  279.  
  280.          case 'E':
  281.             /* If the user pressed E, exit door and return to BBS. */
  282.             od_exit(0, FALSE);
  283.             break;
  284.  
  285.          case 'H':
  286.             /* If the user pressed H, ask whether they wish to hangup. */
  287.             od_printf("\n\rAre you sure you wish to hangup? (Y/N) ");
  288.  
  289.             /* Get user's response */
  290.             chYesOrNo = od_get_answer("YN");
  291.  
  292.             /* If user answered yes, exit door and hangup */
  293.             if(chYesOrNo == 'Y')
  294.             {
  295.                od_exit(0, TRUE);
  296.             }
  297.             break;
  298.       }
  299.    }
  300.  
  301.    return(0);
  302. }
  303.  
  304.  
  305. /* CustomConfigFunction() is called by OpenDoors to process custom */
  306. /* configuration file keywords that EZVote uses.                   */
  307. void CustomConfigFunction(char *pszKeyword, char *pszOptions)
  308. {
  309.    if(stricmp(pszKeyword, "ViewUnanswered") == 0)
  310.    {
  311.       /* If keyword is ViewUnanswered, set local variable based on contents */
  312.       /* of options string.                                                 */
  313.       if(stricmp(pszOptions, "Yes") == 0)
  314.       {
  315.          nViewResultsFrom = QUESTIONS_VOTED_ON | QUESTIONS_NOT_VOTED_ON;
  316.       }
  317.       else if(stricmp(pszOptions, "No") == 0)
  318.       {
  319.          nViewResultsFrom = QUESTIONS_VOTED_ON;
  320.       }
  321.    }
  322.    else if(stricmp(pszKeyword, "AllowAdd") == 0)
  323.    {
  324.       /* If keyword is ViewUnvoted, set local variable based on contents */
  325.       /* of options string.                                              */
  326.       if(stricmp(pszOptions, "Yes") == 0)
  327.       {
  328.          bAllowAdd = TRUE;
  329.       }
  330.       else if(stricmp(pszOptions, "No") == 0)
  331.       {
  332.          bAllowAdd = FALSE;
  333.       }
  334.    }
  335. }
  336.  
  337.  
  338. /* EZVote configures OpenDoors to call the BeforeExitFunction() before    */
  339. /* the door exists for any reason. You can use this function to close any */
  340. /* files or perform any other operations that you wish to have peformed   */
  341. /* before OpenDoors exists for any reason. The od_control.od_before_exit  */
  342. /* variable sets the function to be called before program exit.           */
  343. void BeforeExitFunction(void)
  344. {
  345.    char szLogMessage[80];
  346.    
  347.    /* Write number of messages voted on to log file. */
  348.    sprintf(szLogMessage, "User has voted on %d question(s)",
  349.       nQuestionsVotedOn);
  350.    od_log_write(szLogMessage);
  351. }
  352.  
  353.  
  354. /* EZVote calls the VoteOnQuestion() function when the user chooses the   */
  355. /* vote command from the main menu. This function displays a list of      */
  356. /* available topics, asks for the user's answer to the topic they select, */
  357. /* and display's the results of voting on that topic.                     */
  358. void VoteOnQuestion(void)
  359. {
  360.    int nQuestion;
  361.    int nAnswer;
  362.    tQuestionRecord QuestionRecord;
  363.    char szNewAnswer[ANSWER_STR_SIZE];
  364.    char szUserInput[3];
  365.    FILE *fpFile;
  366.    int nPageLocation = 0;
  367.  
  368.    /* Loop until the user chooses to return to the main menu, or until */
  369.    /* there are no more questions to vote on.                          */
  370.    for(;;)   
  371.    {
  372.       /* Allow the user to choose a question from the list of questions */
  373.       /* that they have not voted on.                                   */
  374.       nQuestion = ChooseQuestion(QUESTIONS_NOT_VOTED_ON,
  375.          "                              Vote On A Question\n\r",
  376.          &nPageLocation);
  377.    
  378.  
  379.       /* If the user did not choose a question, return to main menu. */   
  380.       if(nQuestion == NO_QUESTION)
  381.       {
  382.          return;
  383.       }
  384.  
  385.       /* Read the question chosen by the user. */
  386.       if(!GetQuestion(nQuestion, &QuestionRecord))
  387.       {
  388.          /* If unable to access file, return to main menu. */
  389.          return;
  390.       }
  391.    
  392.       /* Don't allow addition of new answers if maximum number of answers */
  393.       /* have already been added.                                         */
  394.       if(QuestionRecord.nTotalAnswers >= MAX_ANSWERS)
  395.       {
  396.          QuestionRecord.bCanAddAnswers = FALSE;
  397.       }
  398.    
  399.       /* Loop until user makes a valid respose. */
  400.       for(;;)
  401.       {
  402.          /* Display question to user. */
  403.  
  404.          /* Clear the screen. */
  405.          od_clr_scr();
  406.  
  407.          /* Display question itself. */
  408.          od_printf("`bright red`%s\n\r\n\r", QuestionRecord.szQuestion);
  409.  
  410.          /* Loop for each answer to the question. */   
  411.          for(nAnswer = 0; nAnswer < QuestionRecord.nTotalAnswers; ++nAnswer)
  412.          {
  413.             /* Display answer number and answer. */
  414.             od_printf("`bright green`%d. `dark green`%s\n\r",
  415.                nAnswer + 1,
  416.                QuestionRecord.aszAnswer[nAnswer]);
  417.          }
  418.  
  419.          /* Display prompt to user. */
  420.          od_printf("\n\r`bright white`Enter answer number, ");
  421.          if(QuestionRecord.bCanAddAnswers)
  422.          {
  423.             od_printf("[A] to add your own response, ");
  424.          }
  425.          od_printf("[Q] to quit: `dark green`");
  426.    
  427.          /* Get response from user. */
  428.          od_input_str(szUserInput, 2, ' ', 255);
  429.          /* Add a blank line. */      
  430.          od_printf("\n\r");
  431.    
  432.          /* If user entered Q, return to main menu. */
  433.          if(stricmp(szUserInput, "Q") == 0)
  434.          {
  435.             return;
  436.          }
  437.  
  438.          /* If user enetered A, and adding answers is premitted ... */
  439.          else if (stricmp(szUserInput, "A") == 0
  440.             && QuestionRecord.bCanAddAnswers)
  441.          {
  442.             /* ... Prompt for answer from user. */
  443.             od_printf("`bright green`Please enter your new answer:\n\r");
  444.             od_printf("`dark green`[------------------------------]\n\r ");
  445.          
  446.             /* Get string from user. */
  447.             od_input_str(szNewAnswer, ANSWER_STR_SIZE - 1, ' ', 255);
  448.          
  449.             /* Record that user entered a new answer answer. */
  450.             nAnswer = NEW_ANSWER;
  451.  
  452.             /* If user entered a valid answer, then exit loop. */
  453.             if(strlen(szNewAnswer) > 0)
  454.             {
  455.                break;
  456.             }         
  457.          }
  458.  
  459.          /* Otherwise, attempt to get answer number from user. */      
  460.          nAnswer = atoi(szUserInput) - 1;
  461.  
  462.          /* If user input is not a valid answer. */      
  463.          if(nAnswer < 0 || nAnswer >= QuestionRecord.nTotalAnswers)
  464.          {
  465.             /* Display message. */
  466.             od_printf("That is not a valid response.\n\r");
  467.             WaitForEnter();
  468.          }
  469.          else
  470.          {
  471.             /* Otherwise, exit loop. */
  472.             break;
  473.          }
  474.       }
  475.  
  476.       /* Add user's vote to question. */
  477.    
  478.       /* Open question file for exclusive access by this node. */
  479.       fpFile = ExculsiveFileOpen(QUESTION_FILENAME, "r+b");
  480.       if(fpFile == NULL)
  481.       {
  482.          /* If unable to access file, display error and return. */
  483.          od_printf("Unable to access the question file.\n\r");
  484.          WaitForEnter();
  485.          return;
  486.       }
  487.    
  488.       /* Read the answer record from disk, because it may have been changed. */
  489.       /* by another node. */
  490.       fseek(fpFile, (long)nQuestion * sizeof(tQuestionRecord), SEEK_SET);
  491.       if(fread(&QuestionRecord, sizeof(tQuestionRecord), 1, fpFile) != 1)
  492.       {
  493.          /* If unable to access file, display error and return. */
  494.          fclose(fpFile);
  495.          od_printf("Umable to read from question file.\n\r");
  496.          WaitForEnter();
  497.          return;
  498.       }
  499.    
  500.       /* If user entered their own answer, try to add it to the question. */
  501.       if(nAnswer == NEW_ANSWER)
  502.       {
  503.          /* Check that there is still room for another answer. */
  504.          if(QuestionRecord.nTotalAnswers >= MAX_ANSWERS)
  505.          {
  506.             fclose(fpFile);
  507.             od_printf("Sorry, this question already has the maximum number of answers.\n\r");
  508.             WaitForEnter();
  509.             return;
  510.          }
  511.       
  512.          /* Set answer number to number of new answer. */
  513.          nAnswer = QuestionRecord.nTotalAnswers;
  514.       
  515.          /* Add 1 to total number of answers. */
  516.          ++QuestionRecord.nTotalAnswers;
  517.       
  518.          /* Initialize new answer string and count. */
  519.          strcpy(QuestionRecord.aszAnswer[nAnswer], szNewAnswer);
  520.          QuestionRecord.auVotesForAnswer[nAnswer] = 0;
  521.       }
  522.    
  523.       /* Add user's vote to question. */
  524.       ++QuestionRecord.auVotesForAnswer[nAnswer];
  525.       ++QuestionRecord.uTotalVotes;
  526.    
  527.       /* Write the question record back to the file. */
  528.       fseek(fpFile, (long)nQuestion * sizeof(tQuestionRecord), SEEK_SET);
  529.       if(fwrite(&QuestionRecord, sizeof(tQuestionRecord), 1, fpFile) != 1)
  530.       {
  531.          /* If unable to access file, display error and return. */
  532.          fclose(fpFile);
  533.          od_printf("Umable to write question to file.\n\r");
  534.          WaitForEnter();
  535.          return;
  536.       }
  537.    
  538.       /* Close the question file to allow access by other nodes. */
  539.       fclose(fpFile);
  540.    
  541.       /* Record that user has voted on this question. */
  542.       CurrentUserRecord.bVotedOnQuestion[nQuestion] = TRUE;
  543.    
  544.       /* Open user file for exclusive access by this node. */
  545.       fpFile = ExculsiveFileOpen(USER_FILENAME, "r+b");
  546.       if(fpFile == NULL)
  547.       {
  548.          /* If unable to access file, display error and return. */
  549.          od_printf("Unable to access the user file.\n\r");
  550.          WaitForEnter();
  551.          return;
  552.       }
  553.  
  554.       /* Update the user's record in the user file. */
  555.       fseek(fpFile, nCurrentUserNumber * sizeof(tUserRecord), SEEK_SET);
  556.       if(fwrite(&CurrentUserRecord, sizeof(tUserRecord), 1, fpFile) != 1)
  557.       {
  558.          /* If unable to access file, display error and return. */
  559.          fclose(fpFile);
  560.          od_printf("Unable to write to user file.\n\r");
  561.          WaitForEnter();
  562.          return;
  563.       }
  564.    
  565.       /* Close the user file to allow access by other nodes. */
  566.       fclose(fpFile);
  567.    
  568.       /* Display the result of voting on this question to the user. */
  569.       DisplayQuestionResult(&QuestionRecord);
  570.  
  571.       /* Add 1 to count of questions that the user has voted on. */
  572.       nQuestionsVotedOn++;
  573.    }
  574. }
  575.  
  576.  
  577. /* The ViewResults function is called when the user chooses the "view    */
  578. /* results" command from the main menu. This function alows the user to  */
  579. /* choose a question from the list of questions, and then displays the   */
  580. /* results of voting on that question.                                   */
  581. void ViewResults(void)
  582. {
  583.    int nChoice;
  584.    tQuestionRecord QuestionRecord;
  585.    int nPageLocation = 0;
  586.  
  587.    /* Loop until user chooses to return to main menu. */
  588.    for(;;)
  589.    {   
  590.       /* Allow the user to choose a question from the list of questions that */
  591.       /* they have already voted on.                                         */
  592.       nChoice = ChooseQuestion(nViewResultsFrom,
  593.          "                                 View Results\n\r", &nPageLocation);
  594.  
  595.       /* If the user did not choose a question, return to main menu. */   
  596.       if(nChoice == NO_QUESTION)
  597.       {
  598.          return;
  599.       }
  600.    
  601.       /* Read the specified question number from the question file. */
  602.       if(!GetQuestion(nChoice, &QuestionRecord))
  603.       {
  604.          return;
  605.       }
  606.    
  607.       /* Display the results for the selected question. */
  608.       DisplayQuestionResult(&QuestionRecord);
  609.    }
  610. }
  611.  
  612.  
  613. /* The GetQuestion function read the record for the specified question */
  614. /* number from the question file.                                      */
  615. int GetQuestion(int nQuestion, tQuestionRecord *pQuestionRecord)
  616. {
  617.    FILE *fpQuestionFile;
  618.  
  619.    /* Open the question file for exculsive access by this node. */
  620.    fpQuestionFile = ExculsiveFileOpen(QUESTION_FILENAME, "r+b");
  621.    if(fpQuestionFile == NULL)
  622.    {
  623.       /* If unable to access file, display error and return. */
  624.       od_printf("Unable to access the question file.\n\r");
  625.       WaitForEnter();
  626.       return(FALSE);
  627.    }
  628.    
  629.    /* Move to location of question in file. */
  630.    fseek(fpQuestionFile, (long)nQuestion * sizeof(tQuestionRecord), SEEK_SET);
  631.    
  632.    /* Read the question from the file. */
  633.    if(fread(pQuestionRecord, sizeof(tQuestionRecord), 1, fpQuestionFile) != 1)
  634.    {
  635.       /* If unable to access file, display error and return. */
  636.       fclose(fpQuestionFile);
  637.       od_printf("Umable to read from question file.\n\r");
  638.       WaitForEnter();
  639.       return(FALSE);;
  640.    }
  641.    
  642.    /* Close the question file to allow access by other nodes. */
  643.    fclose(fpQuestionFile);
  644.  
  645.    /* Return with success. */
  646.    return(TRUE);
  647. }
  648.  
  649.  
  650. /* The AddQuestion() function is called when the user chooses the "add    */
  651. /* question" option from the main menu. This function allows the user     */
  652. /* to enter a new question, possible responses, and save the question for */
  653. /* other users to vote on.                                                */
  654. void AddQuestion(void)
  655. {
  656.    tQuestionRecord QuestionRecord;
  657.    FILE *fpQuestionFile;
  658.    char szLogMessage[100];
  659.  
  660.    /* Clear the screen. */
  661.    od_clr_scr();
  662.    
  663.    /* Display screen header. */
  664.    od_printf("`bright red`                                Add A Question\n\r");
  665.    od_printf("`dark red`");
  666.    if(od_control.user_ansi || od_control.user_avatar)
  667.    {
  668.       od_repeat((unsigned char)196, 79);
  669.    }
  670.    else
  671.    {
  672.       od_repeat('-', 79);
  673.    }
  674.    od_printf("\n\r\n\r");
  675.    
  676.    /* Obtain quesiton text from the user. */
  677.    od_printf("`bright green`Enter Your Question (blank line cancels)\n\r");
  678.    od_printf("`dark green`[----------------------------------------------------------------------]\n\r ");
  679.    od_input_str(QuestionRecord.szQuestion, QUESTION_STR_SIZE - 1, ' ', 255);
  680.    
  681.    /* If question was empty, then return to main menu. */
  682.    if(strlen(QuestionRecord.szQuestion) == 0)
  683.    {
  684.       return;
  685.    }
  686.    
  687.    /* Display prompt for answers. */
  688.    od_printf("\n\r`bright green`Enter Possible Answers (blank line when done)\n\r");
  689.    od_printf("`dark green`   [------------------------------]\n\r");
  690.    
  691.    /* Loop, getting answers from user. */
  692.    for(QuestionRecord.nTotalAnswers = 0;
  693.        QuestionRecord.nTotalAnswers < MAX_ANSWERS;
  694.        QuestionRecord.nTotalAnswers++)
  695.    {
  696.       /* Display prompt with answer number. */
  697.       od_printf("`bright green`%2d: `dark green`", QuestionRecord.nTotalAnswers + 1);
  698.       
  699.       /* Get string from user. */
  700.       od_input_str(QuestionRecord.aszAnswer[QuestionRecord.nTotalAnswers],
  701.          ANSWER_STR_SIZE - 1, ' ', 255);
  702.          
  703.       /* If string was empty, then exit loop. */
  704.       if(strlen(QuestionRecord.aszAnswer[QuestionRecord.nTotalAnswers]) == 0)
  705.       {
  706.          break;
  707.       }
  708.       
  709.       /* Reset count of votes for this answer to zero. */
  710.       QuestionRecord.auVotesForAnswer[QuestionRecord.nTotalAnswers] = 0;
  711.    }
  712.    
  713.    /* If no answers were supplied, then cancel, returning to main menu. */
  714.    if(QuestionRecord.nTotalAnswers == 0)
  715.    {
  716.       return;
  717.    }
  718.  
  719.    /* Ask whether users should be able to add their own answers. */
  720.    od_printf("\n\r`bright green`Should voters be able to add their own options? (Y/N) `dark green`");
  721.    
  722.    /* Get answer from user. */
  723.    if(od_get_answer("YN") == 'Y')
  724.    {
  725.       /* If user pressed the 'Y' key. */
  726.       od_printf("Yes\n\r\n\r");
  727.       
  728.       /* Record user's response. */
  729.       QuestionRecord.bCanAddAnswers = TRUE;
  730.    }
  731.    else
  732.    {
  733.       /* If user pressed the 'N' key. */
  734.       od_printf("No\n\r\n\r");
  735.  
  736.       /* Record user's response. */
  737.       QuestionRecord.bCanAddAnswers = FALSE;
  738.    }
  739.    
  740.    /* Confirm save of new question. */
  741.    od_printf("`bright green`Do you wish to save this new question? (Y/N) `dark green`");
  742.    
  743.    /* If user does not want to save the question, return to main menu now. */
  744.    if(od_get_answer("YN") == 'N')
  745.    {
  746.       return;
  747.    }
  748.  
  749.    /* Set total number of votes for this question to 0. */   
  750.    QuestionRecord.uTotalVotes = 0;
  751.    
  752.    /* Set creator name and creation time for this question. */
  753.    strcpy(QuestionRecord.szCreatorName, od_control.user_name);
  754.    QuestionRecord.lCreationTime = time(NULL);
  755.    QuestionRecord.bDeleted = FALSE;
  756.    
  757.    /* Open question file for exclusive access by this node. */
  758.    fpQuestionFile = ExculsiveFileOpen(QUESTION_FILENAME, "a+b");
  759.    if(fpQuestionFile == NULL)
  760.    {
  761.       od_printf("Unable to access the question file.\n\r");
  762.       WaitForEnter();
  763.       return;
  764.    }
  765.    
  766.    /* Determine number of records in question file. */
  767.    fseek(fpQuestionFile, 0, SEEK_END);
  768.    
  769.    /* If question file is full, display message and return to main menu */
  770.    /* after closing file.                                               */
  771.    if(ftell(fpQuestionFile) / sizeof(tQuestionRecord) >= MAX_QUESTIONS)
  772.    {
  773.       fclose(fpQuestionFile);
  774.       od_printf("Cannot add another question, EZVote is limisted to %d questions.\n\r", MAX_QUESTIONS);
  775.       WaitForEnter();
  776.       return;
  777.    }
  778.    
  779.    /* Add new question to file. */
  780.    if(fwrite(&QuestionRecord, sizeof(QuestionRecord), 1, fpQuestionFile) != 1)
  781.    {
  782.       fclose(fpQuestionFile);
  783.       od_printf("Umable to write to question file.\n\r");
  784.       WaitForEnter();
  785.       return;
  786.    }
  787.    
  788.    /* Close question file, allowing other nodes to access file. */
  789.    fclose(fpQuestionFile);
  790.  
  791.    /* Record in the logfile that user has added a new question. */
  792.    sprintf(szLogMessage, "User adding questions: %s",
  793.       QuestionRecord.szQuestion);
  794.    od_log_write(szLogMessage);
  795. }
  796.  
  797.  
  798. /* The DeleteQuestion() function is called when the sysop chooses the   */
  799. /* "delete question" option from the main menu. This function displays  */
  800. /* a list of all questions, allowing the sysop to choose a question for */
  801. /* deletion.                                                            */
  802. void DeleteQuestion(void)
  803. {
  804.    int nQuestion;
  805.    tQuestionRecord QuestionRecord;
  806.    FILE *fpFile;
  807.    int nPageLocation = 0;
  808.  
  809.    /* Check that user is system operator. */
  810.    if(strcmp(od_control.user_name, od_control.sysop_name) != 0)
  811.    {
  812.       return;
  813.    }
  814.  
  815.    /* Allow the user to choose a question from the list of all questions. */
  816.    nQuestion = ChooseQuestion(QUESTIONS_NOT_VOTED_ON | QUESTIONS_VOTED_ON,
  817.       "                              Delete A Question\n\r", &nPageLocation);
  818.  
  819.    /* If the user did not choose a question, return to main menu. */   
  820.    if(nQuestion == NO_QUESTION)
  821.    {
  822.       return;
  823.    }
  824.  
  825.    /* Read the question chosen by the user. */
  826.    if(!GetQuestion(nQuestion, &QuestionRecord))
  827.    {
  828.       /* If unable to access file, return to main menu. */
  829.       return;
  830.    }
  831.  
  832.    /* Confirm deletion of this question. */
  833.    od_printf("\n\r`bright green`Are you sure you want to delete the question:\n\r   `dark green`%s\n\r",
  834.       QuestionRecord.szQuestion);
  835.    od_printf("`bright green`[Y]es or [N]o?\n\r`dark green`");
  836.  
  837.    /* If user canceled deletion, return now. */   
  838.    if(od_get_answer("YN") == 'N')
  839.    {
  840.       return;
  841.    }
  842.  
  843.    /* Mark the question as being deleted. */
  844.    QuestionRecord.bDeleted = TRUE;
  845.    
  846.    /* Open question file for exclusive access by this node. */
  847.    fpFile = ExculsiveFileOpen(QUESTION_FILENAME, "r+b");
  848.    if(fpFile == NULL)
  849.    {
  850.       /* If unable to access file, display error and return. */
  851.       od_printf("Unable to access the question file.\n\r");
  852.       WaitForEnter();
  853.       return;
  854.    }
  855.  
  856.    /* Write the question record back to the file. */
  857.    fseek(fpFile, (long)nQuestion * sizeof(tQuestionRecord), SEEK_SET);
  858.    if(fwrite(&QuestionRecord, sizeof(tQuestionRecord), 1, fpFile) != 1)
  859.    {
  860.       /* If unable to access file, display error and return. */
  861.       fclose(fpFile);
  862.       od_printf("Umable to write question to file.\n\r");
  863.       WaitForEnter();
  864.       return;
  865.    }
  866.    
  867.    /* Close the question file to allow access by other nodes. */
  868.    fclose(fpFile);
  869. }
  870.  
  871.  
  872. /* The ChooseQuestion() function provides a list of questions and allows   */
  873. /* the user to choose a particular question, cancel back to the main menu, */ 
  874. /* and page up and down in the list of questions. Depending upon the value */
  875. /* of the nFromWhichQuestions parameter, this function will present a list */
  876. /* of questions that the user has voted on, a list of questions that the   */
  877. /* user has not voted on, or a list of all questions.                      */
  878. int ChooseQuestion(int nFromWhichQuestions, char *pszTitle, int *nLocation)
  879. {
  880.    int nCurrent;
  881.    int nFileQuestion = 0;
  882.    int nPagedToQuestion = *nLocation;
  883.    int nDisplayedQuestion = 0;
  884.    char bVotedOnQuestion;
  885.    char chCurrent;
  886.    tQuestionRecord QuestionRecord;
  887.    FILE *fpQuestionFile;
  888.    static char szQuestionName[MAX_QUESTIONS][QUESTION_STR_SIZE];
  889.    static int nQuestionNumber[MAX_QUESTIONS];
  890.    
  891.    /* Attempt to open question file. */
  892.    fpQuestionFile = ExculsiveFileOpen(QUESTION_FILENAME, "r+b");
  893.  
  894.    /* If unable to open question file, assume that no questions have been */
  895.    /* created.                                                            */
  896.    if(fpQuestionFile == NULL)
  897.    {
  898.       /* Display "no questions yet" message. */
  899.       od_printf("\n\rNo questions have been created so far.\n\r");
  900.       
  901.       /* Wait for user to press enter. */
  902.       WaitForEnter();
  903.       
  904.       /* Indicate that no question has been chosen. */
  905.       return(NO_QUESTION);
  906.    }
  907.    
  908.    /* Loop for every question record in the file. */
  909.    while(fread(&QuestionRecord, sizeof(QuestionRecord), 1, fpQuestionFile) == 1)
  910.    {
  911.       /* Determine whether or not the user has voted on this question. */
  912.       bVotedOnQuestion = CurrentUserRecord.bVotedOnQuestion[nFileQuestion];
  913.       
  914.       /* If this is the kind of question that the user is choosing from */
  915.       /* right now.                                                     */
  916.       if((bVotedOnQuestion && (nFromWhichQuestions & QUESTIONS_VOTED_ON)) ||
  917.          (!bVotedOnQuestion && (nFromWhichQuestions & QUESTIONS_NOT_VOTED_ON)))
  918.       {
  919.          /* If question is not deleted. */
  920.          if(!QuestionRecord.bDeleted)
  921.          {
  922.             /* Add this question to list to be displayed. */
  923.             strcpy(szQuestionName[nDisplayedQuestion],
  924.                QuestionRecord.szQuestion);
  925.             nQuestionNumber[nDisplayedQuestion] = nFileQuestion;
  926.          
  927.             /* Add one to number of questions to be displayed in list. */
  928.             nDisplayedQuestion++;
  929.          }
  930.       }
  931.       
  932.       /* Move to next question in file. */
  933.       ++nFileQuestion;
  934.    }   
  935.    
  936.    /* Close question file to allow other nodes to access the file. */
  937.    fclose(fpQuestionFile);
  938.  
  939.    /* If there are no questions for the user to choose, display an */
  940.    /* appropriate message and return. */
  941.    if(nDisplayedQuestion == 0)
  942.    {
  943.       /* If we were to list all questions. */
  944.       if((nFromWhichQuestions & QUESTIONS_VOTED_ON)
  945.          && (nFromWhichQuestions & QUESTIONS_NOT_VOTED_ON))
  946.       {
  947.          od_printf("\n\rThere are no questions.\n\r");
  948.       }
  949.       /* If we were to list questions that the user has voted on. */
  950.       else if(nFromWhichQuestions & QUESTIONS_VOTED_ON)
  951.       {
  952.          od_printf("\n\rThere are no questions that you have voted on.\n\r");
  953.       }
  954.       /* Otherwise, we were to list questions that use has not voted on. */
  955.       else
  956.       {
  957.          od_printf("\n\rYou have voted on all the questions.\n\r");
  958.       }
  959.       
  960.       /* Wait for user to press enter key. */
  961.       WaitForEnter();
  962.       
  963.       /* Return, indicating that no question was chosen. */
  964.       return(NO_QUESTION);
  965.    }
  966.  
  967.    /* Ensure that initial paged to location is within range. */
  968.    while(nPagedToQuestion >= nDisplayedQuestion)
  969.    {
  970.       nPagedToQuestion -= QUESTION_PAGE_SIZE;
  971.    }
  972.  
  973.    /* Loop, displaying current page of questions, until the user makes a */
  974.    /* choice.                                                            */
  975.    for(;;)
  976.    {
  977.       /* Clear the screen. */
  978.       od_clr_scr();
  979.  
  980.       /* Display header. */
  981.       od_printf("`bright red`");
  982.       od_printf(pszTitle);
  983.       od_printf("`dark red`");
  984.       if(od_control.user_ansi || od_control.user_avatar)
  985.       {
  986.          od_repeat((unsigned char)196, 79);
  987.       }
  988.       else
  989.       {
  990.          od_repeat('-', 79);
  991.       }
  992.       od_printf("\n\r");
  993.    
  994.       /* Display list of questions on this page. */
  995.       for(nCurrent = 0;
  996.          nCurrent < QUESTION_PAGE_SIZE
  997.          && nCurrent < (nDisplayedQuestion - nPagedToQuestion);
  998.          ++nCurrent)
  999.       {
  1000.          /* Determine character to display for current line. */
  1001.          if(nCurrent < 9)
  1002.          {
  1003.             chCurrent = (char)('1' + nCurrent);
  1004.          }
  1005.          else
  1006.          {
  1007.             chCurrent = (char)('A' + (nCurrent - 9));
  1008.          }
  1009.       
  1010.          /* Display this question's title. */
  1011.          od_printf("`bright green`%c.`dark green`", chCurrent);
  1012.          od_printf(" %s\n\r", szQuestionName[nCurrent + nPagedToQuestion]);
  1013.       }
  1014.  
  1015.       /* Display prompt for input. */
  1016.       od_printf("\n\r`bright white`[Page %d]  Choose a question or",
  1017.          (nPagedToQuestion / QUESTION_PAGE_SIZE) + 1);
  1018.       if(nPagedToQuestion < nDisplayedQuestion - QUESTION_PAGE_SIZE)
  1019.       {
  1020.          od_printf(" [N]ext page,");
  1021.       }
  1022.       if(nPagedToQuestion > 0)
  1023.       {
  1024.          od_printf(" [P]revious page,");
  1025.       }
  1026.       od_printf(" [Q]uit.\n\r");
  1027.       
  1028.       /* Loop until the user makes a valid choice. */
  1029.       for(;;)
  1030.       {      
  1031.          /* Get input from user */
  1032.          chCurrent = (char)od_get_key(TRUE);
  1033.          chCurrent = (char)toupper(chCurrent);
  1034.       
  1035.          /* Respond to user's input. */
  1036.       
  1037.          /* If user pressed Q key. */
  1038.          if(chCurrent == 'Q')
  1039.          {
  1040.             /* Return without a choosing a question. */
  1041.             return(NO_QUESTION);
  1042.          }
  1043.       
  1044.          /* If user pressed P key. */
  1045.          else if(chCurrent == 'P')
  1046.          {
  1047.             /* If we are not at the first page. */
  1048.             if(nPagedToQuestion > 0)
  1049.             {
  1050.                /* Move paged to location up one page. */
  1051.                nPagedToQuestion -= QUESTION_PAGE_SIZE;
  1052.                
  1053.                /* Exit user input loop to display next page. */
  1054.                break;
  1055.             }
  1056.          }
  1057.       
  1058.          /* If user pressed N key. */
  1059.          else if(chCurrent == 'N')
  1060.          {
  1061.             /* If there is more questions after this page. */
  1062.             if(nPagedToQuestion < nDisplayedQuestion - QUESTION_PAGE_SIZE)
  1063.             {
  1064.                /* Move paged.to location down one page. */
  1065.                nPagedToQuestion += QUESTION_PAGE_SIZE;
  1066.  
  1067.                /* Exit user input loop to display next page. */
  1068.                break;
  1069.             }
  1070.          }
  1071.       
  1072.          /* Otherwise, check whether the user chose a valid question. */
  1073.          else if ((chCurrent >= '1' && chCurrent <= '9')
  1074.             || (chCurrent >= 'A' && chCurrent <= 'H'))
  1075.          {
  1076.             /* Get question number from key pressed. */
  1077.             if(chCurrent >= '1' && chCurrent <= '9')
  1078.             {
  1079.                nCurrent = chCurrent - '1';
  1080.             }
  1081.             else
  1082.             {
  1083.                nCurrent = (chCurrent - 'A') + 9;
  1084.             }
  1085.          
  1086.             /* Add current paged to position to user's choice. */
  1087.             nCurrent += nPagedToQuestion;
  1088.  
  1089.             /* If this is valid question number. */            
  1090.             if(nCurrent < nDisplayedQuestion)
  1091.             {
  1092.                /* Set caller's current question number. */
  1093.                *nLocation = nPagedToQuestion;
  1094.             
  1095.                /* Return actual question number in file. */
  1096.                return(nQuestionNumber[nCurrent]);
  1097.             }
  1098.          }
  1099.       }
  1100.    }
  1101. }
  1102.  
  1103.  
  1104. /* The DisplayQuestionResult() function is called to display the results */
  1105. /* of voting on a paricular question, and is passed the question record  */
  1106. /* of the question. This function is called when the user selects a      */
  1107. /* question using the "view results" option, and is also called after    */
  1108. /* the user has voted on a question, to display the results of voting on */
  1109. /* that question.                                                        */
  1110. void DisplayQuestionResult(tQuestionRecord *pQuestionRecord)
  1111. {
  1112.    int nAnswer;
  1113.    int uPercent;
  1114.  
  1115.    /* Clear the screen. */
  1116.    od_clr_scr();
  1117.  
  1118.    /* Check that there have been votes on this question. */
  1119.    if(pQuestionRecord->uTotalVotes == 0)
  1120.    {
  1121.       /* If there have been no votes for this question, display a message */
  1122.       /* and return.                                                      */
  1123.       od_printf("Nobody has voted on this question yet.\n\r");
  1124.       WaitForEnter();
  1125.       return;
  1126.    }
  1127.  
  1128.    /* Display question itself. */
  1129.    od_printf("`bright red`%s\n\r", pQuestionRecord->szQuestion);
  1130.  
  1131.    /* Display author's name. */
  1132.    od_printf("`dark red`Question created by %s on %s\n\r",
  1133.       pQuestionRecord->szCreatorName,
  1134.       ctime(&pQuestionRecord->lCreationTime));
  1135.    
  1136.    /* Display heading for responses. */
  1137.    od_printf("`bright green`Response                        Votes  Percent  Graph\n\r`dark green`");
  1138.    if(od_control.user_ansi || od_control.user_avatar)
  1139.    {
  1140.       od_repeat((unsigned char)196, 79);
  1141.    }
  1142.    else
  1143.    {
  1144.       od_repeat('-', 79);
  1145.    }
  1146.    od_printf("\n\r");
  1147.  
  1148.    /* Loop for each answer to the question. */   
  1149.    for(nAnswer = 0; nAnswer < pQuestionRecord->nTotalAnswers; ++nAnswer)
  1150.    {
  1151.       /* Determine percent of users who voted for this answer. */
  1152.       uPercent = (pQuestionRecord->auVotesForAnswer[nAnswer] * 100)
  1153.          / pQuestionRecord->uTotalVotes;
  1154.       
  1155.       /* Display answer, total votes and percentage of votes. */
  1156.       od_printf("`dark green`%-30.30s  %-5u  %3u%%     `bright white`",
  1157.          pQuestionRecord->aszAnswer[nAnswer],
  1158.          pQuestionRecord->auVotesForAnswer[nAnswer],
  1159.          uPercent);
  1160.  
  1161.       /* Display a bar graph corresponding to percent of users who voted */
  1162.       /* for this answer.                                                */
  1163.       if(od_control.user_ansi || od_control.user_avatar)
  1164.       {
  1165.          od_repeat((unsigned char)220, (unsigned char)((uPercent * 31) / 100));
  1166.       }
  1167.       else
  1168.       {
  1169.          od_repeat('=', (unsigned char)((uPercent * 31) / 100));
  1170.       }
  1171.  
  1172.       /* Move to next line. */
  1173.       od_printf("\n\r");
  1174.    }
  1175.    
  1176.    /* Display footer. */
  1177.    od_printf("`dark green`");
  1178.    if(od_control.user_ansi || od_control.user_avatar)
  1179.    {
  1180.       od_repeat((unsigned char)196, 79);
  1181.    }
  1182.    else
  1183.    {
  1184.       od_repeat('-', 79);
  1185.    }
  1186.    od_printf("\n\r");
  1187.    od_printf("`dark green`                         TOTAL: %u\n\r\n\r",
  1188.       pQuestionRecord->uTotalVotes);
  1189.    
  1190.    /* Wait for user to press enter. */
  1191.    WaitForEnter();
  1192. }
  1193.  
  1194.  
  1195. /* The ReadOrAddCurrentUser() function is used by EZVote to search the    */
  1196. /* EZVote user file for the record containing information on the user who */
  1197. /* is currently using the door. If this is the first time that the user   */
  1198. /* has used this door, then their record will not exist in the user file. */
  1199. /* In this case, this function will add a new record for the current      */
  1200. /* user. This function returns TRUE on success, or FALSE on failure.      */
  1201. int ReadOrAddCurrentUser(void)
  1202. {
  1203.    FILE *fpUserFile;
  1204.    int bGotUser = FALSE;
  1205.    int nQuestion;
  1206.  
  1207.    /* Attempt to open the user file for exclusize access by this node.     */
  1208.    /* This function will wait up to the pre-set amount of time (as defined */   
  1209.    /* near the beginning of this file) for access to the user file.        */
  1210.    fpUserFile = ExculsiveFileOpen(USER_FILENAME, "a+b");
  1211.  
  1212.    /* If unable to open user file, return with failure. */   
  1213.    if(fpUserFile == NULL)
  1214.    {
  1215.       return(FALSE);
  1216.    }
  1217.  
  1218.    /* Begin with the current user record number set to 0. */
  1219.    nCurrentUserNumber = 0;
  1220.  
  1221.    /* Loop for each record in the file */
  1222.    while(fread(&CurrentUserRecord, sizeof(tUserRecord), 1, fpUserFile) == 1)
  1223.    {
  1224.       /* If name in record matches the current user name ... */
  1225.       if(strcmp(CurrentUserRecord.szUserName, od_control.user_name) == 0)
  1226.       {
  1227.          /* ... then record that we have found the user's record, */
  1228.          bGotUser = TRUE;
  1229.          
  1230.          /* and exit the loop. */
  1231.          break;
  1232.       }
  1233.  
  1234.       /* Move user record number to next user record. */      
  1235.       nCurrentUserNumber++;
  1236.    }
  1237.  
  1238.    /* If the user was not found in the file, attempt to add them as a */
  1239.    /* new user if the user file is not already full.                  */
  1240.    if(!bGotUser && nCurrentUserNumber < MAX_USERS)
  1241.    {
  1242.       /* Place the user's name in the current user record. */
  1243.       strcpy(CurrentUserRecord.szUserName, od_control.user_name);
  1244.       
  1245.       /* Record that user hasn't voted on any of the questions. */
  1246.       for(nQuestion = 0; nQuestion < MAX_QUESTIONS; ++nQuestion)
  1247.       {
  1248.          CurrentUserRecord.bVotedOnQuestion[nQuestion] = FALSE;
  1249.       }
  1250.       
  1251.       /* Write the new record to the file. */
  1252.       if(fwrite(&CurrentUserRecord, sizeof(tUserRecord), 1, fpUserFile) == 1)
  1253.       {
  1254.          /* If write succeeded, record that we now have a valid user record. */
  1255.          bGotUser = TRUE;
  1256.       }
  1257.    }
  1258.  
  1259.    /* Close the user file to allow other nodes to access it. */
  1260.    fclose(fpUserFile);
  1261.  
  1262.    /* Return, indciating whether or not a valid user record now exists for */
  1263.    /* the user that is currently online.                                   */   
  1264.    return(bGotUser);
  1265. }
  1266.  
  1267.  
  1268. /* The WriteCurrentUser() function is called to save the information on the */
  1269. /* user who is currently using the door, to the EZVOTE.USR file.            */
  1270. void WriteCurrentUser(void)
  1271. {
  1272.    FILE *fpUserFile;
  1273.  
  1274.    /* Attempt to open the user file for exclusize access by this node.     */
  1275.    /* This function will wait up to the pre-set amount of time (as defined */   
  1276.    /* near the beginning of this file) for access to the user file.        */
  1277.    fpUserFile = ExculsiveFileOpen(USER_FILENAME, "r+b");
  1278.  
  1279.    /* If unable to access the user file, display an error message and */
  1280.    /* return.                                                         */
  1281.    if(fpUserFile == NULL)
  1282.    {
  1283.       od_printf("Unable to access the user file.\n\r");
  1284.       WaitForEnter();
  1285.       return;
  1286.    }
  1287.    
  1288.    /* Move to appropriate location in user file for the current user's */
  1289.    /* record. */
  1290.    fseek(fpUserFile, (long)nCurrentUserNumber * sizeof(tUserRecord), SEEK_SET);
  1291.  
  1292.    /* Write the new record to the file. */
  1293.    if(fwrite(&CurrentUserRecord, sizeof(tUserRecord), 1, fpUserFile) == 1)
  1294.    {
  1295.       /* If unable to write the record, display an error message. */
  1296.       fclose(fpUserFile);
  1297.       od_printf("Unable to update your user record file.\n\r");
  1298.       WaitForEnter();
  1299.       return;
  1300.    }
  1301.    
  1302.    /* Close the user file to allow other nodes to access it again. */
  1303.    fclose(fpUserFile);
  1304. }
  1305.  
  1306.  
  1307. /* This function opens the specified file in the specified mode for         */
  1308. /* exculsive access by this node; while the file is open, other nodes will  */
  1309. /* be unable to open the file. This function will wait for up to the number */
  1310. /* of seconds set by FILE_ACCESS_MAX_WAIT, which is defined near the        */
  1311. /* beginning of this file.                                                  */
  1312. FILE *ExculsiveFileOpen(char *pszFileName, char *pszMode)
  1313. {
  1314.    FILE *fpFile = NULL;
  1315.    time_t StartTime = time(NULL);
  1316.  
  1317.    /* Attempt to open the file while there is still time remaining. */    
  1318.    while((fpFile = fopen(pszFileName, pszMode)) == NULL
  1319.       && errno == EACCES
  1320.       && difftime(time(NULL), StartTime) < FILE_ACCESS_MAX_WAIT)
  1321.    {
  1322.       /* If we were unable to open the file, call od_kernal, so that    */
  1323.       /* OpenDoors can continue to respond to sysop function keys, loss */
  1324.       /* of connection, etc.                                            */
  1325.       od_kernal();
  1326.    }
  1327.  
  1328.    /* Return FILE pointer for opened file, if any. */   
  1329.    return(fpFile);
  1330. }
  1331.  
  1332.  
  1333. /* The WaitForEnter() function is used by EZVote to create its custom */
  1334. /* "Press [ENTER] to continue." prompt.                               */
  1335. void WaitForEnter(void)
  1336. {
  1337.    /* Display prompt. */
  1338.    od_printf("`bright white`Press [ENTER] to continue.\n\r");
  1339.    
  1340.    /* Wait for a Carriage Return or Line Feed character from the user. */
  1341.    od_get_answer("\n\r");
  1342. }
  1343.