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);
35 void PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1, const char *Mid, char Pad2, const char *Right, ...);
37 int sendf(int Socket, const char *Format, ...);
38 int OpenConnection(const char *Host, int Port);
39 void Authenticate(int Socket);
40 char *trim(char *string);
41 int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage);
42 void CompileRegex(regex_t *regex, const char *pattern, int flags);
45 char *gsDispenseServer = "localhost";
46 int giDispensePort = 11020;
53 int main(int argc, char *argv[])
56 int i, responseCode, len;
59 // -- Create regular expressions
60 // > Code Type Count ...
61 CompileRegex(&gArrayRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([0-9]+)", REG_EXTENDED); //
62 // > Code Type Ident Price Desc
63 CompileRegex(&gItemRegex, "^([0-9]{3})\\s+(.+?)\\s+(.+?)\\s+([0-9]+)\\s+(.+)$", REG_EXTENDED);
66 sock = OpenConnection(gsDispenseServer, giDispensePort);
67 if( sock < 0 ) return -1;
69 // Determine what to do
72 if( strcmp(argv[1], "acct") == 0 )
80 // Ask server for stock list
81 send(sock, "ENUM_ITEMS\n", 11, 0);
82 len = recv(sock, buffer, BUFSIZ-1, 0);
87 printf("Output: %s\n", buffer);
89 responseCode = atoi(buffer);
90 if( responseCode != 201 )
92 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
98 char *itemType, *itemStart;
100 regmatch_t matches[4];
102 // Expected format: 201 Items <count> <item1> <item2> ...
103 RunRegex(&gArrayRegex, buffer, 4, matches, "Malformed server response");
105 itemType = &buffer[ matches[2].rm_so ]; buffer[ matches[2].rm_eo ] = '\0';
106 count = atoi( &buffer[ matches[3].rm_so ] );
109 if( strcmp(itemType, "Items") != 0 ) {
111 fprintf(stderr, "Unexpected array type, expected 'Items', got '%s'\n",
116 itemStart = &buffer[ matches[3].rm_eo ];
118 gaItems = malloc( count * sizeof(tItem) );
120 for( giNumItems = 0; giNumItems < count && itemStart; giNumItems ++ )
122 char *next = strchr( ++itemStart, ' ' );
123 if( next ) *next = '\0';
124 gaItems[giNumItems].Ident = strdup(itemStart);
129 // Get item information
130 for( i = 0; i < giNumItems; i ++ )
132 regmatch_t matches[6];
135 printf("%2i %s\t", i, gaItems[i].Ident);
138 sendf(sock, "ITEM_INFO %s\n", gaItems[i].Ident);
139 len = recv(sock, buffer, BUFSIZ-1, 0);
143 responseCode = atoi(buffer);
144 if( responseCode != 202 ) {
145 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
149 RunRegex(&gItemRegex, buffer, 6, matches, "Malformed server response");
151 buffer[ matches[3].rm_eo ] = '\0';
153 gaItems[i].Price = atoi( buffer + matches[4].rm_so );
154 gaItems[i].Desc = strdup( buffer + matches[5].rm_so );
156 printf("%3i %s\n", gaItems[i].Price, gaItems[i].Desc);
161 // and choose what to dispense
162 // TODO: ncurses interface (with separation between item classes)
163 // - Hmm... that would require standardising the item ID to be <class>:<index>
174 fgets(buffer, BUFSIZ, stdin);
178 if( buf[0] == 'q' ) break;
182 printf("buf = '%s', atoi(buf) = %i\n", buf, i);
184 if( i != 0 || buf[0] == '0' )
186 printf("i = %i\n", i);
188 if( i < 0 || i >= giNumItems ) {
189 printf("Bad item (should be between 0 and %i)\n", giNumItems);
200 sendf(sock, "DISPENSE %s\n", gaItems[i].Ident);
202 len = recv(sock, buffer, BUFSIZ-1, 0);
206 responseCode = atoi(buffer);
207 switch( responseCode )
210 printf("Dispense OK\n");
213 printf("Not authenticated\n");
216 printf("Insufficient balance\n");
219 printf("Bad item name, bug report\n");
222 printf("Item failed to dispense, is the slot empty?\n");
225 printf("Unknown response code %i\n", responseCode);
235 void ShowItemAt(int Row, int Col, int Width, int Index)
241 if( Index < 0 || Index >= giNumItems ) {
242 printw("%02i OOR", Index);
245 printw("%02i %s", Index, gaItems[Index].Desc);
247 getyx(stdscr, _y, _x);
248 // Assumes max 4 digit prices
249 times = Width - 4 - (_x - Col); // TODO: Better handling for large prices
250 while(times--) addch(' ');
251 printw("%4i", gaItems[Index].Price);
256 int ShowNCursesUI(void)
261 const int displayMinWidth = 34;
262 const int displayMinItems = 8;
263 char *titleString = "Dispense";
264 int itemCount = displayMinItems;
267 int height = itemCount + 3;
268 int width = displayMinWidth;
274 xBase = COLS/2 - width/2;
275 yBase = LINES/2 - height/2;
280 PrintAlign(yBase, xBase, width, "/", '-', titleString, '-', "\\");
283 for( i = 0; i < itemCount; i ++ )
285 move( yBase + 1 + i, xBase );
290 if( i == 0 && itemBase > 0 ) {
292 times = width - 1 - 8;
293 while(times--) addch(' ');
295 else if( i == itemCount - 1 && itemBase < giNumItems - itemCount ) {
297 times = width - 1 - 8;
298 while(times--) addch(' ');
302 ShowItemAt( yBase + 1 + i, xBase + 2, width - 4, itemBase + i);
306 // Scrollbar (if needed)
307 if( giNumItems > itemCount ) {
311 else if( i == itemCount - 1 ) {
315 int percentage = itemBase * 100 / (giNumItems-itemCount);
316 if( i-1 == percentage*(itemCount-3)/100 ) {
330 PrintAlign(yBase+height-2, xBase, width, "\\", '-', "", '-', "/");
343 if( itemBase < giNumItems - (itemCount) )
368 void PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1, const char *Mid, char Pad2, const char *Right, ...)
370 int lLen, mLen, rLen;
375 // Get the length of the strings
376 va_start(args, Right);
377 lLen = vsnprintf(NULL, 0, Left, args);
378 mLen = vsnprintf(NULL, 0, Mid, args);
379 rLen = vsnprintf(NULL, 0, Right, args);
383 if( lLen + mLen/2 > Width/2 || mLen/2 + rLen > Width/2 ) {
384 return ; // TODO: What to do?
390 va_start(args, Right);
394 vsnprintf(tmp, lLen+1, Left, args);
398 times = Width/2 - mLen/2 - lLen;
399 while(times--) addch(Pad1);
403 vsnprintf(tmp, mLen+1, Mid, args);
407 times = Width/2 - mLen/2 - rLen;
408 while(times--) addch(Pad2);
412 vsnprintf(tmp, rLen+1, Right, args);
418 int sendf(int Socket, const char *Format, ...)
423 va_start(args, Format);
424 len = vsnprintf(NULL, 0, Format, args);
429 va_start(args, Format);
430 vsnprintf(buf, len+1, Format, args);
433 return send(Socket, buf, len, 0);
437 int OpenConnection(const char *Host, int Port)
439 struct hostent *host;
440 struct sockaddr_in serverAddr;
443 host = gethostbyname(Host);
445 fprintf(stderr, "Unable to look up '%s'\n", Host);
449 memset(&serverAddr, 0, sizeof(serverAddr));
451 serverAddr.sin_family = AF_INET; // IPv4
452 // NOTE: I have a suspicion that IPv6 will play sillybuggers with this :)
453 serverAddr.sin_addr.s_addr = *((unsigned long *) host->h_addr_list[0]);
454 serverAddr.sin_port = htons(Port);
456 sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
458 fprintf(stderr, "Failed to create socket\n");
464 struct sockaddr_in localAddr;
465 memset(&localAddr, 0, sizeof(localAddr));
466 localAddr.sin_family = AF_INET; // IPv4
467 localAddr.sin_port = 1023; // IPv4
468 // Attempt to bind to low port for autoauth
469 bind(sock, &localAddr, sizeof(localAddr));
473 if( connect(sock, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0 ) {
474 fprintf(stderr, "Failed to connect to server\n");
481 void Authenticate(int Socket)
488 pwd = getpwuid( getuid() );
490 // Attempt automatic authentication
491 sendf(Socket, "AUTOAUTH %s\n", pwd->pw_name);
493 // Check if it worked
494 recv(Socket, buf, 511, 0);
497 responseCode = atoi(buf);
498 switch( responseCode )
500 case 200: // Authenticated, return :)
502 case 401: // Untrusted, attempt password authentication
504 case 404: // Bad Username
505 fprintf(stderr, "Bad Username '%s'\n", pwd->pw_name);
508 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
516 char *trim(char *string)
520 while( isspace(*string) )
523 for( i = strlen(string); i--; )
525 if( isspace(string[i]) )
534 int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage)
538 ret = regexec(regex, string, nMatches, matches, 0);
540 size_t len = regerror(ret, regex, NULL, 0);
542 regerror(ret, regex, errorStr, len);
543 printf("string = '%s'\n", string);
544 fprintf(stderr, "%s\n%s", errorMessage, errorStr);
551 void CompileRegex(regex_t *regex, const char *pattern, int flags)
553 int ret = regcomp(regex, pattern, flags);
555 size_t len = regerror(ret, regex, NULL, 0);
557 regerror(ret, regex, errorStr, len);
558 fprintf(stderr, "Regex compilation failed - %s\n", errorStr);