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

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