home *** CD-ROM | disk | FTP | other *** search
/ Visual Basic Source Code / Visual Basic Source Code.iso / vbsource / opndor / ex_vote.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-02-26  |  45.9 KB  |  1,297 lines

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