632eb504ec19fd8b579d4b1ff0f9ad01ba2f21dd
[tpg/opendispense2.git] / src / server / itemdb.c
1 /*
2  * OpenDispense 2 
3  * UCC (University [of WA] Computer Club) Electronic Accounting System
4  *
5  * itemdb.c - Dispense Item Databse
6  *
7  * This file is licenced under the 3-clause BSD Licence. See the file COPYING
8  * for full details.
9  */
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <ctype.h>
14 #include "common.h"
15 #include <regex.h>
16 #include <sys/stat.h>
17 #include <time.h>
18
19 #define DUMP_ITEMS      0
20
21 // === IMPORTS ===
22 extern tHandler gCoke_Handler;
23 extern tHandler gSnack_Handler;
24 extern tHandler gDoor_Handler;
25
26 // === PROTOTYPES ===
27 void    Init_Handlers(void);
28 void    Load_Itemlist(void);
29 void    Items_ReadFromFile(void);
30 char    *trim(char *__str);
31
32 // === GLOBALS ===
33  int    giNumItems = 0;
34 tItem   *gaItems = NULL;
35 time_t  gItems_LastUpdated;
36 tHandler        gPseudo_Handler = {.Name="pseudo"};
37 tHandler        *gaHandlers[] = {&gPseudo_Handler, &gCoke_Handler, &gSnack_Handler, &gDoor_Handler};
38  int    giNumHandlers = sizeof(gaHandlers)/sizeof(gaHandlers[0]);
39 char    *gsItemListFile = DEFAULT_ITEM_FILE;
40 #if USE_INOTIFY
41  int    giItem_INotifyFD;
42 #endif
43 regex_t gItemFile_Regex;
44
45 // === CODE ===
46 void Init_Handlers()
47 {
48          int    i;
49         for( i = 0; i < giNumHandlers; i ++ )
50         {
51                 if( gaHandlers[i]->Init )
52                         gaHandlers[i]->Init(0, NULL);   // TODO: Arguments
53         }
54         
55         // Use inotify to watch the snack config file
56         #if USE_INOTIFY
57         {
58                 int oflags;
59                 
60                 giItem_INotifyFD = inotify_init();
61                 inotify_add_watch(giItem_INotifyFD, gsItemListFile, IN_MODIFY);
62                 
63                 // Handle SIGIO
64                 signal(SIGIO, &ItemList_Changed);
65                 
66                 // Fire SIGIO when data is ready on the FD
67                 fcntl(giItem_INotifyFD, F_SETOWN, getpid());
68                 oflags = fcntl(giItem_INotifyFD, F_GETFL);
69                 fcntl(giItem_INotifyFD, F_SETFL, oflags | FASYNC);
70         }
71         #endif
72 }
73
74 #if USE_INOTIFY
75 void ItemList_Changed(int signum)
76 {
77         char    buf[512];
78         read(giItem_INotifyFD, buf, 512);
79         Load_Itemlist();
80         
81         signum = 0;     // Shut up GCC
82 }
83 #endif
84
85 /**
86  * \brief Read the initial item list
87  */
88 void Load_Itemlist(void)
89 {
90          int    rv;
91         rv = regcomp(&gItemFile_Regex, "^-?([a-zA-Z][a-zA-Z]*)\\s+([0-9]+)\\s+([0-9]+)\\s+(.*)", REG_EXTENDED);
92         if( rv )
93         {
94                 size_t  len = regerror(rv, &gItemFile_Regex, NULL, 0);
95                 char    errorStr[len];
96                 regerror(rv, &gItemFile_Regex, errorStr, len);
97                 fprintf(stderr, "Rexex compilation failed - %s\n", errorStr);
98                 exit(-1);
99         }
100         
101         Items_ReadFromFile();
102         
103         // Re-read the item file periodically
104         // TODO: Be less lazy here and check the timestamp
105         AddPeriodicFunction( Items_ReadFromFile );
106 }
107
108 /**
109  * \brief Read the item list from disk
110  */
111 void Items_ReadFromFile(void)
112 {
113         FILE    *fp;
114         char    buffer[BUFSIZ];
115         char    *line;
116          int    lineNum = 0;
117          int    i, numItems = 0;
118         tItem   *items = NULL;
119         regmatch_t      matches[5];
120
121         if( gItems_LastUpdated ) 
122         {
123                 struct stat buf;
124                 if( stat(gsItemListFile, &buf) ) {
125                         fprintf(stderr, "Unable to stat() item file '%s'\n", gsItemListFile);
126                         return ;
127                 }
128                 
129                 // Check if the update is needed
130                 if( gItems_LastUpdated > buf.st_mtime )
131                         return ;
132         }
133
134         // Error check
135         fp = fopen(gsItemListFile, "r");
136         if(!fp) {
137                 fprintf(stderr, "Unable to open item file '%s'\n", gsItemListFile);
138                 perror("Unable to open item file");
139                 return ;
140         }
141         
142         while( fgets(buffer, BUFSIZ, fp) )
143         {
144                 char    *tmp;
145                 char    *type, *desc;
146                  int    num, price;
147                 tHandler        *handler;
148
149                 lineNum ++;
150
151                 // Remove comments
152                 tmp = strchr(buffer, '#');
153                 if(tmp) *tmp = '\0';
154                 tmp = strchr(buffer, ';');
155                 if(tmp) *tmp = '\0';
156                 
157                 // Trim whitespace
158                 line = trim(buffer);
159                 
160                 if(strlen(line) == 0)   continue;
161                 
162                 // Pass regex over line
163                 if( RunRegex( &gItemFile_Regex, line, 5, matches, NULL) ) {
164                         fprintf(stderr, "Syntax error on line %i of item file '%s'\n", lineNum, gsItemListFile);
165                         return ;
166                 }
167
168                 // Read line data
169                 type  = line + matches[1].rm_so;        line[ matches[1].rm_eo ] = '\0';
170                 num   = atoi( line + matches[2].rm_so );
171                 price = atoi( line + matches[3].rm_so );
172                 desc  = line + matches[4].rm_so;        
173
174                 #if DUMP_ITEMS
175                 printf("Item '%s' - %i cents, %s:%i\n", desc, price, type, num);
176                 #endif
177
178                 handler = NULL;
179                 for( i = 0; i < giNumHandlers; i ++ )
180                 {
181                         if( strcmp(type, gaHandlers[i]->Name) == 0 ) {
182                                 handler = gaHandlers[i];
183                                 break;
184                         }
185                 }
186
187                 if( !handler ) {
188                         fprintf(stderr, "Unknow item type '%s' on line %i (%s)\n", type, lineNum, desc);
189                         continue ;
190                 }
191
192                 for( i = 0; i < numItems; i ++ )
193                 {
194                         if( items[i].Handler != handler )       continue;
195                         if( items[i].ID != num )        continue;
196
197                         #if DUMP_ITEMS
198                         printf("Redefinition of %s:%i, updated\n", handler->Name, num);
199                         #endif
200                         items[i].Price = price;
201                         free(items[i].Name);
202                         items[i].Name = strdup(desc);
203                         break;
204                 }
205                 if( i < numItems )      continue;
206
207                 items = realloc( items, (numItems + 1)*sizeof(items[0]) );
208                 items[numItems].Handler = handler;
209                 items[numItems].ID = num;
210                 if( gbNoCostMode )
211                         items[numItems].Price = 0;
212                 else
213                         items[numItems].Price = price;
214                 items[numItems].Name = strdup(desc);
215                 items[numItems].bHidden = (line[0] == '-');
216                 numItems ++;
217         }
218         
219         // Clean up old
220         if( giNumItems )
221         {
222                 giNumItems = 0;
223                 free(gaItems);
224                 gaItems = NULL;
225         }
226         
227         // Replace with new
228         giNumItems = numItems;
229         gaItems = items;
230         
231         gItems_LastUpdated = time(NULL);
232 }
233
234 /**
235  * \brief Update the item file from the internal database
236  */
237 void Items_UpdateFile(void)
238 {
239         FILE    *fp;
240         char    buffer[BUFSIZ];
241         char    *line;
242          int    lineNum = 0;
243          int    i;
244         regmatch_t      matches[5];
245         char    **line_comments;
246          int    *line_items;
247
248         // Error check
249         fp = fopen(gsItemListFile, "r");
250         if(!fp) {
251                 fprintf(stderr, "Unable to open item file '%s'\n", gsItemListFile);
252                 perror("Unable to open item file");
253                 return ;
254         }
255         
256         // Count lines
257         while( fgets(buffer, BUFSIZ, fp) )
258         {
259                 lineNum ++;
260         }
261         
262         line_comments = malloc(lineNum * sizeof(char*));
263         line_items = malloc(lineNum * sizeof(int));
264         
265         // Parse file
266         lineNum = 0;
267         fseek(fp, 0, SEEK_SET);
268         while( fgets(buffer, BUFSIZ, fp) )
269         {
270                 char    *hashPos, *semiPos;
271                 char    *type;
272                  int    num;
273                 tHandler        *handler;
274
275                 trim(buffer);
276
277                 lineNum ++;
278                 line_items[lineNum-1] = -1;
279                 line_comments[lineNum-1] = NULL;
280
281                 // Get comments
282                 hashPos = strchr(buffer, '#');
283                 semiPos = strchr(buffer, ';');
284                 if( hashPos && semiPos ) {
285                         if( hashPos < semiPos )
286                                 line_comments[lineNum-1] = strdup(hashPos);
287                 }
288                 else if( hashPos ) {
289                         line_comments[lineNum-1] = strdup(hashPos);
290                 }
291                 else if( semiPos ) {
292                         line_comments[lineNum-1] = strdup(semiPos);
293                 }
294                 if(hashPos)     *hashPos = '\0';
295                 if(semiPos)     *semiPos = '\0';
296                 
297                 // Trim whitespace
298                 line = trim(buffer);
299                 if(strlen(line) == 0)   continue;
300                 
301                 // Pass regex over line
302                 if( RunRegex( &gItemFile_Regex, line, 5, matches, NULL) ) {
303                         fprintf(stderr, "Syntax error on line %i of item file '%s'\n", lineNum, gsItemListFile);
304                         return ;
305                 }
306
307                 // Read line data
308                 type  = line + matches[1].rm_so;        line[ matches[1].rm_eo ] = '\0';
309                 num   = atoi( line + matches[2].rm_so );
310
311                 // Find handler
312                 handler = NULL;
313                 for( i = 0; i < giNumHandlers; i ++ )
314                 {
315                         if( strcmp(type, gaHandlers[i]->Name) == 0 ) {
316                                 handler = gaHandlers[i];
317                                 break;
318                         }
319                 }
320                 if( !handler ) {
321                         fprintf(stderr, "Warning: Unknown item type '%s' on line %i\n", type, lineNum);
322                         continue ;
323                 }
324
325                 for( i = 0; i < giNumItems; i ++ )
326                 {
327                         if( gaItems[i].Handler != handler )     continue;
328                         if( gaItems[i].ID != num )      continue;
329                         
330                         line_items[lineNum-1] = i;
331                         break;
332                 }
333                 if( i >= giNumItems ) {
334                         continue;
335                 }
336         }
337         
338         fclose(fp);
339         
340         //fp = fopen("items.cfg.new", "w");     // DEBUG: Don't kill the real item file until debugged
341         fp = fopen(gsItemListFile, "w");
342         
343         // Create new file
344         {
345                  int    done_items[giNumItems];
346                 memset(done_items, 0, sizeof(done_items));
347                 
348                 // Existing items
349                 for( i = 0; i < lineNum; i ++ )
350                 {
351                         if( line_items[i] != -1 ) {
352                                 tItem   *item = &gaItems[ line_items[i] ];
353                                 
354                                 if( done_items[ line_items[i] ] ) {
355                                         fprintf(fp, "; DUP -");
356                                 }
357                                 done_items[ line_items[i] ] = 1;
358                                 
359                                 if( item->bHidden )
360                                         fprintf(fp, "-");
361                                 
362                                 fprintf(fp, "%s\t%i\t%i\t%s\t",
363                                         item->Handler->Name, item->ID, item->Price, item->Name
364                                         );
365                         }
366                         
367                         if( line_comments[i] ) {
368                                 fprintf(fp, "%s", line_comments[i]);
369                                 free( line_comments[i] );
370                         }
371                         
372                         fprintf(fp, "\n");
373                 }
374                 
375                 // New items
376                 for( i = 0; i < giNumItems; i ++ )
377                 {
378                         tItem   *item = &gaItems[i];
379                         if( done_items[i] )     continue ;
380                         
381                         if( item->bHidden )
382                                 fprintf(fp, "-");
383                         
384                         fprintf(fp, "%s\t%i\t%i\t%s\n",
385                                 item->Handler->Name, item->ID, item->Price, item->Name
386                                 );
387                 }
388         }
389         
390         free( line_comments );
391         free( line_items );
392         fclose(fp);
393 }
394
395
396 char *trim(char *__str)
397 {
398         char    *ret;
399          int    i;
400         
401         while( isspace(*__str) )
402                 __str++;
403         ret = __str;
404
405         i = strlen(ret);
406         while( i-- && isspace(__str[i]) ) {
407                 __str[i] = '\0';
408         }
409
410         return ret;
411 }

UCC git Repository :: git.ucc.asn.au