Misc changes - Fixed coke, shut up boot, fixed disabled users dispensing
[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                 items[numItems].Price = price;
211                 items[numItems].Name = strdup(desc);
212                 items[numItems].bHidden = (line[0] == '-');
213                 numItems ++;
214         }
215         
216         // Clean up old
217         if( giNumItems )
218         {
219                 giNumItems = 0;
220                 free(gaItems);
221                 gaItems = NULL;
222         }
223         
224         // Replace with new
225         giNumItems = numItems;
226         gaItems = items;
227         
228         gItems_LastUpdated = time(NULL);
229 }
230
231 /**
232  * \brief Update the item file from the internal database
233  */
234 void Items_UpdateFile(void)
235 {
236         FILE    *fp;
237         char    buffer[BUFSIZ];
238         char    *line;
239          int    lineNum = 0;
240          int    i;
241         regmatch_t      matches[5];
242         char    **line_comments;
243          int    *line_items;
244
245         // Error check
246         fp = fopen(gsItemListFile, "r");
247         if(!fp) {
248                 fprintf(stderr, "Unable to open item file '%s'\n", gsItemListFile);
249                 perror("Unable to open item file");
250                 return ;
251         }
252         
253         // Count lines
254         while( fgets(buffer, BUFSIZ, fp) )
255         {
256                 lineNum ++;
257         }
258         
259         line_comments = malloc(lineNum * sizeof(char*));
260         line_items = malloc(lineNum * sizeof(int));
261         
262         // Parse file
263         lineNum = 0;
264         fseek(fp, 0, SEEK_SET);
265         while( fgets(buffer, BUFSIZ, fp) )
266         {
267                 char    *hashPos, *semiPos;
268                 char    *type;
269                  int    num;
270                 tHandler        *handler;
271
272                 trim(buffer);
273
274                 lineNum ++;
275                 line_items[lineNum-1] = -1;
276                 line_comments[lineNum-1] = NULL;
277
278                 // Get comments
279                 hashPos = strchr(buffer, '#');
280                 semiPos = strchr(buffer, ';');
281                 if( hashPos && semiPos ) {
282                         if( hashPos < semiPos )
283                                 line_comments[lineNum-1] = strdup(hashPos);
284                 }
285                 else if( hashPos ) {
286                         line_comments[lineNum-1] = strdup(hashPos);
287                 }
288                 else if( semiPos ) {
289                         line_comments[lineNum-1] = strdup(semiPos);
290                 }
291                 if(hashPos)     *hashPos = '\0';
292                 if(semiPos)     *semiPos = '\0';
293                 
294                 // Trim whitespace
295                 line = trim(buffer);
296                 if(strlen(line) == 0)   continue;
297                 
298                 // Pass regex over line
299                 if( RunRegex( &gItemFile_Regex, line, 5, matches, NULL) ) {
300                         fprintf(stderr, "Syntax error on line %i of item file '%s'\n", lineNum, gsItemListFile);
301                         return ;
302                 }
303
304                 // Read line data
305                 type  = line + matches[1].rm_so;        line[ matches[1].rm_eo ] = '\0';
306                 num   = atoi( line + matches[2].rm_so );
307
308                 // Find handler
309                 handler = NULL;
310                 for( i = 0; i < giNumHandlers; i ++ )
311                 {
312                         if( strcmp(type, gaHandlers[i]->Name) == 0 ) {
313                                 handler = gaHandlers[i];
314                                 break;
315                         }
316                 }
317                 if( !handler ) {
318                         fprintf(stderr, "Warning: Unknown item type '%s' on line %i\n", type, lineNum);
319                         continue ;
320                 }
321
322                 for( i = 0; i < giNumItems; i ++ )
323                 {
324                         if( gaItems[i].Handler != handler )     continue;
325                         if( gaItems[i].ID != num )      continue;
326                         
327                         line_items[lineNum-1] = i;
328                         break;
329                 }
330                 if( i >= giNumItems ) {
331                         continue;
332                 }
333         }
334         
335         fclose(fp);
336         
337         //fp = fopen("items.cfg.new", "w");     // DEBUG: Don't kill the real item file until debugged
338         fp = fopen(gsItemListFile, "w");
339         
340         // Create new file
341         {
342                  int    done_items[giNumItems];
343                 memset(done_items, 0, sizeof(done_items));
344                 
345                 // Existing items
346                 for( i = 0; i < lineNum; i ++ )
347                 {
348                         if( line_items[i] != -1 ) {
349                                 tItem   *item = &gaItems[ line_items[i] ];
350                                 
351                                 if( done_items[ line_items[i] ] ) {
352                                         fprintf(fp, "; DUP -");
353                                 }
354                                 done_items[ line_items[i] ] = 1;
355                                 
356                                 if( item->bHidden )
357                                         fprintf(fp, "-");
358                                 
359                                 fprintf(fp, "%s\t%i\t%i\t%s\t",
360                                         item->Handler->Name, item->ID, item->Price, item->Name
361                                         );
362                         }
363                         
364                         if( line_comments[i] ) {
365                                 fprintf(fp, "%s", line_comments[i]);
366                                 free( line_comments[i] );
367                         }
368                         
369                         fprintf(fp, "\n");
370                 }
371                 
372                 // New items
373                 for( i = 0; i < giNumItems; i ++ )
374                 {
375                         tItem   *item = &gaItems[i];
376                         if( done_items[i] )     continue ;
377                         
378                         if( item->bHidden )
379                                 fprintf(fp, "-");
380                         
381                         fprintf(fp, "%s\t%i\t%i\t%s\n",
382                                 item->Handler->Name, item->ID, item->Price, item->Name
383                                 );
384                 }
385         }
386         
387         free( line_comments );
388         free( line_items );
389         fclose(fp);
390 }
391
392
393 char *trim(char *__str)
394 {
395         char    *ret;
396          int    i;
397         
398         while( isspace(*__str) )
399                 __str++;
400         ret = __str;
401
402         i = strlen(ret);
403         while( i-- && isspace(__str[i]) ) {
404                 __str[i] = '\0';
405         }
406
407         return ret;
408 }

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