3 * UCC (University [of WA] Computer Club) Electronic Accounting System
6 * main.c - Core and Initialisation
8 * This file is licenced under the 3-clause BSD Licence. See the file
9 * COPYING for full details.
14 #include <ctype.h> // isspace
19 #include <unistd.h> // close
20 #include <netdb.h> // gethostbyname
21 #include <pwd.h> // getpwuids
22 #include <sys/socket.h>
23 #include <netinet/in.h>
24 #include <arpa/inet.h>
27 typedef struct sItem {
34 int ShowNCursesUI(void);
36 int sendf(int Socket, const char *Format, ...);
37 int OpenConnection(const char *Host, int Port);
38 void Authenticate(int Socket);
39 char *trim(char *string);
40 int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage);
41 void CompileRegex(regex_t *regex, const char *pattern, int flags);
44 char *gsDispenseServer = "localhost";
45 int giDispensePort = 11020;
52 int main(int argc, char *argv[])
55 int i, responseCode, len;
58 // -- Create regular expressions
59 // > Code Type Count ...
60 CompileRegex(&gArrayRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([0-9]+)", REG_EXTENDED); //
61 // > Code Type Ident Price Desc
62 CompileRegex(&gItemRegex, "^([0-9]{3})\\s+(.+?)\\s+(.+?)\\s+([0-9]+)\\s+(.+)$", REG_EXTENDED);
65 sock = OpenConnection(gsDispenseServer, giDispensePort);
66 if( sock < 0 ) return -1;
68 // Determine what to do
71 if( strcmp(argv[1], "acct") == 0 )
79 // Ask server for stock list
80 send(sock, "ENUM_ITEMS\n", 11, 0);
81 len = recv(sock, buffer, BUFSIZ-1, 0);
86 printf("Output: %s\n", buffer);
88 responseCode = atoi(buffer);
89 if( responseCode != 201 )
91 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
97 char *itemType, *itemStart;
99 regmatch_t matches[4];
101 // Expected format: 201 Items <count> <item1> <item2> ...
102 RunRegex(&gArrayRegex, buffer, 4, matches, "Malformed server response");
104 itemType = &buffer[ matches[2].rm_so ]; buffer[ matches[2].rm_eo ] = '\0';
105 count = atoi( &buffer[ matches[3].rm_so ] );
108 if( strcmp(itemType, "Items") != 0 ) {
110 fprintf(stderr, "Unexpected array type, expected 'Items', got '%s'\n",
115 itemStart = &buffer[ matches[3].rm_eo ];
117 gaItems = malloc( count * sizeof(tItem) );
119 for( giNumItems = 0; giNumItems < count && itemStart; giNumItems ++ )
121 char *next = strchr( ++itemStart, ' ' );
122 if( next ) *next = '\0';
123 gaItems[giNumItems].Ident = strdup(itemStart);
128 // Get item information
129 for( i = 0; i < giNumItems; i ++ )
131 regmatch_t matches[6];
134 printf("%2i %s\t", i, gaItems[i].Ident);
137 sendf(sock, "ITEM_INFO %s\n", gaItems[i].Ident);
138 len = recv(sock, buffer, BUFSIZ-1, 0);
142 responseCode = atoi(buffer);
143 if( responseCode != 202 ) {
144 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
148 RunRegex(&gItemRegex, buffer, 6, matches, "Malformed server response");
150 buffer[ matches[3].rm_eo ] = '\0';
152 gaItems[i].Price = atoi( buffer + matches[4].rm_so );
153 gaItems[i].Desc = strdup( buffer + matches[5].rm_so );
155 printf("%3i %s\n", gaItems[i].Price, gaItems[i].Desc);
160 // and choose what to dispense
161 // TODO: ncurses interface (with separation between item classes)
162 // - Hmm... that would require standardising the item ID to be <class>:<index>
173 fgets(buffer, BUFSIZ, stdin);
177 if( buf[0] == 'q' ) break;
181 printf("buf = '%s', atoi(buf) = %i\n", buf, i);
183 if( i != 0 || buf[0] == '0' )
185 printf("i = %i\n", i);
187 if( i < 0 || i >= giNumItems ) {
188 printf("Bad item (should be between 0 and %i)\n", giNumItems);
199 sendf(sock, "DISPENSE %s\n", gaItems[i].Ident);
201 len = recv(sock, buffer, BUFSIZ-1, 0);
205 responseCode = atoi(buffer);
206 switch( responseCode )
209 printf("Dispense OK\n");
212 printf("Not authenticated\n");
215 printf("Insufficient balance\n");
218 printf("Bad item name, bug report\n");
221 printf("Item failed to dispense, is the slot empty?\n");
224 printf("Unknown response code %i\n", responseCode);
236 int ShowNCursesUI(void)
241 const int displayMinWidth = 34;
242 const int displayMinItems = 8;
243 char *titleString = "Dispense";
244 int titleStringLen = strlen(titleString);
245 int itemCount = displayMinItems;
248 int height = itemCount + 3;
249 int width = displayMinWidth;
255 xBase = COLS/2 - width/2;
256 yBase = LINES/2 - height/2;
261 move( yBase, xBase );
263 times = width/2 - titleStringLen/2 - 2;
264 while(times --) addch('-');
268 times = width/2 - titleStringLen/2 - 2;
269 while(times --) addch('-');
273 for( i = 0; i < itemCount; i ++ )
276 move( yBase + 1 + i, xBase );
281 if( i == 0 && itemBase > 0 ) {
283 times = width - 1 - 8;
284 while(times--) addch(' ');
286 else if( i == itemCount - 1 && itemBase < giNumItems - itemCount ) {
288 times = width - 1 - 8;
289 while(times--) addch(' ');
293 if( itemBase + i < 0 || itemBase + i >= giNumItems ) {
294 printw("%02i %i OOR", itemBase + i, i);
297 printw("%02i %s", itemBase + i, gaItems[itemBase + i].Desc);
299 getyx(stdscr, _y, _x);
300 times = width - 6 - (_x - xBase); // TODO: Better handling for large prices
301 while(times--) addch(' ');
302 printw("%4i ", gaItems[itemBase + i].Price);
305 // Scrollbar (if needed)
306 if( giNumItems > itemCount ) {
310 else if( i == itemCount - 1 ) {
314 int percentage = itemBase * 100 / (giNumItems-itemCount);
315 if( i-1 == percentage*(itemCount-3)/100 ) {
329 move( yBase + 1 + itemCount, xBase );
331 times = width/2 - titleStringLen/2 - 2;
332 while(times --) addch('-');
336 times = width/2 - titleStringLen/2 - 2;
337 while(times --) addch('-');
340 move( yBase + 1 + itemCount + 1, xBase );
342 int count = itemCount-2;
344 if( itemBase == 0 ) count ++;
346 if( itemBase == giNumItems-itemCount) {
350 printw("%i - %i / %i items", itemBase, itemBase+count, giNumItems);
363 if( itemBase < giNumItems - (itemCount) )
389 int sendf(int Socket, const char *Format, ...)
394 va_start(args, Format);
395 len = vsnprintf(NULL, 0, Format, args);
400 va_start(args, Format);
401 vsnprintf(buf, len+1, Format, args);
404 return send(Socket, buf, len, 0);
408 int OpenConnection(const char *Host, int Port)
410 struct hostent *host;
411 struct sockaddr_in serverAddr;
414 host = gethostbyname(Host);
416 fprintf(stderr, "Unable to look up '%s'\n", Host);
420 memset(&serverAddr, 0, sizeof(serverAddr));
422 serverAddr.sin_family = AF_INET; // IPv4
423 // NOTE: I have a suspicion that IPv6 will play sillybuggers with this :)
424 serverAddr.sin_addr.s_addr = *((unsigned long *) host->h_addr_list[0]);
425 serverAddr.sin_port = htons(Port);
427 sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
429 fprintf(stderr, "Failed to create socket\n");
435 struct sockaddr_in localAddr;
436 memset(&localAddr, 0, sizeof(localAddr));
437 localAddr.sin_family = AF_INET; // IPv4
438 localAddr.sin_port = 1023; // IPv4
439 // Attempt to bind to low port for autoauth
440 bind(sock, &localAddr, sizeof(localAddr));
444 if( connect(sock, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0 ) {
445 fprintf(stderr, "Failed to connect to server\n");
452 void Authenticate(int Socket)
459 pwd = getpwuid( getuid() );
461 // Attempt automatic authentication
462 sendf(Socket, "AUTOAUTH %s\n", pwd->pw_name);
464 // Check if it worked
465 recv(Socket, buf, 511, 0);
468 responseCode = atoi(buf);
469 switch( responseCode )
471 case 200: // Authenticated, return :)
473 case 401: // Untrusted, attempt password authentication
475 case 404: // Bad Username
476 fprintf(stderr, "Bad Username '%s'\n", pwd->pw_name);
479 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
487 char *trim(char *string)
491 while( isspace(*string) )
494 for( i = strlen(string); i--; )
496 if( isspace(string[i]) )
505 int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage)
509 ret = regexec(regex, string, nMatches, matches, 0);
511 size_t len = regerror(ret, regex, NULL, 0);
513 regerror(ret, regex, errorStr, len);
514 printf("string = '%s'\n", string);
515 fprintf(stderr, "%s\n%s", errorMessage, errorStr);
522 void CompileRegex(regex_t *regex, const char *pattern, int flags)
524 int ret = regcomp(regex, pattern, flags);
526 size_t len = regerror(ret, regex, NULL, 0);
528 regerror(ret, regex, errorStr, len);
529 fprintf(stderr, "Regex compilation failed - %s\n", errorStr);