+ sock = OpenConnection(gsDispenseServer, giDispensePort);
+ if( sock < 0 ) return RV_SOCKET_ERROR;
+
+ ret = Dispense_ItemInfo(sock, type, id);
+ close(sock);
+ return ret;
+ }
+ // Set slot
+ else if( strcmp(gsTextArgs[0], "slot") == 0 )
+ {
+ regmatch_t matches[3];
+ char *item_type, *newname;
+ int item_id, price;
+
+ // Check arguments
+ if( giTextArgc != 4 ) {
+ fprintf(stderr, "Error: `dispense slot` takes three arguments\n");
+ ShowUsage();
+ return RV_ARGUMENTS;
+ }
+
+ // Parse arguments
+ if( RunRegex(&gUserItemIdentRegex, gsTextArgs[1], 3, matches, NULL) != 0 ) {
+ fprintf(stderr, "Error: Invalid item ID passed (<type>:<id> expected)\n");
+ return RV_ARGUMENTS;
+ }
+ item_type = gsTextArgs[1] + matches[1].rm_so;
+ gsTextArgs[1][ matches[1].rm_eo ] = '\0';
+ item_id = atoi( gsTextArgs[1] + matches[2].rm_so );
+
+ // - Price
+ price = atoi( gsTextArgs[2] );
+ if( price <= 0 && gsTextArgs[2][0] != '0' ) {
+ fprintf(stderr, "Error: Invalid price passed (must be >= 0)\n");
+ return RV_ARGUMENTS;
+ }
+
+ // - New name
+ newname = gsTextArgs[3];
+ // -- Sanity
+ {
+ char *pos;
+ for( pos = newname; *pos; pos ++ )
+ {
+ if( !isalnum(*pos) && *pos != ' ' ) {
+ fprintf(stderr, "Error: You should only have letters, numbers and spaces in an item name\n");
+ return RV_ARGUMENTS;
+ }
+ }
+ }
+
+ // Connect & Authenticate
+ sock = OpenConnection(gsDispenseServer, giDispensePort);
+ if( sock < 0 ) return RV_SOCKET_ERROR;
+ ret = Authenticate(sock);
+ if(ret) return ret;
+ // Update the slot
+ ret = Dispense_SetItem(sock, item_type, item_id, price, newname);
+
+ close(sock);
+ return ret;
+ }
+ // Check a user's pin
+ else if(strcmp(gsTextArgs[0], "pincheck") == 0)
+ {
+ if( giTextArgc < 2 || giTextArgc > 3 ) {
+ fprintf(stderr, "Error: `dispense pincheck` takes one/two arguments\n");
+ ShowUsage();
+ return RV_ARGUMENTS;
+ }
+ struct passwd *pwd = getpwuid( getuid() );
+ gsUserName = strdup(pwd->pw_name);
+
+ const char *pin = gsTextArgs[1];
+ const char *user = gsUserName;
+ if( giTextArgc == 3 )
+ user = gsTextArgs[2];
+
+
+ sock = OpenConnection(gsDispenseServer, giDispensePort);
+ if( sock < 0 ) return RV_SOCKET_ERROR;
+ ret = Authenticate(sock);
+ if(ret) return ret;
+
+ ret = DispenseCheckPin(sock, user, pin);
+
+ close(sock);
+ return ret;
+ }
+ // Update 'your' pin
+ else if(strcmp(gsTextArgs[0], "pinset") == 0)
+ {
+ if( giTextArgc != 2 ) {
+ fprintf(stderr, "Error: `dispense pinset` takes one argument\n");
+ ShowUsage();
+ return RV_ARGUMENTS;
+ }
+
+ const char *pin = gsTextArgs[1];
+
+ sock = OpenConnection(gsDispenseServer, giDispensePort);
+ if( sock < 0 ) return RV_SOCKET_ERROR;
+ ret = Authenticate(sock);
+ if(ret) return ret;
+
+ ret = DispenseSetPin(sock, pin);
+
+ close(sock);
+ return ret;
+ }
+ // Item name / pattern
+ else
+ {
+ gsItemPattern = gsTextArgs[0];
+ }
+
+ // Connect to server
+ sock = OpenConnection(gsDispenseServer, giDispensePort);
+ if( sock < 0 ) return RV_SOCKET_ERROR;
+
+ // Get the user's balance
+ ret = GetUserBalance(sock);
+ if(ret) return ret;
+
+ // Get items
+ PopulateItemList(sock);
+
+ // Disconnect from server
+ close(sock);
+
+ if( gsItemPattern && gsItemPattern[0] )
+ {
+ regmatch_t matches[3];
+ // Door (hard coded)
+ if( strcmp(gsItemPattern, "door") == 0 )
+ {
+ // Connect, Authenticate, dispense and close
+ sock = OpenConnection(gsDispenseServer, giDispensePort);
+ if( sock < 0 ) return RV_SOCKET_ERROR;
+ ret = Authenticate(sock);
+ if(ret) return ret;
+ ret = DispenseItem(sock, "door", 0);
+ close(sock);
+ return ret;
+ }
+ // Item id (<type>:<num>)
+ else if( RunRegex(&gUserItemIdentRegex, gsItemPattern, 3, matches, NULL) == 0 )
+ {
+ char *ident;
+ int id;
+
+ // Get and finish ident
+ ident = gsItemPattern + matches[1].rm_so;
+ gsItemPattern[matches[1].rm_eo] = '\0';
+ // Get ID
+ id = atoi( gsItemPattern + matches[2].rm_so );
+
+ // Connect, Authenticate, dispense and close
+ sock = OpenConnection(gsDispenseServer, giDispensePort);
+ if( sock < 0 ) return RV_SOCKET_ERROR;
+
+ Dispense_ItemInfo(sock, ident, id);
+
+ ret = Authenticate(sock);
+ if(ret) return ret;
+ ret = DispenseItem(sock, ident, id);
+ close(sock);
+ return ret;
+ }
+ // Item number (6 = coke)
+ else if( strcmp(gsItemPattern, "0") == 0 || atoi(gsItemPattern) > 0 )
+ {
+ i = atoi(gsItemPattern);
+ }
+ // Item prefix
+ else
+ {
+ int j;
+ int best = -1;
+ for( i = 0; i < giNumItems; i ++ )
+ {
+ // Prefix match (with case-insensitive match)
+ for( j = 0; gsItemPattern[j]; j ++ )
+ {
+ if( gaItems[i].Desc[j] == gsItemPattern[j] )
+ continue;
+ if( tolower(gaItems[i].Desc[j]) == tolower(gsItemPattern[j]) )
+ continue;
+ break;
+ }
+ // Check if the prefix matched
+ if( gsItemPattern[j] != '\0' )
+ continue;
+
+ // Prefect match
+ if( gaItems[i].Desc[j] == '\0' ) {
+ best = i;
+ break;
+ }
+
+ // Only one match allowed
+ if( best == -1 ) {
+ best = i;
+ }
+ else {
+ // TODO: Allow ambiguous matches?
+ // or just print a wanrning
+ printf("Warning - Ambiguous pattern, stopping\n");
+ return RV_BAD_ITEM;
+ }
+ }
+
+ // Was a match found?
+ if( best == -1 )
+ {
+ fprintf(stderr, "No item matches the passed string\n");
+ return RV_BAD_ITEM;
+ }
+
+ i = best;
+ }
+ }
+ else if( giUIMode != UI_MODE_BASIC )
+ {
+ i = ShowNCursesUI();
+ }
+ else
+ {
+ // Very basic dispense interface
+ for( i = 0; i < giNumItems; i ++ ) {
+ // Add a separator
+ if( i && strcmp(gaItems[i].Type, gaItems[i-1].Type) != 0 )
+ printf(" ---\n");
+
+ printf("%2i %s:%i\t%3i %s\n", i, gaItems[i].Type, gaItems[i].ID,
+ gaItems[i].Price, gaItems[i].Desc);
+ }
+ printf(" q Quit\n");
+ for(;;)
+ {
+ char *buf;
+
+ i = -1;
+
+ fgets(buffer, BUFSIZ, stdin);
+
+ buf = trim(buffer);
+
+ if( buf[0] == 'q' ) break;
+
+ i = atoi(buf);
+
+ if( i != 0 || buf[0] == '0' )
+ {
+ if( i < 0 || i >= giNumItems ) {
+ printf("Bad item %i (should be between 0 and %i)\n", i, giNumItems);
+ continue;
+ }
+ break;
+ }
+ }
+ }
+
+
+ // Check for a valid item ID
+ if( i >= 0 )
+ {
+ int j;
+ // Connect, Authenticate, dispense and close
+ sock = OpenConnection(gsDispenseServer, giDispensePort);
+ if( sock < 0 ) return RV_SOCKET_ERROR;
+
+ ret = Dispense_ItemInfo(sock, gaItems[i].Type, gaItems[i].ID);
+ if(ret) return ret;
+
+ ret = Authenticate(sock);
+ if(ret) return ret;
+
+ for( j = 0; j < giDispenseCount; j ++ ) {
+ ret = DispenseItem(sock, gaItems[i].Type, gaItems[i].ID);
+ if( ret ) break;
+ }
+ if( j > 1 ) {
+ printf("%i items dispensed\n", j);
+ }
+ Dispense_ShowUser(sock, gsUserName);
+ close(sock);
+
+ }
+
+ return ret;
+}
+
+// ---------------
+// --- Helpers ---
+// ---------------
+char *trim(char *string)
+{
+ int i;
+
+ while( isspace(*string) )
+ string ++;
+
+ for( i = strlen(string); i--; )
+ {
+ if( isspace(string[i]) )
+ string[i] = '\0';
+ else
+ break;
+ }
+
+ return string;
+}
+
+int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage)
+{
+ int ret;
+
+ ret = regexec(regex, string, nMatches, matches, 0);
+ if( ret && errorMessage ) {
+ size_t len = regerror(ret, regex, NULL, 0);
+ char errorStr[len];
+ regerror(ret, regex, errorStr, len);
+ printf("string = '%s'\n", string);
+ fprintf(stderr, "%s\n%s", errorMessage, errorStr);
+ exit(-1);
+ }
+
+ return ret;
+}
+
+void CompileRegex(regex_t *regex, const char *pattern, int flags)
+{
+ int ret = regcomp(regex, pattern, flags);
+ if( ret ) {
+ size_t len = regerror(ret, regex, NULL, 0);
+ char errorStr[len];
+ regerror(ret, regex, errorStr, len);
+ fprintf(stderr, "Regex compilation failed - %s\n", errorStr);
+ exit(-1);
+ }