Client - Document command line flags
[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         Config_ParseFile(gsConfigFile);
622
623         // Parse config values
624         if (!giDispenseServerSet) {
625                 gsDispenseServer        = Config_GetValue("dispense_server",0);
626         }
627         if (!giDispensePortSet) {
628                 giDispensePort          = Config_GetValue_Int("dispense_port",0);
629         }
630
631
632         // Sub-commands
633         if( strcmp(gsTextArgs[0], "finger") == 0 ) {
634                 return subcommand_finger();
635         }
636         else if( strcmp(gsTextArgs[0], "acct") == 0 ) {
637                 return subcommand_acct();
638         }
639         else if( strcmp(gsTextArgs[0], "give") == 0 ) {
640                 return subcommand_give(giTextArgc-1, gsTextArgs+1);
641         }
642         else if( strcmp(gsTextArgs[0], "user") == 0 ) {
643                 return subcommand_user(giTextArgc-1, gsTextArgs+1);
644         }
645         else if( strcmp(gsTextArgs[0], "donate") == 0 ) {
646                 return subcommand_donate(giTextArgc-1, gsTextArgs+1);
647         }
648         else if( strcmp(gsTextArgs[0], "refund") == 0 ) {
649                 return subcommand_refund(giTextArgc-1, gsTextArgs+1);
650         }
651         else if( strcmp(gsTextArgs[0], "iteminfo") == 0 ) {
652                 return subcommand_iteminfo(giTextArgc-1, gsTextArgs+1);
653         }
654         else if( strcmp(gsTextArgs[0], "slot") == 0 ) {
655                 return subcommand_slot(giTextArgc-1, gsTextArgs+1);
656         }
657         else if(strcmp(gsTextArgs[0], "pincheck") == 0) {
658                 return subcommand_pincheck(giTextArgc-1, gsTextArgs+1);
659         }
660         else if(strcmp(gsTextArgs[0], "pinset") == 0) {
661                 return subcommand_pinset(giTextArgc-1, gsTextArgs+1);
662         }
663         else {
664                 // Item name / pattern
665                 gsItemPattern = gsTextArgs[0];
666         }
667         
668         // Connect to server
669         int sock = OpenConnection(gsDispenseServer, giDispensePort);
670         if( sock < 0 )  return RV_SOCKET_ERROR;
671
672         // Get the user's balance
673         ret = GetUserBalance(sock);
674         if(ret) return ret;
675
676         // Get items
677         PopulateItemList(sock);
678         
679         // Disconnect from server
680         close(sock);
681         
682         if( gsItemPattern && gsItemPattern[0] )
683         {
684                 regmatch_t matches[3];
685                 // Door (hard coded)
686                 if( strcmp(gsItemPattern, "door") == 0 )
687                 {
688                         // Connect, Authenticate, dispense and close
689                         sock = OpenConnection(gsDispenseServer, giDispensePort);
690                         if( sock < 0 )  return RV_SOCKET_ERROR;
691                         ret = Authenticate(sock);
692                         if(ret) return ret;
693                         ret = DispenseItem(sock, "door", 0);
694                         close(sock);
695                         return ret;
696                 }
697                 // Item id (<type>:<num>)
698                 else if( RunRegex(&gUserItemIdentRegex, gsItemPattern, 3, matches, NULL) == 0 )
699                 {
700                         char    *ident;
701                          int    id;
702                         
703                         // Get and finish ident
704                         ident = gsItemPattern + matches[1].rm_so;
705                         gsItemPattern[matches[1].rm_eo] = '\0';
706                         // Get ID
707                         id = atoi( gsItemPattern + matches[2].rm_so );
708                         
709                         // Connect, Authenticate, dispense and close
710                         sock = OpenConnection(gsDispenseServer, giDispensePort);
711                         if( sock < 0 )  return RV_SOCKET_ERROR;
712                         
713                         Dispense_ItemInfo(sock, ident, id);
714                         
715                         ret = Authenticate(sock);
716                         if(ret) return ret;
717                         ret = DispenseItem(sock, ident, id);
718                         close(sock);
719                         return ret;
720                 }
721                 // Item number (6 = coke)
722                 else if( strcmp(gsItemPattern, "0") == 0 || atoi(gsItemPattern) > 0 )
723                 {
724                         i = atoi(gsItemPattern);
725                 }
726                 // Item prefix
727                 else
728                 {
729                          int    j;
730                          int    best = -1;
731                         for( i = 0; i < giNumItems; i ++ )
732                         {
733                                 // Prefix match (with case-insensitive match)
734                                 for( j = 0; gsItemPattern[j]; j ++ )
735                                 {
736                                         if( gaItems[i].Desc[j] == gsItemPattern[j] )
737                                                 continue;
738                                         if( tolower(gaItems[i].Desc[j]) == tolower(gsItemPattern[j]) )
739                                                 continue;
740                                         break;
741                                 }
742                                 // Check if the prefix matched
743                                 if( gsItemPattern[j] != '\0' )
744                                         continue;
745                                 
746                                 // Prefect match
747                                 if( gaItems[i].Desc[j] == '\0' ) {
748                                         best = i;
749                                         break;
750                                 }
751                                 
752                                 // Only one match allowed
753                                 if( best == -1 ) {
754                                         best = i;
755                                 }
756                                 else {
757                                         // TODO: Allow ambiguous matches?
758                                         // or just print a wanrning
759                                         printf("Warning - Ambiguous pattern, stopping\n");
760                                         return RV_BAD_ITEM;
761                                 }
762                         }
763                         
764                         // Was a match found?
765                         if( best == -1 )
766                         {
767                                 fprintf(stderr, "No item matches the passed string\n");
768                                 return RV_BAD_ITEM;
769                         }
770                         
771                         i = best;
772                 }
773         }
774         else if( giUIMode != UI_MODE_BASIC )
775         {
776                 i = ShowNCursesUI();
777         }
778         else
779         {
780                 // Very basic dispense interface
781                 for( i = 0; i < giNumItems; i ++ ) {
782                         // Add a separator
783                         if( i && strcmp(gaItems[i].Type, gaItems[i-1].Type) != 0 )
784                                 printf("   ---\n");
785                         
786                         printf("%2i %s:%i\t%3i %s\n", i, gaItems[i].Type, gaItems[i].ID,
787                                 gaItems[i].Price, gaItems[i].Desc);
788                 }
789                 printf(" q Quit\n");
790                 for(;;)
791                 {
792                         char    *buf;
793                         
794                         i = -1;
795                         
796                         fgets(buffer, BUFSIZ, stdin);
797                         
798                         buf = trim(buffer);
799                         
800                         if( buf[0] == 'q' )     break;
801                         
802                         i = atoi(buf);
803                         
804                         if( i != 0 || buf[0] == '0' )
805                         {
806                                 if( i < 0 || i >= giNumItems ) {
807                                         printf("Bad item %i (should be between 0 and %i)\n", i, giNumItems);
808                                         continue;
809                                 }
810                                 break;
811                         }
812                 }
813         }
814         
815         
816         // Check for a valid item ID
817         if( i >= 0 )
818         {
819                  int j;
820                 // Connect, Authenticate, dispense and close
821                 sock = OpenConnection(gsDispenseServer, giDispensePort);
822                 if( sock < 0 )  return RV_SOCKET_ERROR;
823                         
824                 ret = Dispense_ItemInfo(sock, gaItems[i].Type, gaItems[i].ID);
825                 if(ret) return ret;
826                 
827                 ret = Authenticate(sock);
828                 if(ret) return ret;
829                 
830                 for( j = 0; j < giDispenseCount; j ++ ) {
831                         ret = DispenseItem(sock, gaItems[i].Type, gaItems[i].ID);
832                         if( ret )       break;
833                 }
834                 if( j > 1 ) {
835                         printf("%i items dispensed\n", j);
836                 }
837                 Dispense_ShowUser(sock, gsUserName);
838                 close(sock);
839
840         }
841
842         return ret;
843 }
844
845 int ParseArguments(int argc, char *argv[])
846 {
847         for( int i = 1; i < argc; i ++ )
848         {
849                 char    *arg = argv[i];
850                 
851                 if( arg[0] == '-' )
852                 {                       
853                         switch(arg[1])
854                         {
855                         case 'h':
856                         case '?':
857                                 ShowUsage();
858                                 return 0;
859                                         
860                         case 'c':
861                                 if( i > 2 && strcmp(argv[i-1], "type") == 0 )
862                                         goto _default;
863                                 if( i + 1 >= argc ) {
864                                         fprintf(stderr, "%s: -c takes an argument\n", argv[0]);
865                                         ShowUsage();
866                                         return -1;
867                                 }
868                                 giDispenseCount = atoi(argv[++i]);
869                                 if( giDispenseCount < 1 || giDispenseCount > DISPENSE_MULTIPLE_MAX ) {
870                                         fprintf(stderr, "Sorry, only 1-20 can be passed to -c (safety)\n");
871                                         return -1;
872                                 }
873                                 
874                                 break ;
875         
876                         case 'm':       // Minimum balance
877                                 if( i + 1 >= argc ) {
878                                         fprintf(stderr, "%s: -m takes an argument\n", argv[0]);
879                                         ShowUsage();
880                                         return RV_ARGUMENTS;
881                                 }
882                                 giMinimumBalance = atoi(argv[++i]);
883                                 break;
884                         case 'M':       // Maximum balance
885                                 if( i + 1 >= argc ) {
886                                         fprintf(stderr, "%s: -M takes an argument\n", argv[0]);
887                                         ShowUsage();
888                                         return RV_ARGUMENTS;
889                                 }
890                                 giMaximumBalance = atoi(argv[++i]);
891                                 break;
892                         
893                         case 'f':       // Override Config File
894                                 if( i + 1 >= argc ) {
895                                         fprintf(stderr, "%s: -f takes an argument\n", argv[0]);
896                                         ShowUsage();
897                                         return RV_ARGUMENTS;
898                                 }
899                                 gsConfigFile = argv[++i];
900                                 break;
901                         
902                         case 'u':       // Override User
903                                 if( i + 1 >= argc ) {
904                                         fprintf(stderr, "%s: -u takes an argument\n", argv[0]);
905                                         ShowUsage();
906                                         return RV_ARGUMENTS;
907                                 }
908                                 gsEffectiveUser = argv[++i];
909                                 break;
910                         
911                         case 'H':       // Override remote host
912                                 if( i + 1 >= argc ) {
913                                         fprintf(stderr, "%s: -H takes an argument\n", argv[0]);
914                                         ShowUsage();
915                                         return RV_ARGUMENTS;
916                                 }
917                                 gsDispenseServer = argv[++i];
918                                 giDispenseServerSet = 1;
919                                 break;
920                         case 'P':       // Override remote port
921                                 if( i + 1 >= argc ) {
922                                         fprintf(stderr, "%s: -P takes an argument\n", argv[0]);
923                                         ShowUsage();
924                                         return RV_ARGUMENTS;
925                                 }
926                                 giDispensePort = atoi(argv[++i]);
927                                 giDispensePortSet = 1;
928                                 break;
929                         
930                         // Set slot name/price
931                         case 's':
932                                 if( giTextArgc != 0 ) {
933                                         fprintf(stderr, "%s: -s must appear before other arguments\n", argv[0]);
934                                         ShowUsage();
935                                         return RV_ARGUMENTS;
936                                 }
937                                 gsTextArgs[0] = "slot"; // HACK!!
938                                 giTextArgc ++;
939                                 break;
940                         
941                         case 'G':       // Don't use GUI
942                                 giUIMode = UI_MODE_BASIC;
943                                 break;
944                         case 'D':       // Drinks only
945                                 giUIMode = UI_MODE_DRINKSONLY;
946                                 break;
947                         case 'n':       // Dry Run / read-only
948                                 gbDryRun = 1;
949                                 break;
950                         case '-':
951                                 if( strcmp(argv[i], "--help") == 0 ) {
952                                         ShowUsage();
953                                         return 0;
954                                 }
955                                 else if( strcmp(argv[i], "--dry-run") == 0 ) {
956                                         gbDryRun = 1;
957                                 }
958                                 else if( strcmp(argv[i], "--drinks-only") == 0 ) {
959                                         giUIMode = UI_MODE_DRINKSONLY;
960                                 }
961                                 else if( strcmp(argv[i], "--can-select-all") == 0 ) {
962                                         gbDisallowSelectWithoutBalance = 0;
963                                 }
964                                 else {
965                                         fprintf(stderr, "%s: Unknown switch '%s'\n", argv[0], argv[i]);
966                                         ShowUsage();
967                                         return RV_ARGUMENTS;
968                                 }
969                                 break;
970                         default: _default:
971                                 // The first argument is not allowed to begin with 'i'
972                                 // (catches most bad flags)
973                                 if( giTextArgc == 0 ) {
974                                         fprintf(stderr, "%s: Unknown switch '%s'\n", argv[0], argv[i]);
975                                         ShowUsage();
976                                         return RV_ARGUMENTS;
977                                 }
978                                 if( giTextArgc == MAX_TXT_ARGS )
979                                 {
980                                         fprintf(stderr, "ERROR: Too many arguments\n");
981                                         return RV_ARGUMENTS;
982                                 }
983                                 gsTextArgs[giTextArgc++] = argv[i];
984                                 break;
985                         }
986
987                         continue;
988                 }
989
990                 if( giTextArgc == MAX_TXT_ARGS )
991                 {
992                         fprintf(stderr, "ERROR: Too many arguments\n");
993                         return RV_ARGUMENTS;
994                 }
995         
996                 gsTextArgs[giTextArgc++] = argv[i];
997         
998         }
999         return 0;
1000 }
1001
1002 // ---------------
1003 // --- Helpers ---
1004 // ---------------
1005 char *trim(char *string)
1006 {
1007          int    i;
1008         
1009         while( isspace(*string) )
1010                 string ++;
1011         
1012         for( i = strlen(string); i--; )
1013         {
1014                 if( isspace(string[i]) )
1015                         string[i] = '\0';
1016                 else
1017                         break;
1018         }
1019         
1020         return string;
1021 }

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