ex_vote.c 45 KB

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