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

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