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

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