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

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