Merge branch 'master' of ssh://git.code.sf.net/p/feistymeow/trunk into dev
[feisty_meow.git] / nucleus / applications / nechung / nechung_oracle.cpp
1 /*****************************************************************************\
2 *                                                                             *
3 *  Name   : nechung_oracle                                                    *
4 *  Author : Chris Koeritz                                                     *
5 *                                                                             *
6 *******************************************************************************
7 * Copyright (c) 1991-$now By Author.  This program is free software; you can  *
8 * redistribute it and/or modify it under the terms of the GNU General Public  *
9 * License as published by the Free Software Foundation; either version 2 of   *
10 * the License or (at your option) any later version.  This is online at:      *
11 *     http://www.fsf.org/copyleft/gpl.html                                    *
12 * Please send any updates to: fred@gruntose.com                               *
13 *****************************************************************************/
14
15 #include "nechung_oracle.h"
16
17 #include <basis/astring.h>
18 #include <filesystem/byte_filer.h>
19 #include <filesystem/file_time.h>
20 #include <loggers/critical_events.h>
21 #include <loggers/program_wide_logger.h>
22
23 #include <stdio.h>
24 #include <string.h>
25
26 //using namespace application;
27 using namespace basis;
28 using namespace filesystem;
29 using namespace loggers;
30
31 #undef LOG
32 #define LOG(s) program_wide_logger::get().log(s, 0)
33 ///hmmm: fix filter value to be ALWAYS_PRINT!
34
35 const int MAX_LINE_LENGTH = 2048;
36
37 nechung_oracle::nechung_oracle(const astring &nechung_filename,
38     const astring &index_filename)
39 : c_randomizer(),
40   c_filename_held(nechung_filename),
41   c_index_held(index_filename),
42   c_number_of_fortunes(0)
43 { parse_file(); }
44
45 nechung_oracle::~nechung_oracle() {}
46
47 void nechung_oracle::parse_file()
48 {
49   FUNCDEF("parse_file");
50   // below is code for comparing dates on the fortune file and the index file.
51   byte_filer fortune_file(c_filename_held.s(), "rb");
52 #ifdef DEBUG_NECHUNG
53   LOG(astring("filename=") + c_filename_held + " idx file=" + c_index_held);
54 #endif
55   if (!fortune_file.good())
56     non_continuable_error(class_name(), func, "Cannot open fortune file.");
57
58   byte_array buffer(MAX_LINE_LENGTH + 1);
59     // used throughout parsing for line storage.
60
61   byte_filer index_file(c_index_held.observe(), "r");
62   if (index_file.good()) {
63 #ifdef DEBUG_NECHUNG
64     LOG("index file exists");
65 #endif
66     file_time index_time((FILE *)index_file.file_handle());
67     file_time fortune_time((FILE *)fortune_file.file_handle());
68     if (index_time >= fortune_time) {
69       // need to read in the list of indices
70       index_file.getline(buffer, MAX_LINE_LENGTH);
71       sscanf((char *)buffer.access(), "%d", &c_number_of_fortunes);
72 #ifdef DEBUG_NECHUNG
73       LOG(astring(astring::SPRINTF, "%d entries in index", 
74           c_number_of_fortunes));
75 #endif
76       return;
77     }
78   }
79   index_file.close();
80
81   // below is code for creating the list.
82   enum fortune_states {
83     chowing_separators,  // looking for the breaks between fortunes.
84     adding_fortunes,     // saw the separator so get ready for a new fortune.
85     chowing_fortunes,    // currently in a fortune accumulating lines.
86     done_parsing         // finished parsing the fortune file.
87   };
88
89   c_number_of_fortunes = 0;
90   fortune_states state = chowing_separators;
91
92   int posn;
93   int_array fortune_posns;  // our list of fortunes.
94   while (state != done_parsing) {
95 #ifdef DEBUG_NECHUNG
96     LOG(astring(astring::SPRINTF, "#%d", c_number_of_fortunes));
97 #endif
98     if (fortune_file.eof()) {
99       // exit from the loop now...
100       state = done_parsing;
101       continue;
102     }
103     switch (state) {
104       case chowing_separators: {
105 #ifdef DEBUG_NECHUNG
106         LOG("chowseps, ");
107 #endif
108         posn = int(fortune_file.tell());
109         if (posn < 0)
110           non_continuable_error(class_name(), func, "Cannot get file position.");
111         fortune_file.getline(buffer, MAX_LINE_LENGTH);
112 #ifdef DEBUG_NECHUNG
113         LOG(astring("got a line: ") + buffer);
114 #endif
115         if (buffer[0] != NECHUNG_SEPARATION_CHARACTER) state = adding_fortunes;
116         else {
117           // special casing is for when we see a separator on the line
118           // by itself versus when it is the beginning of a line.  if the
119           // beginning of a line, we currently take that to mean the rest
120           // of the line is the fortune.
121           if (strlen((char *)buffer.access()) == 2) posn += 2;
122           else posn++;
123           state = adding_fortunes;
124         }
125         break;
126       }
127       case adding_fortunes: {
128 #ifdef DEBUG_NECHUNG
129         LOG("add forts, ");
130 #endif
131         fortune_posns += posn;
132         c_number_of_fortunes++;
133         state = chowing_fortunes;
134         break;
135       }
136       case chowing_fortunes: {
137 #ifdef DEBUG_NECHUNG
138         LOG("chow forts, ");
139 #endif
140         posn = int(fortune_file.tell());
141         if (posn < 0)
142           non_continuable_error(class_name(), func, "Cannot get file size.");
143         fortune_file.getline(buffer, MAX_LINE_LENGTH);
144 #ifdef DEBUG_NECHUNG
145         LOG(astring(astring::SPRINTF, "got a line: %s", buffer.access()));
146         LOG(astring(astring::SPRINTF, "len is %d", strlen((char *)buffer.access())));
147 #endif
148         if ( (buffer[0] == NECHUNG_SEPARATION_CHARACTER)
149             && (strlen((char *)buffer.access()) == 2) )
150           state = chowing_separators;
151         else if (buffer[0] == NECHUNG_SEPARATION_CHARACTER) {
152           posn++;
153           state = adding_fortunes;
154         }
155         break;
156       }
157       case done_parsing: {
158         non_continuable_error(class_name(), func, "Illegal state reached.");
159       }
160     }
161   }
162   fortune_file.close();
163
164   // make a new index file.
165   index_file.open(c_index_held.observe(), "w");
166   if (!index_file.good())
167     non_continuable_error(class_name(), func, astring("Cannot open index file: ") + c_index_held);
168   astring to_write(astring::SPRINTF, "%d\n", c_number_of_fortunes);
169   index_file.write((abyte *)to_write.s(), to_write.length());
170   for (int j = 0; j < c_number_of_fortunes; j++) {
171     to_write.sprintf("%d\n", fortune_posns[j]);
172     index_file.write((abyte *)to_write.s(), to_write.length());
173   }
174   index_file.close();
175 }
176
177 astring nechung_oracle::pick_random()
178 {
179   FUNCDEF("pick_random");
180 #ifdef DEBUG_NECHUNG
181   LOG(astring("got to ") + func);
182 #endif
183
184   byte_filer fortune_file(c_filename_held.s(), "rb");
185
186 ///printf("num forts = %d\n", c_number_of_fortunes );
187
188   if (!fortune_file.good())
189     non_continuable_error(class_name(), func, "Cannot open data file.");
190   int to_display = c_randomizer.inclusive(0, c_number_of_fortunes - 1);
191
192 ///printf("rand chose= %d\n", to_display);
193
194 /////
195 ///hmmm: this bit could be more efficient by just jumping to the Nth line
196 ///      instead of reading through up to the Nth line.
197 /////
198   byte_filer index_file(c_index_held.observe(), "r");
199   int chosen_posn = 0;  // which position to read the chosen line at.
200   if (index_file.good()) {
201     astring accumulated_text;
202     byte_array buffer(MAX_LINE_LENGTH + 1);
203     for (int i = 0; i <= to_display; i++) {
204 #ifdef DEBUG_NECHUNG
205       accumulated_text += astring(astring::SPRINTF, "#%d: ", i);
206 #endif
207       index_file.getline(buffer, MAX_LINE_LENGTH);
208       sscanf((char *)buffer.access(), "%d", &chosen_posn);
209 #ifdef DEBUG_NECHUNG
210       accumulated_text += astring(astring::SPRINTF, "%d, ", chosen_posn);
211       if ((i + 1) % 5 == 0) accumulated_text += "\n";
212 #endif
213     }
214 #ifdef DEBUG_NECHUNG
215     LOG(accumulated_text);
216 #endif
217     
218   } else {
219     non_continuable_error(class_name(), func, \
220         astring("Could not open the index file \"") + c_index_held + "\"");
221   }
222   index_file.close();
223 #ifdef DEBUG_NECHUNG
224   LOG(astring(astring::SPRINTF, "about to seek @ num %d and "
225       "index %d", to_display, chosen_posn));
226 #endif
227   if (!fortune_file.seek(chosen_posn, byte_filer::FROM_START))
228     non_continuable_error(class_name(), func, "Cannot seek to indexed position.");
229 #ifdef DEBUG_NECHUNG
230   LOG("after seek");
231 #endif
232
233   astring to_return;
234   byte_array temp(MAX_LINE_LENGTH + 1);
235   while (!fortune_file.eof()) {
236     int chars_read = fortune_file.getline(temp, MAX_LINE_LENGTH);
237     if (!chars_read) {
238       if (!fortune_file.eof()) {
239         non_continuable_error(class_name(), func, "Error while reading fortune.");
240       } else break;
241     }
242     if (temp[0] == NECHUNG_SEPARATION_CHARACTER) break;
243     else to_return += astring((char *)temp.access());
244   }
245   return to_return;
246 }
247
248 //hmmm: stolen from parser bits.  reconnect when available.
249 bool is_eol(char to_check)
250 { return (to_check == '\n') || (to_check == '\r'); }
251
252 void nechung_oracle::display_random()
253 {
254   astring to_show = pick_random();
255   while (is_eol(to_show[to_show.end()]))
256     to_show.zap(to_show.end(), to_show.end());
257   LOG(to_show);
258 }
259