Merge branch 'master' of ssh.ucc.asn.au:tpg/opendispense2
[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 initiali 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                 lineNum ++;
267                 line_items[lineNum-1] = -1;
268                 line_comments[lineNum-1] = NULL;
269
270                 // Get comments
271                 hashPos = strchr(buffer, '#');
272                 semiPos = strchr(buffer, ';');
273                 if( hashPos && semiPos ) {
274                         if( hashPos < semiPos )
275                                 line_comments[lineNum-1] = strdup(hashPos);
276                 }
277                 else if( hashPos ) {
278                         line_comments[lineNum-1] = strdup(hashPos);
279                 }
280                 else if( semiPos ) {
281                         line_comments[lineNum-1] = strdup(semiPos);
282                 }
283                 if(hashPos)     *hashPos = '\0';
284                 if(semiPos)     *semiPos = '\0';
285                 
286                 // Trim whitespace
287                 line = trim(buffer);
288                 if(strlen(line) == 0)   continue;
289                 
290                 // Pass regex over line
291                 if( RunRegex( &gItemFile_Regex, line, 5, matches, NULL) ) {
292                         fprintf(stderr, "Syntax error on line %i of item file '%s'\n", lineNum, gsItemListFile);
293                         return ;
294                 }
295
296                 // Read line data
297                 type  = line + matches[1].rm_so;        line[ matches[1].rm_eo ] = '\0';
298                 num   = atoi( line + matches[2].rm_so );
299
300                 // Find handler
301                 handler = NULL;
302                 for( i = 0; i < giNumHandlers; i ++ )
303                 {
304                         if( strcmp(type, gaHandlers[i]->Name) == 0 ) {
305                                 handler = gaHandlers[i];
306                                 break;
307                         }
308                 }
309                 if( !handler ) {
310                         fprintf(stderr, "Warning: Unknown item type '%s' on line %i\n", type, lineNum);
311                         continue ;
312                 }
313
314                 for( i = 0; i < giNumItems; i ++ )
315                 {
316                         if( gaItems[i].Handler != handler )     continue;
317                         if( gaItems[i].ID != num )      continue;
318                         
319                         line_items[lineNum-1] = i;
320                         break;
321                 }
322                 if( i >= giNumItems ) {
323                         continue;
324                 }
325         }
326         
327         fclose(fp);
328         
329         fp = fopen("items.cfg.new", "w");       // DEBUG: Don't kill the real item file until debugged
330         
331         // Create new file
332         {
333                  int    done_items[giNumItems];
334                 memset(done_items, 0, sizeof(done_items));
335                 
336                 // Existing items
337                 for( i = 0; i < lineNum; i ++ )
338                 {
339                         if( line_items[i] != -1 ) {
340                                 tItem   *item = &gaItems[ line_items[i] ];
341                                 
342                                 if( done_items[ line_items[i] ] ) {
343                                         fprintf(fp, "; DUP -");
344                                 }
345                                 
346                                 done_items[ line_items[i] ] = 1;
347                                 fprintf(fp, "%s\t%i\t%i\t%s\t",
348                                         item->Handler->Name, item->ID, item->Price, item->Name
349                                         );
350                         }
351                         
352                         if( line_comments[i] ) {
353                                 fprintf(fp, "%s", line_comments[i]);
354                                 free( line_comments[i] );
355                         }
356                         
357                         fprintf(fp, "\n");
358                 }
359                 
360                 // New items
361                 for( i = 0; i < giNumItems; i ++ )
362                 {
363                         tItem   *item = &gaItems[i];
364                         if( done_items[i] )     continue ;
365                         
366                         fprintf(fp, "%s\t%i\t%i\t%s\n",
367                                 item->Handler->Name, item->ID, item->Price, item->Name
368                                 );
369                 }
370         }
371         
372         free( line_comments );
373         free( line_items );
374         fclose(fp);
375 }
376
377
378 char *trim(char *__str)
379 {
380         char    *ret;
381          int    i;
382         
383         while( isspace(*__str) )
384                 __str++;
385         ret = __str;
386
387         i = strlen(ret);
388         while( i-- && isspace(__str[i]) ) {
389                 __str[i] = '\0';
390         }
391
392         return ret;
393 }

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