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

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