444ab34c9aba8880105e3acea515182dab12937f
[tpg/opendispense2.git] / src / client / main.c
1 /*
2  * OpenDispense 2 
3  * UCC (University [of WA] Computer Club) Electronic Accounting System
4  * - Dispense Client
5  *
6  * main.c - Core and Initialisation
7  *
8  * This file is licenced under the 3-clause BSD Licence. See the file
9  * COPYING for full details.
10  */
11 #include <stdlib.h>
12 #include <stdio.h>
13 #include <string.h>
14 #include <ctype.h>      // isspace
15 #include <unistd.h>     // close
16 #include <limits.h>     // INT_MIN/INT_MAX
17 #include "common.h"
18
19 #define USE_NCURSES_INTERFACE   0
20 #define DEBUG_TRACE_SERVER      0
21 #define USE_AUTOAUTH    1
22
23 #define MAX_TXT_ARGS    5       // Maximum number of textual arguments (including command)
24 #define DISPENSE_MULTIPLE_MAX   20      // Maximum argument to -c
25
26 // === TYPES ===
27
28 // === PROTOTYPES ===
29 void    ShowUsage(void);
30  int    main(int argc, char *argv[]);
31 // --- Coke Server Communication ---
32 // --- Helpers ---
33 char    *trim(char *string);
34  int    RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage);
35 void    CompileRegex(regex_t *regex, const char *pattern, int flags);
36
37 // === GLOBALS ===
38 char    *gsDispenseServer = "merlo.ucc.gu.uwa.edu.au";
39  int    giDispensePort = 11020;
40
41 tItem   *gaItems;
42  int    giNumItems;
43 regex_t gArrayRegex, gItemRegex, gSaltRegex, gUserInfoRegex, gUserItemIdentRegex;
44  int    gbIsAuthenticated = 0;
45
46 char    *gsItemPattern; //!< Item pattern
47 char    *gsEffectiveUser;       //!< '-u' Dispense as another user
48
49 enum eUI_Modes  giUIMode = UI_MODE_STANDARD;
50  int    gbDryRun = 0;   //!< '-n' Read-only
51  int    gbDisallowSelectWithoutBalance = 1;     //!< Don't allow items to be hilighted if not affordable
52
53  int    giMinimumBalance = INT_MIN;     //!< '-m' Minumum balance for `dispense acct`
54  int    giMaximumBalance = INT_MAX;     //!< '-M' Maximum balance for `dispense acct`
55
56  char   *gsUserName;    //!< User that dispense will happen as
57 char    *gsUserFlags;   //!< User's flag set
58  int    giUserBalance = -1;     //!< User balance (set by Authenticate)
59  int    giDispenseCount = 1;    //!< Number of dispenses to do
60
61 char    *gsTextArgs[MAX_TXT_ARGS];
62  int    giTextArgc;
63
64 // === CODE ===
65 void ShowUsage(void)
66 {
67         printf( "Usage:\n" );
68         if( giTextArgc == 0 )
69                 printf(
70                         "  == Everyone ==\n"
71                         "    dispense\n"
72                         "        Show interactive list\n"
73                         "    dispense <name>|<index>|<itemid>\n"
74                         "        Dispense named item (<name> matches if it is a unique prefix)\n"
75                         "    dispense finger\n"
76                         "        Show the finger output\n"
77                         );
78         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "give") == 0 )
79                 printf(
80                         "    dispense give <user> <amount> \"<reason>\"\n"
81                         "        Give money to another user\n"
82                         );
83         
84         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "donate") == 0 )
85                 printf(
86                         "    dispense donate <amount> \"<reason>\"\n"
87                         "        Donate to the club\n"
88                         );
89         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "iteminfo") == 0 )
90                 printf(
91                         "    dispense iteminfo <itemid>\n"
92                         "        Get the name and price for an item\n"
93                         );
94 //      if( giTextArgc == 0 || strcmp(gsTextArgs[0], "enumitems") == 0 )
95 //              printf(
96 //                      "    dispense enumitems\n"
97 //                      "        List avaliable items\n"
98 //                      );
99         if( giTextArgc == 0 )
100                 printf("  == Coke members == \n");
101         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "acct") == 0 )
102                 printf(
103                         "    dispense acct [<user>]\n"
104                         "        Show user balances\n"
105                         "    dispense acct <user> [+-]<amount> \"<reason>\"\n"
106                         "        Alter a account value\n"
107                         "    dispense acct <user> =<amount> \"<reason>\"\n"
108                         "        Set an account balance\n"
109                         );
110         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "refund") == 0 )
111                 printf(
112                         "    dispense refund <user> <itemid> [<price>]\n"
113                         "        Refund an item to a user (with optional price override)\n"
114                         "        Item IDs can be seen in the cokelog (in the brackets after the item name)\n"
115                         "        e.g. coke:6 for a coke, snack:33 for slot 33 of the snack machine\n"
116                         );
117         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "slot") == 0 )
118                 printf(
119                         "    dispense slot <itemid> <price> <name>\n"
120                         "        Rename/Re-price a slot\n"
121                         );
122         if( giTextArgc == 0 )
123                 printf("  == Dispense administrators ==\n");
124         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "user") == 0 )
125                 printf(
126                         "    dispense user add <user>\n"
127                         "        Create new account\n"
128                         "    dispense user type <user> <flags> <reason>\n"
129                         "        Alter a user's flags\n"
130                         "        <flags> is a comma-separated list of user, coke, admin, internal or disabled\n"
131                         "        Flags are removed by preceding the name with '-' or '!'\n"
132                         );
133         if( giTextArgc == 0 )
134                 printf( "\n"
135                         "General Options:\n"
136                         "    -c <count>\n"
137                         "        Dispense multiple times\n"
138                         "    -u <username>\n"
139                         "        Set a different user (Coke members only)\n"
140                         "    -h / -?\n"
141                         "        Show help text\n"
142                         "    -G\n"
143                         "        Use simple textual interface (instead of ncurses)\n"
144                         "    -n\n"
145                         "        Dry run - Do not actually do dispenses\n"
146                         "    -m <min balance>\n"
147                         "    -M <max balance>\n"
148                         "        Set the Maximum/Minimum balances shown in `dispense acct`\n"
149                         "Definitions:\n"
150                         "    <itemid>\n"
151                         "        Item ID of the form <type>:<num> where <type> is a non-empty string of alpha-numeric characters, and <num> is a non-negative integer\n"
152 //                      "    <user>\n"
153 //                      "        Account name\n"
154                         );
155 }
156
157 int main(int argc, char *argv[])
158 {
159          int    sock;
160          int    i, ret = 0;
161         char    buffer[BUFSIZ];
162         
163         gsTextArgs[0] = "";
164
165         // -- Create regular expressions
166         // > Code Type Count ...
167         CompileRegex(&gArrayRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([0-9]+)", REG_EXTENDED);     //
168         // > Code Type Ident Status Price Desc
169         CompileRegex(&gItemRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([A-Za-z]+):([0-9]+)\\s+(avail|sold|error)\\s+([0-9]+)\\s+(.+)$", REG_EXTENDED);
170         // > Code 'SALT' salt
171         CompileRegex(&gSaltRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+(.+)$", REG_EXTENDED);
172         // > Code 'User' Username Balance Flags
173         CompileRegex(&gUserInfoRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([^ ]+)\\s+(-?[0-9]+)\\s+(.+)$", REG_EXTENDED);
174         // > Item Ident
175         CompileRegex(&gUserItemIdentRegex, "^([A-Za-z]+):([0-9]+)$", REG_EXTENDED);
176
177         // Parse Arguments
178         for( i = 1; i < argc; i ++ )
179         {
180                 char    *arg = argv[i];
181                 
182                 if( arg[0] == '-' )
183                 {                       
184                         switch(arg[1])
185                         {
186                         case 'h':
187                         case '?':
188                                 ShowUsage();
189                                 return 0;
190                                         
191                         case 'c':
192                                 if( i > 2 && strcmp(argv[i-1], "type") == 0 )
193                                         goto _default;
194                                 if( i + 1 >= argc ) {
195                                         fprintf(stderr, "%s: -c takes an argument\n", argv[0]);
196                                         ShowUsage();
197                                         return -1;
198                                 }
199                                 giDispenseCount = atoi(argv[++i]);
200                                 if( giDispenseCount < 1 || giDispenseCount > DISPENSE_MULTIPLE_MAX ) {
201                                         fprintf(stderr, "Sorry, only 1-20 can be passed to -c (safety)\n");
202                                         return -1;
203                                 }
204                                 
205                                 break ;
206         
207                         case 'm':       // Minimum balance
208                                 if( i + 1 >= argc ) {
209                                         fprintf(stderr, "%s: -m takes an argument\n", argv[0]);
210                                         ShowUsage();
211                                         return RV_ARGUMENTS;
212                                 }
213                                 giMinimumBalance = atoi(argv[++i]);
214                                 break;
215                         case 'M':       // Maximum balance
216                                 if( i + 1 >= argc ) {
217                                         fprintf(stderr, "%s: -M takes an argument\n", argv[0]);
218                                         ShowUsage();
219                                         return RV_ARGUMENTS;
220                                 }
221                                 giMaximumBalance = atoi(argv[++i]);
222                                 break;
223                         
224                         case 'u':       // Override User
225                                 if( i + 1 >= argc ) {
226                                         fprintf(stderr, "%s: -u takes an argument\n", argv[0]);
227                                         ShowUsage();
228                                         return RV_ARGUMENTS;
229                                 }
230                                 gsEffectiveUser = argv[++i];
231                                 break;
232                         
233                         case 'H':       // Override remote host
234                                 if( i + 1 >= argc ) {
235                                         fprintf(stderr, "%s: -H takes an argument\n", argv[0]);
236                                         ShowUsage();
237                                         return RV_ARGUMENTS;
238                                 }
239                                 gsDispenseServer = argv[++i];
240                                 break;
241                         case 'P':       // Override remote port
242                                 if( i + 1 >= argc ) {
243                                         fprintf(stderr, "%s: -P takes an argument\n", argv[0]);
244                                         ShowUsage();
245                                         return RV_ARGUMENTS;
246                                 }
247                                 giDispensePort = atoi(argv[++i]);
248                                 break;
249                         
250                         // Set slot name/price
251                         case 's':
252                                 if( giTextArgc != 0 ) {
253                                         fprintf(stderr, "%s: -s must appear before other arguments\n", argv[0]);
254                                         ShowUsage();
255                                         return RV_ARGUMENTS;
256                                 }
257                                 gsTextArgs[0] = "slot"; // HACK!!
258                                 giTextArgc ++;
259                                 break;
260                         
261                         case 'G':       // Don't use GUI
262                                 giUIMode = UI_MODE_BASIC;
263                                 break;
264                         case 'D':       // Drinks only
265                                 giUIMode = UI_MODE_DRINKSONLY;
266                                 break;
267                         case 'n':       // Dry Run / read-only
268                                 gbDryRun = 1;
269                                 break;
270                         case '-':
271                                 if( strcmp(argv[i], "--help") == 0 ) {
272                                         ShowUsage();
273                                         return 0;
274                                 }
275                                 else if( strcmp(argv[i], "--dry-run") == 0 ) {
276                                         gbDryRun = 1;
277                                 }
278                                 else if( strcmp(argv[i], "--drinks-only") == 0 ) {
279                                         giUIMode = UI_MODE_DRINKSONLY;
280                                 }
281                                 else if( strcmp(argv[i], "--can-select-all") == 0 ) {
282                                         gbDisallowSelectWithoutBalance = 0;
283                                 }
284                                 else {
285                                         fprintf(stderr, "%s: Unknown switch '%s'\n", argv[0], argv[i]);
286                                         ShowUsage();
287                                         return RV_ARGUMENTS;
288                                 }
289                                 break;
290                         default: _default:
291                                 // The first argument is not allowed to begin with 'i'
292                                 // (catches most bad flags)
293                                 if( giTextArgc == 0 ) {
294                                         fprintf(stderr, "%s: Unknown switch '%s'\n", argv[0], argv[i]);
295                                         ShowUsage();
296                                         return RV_ARGUMENTS;
297                                 }
298                                 if( giTextArgc == MAX_TXT_ARGS )
299                                 {
300                                         fprintf(stderr, "ERROR: Too many arguments\n");
301                                         return RV_ARGUMENTS;
302                                 }
303                                 gsTextArgs[giTextArgc++] = argv[i];
304                                 break;
305                         }
306
307                         continue;
308                 }
309
310                 if( giTextArgc == MAX_TXT_ARGS )
311                 {
312                         fprintf(stderr, "ERROR: Too many arguments\n");
313                         return RV_ARGUMENTS;
314                 }
315         
316                 gsTextArgs[giTextArgc++] = argv[i];
317         
318         }
319
320         //
321         // `dispense finger`
322         // -
323         if( strcmp(gsTextArgs[0], "finger") == 0 )
324         {
325                 // Connect to server
326                 sock = OpenConnection(gsDispenseServer, giDispensePort);
327                 if( sock < 0 )  return RV_SOCKET_ERROR;
328
329                 // Get items
330                 PopulateItemList(sock);
331
332                 printf("The UCC Coke machine.\n\n");
333
334                 // Only get coke slot statuses
335                 for( i = 0; i <= 6; i ++ )
336                 {
337                         const char *status;
338                         switch(gaItems[i].Status)
339                         {
340                         case 0: status = "Avail";       break;
341                         case 1: status = "Sold ";       break;
342                         default:
343                                 status = "Error";
344                                 break;
345                         }
346                         printf("%i - %s %3i %s\n", gaItems[i].ID, status, gaItems[i].Price, gaItems[i].Desc);
347                         
348                 }
349
350                 printf("\nMay your pink fish bing into the distance.\n");
351
352                 return 0;
353         }
354
355         //
356         // `dispense acct`
357         // - 
358         if( strcmp(gsTextArgs[0], "acct") == 0 )
359         {
360                 // Connect to server
361                 sock = OpenConnection(gsDispenseServer, giDispensePort);
362                 if( sock < 0 )  return RV_SOCKET_ERROR;
363                 // List accounts?
364                 if( giTextArgc == 1 ) {
365                         ret = Dispense_EnumUsers(sock);
366                         close(sock);
367                         return ret;
368                 }
369                         
370                 // gsTextArgs[1]: Username
371                 
372                 // Alter account?
373                 if( giTextArgc != 2 )
374                 {
375                         if( giTextArgc != 4 ) {
376                                 fprintf(stderr, "`dispense acct` requires a reason\n");
377                                 ShowUsage();
378                                 return RV_ARGUMENTS;
379                         }
380                         
381                         // Authentication required
382                         ret = Authenticate(sock);
383                         if(ret) return ret;
384                         
385                         // gsTextArgs[1]: Username
386                         // gsTextArgs[2]: Ammount
387                         // gsTextArgs[3]: Reason
388                         
389                         if( gsTextArgs[2][0] == '=' ) {
390                                 // Set balance
391                                 if( gsTextArgs[2][1] != '0' && atoi(gsTextArgs[2]+1) == 0 ) {
392                                         fprintf(stderr, "Error: Invalid balance to be set\n");
393                                         exit(1);
394                                 }
395                                 
396                                 ret = Dispense_SetBalance(sock, gsTextArgs[1], atoi(gsTextArgs[2]+1), gsTextArgs[3]);
397                         }
398                         else {
399                                 // Alter balance
400                                 ret = Dispense_AlterBalance(sock, gsTextArgs[1], atoi(gsTextArgs[2]), gsTextArgs[3]);
401                         }
402                 }
403                 // On error, quit
404                 if( ret ) {
405                         close(sock);
406                         return ret;
407                 }
408                 
409                 // Show user information
410                 ret = Dispense_ShowUser(sock, gsTextArgs[1]);
411                 
412                 close(sock);
413                 return ret;
414         }
415         //
416         // `dispense give`
417         // - "Here, have some money."
418         else if( strcmp(gsTextArgs[0], "give") == 0 )
419         {
420                 if( giTextArgc != 4 ) {
421                         fprintf(stderr, "`dispense give` takes three arguments\n");
422                         ShowUsage();
423                         return RV_ARGUMENTS;
424                 }
425                 
426                 // gsTextArgs[1]: Destination
427                 // gsTextArgs[2]: Ammount
428                 // gsTextArgs[3]: Reason
429                 
430                 // Connect to server
431                 sock = OpenConnection(gsDispenseServer, giDispensePort);
432                 if( sock < 0 )  return RV_SOCKET_ERROR;
433                 
434                 // Authenticate
435                 ret = Authenticate(sock);
436                 if(ret) return ret;
437                 
438                 ret = Dispense_Give(sock, gsTextArgs[1], atoi(gsTextArgs[2]), gsTextArgs[3]);
439
440                 close(sock);
441         
442                 return ret;
443         }
444         // 
445         // `dispense user`
446         // - User administration (Admin Only)
447         if( strcmp(gsTextArgs[0], "user") == 0 )
448         {
449                 // Check argument count
450                 if( giTextArgc == 1 ) {
451                         fprintf(stderr, "Error: `dispense user` requires arguments\n");
452                         ShowUsage();
453                         return RV_ARGUMENTS;
454                 }
455                 
456                 // Connect to server
457                 sock = OpenConnection(gsDispenseServer, giDispensePort);
458                 if( sock < 0 )  return RV_SOCKET_ERROR;
459                 
460                 // Attempt authentication
461                 ret = Authenticate(sock);
462                 if(ret) return ret;
463                 
464                 // Add new user?
465                 if( strcmp(gsTextArgs[1], "add") == 0 )
466                 {
467                         if( giTextArgc != 3 ) {
468                                 fprintf(stderr, "Error: `dispense user add` requires an argument\n");
469                                 ShowUsage();
470                                 return RV_ARGUMENTS;
471                         }
472                         
473                         ret = Dispense_AddUser(sock, gsTextArgs[2]);
474                 }
475                 // Update a user
476                 else if( strcmp(gsTextArgs[1], "type") == 0 || strcmp(gsTextArgs[1], "flags") == 0 )
477                 {
478                         if( giTextArgc < 4 || giTextArgc > 5 ) {
479                                 fprintf(stderr, "Error: `dispense user type` requires two arguments\n");
480                                 ShowUsage();
481                                 return RV_ARGUMENTS;
482                         }
483                         
484                         if( giTextArgc == 4 )
485                                 ret = Dispense_SetUserType(sock, gsTextArgs[2], gsTextArgs[3], "");
486                         else
487                                 ret = Dispense_SetUserType(sock, gsTextArgs[2], gsTextArgs[3], gsTextArgs[4]);
488                 }
489                 else
490                 {
491                         fprintf(stderr, "Error: Unknown sub-command for `dispense user`\n");
492                         ShowUsage();
493                         return RV_ARGUMENTS;
494                 }
495                 close(sock);
496                 return ret;
497         }
498         // Donation!
499         else if( strcmp(gsTextArgs[0], "donate") == 0 )
500         {
501                 // Check argument count
502                 if( giTextArgc != 3 ) {
503                         fprintf(stderr, "Error: `dispense donate` requires two arguments\n");
504                         ShowUsage();
505                         return RV_ARGUMENTS;
506                 }
507                 
508                 // Connect to server
509                 sock = OpenConnection(gsDispenseServer, giDispensePort);
510                 if( sock < 0 )  return RV_SOCKET_ERROR;
511                 
512                 // Attempt authentication
513                 ret = Authenticate(sock);
514                 if(ret) return ret;
515                 
516                 // Do donation
517                 ret = Dispense_Donate(sock, atoi(gsTextArgs[1]), gsTextArgs[2]);
518                                 
519                 close(sock);
520
521                 return ret;
522         }
523         // Refund an item
524         else if( strcmp(gsTextArgs[0], "refund") == 0 )
525         {
526                  int     price = 0;
527                 // Check argument count
528                 if( giTextArgc != 3 && giTextArgc != 4 ) {
529                         fprintf(stderr, "Error: `dispense refund` takes 2 or 3 arguments\n");
530                         ShowUsage();
531                         return RV_ARGUMENTS;
532                 }
533         
534                 // Connect to server
535                 sock = OpenConnection(gsDispenseServer, giDispensePort);
536                 if(sock < 0)    return RV_SOCKET_ERROR; 
537
538                 // Attempt authentication
539                 ret = Authenticate(sock);
540                 if(ret) return ret;
541
542                 if( giTextArgc == 4 ) {
543                         price = atoi(gsTextArgs[3]);
544                         if( price <= 0 ) {
545                                 fprintf(stderr, "Error: Override price is invalid (should be > 0)\n");
546                                 return RV_ARGUMENTS;
547                         }
548                 }
549
550                 // Username, Item, cost
551                 ret = Dispense_Refund(sock, gsTextArgs[1], gsTextArgs[2], price);
552
553                 // TODO: More
554                 close(sock);
555                 return ret;
556         }
557         // Query an item price
558         else if( strcmp(gsTextArgs[0], "iteminfo") == 0 )
559         {
560                 regmatch_t matches[3];
561                 char    *type;
562                  int    id;
563                 // Check argument count
564                 if( giTextArgc != 2 ) {
565                         fprintf(stderr, "Error: `dispense iteminfo` requires an argument\n");
566                         ShowUsage();
567                         return RV_ARGUMENTS;
568                 }
569                 // Parse item ID
570                 if( RunRegex(&gUserItemIdentRegex, gsTextArgs[1], 3, matches, NULL) != 0 ) {
571                         fprintf(stderr, "Error: Invalid item ID passed (<type>:<id> expected)\n");
572                         return RV_ARGUMENTS;
573                 }
574                 type = gsTextArgs[1] + matches[1].rm_so;
575                 gsTextArgs[1][ matches[1].rm_eo ] = '\0';
576                 id = atoi( gsTextArgs[1] + matches[2].rm_so );
577
578                 sock = OpenConnection(gsDispenseServer, giDispensePort);
579                 if( sock < 0 )  return RV_SOCKET_ERROR;
580                 
581                 ret = Dispense_ItemInfo(sock, type, id);
582                 close(sock);
583                 return ret;
584         }
585         // Set slot
586         else if( strcmp(gsTextArgs[0], "slot") == 0 )
587         {
588                 regmatch_t matches[3];
589                 char    *item_type, *newname;
590                  int    item_id, price;
591                 
592                 // Check arguments
593                 if( giTextArgc != 4 ) {
594                         fprintf(stderr, "Error: `dispense slot` takes three arguments\n");
595                         ShowUsage();
596                         return RV_ARGUMENTS;
597                 }
598                 
599                 // Parse arguments
600                 if( RunRegex(&gUserItemIdentRegex, gsTextArgs[1], 3, matches, NULL) != 0 ) {
601                         fprintf(stderr, "Error: Invalid item ID passed (<type>:<id> expected)\n");
602                         return RV_ARGUMENTS;
603                 }
604                 item_type = gsTextArgs[1] + matches[1].rm_so;
605                 gsTextArgs[1][ matches[1].rm_eo ] = '\0';
606                 item_id = atoi( gsTextArgs[1] + matches[2].rm_so );
607
608                 // - Price
609                 price = atoi( gsTextArgs[2] );
610                 if( price <= 0 && gsTextArgs[2][0] != '0' ) {
611                         fprintf(stderr, "Error: Invalid price passed (must be >= 0)\n");
612                         return RV_ARGUMENTS;
613                 }
614                 
615                 // - New name
616                 newname = gsTextArgs[3];
617                 // -- Sanity
618                 {
619                         char *pos;
620                         for( pos = newname; *pos; pos ++ )
621                         {
622                                 if( !isalnum(*pos) && *pos != ' ' ) {
623                                         fprintf(stderr, "Error: You should only have letters, numbers and spaces in an item name\n");
624                                         return RV_ARGUMENTS;
625                                 }
626                         }
627                 }
628                 
629                 // Connect & Authenticate
630                 sock = OpenConnection(gsDispenseServer, giDispensePort);
631                 if( sock < 0 )  return RV_SOCKET_ERROR;
632                 ret = Authenticate(sock);
633                 if(ret) return ret;
634                 // Update the slot
635                 ret = Dispense_SetItem(sock, item_type, item_id, price, newname);
636                 
637                 close(sock);
638                 return ret;
639         }
640         // Item name / pattern
641         else
642         {
643                 gsItemPattern = gsTextArgs[0];
644         }
645         
646         // Connect to server
647         sock = OpenConnection(gsDispenseServer, giDispensePort);
648         if( sock < 0 )  return RV_SOCKET_ERROR;
649
650         // Get the user's balance
651         ret = GetUserBalance(sock);
652         if(ret) return ret;
653
654         // Get items
655         PopulateItemList(sock);
656         
657         // Disconnect from server
658         close(sock);
659         
660         if( gsItemPattern && gsItemPattern[0] )
661         {
662                 regmatch_t matches[3];
663                 // Door (hard coded)
664                 if( strcmp(gsItemPattern, "door") == 0 )
665                 {
666                         // Connect, Authenticate, dispense and close
667                         sock = OpenConnection(gsDispenseServer, giDispensePort);
668                         if( sock < 0 )  return RV_SOCKET_ERROR;
669                         ret = Authenticate(sock);
670                         if(ret) return ret;
671                         ret = DispenseItem(sock, "door", 0);
672                         close(sock);
673                         return ret;
674                 }
675                 // Item id (<type>:<num>)
676                 else if( RunRegex(&gUserItemIdentRegex, gsItemPattern, 3, matches, NULL) == 0 )
677                 {
678                         char    *ident;
679                          int    id;
680                         
681                         // Get and finish ident
682                         ident = gsItemPattern + matches[1].rm_so;
683                         gsItemPattern[matches[1].rm_eo] = '\0';
684                         // Get ID
685                         id = atoi( gsItemPattern + matches[2].rm_so );
686                         
687                         // Connect, Authenticate, dispense and close
688                         sock = OpenConnection(gsDispenseServer, giDispensePort);
689                         if( sock < 0 )  return RV_SOCKET_ERROR;
690                         
691                         Dispense_ItemInfo(sock, ident, id);
692                         
693                         ret = Authenticate(sock);
694                         if(ret) return ret;
695                         ret = DispenseItem(sock, ident, id);
696                         close(sock);
697                         return ret;
698                 }
699                 // Item number (6 = coke)
700                 else if( strcmp(gsItemPattern, "0") == 0 || atoi(gsItemPattern) > 0 )
701                 {
702                         i = atoi(gsItemPattern);
703                 }
704                 // Item prefix
705                 else
706                 {
707                          int    j;
708                          int    best = -1;
709                         for( i = 0; i < giNumItems; i ++ )
710                         {
711                                 // Prefix match (with case-insensitive match)
712                                 for( j = 0; gsItemPattern[j]; j ++ )
713                                 {
714                                         if( gaItems[i].Desc[j] == gsItemPattern[j] )
715                                                 continue;
716                                         if( tolower(gaItems[i].Desc[j]) == tolower(gsItemPattern[j]) )
717                                                 continue;
718                                         break;
719                                 }
720                                 // Check if the prefix matched
721                                 if( gsItemPattern[j] != '\0' )
722                                         continue;
723                                 
724                                 // Prefect match
725                                 if( gaItems[i].Desc[j] == '\0' ) {
726                                         best = i;
727                                         break;
728                                 }
729                                 
730                                 // Only one match allowed
731                                 if( best == -1 ) {
732                                         best = i;
733                                 }
734                                 else {
735                                         // TODO: Allow ambiguous matches?
736                                         // or just print a wanrning
737                                         printf("Warning - Ambiguous pattern, stopping\n");
738                                         return RV_BAD_ITEM;
739                                 }
740                         }
741                         
742                         // Was a match found?
743                         if( best == -1 )
744                         {
745                                 fprintf(stderr, "No item matches the passed string\n");
746                                 return RV_BAD_ITEM;
747                         }
748                         
749                         i = best;
750                 }
751         }
752         else if( giUIMode != UI_MODE_BASIC )
753         {
754                 i = ShowNCursesUI();
755         }
756         else
757         {
758                 // Very basic dispense interface
759                 for( i = 0; i < giNumItems; i ++ ) {
760                         // Add a separator
761                         if( i && strcmp(gaItems[i].Type, gaItems[i-1].Type) != 0 )
762                                 printf("   ---\n");
763                         
764                         printf("%2i %s:%i\t%3i %s\n", i, gaItems[i].Type, gaItems[i].ID,
765                                 gaItems[i].Price, gaItems[i].Desc);
766                 }
767                 printf(" q Quit\n");
768                 for(;;)
769                 {
770                         char    *buf;
771                         
772                         i = -1;
773                         
774                         fgets(buffer, BUFSIZ, stdin);
775                         
776                         buf = trim(buffer);
777                         
778                         if( buf[0] == 'q' )     break;
779                         
780                         i = atoi(buf);
781                         
782                         if( i != 0 || buf[0] == '0' )
783                         {
784                                 if( i < 0 || i >= giNumItems ) {
785                                         printf("Bad item %i (should be between 0 and %i)\n", i, giNumItems);
786                                         continue;
787                                 }
788                                 break;
789                         }
790                 }
791         }
792         
793         
794         // Check for a valid item ID
795         if( i >= 0 )
796         {
797                  int j;
798                 // Connect, Authenticate, dispense and close
799                 sock = OpenConnection(gsDispenseServer, giDispensePort);
800                 if( sock < 0 )  return RV_SOCKET_ERROR;
801                         
802                 ret = Dispense_ItemInfo(sock, gaItems[i].Type, gaItems[i].ID);
803                 if(ret) return ret;
804                 
805                 ret = Authenticate(sock);
806                 if(ret) return ret;
807                 
808                 for( j = 0; j < giDispenseCount; j ++ ) {
809                         ret = DispenseItem(sock, gaItems[i].Type, gaItems[i].ID);
810                         if( ret )       break;
811                 }
812                 if( j > 1 ) {
813                         printf("%i items dispensed\n", j);
814                 }
815                 Dispense_ShowUser(sock, gsUserName);
816                 close(sock);
817
818         }
819
820         return ret;
821 }
822
823 // ---------------
824 // --- Helpers ---
825 // ---------------
826 char *trim(char *string)
827 {
828          int    i;
829         
830         while( isspace(*string) )
831                 string ++;
832         
833         for( i = strlen(string); i--; )
834         {
835                 if( isspace(string[i]) )
836                         string[i] = '\0';
837                 else
838                         break;
839         }
840         
841         return string;
842 }
843
844 int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage)
845 {
846          int    ret;
847         
848         ret = regexec(regex, string, nMatches, matches, 0);
849         if( ret && errorMessage ) {
850                 size_t  len = regerror(ret, regex, NULL, 0);
851                 char    errorStr[len];
852                 regerror(ret, regex, errorStr, len);
853                 printf("string = '%s'\n", string);
854                 fprintf(stderr, "%s\n%s", errorMessage, errorStr);
855                 exit(-1);
856         }
857         
858         return ret;
859 }
860
861 void CompileRegex(regex_t *regex, const char *pattern, int flags)
862 {
863          int    ret = regcomp(regex, pattern, flags);
864         if( ret ) {
865                 size_t  len = regerror(ret, regex, NULL, 0);
866                 char    errorStr[len];
867                 regerror(ret, regex, errorStr, len);
868                 fprintf(stderr, "Regex compilation failed - %s\n", errorStr);
869                 exit(-1);
870         }
871 }

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