7a82c09b69fb0f67b1ad66f7008eba08d2e70313
[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 <pwd.h>        // getpwuids
16 #include <unistd.h>     // close/getuid
17 #include <limits.h>     // INT_MIN/INT_MAX
18 #include "common.h"
19 #include "../common/doregex.h"
20 #include "../common/config.h"
21
22 #define USE_NCURSES_INTERFACE   0
23 #define DEBUG_TRACE_SERVER      0
24 #define USE_AUTOAUTH    1
25
26 #define MAX_TXT_ARGS    5       // Maximum number of textual arguments (including command)
27 #define DISPENSE_MULTIPLE_MAX   20      // Maximum argument to -c
28
29 // === TYPES ===
30
31 // === PROTOTYPES ===
32 void    ShowUsage(void);
33  int    main(int argc, char *argv[]);
34  int    ParseArguments(int argc, char *argv[]);
35 // --- Coke Server Communication ---
36 // --- Helpers ---
37 char    *trim(char *string);
38  int    RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage);
39 void    CompileRegex(regex_t *regex, const char *pattern, int flags);
40
41 // === GLOBALS ===
42 const   char    *gsConfigFile = "/etc/opendispense/client.conf";
43
44 const   char    *gsDispenseServer = "merlo.ucc.gu.uwa.edu.au";
45  int    giDispensePort = 11020;
46  int    giDispenseServerSet = 0; // True if set by command line
47  int    giDispensePortSet = 0; // True if set by command line
48
49 tItem   *gaItems;
50  int    giNumItems;
51 regex_t gArrayRegex, gItemRegex, gSaltRegex, gUserInfoRegex, gUserItemIdentRegex;
52  int    gbIsAuthenticated = 0;
53
54 char    *gsItemPattern; //!< Item pattern
55 char    *gsEffectiveUser;       //!< '-u' Dispense as another user
56
57 enum eUI_Modes  giUIMode = UI_MODE_STANDARD;
58  int    gbDryRun = 0;   //!< '-n' Read-only
59  int    gbDisallowSelectWithoutBalance = 1;     //!< Don't allow items to be hilighted if not affordable
60
61  int    giMinimumBalance = INT_MIN;     //!< '-m' Minumum balance for `dispense acct`
62  int    giMaximumBalance = INT_MAX;     //!< '-M' Maximum balance for `dispense acct`
63
64  char   *gsUserName;    //!< User that dispense will happen as
65 char    *gsUserFlags;   //!< User's flag set
66  int    giUserBalance = -1;     //!< User balance (set by Authenticate)
67  int    giDispenseCount = 1;    //!< Number of dispenses to do
68
69 char    *gsTextArgs[MAX_TXT_ARGS];
70  int    giTextArgc;
71
72 // === CODE ===
73 void ShowUsage(void)
74 {
75         printf( "Usage:\n" );
76         if( giTextArgc == 0 )
77                 printf(
78                         "  == Everyone ==\n"
79                         "    dispense\n"
80                         "        Show interactive list\n"
81                         "    dispense <name>|<index>|<itemid>\n"
82                         "        Dispense named item (<name> matches if it is a unique prefix)\n"
83                         "    dispense finger\n"
84                         "        Show the finger output\n"
85                         );
86         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "give") == 0 )
87                 printf(
88                         "    dispense give <user> <amount> \"<reason>\"\n"
89                         "        Give money to another user\n"
90                         );
91         
92         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "donate") == 0 )
93                 printf(
94                         "    dispense donate <amount> \"<reason>\"\n"
95                         "        Donate to the club\n"
96                         );
97         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "iteminfo") == 0 )
98                 printf(
99                         "    dispense iteminfo <itemid>\n"
100                         "        Get the name and price for an item\n"
101                         );
102 //      if( giTextArgc == 0 || strcmp(gsTextArgs[0], "enumitems") == 0 )
103 //              printf(
104 //                      "    dispense enumitems\n"
105 //                      "        List avaliable items\n"
106 //                      );
107         if( giTextArgc == 0 )
108                 printf("  == Coke members == \n");
109         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "acct") == 0 )
110                 printf(
111                         "    dispense acct [<user>]\n"
112                         "        Show user balances\n"
113                         "    dispense acct <user> [+-]<amount> \"<reason>\"\n"
114                         "        Alter a account value\n"
115                         "    dispense acct <user> =<amount> \"<reason>\"\n"
116                         "        Set an account balance\n"
117                         );
118         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "refund") == 0 )
119                 printf(
120                         "    dispense refund <user> <itemid> [<price>]\n"
121                         "        Refund an item to a user (with optional price override)\n"
122                         "        Item IDs can be seen in the cokelog (in the brackets after the item name)\n"
123                         "        e.g. coke:6 for a coke, snack:33 for slot 33 of the snack machine\n"
124                         );
125         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "slot") == 0 )
126                 printf(
127                         "    dispense slot <itemid> <price> <name>\n"
128                         "        Rename/Re-price a slot\n"
129                         );
130         if( giTextArgc == 0 )
131                 printf("  == Dispense administrators ==\n");
132         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "user") == 0 )
133                 printf(
134                         "    dispense user add <user>\n"
135                         "        Create new account\n"
136                         "    dispense user type <user> <flags> <reason>\n"
137                         "        Alter a user's flags\n"
138                         "        <flags> is a comma-separated list of user, coke, admin, internal or disabled\n"
139                         "        Flags are removed by preceding the name with '-' or '!'\n"
140                         );
141         if( giTextArgc == 0 )
142                 printf( "\n"
143                         "General Options:\n"
144                         "    -c <count>\n"
145                         "        Dispense multiple times\n"
146                         "    -u <username>\n"
147                         "        Set a different user (Coke members only)\n"
148                         "    -h / -?\n"
149                         "        Show help text\n"
150                         "    -G\n"
151                         "        Use simple textual interface (instead of ncurses)\n"
152                         "    -D\n"
153                         "        Drinks only in user interface\n"
154                         "    -n\n"
155                         "        Dry run - Do not actually do dispenses\n"
156                         "    -m <min balance>\n"
157                         "    -M <max balance>\n"
158                         "        Set the Maximum/Minimum balances shown in `dispense acct`\n"
159                         "    -f <configfile>\n"
160                         "        Set the config file path (default: `/etc/opendispense/client.conf'\n"
161                         "    -H <host>\n"
162                         "        Set a different dispense host\n"
163                         "    -P <port>\n"
164                         "        Set a different dispense port\n"
165                         "Definitions:\n"
166                         "    <itemid>\n"
167                         "        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"
168 //                      "    <user>\n"
169 //                      "        Account name\n"
170                         );
171 }
172
173 //
174 // `dispense finger`
175 // - Display [email protected] finger output
176 //
177 int subcommand_finger(void)
178 {
179         // Connect to server
180         int sock = OpenConnection(gsDispenseServer, giDispensePort);
181         if( sock < 0 )  return RV_SOCKET_ERROR;
182
183         // Get items
184         PopulateItemList(sock);
185
186         printf("The UCC Coke machine.\n\n");
187
188         // Only get coke slot statuses
189         for( int i = 0; i <= 6; i ++ )
190         {
191                 const char *status;
192                 switch(gaItems[i].Status)
193                 {
194                 case 0: status = "Avail";       break;
195                 case 1: status = "Sold ";       break;
196                 default:
197                         status = "Error";
198                         break;
199                 }
200                 printf("%i - %s %3i %s\n", gaItems[i].ID, status, gaItems[i].Price, gaItems[i].Desc);
201         }
202
203         printf("\nMay your pink fish bing into the distance.\n");
204
205         return 0;
206 }
207
208 //
209 // `dispense acct`
210 // - Display/manipulate account balances
211 //
212 int subcommand_acct(void)
213 {
214         int ret = 0;
215
216         // Connect to server
217         int sock = OpenConnection(gsDispenseServer, giDispensePort);
218         if( sock < 0 )  return RV_SOCKET_ERROR;
219         // List accounts?
220         if( giTextArgc == 1 ) {
221                 ret = Dispense_EnumUsers(sock);
222                 close(sock);
223                 return ret;
224         }
225                 
226         // gsTextArgs[1]: Username
227         
228         // Alter account?
229         if( giTextArgc != 2 )
230         {
231                 if( giTextArgc != 4 ) {
232                         fprintf(stderr, "`dispense acct` requires a reason\n");
233                         ShowUsage();
234                         return RV_ARGUMENTS;
235                 }
236                 
237                 // Authentication required
238                 ret = Authenticate(sock);
239                 if(ret) return ret;
240                 
241                 // gsTextArgs[1]: Username
242                 // gsTextArgs[2]: Ammount
243                 // gsTextArgs[3]: Reason
244                  char   *tmp = NULL;
245                 long int balance = strtol(gsTextArgs[2]+(gsTextArgs[2][0] == '='), &tmp, 10);
246                 if(!tmp || *tmp != '\0') {
247                         fprintf(stderr, "dispense acct: Value must be a decimal number of cents\n");
248                         return RV_ARGUMENTS;
249                 }
250                 
251                 if( gsTextArgs[2][0] == '=' ) {
252                         // Set balance
253                         ret = Dispense_ShowUser(sock, gsTextArgs[1]);
254                         ret = Dispense_SetBalance(sock, gsTextArgs[1], balance, gsTextArgs[3]);
255                 }
256                 else {
257                         // Alter balance
258                         ret = Dispense_AlterBalance(sock, gsTextArgs[1], balance, gsTextArgs[3]);
259                 }
260         }
261         // On error, quit
262         if( ret ) {
263                 close(sock);
264                 return ret;
265         }
266         
267         // Show user information
268         ret = Dispense_ShowUser(sock, gsTextArgs[1]);
269         
270         close(sock);
271         return ret;
272 }
273
274 //
275 // `dispense give`
276 // - Transfer credit from the current user to another account
277 //
278 // "Here, have some money."
279 //
280 int subcommand_give(int argc, char *args[])
281 {
282         int ret;
283         
284         if( argc != 3 ) {
285                 fprintf(stderr, "`dispense give` takes three arguments\n");
286                 ShowUsage();
287                 return RV_ARGUMENTS;
288         }
289         
290         const char *dst_acct = args[0];
291         const char *amt_str = args[1];
292         const char *message = args[2];
293         
294         // Connect to server
295         int sock = OpenConnection(gsDispenseServer, giDispensePort);
296         if( sock < 0 )  return RV_SOCKET_ERROR;
297         
298         // Authenticate
299         ret = Authenticate(sock);
300         if(ret) return ret;
301
302         char *end = NULL;
303         long amt = strtol(amt_str, &end, 10);
304         if( !end || *end != '\0' ) {
305                 fprintf(stderr, "dispense give: Balance is invalid, must be decimal number of cents");
306                 return RV_ARGUMENTS;
307         }
308         ret = Dispense_Give(sock, dst_acct, amt, message);
309
310         close(sock);
311         return ret;
312 }
313
314 // 
315 // `dispense user`
316 // - User administration (Admin Only)
317 //
318 int subcommand_user(int argc, char *args[])
319 {
320          int    ret;
321         
322         // Check argument count
323         if( argc == 0 ) {
324                 fprintf(stderr, "Error: `dispense user` requires arguments\n");
325                 ShowUsage();
326                 return RV_ARGUMENTS;
327         }
328         
329         // Connect to server
330         int sock = OpenConnection(gsDispenseServer, giDispensePort);
331         if( sock < 0 )  return RV_SOCKET_ERROR;
332         
333         // Attempt authentication
334         ret = Authenticate(sock);
335         if(ret) return ret;
336         
337         // Add new user?
338         if( strcmp(args[0], "add") == 0 )
339         {
340                 if( giTextArgc != 3 ) {
341                         fprintf(stderr, "Error: `dispense user add` requires an argument\n");
342                         ShowUsage();
343                         return RV_ARGUMENTS;
344                 }
345                 
346                 ret = Dispense_AddUser(sock, args[1]);
347         }
348         // Update a user
349         else if( strcmp(args[0], "type") == 0 || strcmp(args[0], "flags") == 0 )
350         {
351                 if( argc < 3 || argc > 4 ) {
352                         fprintf(stderr, "Error: `dispense user type` requires two arguments\n");
353                         ShowUsage();
354                         return RV_ARGUMENTS;
355                 }
356                 
357                 ret = Dispense_SetUserType(sock, args[1], args[2], (argc == 3 ? "" : args[3]));
358         }
359         else
360         {
361                 fprintf(stderr, "Error: Unknown sub-command for `dispense user`\n");
362                 ShowUsage();
363                 return RV_ARGUMENTS;
364         }
365         close(sock);
366         return ret;
367 }
368
369 //
370 // `dispense donate`
371 // - Donate money to the club
372 //
373 int subcommand_donate(int argc, char *args[])
374 {
375          int    ret;
376         
377         // Check argument count
378         if( argc != 2 ) {
379                 fprintf(stderr, "Error: `dispense donate` requires two arguments\n");
380                 ShowUsage();
381                 return RV_ARGUMENTS;
382         }
383         
384         // Connect to server
385         int sock = OpenConnection(gsDispenseServer, giDispensePort);
386         if( sock < 0 )  return RV_SOCKET_ERROR;
387         
388         // Attempt authentication
389         ret = Authenticate(sock);
390         if(ret) return ret;
391         
392         // Do donation
393         ret = Dispense_Donate(sock, atoi(args[0]), args[1]);
394                         
395         close(sock);
396
397         return ret;
398 }
399
400 //
401 // `dispense refund`
402 // - Refund a purchased item
403 //
404 // "Well excuuuuse me, princess"
405 //
406 int subcommand_refund(int argc, char *args[])
407 {
408          int    ret;
409
410         // Check argument count
411         if( argc != 2 && argc != 3 ) {
412                fprintf(stderr, "Error: `dispense refund` takes 2 or 3 arguments\n");
413                ShowUsage();
414                return RV_ARGUMENTS;
415         }
416
417         // Connect to server
418         int sock = OpenConnection(gsDispenseServer, giDispensePort);
419         if(sock < 0)    return RV_SOCKET_ERROR; 
420
421         // Attempt authentication
422         ret = Authenticate(sock);
423         if(ret) return ret;
424
425          int     price = 0;
426         if( argc > 2 ) {
427                price = atoi(args[2]);
428                if( price <= 0 ) {
429                        fprintf(stderr, "Error: Override price is invalid (should be > 0)\n");
430                        return RV_ARGUMENTS;
431                }
432         }
433
434         // Username, Item, cost
435         ret = Dispense_Refund(sock, args[0], args[1], price);
436
437         // TODO: More
438         close(sock);
439         return ret;
440 }
441
442 //
443 // `dispense iteminfo`
444 // - Get the state of an item
445 //
446 int subcommand_iteminfo(int argc, char *args[])
447 {
448          int    ret;
449
450          // Check argument count
451         if( argc != 1 ) {
452                 fprintf(stderr, "Error: `dispense iteminfo` takes one argument\n");
453                 ShowUsage();
454                 return RV_ARGUMENTS;
455         }
456
457         char *item_id = args[0];
458
459         regmatch_t matches[3];
460         // Parse item ID
461         if( RunRegex(&gUserItemIdentRegex, item_id, 3, matches, NULL) != 0 ) {
462                 fprintf(stderr, "Error: Invalid item ID passed (<type>:<id> expected)\n");
463                 return RV_ARGUMENTS;
464         }
465         char    *type = item_id + matches[1].rm_so;
466         item_id[ matches[1].rm_eo ] = '\0';
467          int    id = atoi( item_id + matches[2].rm_so );
468
469         int sock = OpenConnection(gsDispenseServer, giDispensePort);
470         if( sock < 0 )  return RV_SOCKET_ERROR;
471         
472         ret = Dispense_ItemInfo(sock, type, id);
473         close(sock);
474         return ret;
475 }
476
477 //
478 // `dispense slot`
479 // - Update the name/price of an item
480 //
481 int subcommand_slot(int argc, char *args[])
482 {
483          int    ret;
484         
485         // Check arguments
486         if( argc != 3 ) {
487                 fprintf(stderr, "Error: `dispense slot` takes three arguments\n");
488                 ShowUsage();
489                 return RV_ARGUMENTS;
490         }
491         char *slot_id = args[0];
492         char *price_str = args[1];
493         char *newname = args[2];
494         
495         // Parse arguments
496         regmatch_t matches[3];
497         if( RunRegex(&gUserItemIdentRegex, slot_id, 3, matches, NULL) != 0 ) {
498                 fprintf(stderr, "Error: Invalid item ID passed (<type>:<id> expected)\n");
499                 return RV_ARGUMENTS;
500         }
501         const char *item_type = slot_id + matches[1].rm_so;
502         slot_id[ matches[1].rm_eo ] = '\0';
503         int item_id = atoi( slot_id + matches[2].rm_so );
504
505         // - Price
506         char *end;
507         int price = strtol( price_str, &end, 0 );
508         if( price < 0 || *end != '\0' ) {
509                 fprintf(stderr, "Error: Invalid price passed (must be >= 0)\n");
510                 return RV_ARGUMENTS;
511         }
512         
513         // -- Sanity
514         for( char *pos = newname; *pos; pos ++ )
515         {
516                 if( !isalnum(*pos) && *pos != ' ' ) {
517                         fprintf(stderr, "Error: You should only have letters, numbers and spaces in an item name\n");
518                         return RV_ARGUMENTS;
519                 }
520         }
521         
522         // Connect & Authenticate
523         int sock = OpenConnection(gsDispenseServer, giDispensePort);
524         if( sock < 0 )  return RV_SOCKET_ERROR;
525         
526         ret = Authenticate(sock);
527         if(ret) return ret;
528         
529         // Update the slot
530         ret = Dispense_SetItem(sock, item_type, item_id, price, newname);
531         
532         close(sock);
533         return ret;
534 }
535
536 //
537 // `dispense pincheck`
538 // - Validate a user's pin (used by the snack machine)
539 //
540 int subcommand_pincheck(int argc, char *args[])
541 {
542          int    ret;
543         
544         if( argc < 1 || argc > 2 ) {
545                 fprintf(stderr, "Error: `dispense pincheck` takes one/two arguments\n");
546                 ShowUsage();
547                 return RV_ARGUMENTS;
548         }
549
550         struct passwd   *pwd = getpwuid( getuid() );
551         gsUserName = strdup(pwd->pw_name);
552         
553         const char *pin = args[0];
554         const char *user = (argc > 1 ? args[1] : gsUserName);
555         
556         int sock = OpenConnection(gsDispenseServer, giDispensePort);
557         if( sock < 0 )  return RV_SOCKET_ERROR;
558         
559         ret = Authenticate(sock);
560         if(ret) return ret;
561         
562         ret = DispenseCheckPin(sock, user, pin);
563         
564         close(sock);
565         return ret;
566 }
567
568 //
569 // `dispense pinset`
570 // - Set the pin of the current account
571 //
572 int subcommand_pinset(int argc, char *args[])
573 {
574          int    ret;
575         
576         if( argc != 1 ) {
577                 fprintf(stderr, "Error: `dispense pinset` takes one argument\n");
578                 ShowUsage();
579                 return RV_ARGUMENTS;
580         }
581         
582         const char *pin = args[0];
583         
584         int sock = OpenConnection(gsDispenseServer, giDispensePort);
585         if( sock < 0 )  return RV_SOCKET_ERROR;
586
587         ret = Authenticate(sock);
588         if(ret) return ret;
589
590         ret = DispenseSetPin(sock, pin);
591         
592         close(sock);
593         return ret;
594 }
595
596 int main(int argc, char *argv[])
597 {
598          int    i, ret = 0;
599         char    buffer[BUFSIZ];
600         
601         gsTextArgs[0] = "";
602
603         // -- Create regular expressions
604         // > Code Type Count ...
605         CompileRegex(&gArrayRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([0-9]+)", REG_EXTENDED);     //
606         // > Code Type Ident Status Price Desc
607         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);
608         // > Code 'SALT' salt
609         CompileRegex(&gSaltRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+(.+)$", REG_EXTENDED);
610         // > Code 'User' Username Balance Flags
611         CompileRegex(&gUserInfoRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([^ ]+)\\s+(-?[0-9]+)\\s+(.+)$", REG_EXTENDED);
612         // > Item Ident
613         CompileRegex(&gUserItemIdentRegex, "^([A-Za-z]+):([0-9]+)$", REG_EXTENDED);
614
615         // Parse Arguments
616         ret = ParseArguments(argc, argv);
617         if( ret )
618                 return ret;
619
620         // Load config file
621         // - Don't check return value, leads to defaults being used
622         if( Config_ParseFile(gsConfigFile) ) {
623                 fprintf(stderr, "NOTICE: Loading of config file '%s' failed, using defaults\n", gsConfigFile);
624         }
625
626         // Parse config values
627         if( !giDispenseServerSet ) {
628                 Config_GetValue_Str("dispense_server", &gsDispenseServer);
629         }
630         if (!giDispensePortSet) {
631                 Config_GetValue_Int("dispense_port", &giDispensePort);
632         }
633
634
635         // Sub-commands
636         if( strcmp(gsTextArgs[0], "finger") == 0 ) {
637                 return subcommand_finger();
638         }
639         else if( strcmp(gsTextArgs[0], "acct") == 0 ) {
640                 return subcommand_acct();
641         }
642         else if( strcmp(gsTextArgs[0], "give") == 0 ) {
643                 return subcommand_give(giTextArgc-1, gsTextArgs+1);
644         }
645         else if( strcmp(gsTextArgs[0], "user") == 0 ) {
646                 return subcommand_user(giTextArgc-1, gsTextArgs+1);
647         }
648         else if( strcmp(gsTextArgs[0], "donate") == 0 ) {
649                 return subcommand_donate(giTextArgc-1, gsTextArgs+1);
650         }
651         else if( strcmp(gsTextArgs[0], "refund") == 0 ) {
652                 return subcommand_refund(giTextArgc-1, gsTextArgs+1);
653         }
654         else if( strcmp(gsTextArgs[0], "iteminfo") == 0 ) {
655                 return subcommand_iteminfo(giTextArgc-1, gsTextArgs+1);
656         }
657         else if( strcmp(gsTextArgs[0], "slot") == 0 ) {
658                 return subcommand_slot(giTextArgc-1, gsTextArgs+1);
659         }
660         else if(strcmp(gsTextArgs[0], "pincheck") == 0) {
661                 return subcommand_pincheck(giTextArgc-1, gsTextArgs+1);
662         }
663         else if(strcmp(gsTextArgs[0], "pinset") == 0) {
664                 return subcommand_pinset(giTextArgc-1, gsTextArgs+1);
665         }
666         else {
667                 // Item name / pattern
668                 gsItemPattern = gsTextArgs[0];
669         }
670         
671         // Connect to server
672         int sock = OpenConnection(gsDispenseServer, giDispensePort);
673         if( sock < 0 )  return RV_SOCKET_ERROR;
674
675         // Get the user's balance
676         ret = GetUserBalance(sock);
677         if(ret) return ret;
678
679         // Get items
680         PopulateItemList(sock);
681         
682         // Disconnect from server
683         close(sock);
684         
685         if( gsItemPattern && gsItemPattern[0] )
686         {
687                 regmatch_t matches[3];
688                 // Door (hard coded)
689                 if( strcmp(gsItemPattern, "door") == 0 )
690                 {
691                         // Connect, Authenticate, dispense and close
692                         sock = OpenConnection(gsDispenseServer, giDispensePort);
693                         if( sock < 0 )  return RV_SOCKET_ERROR;
694                         ret = Authenticate(sock);
695                         if(ret) return ret;
696                         ret = DispenseItem(sock, "door", 0);
697                         close(sock);
698                         return ret;
699                 }
700                 // Item id (<type>:<num>)
701                 else if( RunRegex(&gUserItemIdentRegex, gsItemPattern, 3, matches, NULL) == 0 )
702                 {
703                         char    *ident;
704                          int    id;
705                         
706                         // Get and finish ident
707                         ident = gsItemPattern + matches[1].rm_so;
708                         gsItemPattern[matches[1].rm_eo] = '\0';
709                         // Get ID
710                         id = atoi( gsItemPattern + matches[2].rm_so );
711                         
712                         // Connect, Authenticate, dispense and close
713                         sock = OpenConnection(gsDispenseServer, giDispensePort);
714                         if( sock < 0 )  return RV_SOCKET_ERROR;
715                         
716                         Dispense_ItemInfo(sock, ident, id);
717                         
718                         ret = Authenticate(sock);
719                         if(ret) return ret;
720                         ret = DispenseItem(sock, ident, id);
721                         close(sock);
722                         return ret;
723                 }
724                 // Item number (6 = coke)
725                 else if( strcmp(gsItemPattern, "0") == 0 || atoi(gsItemPattern) > 0 )
726                 {
727                         i = atoi(gsItemPattern);
728                 }
729                 // Item prefix
730                 else
731                 {
732                          int    j;
733                          int    best = -1;
734                         for( i = 0; i < giNumItems; i ++ )
735                         {
736                                 // Prefix match (with case-insensitive match)
737                                 for( j = 0; gsItemPattern[j]; j ++ )
738                                 {
739                                         if( gaItems[i].Desc[j] == gsItemPattern[j] )
740                                                 continue;
741                                         if( tolower(gaItems[i].Desc[j]) == tolower(gsItemPattern[j]) )
742                                                 continue;
743                                         break;
744                                 }
745                                 // Check if the prefix matched
746                                 if( gsItemPattern[j] != '\0' )
747                                         continue;
748                                 
749                                 // Prefect match
750                                 if( gaItems[i].Desc[j] == '\0' ) {
751                                         best = i;
752                                         break;
753                                 }
754                                 
755                                 // Only one match allowed
756                                 if( best == -1 ) {
757                                         best = i;
758                                 }
759                                 else {
760                                         // TODO: Allow ambiguous matches?
761                                         // or just print a wanrning
762                                         printf("Warning - Ambiguous pattern, stopping\n");
763                                         return RV_BAD_ITEM;
764                                 }
765                         }
766                         
767                         // Was a match found?
768                         if( best == -1 )
769                         {
770                                 fprintf(stderr, "No item matches the passed string\n");
771                                 return RV_BAD_ITEM;
772                         }
773                         
774                         i = best;
775                 }
776         }
777         else if( giUIMode != UI_MODE_BASIC )
778         {
779                 i = ShowNCursesUI();
780         }
781         else
782         {
783                 // Very basic dispense interface
784                 for( i = 0; i < giNumItems; i ++ ) {
785                         // Add a separator
786                         if( i && strcmp(gaItems[i].Type, gaItems[i-1].Type) != 0 )
787                                 printf("   ---\n");
788                         
789                         printf("%2i %s:%i\t%3i %s\n", i, gaItems[i].Type, gaItems[i].ID,
790                                 gaItems[i].Price, gaItems[i].Desc);
791                 }
792                 printf(" q Quit\n");
793                 for(;;)
794                 {
795                         char    *buf;
796                         
797                         i = -1;
798                         
799                         fgets(buffer, BUFSIZ, stdin);
800                         
801                         buf = trim(buffer);
802                         
803                         if( buf[0] == 'q' )     break;
804                         
805                         i = atoi(buf);
806                         
807                         if( i != 0 || buf[0] == '0' )
808                         {
809                                 if( i < 0 || i >= giNumItems ) {
810                                         printf("Bad item %i (should be between 0 and %i)\n", i, giNumItems);
811                                         continue;
812                                 }
813                                 break;
814                         }
815                 }
816         }
817         
818         
819         // Check for a valid item ID
820         if( i >= 0 )
821         {
822                  int j;
823                 // Connect, Authenticate, dispense and close
824                 sock = OpenConnection(gsDispenseServer, giDispensePort);
825                 if( sock < 0 )  return RV_SOCKET_ERROR;
826                         
827                 ret = Dispense_ItemInfo(sock, gaItems[i].Type, gaItems[i].ID);
828                 if(ret) return ret;
829                 
830                 ret = Authenticate(sock);
831                 if(ret) return ret;
832                 
833                 for( j = 0; j < giDispenseCount; j ++ ) {
834                         ret = DispenseItem(sock, gaItems[i].Type, gaItems[i].ID);
835                         if( ret )       break;
836                 }
837                 if( j > 1 ) {
838                         printf("%i items dispensed\n", j);
839                 }
840                 Dispense_ShowUser(sock, gsUserName);
841                 close(sock);
842
843         }
844
845         return ret;
846 }
847
848 int ParseArguments(int argc, char *argv[])
849 {
850         bool    rest_free = false;
851         for( int i = 1; i < argc; i ++ )
852         {
853                 char    *arg = argv[i];
854                 
855                 // If it doesn't start with a '-', or -- has been seen
856                 // XXX: Hack - If parsing "user type", don't parse - options 
857                 bool hack_usertype = (i > 2 && strcmp(argv[i-2], "user") == 0 && strcmp(argv[i-1], "type") == 0);
858                 // XXX: Hack - Treat negative numbers as free
859                 bool hack_is_int = (arg[0] == '-' && isdigit(arg[1]) != 0);
860                 if( rest_free || arg[0] != '-' || hack_usertype || hack_is_int )
861                 {
862                         if( giTextArgc == MAX_TXT_ARGS )
863                         {
864                                 fprintf(stderr, "ERROR: Too many arguments\n");
865                                 return RV_ARGUMENTS;
866                         }
867                 
868                         gsTextArgs[giTextArgc++] = argv[i];
869                 }
870                 else if( arg[1] != '-' )
871                 {                       
872                         switch(arg[1])
873                         {
874                         case 'h':
875                         case '?':
876                                 ShowUsage();
877                                 exit(0);
878                                         
879                         case 'c':
880                                 if( i + 1 >= argc ) {
881                                         fprintf(stderr, "%s: -c takes an argument\n", argv[0]);
882                                         ShowUsage();
883                                         return -1;
884                                 }
885                                 giDispenseCount = atoi(argv[++i]);
886                                 if( giDispenseCount < 1 || giDispenseCount > DISPENSE_MULTIPLE_MAX ) {
887                                         fprintf(stderr, "Sorry, only 1-20 can be passed to -c (safety)\n");
888                                         return -1;
889                                 }
890                                 
891                                 break ;
892         
893                         case 'm':       // Minimum balance
894                                 if( i + 1 >= argc ) {
895                                         fprintf(stderr, "%s: -m takes an argument\n", argv[0]);
896                                         ShowUsage();
897                                         return RV_ARGUMENTS;
898                                 }
899                                 giMinimumBalance = atoi(argv[++i]);
900                                 break;
901                         case 'M':       // Maximum balance
902                                 if( i + 1 >= argc ) {
903                                         fprintf(stderr, "%s: -M takes an argument\n", argv[0]);
904                                         ShowUsage();
905                                         return RV_ARGUMENTS;
906                                 }
907                                 giMaximumBalance = atoi(argv[++i]);
908                                 break;
909                         
910                         case 'f':       // Override Config File
911                                 if( i + 1 >= argc ) {
912                                         fprintf(stderr, "%s: -f takes an argument\n", argv[0]);
913                                         ShowUsage();
914                                         return RV_ARGUMENTS;
915                                 }
916                                 gsConfigFile = argv[++i];
917                                 break;
918                         
919                         case 'u':       // Override User
920                                 if( i + 1 >= argc ) {
921                                         fprintf(stderr, "%s: -u takes an argument\n", argv[0]);
922                                         ShowUsage();
923                                         return RV_ARGUMENTS;
924                                 }
925                                 gsEffectiveUser = argv[++i];
926                                 break;
927                         
928                         case 'H':       // Override remote host
929                                 if( i + 1 >= argc ) {
930                                         fprintf(stderr, "%s: -H takes an argument\n", argv[0]);
931                                         ShowUsage();
932                                         return RV_ARGUMENTS;
933                                 }
934                                 gsDispenseServer = argv[++i];
935                                 giDispenseServerSet = 1;
936                                 break;
937                         case 'P':       // Override remote port
938                                 if( i + 1 >= argc ) {
939                                         fprintf(stderr, "%s: -P takes an argument\n", argv[0]);
940                                         ShowUsage();
941                                         return RV_ARGUMENTS;
942                                 }
943                                 giDispensePort = atoi(argv[++i]);
944                                 giDispensePortSet = 1;
945                                 break;
946                         
947                         // Set slot name/price
948                         case 's':
949                                 if( giTextArgc != 0 ) {
950                                         fprintf(stderr, "%s: -s must appear before other arguments\n", argv[0]);
951                                         ShowUsage();
952                                         return RV_ARGUMENTS;
953                                 }
954                                 gsTextArgs[0] = "slot"; // HACK!!
955                                 giTextArgc ++;
956                                 break;
957                         
958                         case 'G':       // Don't use GUI
959                                 giUIMode = UI_MODE_BASIC;
960                                 break;
961                         case 'D':       // Drinks only
962                                 giUIMode = UI_MODE_DRINKSONLY;
963                                 break;
964                         case 'n':       // Dry Run / read-only
965                                 gbDryRun = 1;
966                                 break;
967                         default:
968                                 fprintf(stderr, "%s: Unknown switch '%s'\n", argv[0], argv[i]);
969                                 ShowUsage();
970                                 return RV_ARGUMENTS;
971                         }
972
973                         continue;
974                 }
975                 else
976                 {
977                         // '--' : Terminate argument processing (remainder is free)
978                         if( arg[2] == '\0' ) {
979                                 rest_free = true;
980                         }
981                         else if( strcmp(arg, "--help") == 0 ) {
982                                 ShowUsage();
983                                 exit(0);
984                         }
985                         else if( strcmp(arg, "--dry-run") == 0 ) {
986                                 gbDryRun = 1;
987                         }
988                         else if( strcmp(arg, "--drinks-only") == 0 ) {
989                                 giUIMode = UI_MODE_DRINKSONLY;
990                         }
991                         else if( strcmp(arg, "--can-select-all") == 0 ) {
992                                 gbDisallowSelectWithoutBalance = 0;
993                         }
994                         else if( strcmp(arg, "--configfile") == 0 ) {
995                                 if( i + 1 >= argc ) {
996                                         fprintf(stderr, "%s: %s takes an argument\n", argv[0], arg);
997                                         ShowUsage();
998                                         return RV_ARGUMENTS;
999                                 }
1000                                 gsConfigFile = argv[++i];
1001                         }
1002                         else {
1003                                 fprintf(stderr, "%s: Unknown switch '%s'\n", argv[0], arg);
1004                                 ShowUsage();
1005                                 return RV_ARGUMENTS;
1006                         }
1007                 }
1008         }
1009         return 0;
1010 }
1011
1012 // ---------------
1013 // --- Helpers ---
1014 // ---------------
1015 char *trim(char *string)
1016 {
1017         // Increment pointer while it points to a space
1018         while( isspace(*string) )
1019                 string ++;
1020         
1021         // And repalce trailing spaces with NUL bytes
1022         for( int i = strlen(string); i-- && isspace(string[i]); )
1023                 string[i] = '\0';
1024         
1025         return string;
1026 }

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