<commit_before>/*
 * OpenDispense 2 
 * UCC (University [of WA] Computer Club) Electronic Accounting System
 * - Dispense Client
 *
 * main.c - Core and Initialisation
 *
 * This file is licenced under the 3-clause BSD Licence. See the file
 * COPYING for full details.
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>	// isspace
#include <stdarg.h>
#include <regex.h>
#include <ncurses.h>
#include <limits.h>

#include <unistd.h>	// close
#include <netdb.h>	// gethostbyname
#include <pwd.h>	// getpwuids
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//#include <openssl/sha.h>	// SHA1

#define	USE_NCURSES_INTERFACE	0
#define DEBUG_TRACE_SERVER	0
#define USE_AUTOAUTH	1

#define MAX_TXT_ARGS	5	// Maximum number of textual arguments (including command)
#define DISPENSE_MULTIPLE_MAX	20	// Maximum argument to -c

enum eUI_Modes
{
	UI_MODE_BASIC,	// Non-NCurses
	UI_MODE_STANDARD,
	UI_MODE_DRINKSONLY,
	UI_MODE_ALL,
	NUM_UI_MODES
};

enum eReturnValues
{
	RV_SUCCESS,
	RV_BAD_ITEM,
	RV_INVALID_USER,
	RV_PERMISSIONS,
	RV_ARGUMENTS,
	RV_BALANCE,
	RV_SERVER_ERROR,	// Generic for 5xx codes
	RV_UNKNOWN_ERROR = -1,
	RV_SOCKET_ERROR = -2,
	RV_UNKNOWN_RESPONSE = -3,
};

// === TYPES ===
typedef struct sItem {
	char	*Type;
	 int	ID;
	 int	Status;	// 0: Availiable, 1: Sold out, -1: Error
	char	*Desc;
	 int	Price;
}	tItem;

// === PROTOTYPES ===
void	ShowUsage(void);
 int	main(int argc, char *argv[]);
// --- GUI ---
 int	ShowNCursesUI(void);
 int	ShowItemAt(int Row, int Col, int Width, int Index, int bHilighted);
void	PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1, const char *Mid, char Pad2, const char *Right, ...);
// --- Coke Server Communication ---
 int	OpenConnection(const char *Host, int Port);
 int	Authenticate(int Socket);
 int	GetUserBalance(int Socket);
void	PopulateItemList(int Socket);
 int	Dispense_ItemInfo(int Socket, const char *Type, int ID);
 int	DispenseItem(int Socket, const char *Type, int ID);
 int	Dispense_AlterBalance(int Socket, const char *Username, int Ammount, const char *Reason);
 int	Dispense_SetBalance(int Socket, const char *Username, int Balance, const char *Reason);
 int	Dispense_Give(int Socket, const char *Username, int Ammount, const char *Reason);
 int	Dispense_Refund(int Socket, const char *Username, const char *Item, int PriceOverride);
 int	Dispense_Donate(int Socket, int Ammount, const char *Reason);
 int	Dispense_EnumUsers(int Socket);
 int	Dispense_ShowUser(int Socket, const char *Username);
void	_PrintUserLine(const char *Line);
 int	Dispense_AddUser(int Socket, const char *Username);
 int	Dispense_SetUserType(int Socket, const char *Username, const char *TypeString, const char *Reason);
 int	Dispense_SetItem(int Socket, const char *Type, int ID, int NewPrice, const char *NewName);
// --- Helpers ---
char	*ReadLine(int Socket);
 int	sendf(int Socket, const char *Format, ...);
char	*trim(char *string);
 int	RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage);
void	CompileRegex(regex_t *regex, const char *pattern, int flags);

// === GLOBALS ===
char	*gsDispenseServer = "merlo.ucc.gu.uwa.edu.au";
 int	giDispensePort = 11020;

tItem	*gaItems;
 int	giNumItems;
regex_t	gArrayRegex, gItemRegex, gSaltRegex, gUserInfoRegex, gUserItemIdentRegex;
 int	gbIsAuthenticated = 0;

char	*gsItemPattern;	//!< Item pattern
char	*gsEffectiveUser;	//!< '-u' Dispense as another user
 int	giUIMode = UI_MODE_STANDARD;
 int	gbDryRun = 0;	//!< '-n' Read-only
 int	giMinimumBalance = INT_MIN;	//!< '-m' Minumum balance for `dispense acct`
 int	giMaximumBalance = INT_MAX;	//!< '-M' Maximum balance for `dispense acct`
char	*gsUserName;	//!< User that dispense will happen as
char	*gsUserFlags;	//!< User's flag set
 int	giUserBalance=-1;	//!< User balance (set by Authenticate)
 int	giDispenseCount = 1;	//!< Number of dispenses to do
char	*gsTextArgs[MAX_TXT_ARGS];
 int	giTextArgc;

// === CODE ===
void ShowUsage(void)
{
	printf(	"Usage:\n" );
	if( giTextArgc == 0 )
		printf(
			"  == Everyone ==\n"
			"    dispense\n"
			"        Show interactive list\n"
			"    dispense <name>|<index>|<itemid>\n"
			"        Dispense named item (<name> matches if it is a unique prefix)\n"
			);
	if( giTextArgc == 0 || strcmp(gsTextArgs[0], "give") == 0 )
		printf(
			"    dispense give <user> <ammount> \"<reason>\"\n"
			"        Give money to another user\n"
			);
	
	if( giTextArgc == 0 || strcmp(gsTextArgs[0], "donate") == 0 )
		printf(
			"    dispense donate <ammount> \"<reason>\"\n"
			"        Donate to the club\n"
			);
	if( giTextArgc == 0 || strcmp(gsTextArgs[0], "iteminfo") == 0 )
		printf(
			"    dispense iteminfo <itemid>\n"
			"        Get the name and price for an item\n"
			);
	if( giTextArgc == 0 )
		printf("  == Coke members == \n");
	if( giTextArgc == 0 || strcmp(gsTextArgs[0], "acct") == 0 )
		printf(
			"    dispense acct [<user>]\n"
			"        Show user balances\n"
			"    dispense acct <user> [+-]<ammount> \"<reason>\"\n"
			"        Alter a account value\n"
			"    dispense acct <user> =<ammount> \"<reason>\"\n"
			"        Set an account balance\n"
			);
	if( giTextArgc == 0 || strcmp(gsTextArgs[0], "refund") == 0 )
		printf(
			"    dispense refund <user> <itemid> [<price>]\n"
			"        Refund an item to a user (with optional price override)\n"
			"        Item IDs can be seen in the cokelog (in the brackets after the item name)\n"
			"        e.g. coke:6 for a coke, snack:33 for slot 33 of the snack machine\n"
			);
	if( giTextArgc == 0 || strcmp(gsTextArgs[0], "slot") == 0 )
		printf(
			"    dispense slot <itemid> <price> <name>\n"
			"        Rename/Re-price a slot\n"
			);
	if( giTextArgc == 0 )
		printf("  == Dispense administrators ==\n");
	if( giTextArgc == 0 || strcmp(gsTextArgs[0], "user") == 0 )
		printf(
			"    dispense user add <user>\n"
			"        Create new account\n"
			"    dispense user type <user> <flags> <reason>\n"<commit_msg>Client - Documentation for `enumitems` (commented out)<commit_after>/*
 * OpenDispense 2 
 * UCC (University [of WA] Computer Club) Electronic Accounting System
 * - Dispense Client
 *
 * main.c - Core and Initialisation
 *
 * This file is licenced under the 3-clause BSD Licence. See the file
 * COPYING for full details.
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>	// isspace
#include <stdarg.h>
#include <regex.h>
#include <ncurses.h>
#include <limits.h>

#include <unistd.h>	// close
#include <netdb.h>	// gethostbyname
#include <pwd.h>	// getpwuids
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//#include <openssl/sha.h>	// SHA1

#define	USE_NCURSES_INTERFACE	0
#define DEBUG_TRACE_SERVER	0
#define USE_AUTOAUTH	1

#define MAX_TXT_ARGS	5	// Maximum number of textual arguments (including command)
#define DISPENSE_MULTIPLE_MAX	20	// Maximum argument to -c

enum eUI_Modes
{
	UI_MODE_BASIC,	// Non-NCurses
	UI_MODE_STANDARD,
	UI_MODE_DRINKSONLY,
	UI_MODE_ALL,
	NUM_UI_MODES
};

enum eReturnValues
{
	RV_SUCCESS,
	RV_BAD_ITEM,
	RV_INVALID_USER,
	RV_PERMISSIONS,
	RV_ARGUMENTS,
	RV_BALANCE,
	RV_SERVER_ERROR,	// Generic for 5xx codes
	RV_UNKNOWN_ERROR = -1,
	RV_SOCKET_ERROR = -2,
	RV_UNKNOWN_RESPONSE = -3,
};

// === TYPES ===
typedef struct sItem {
	char	*Type;
	 int	ID;
	 int	Status;	// 0: Availiable, 1: Sold out, -1: Error
	char	*Desc;
	 int	Price;
}	tItem;

// === PROTOTYPES ===
void	ShowUsage(void);
 int	main(int argc, char *argv[]);
// --- GUI ---
 int	ShowNCursesUI(void);
 int	ShowItemAt(int Row, int Col, int Width, int Index, int bHilighted);
void	PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1, const char *Mid, char Pad2, const char *Right, ...);
// --- Coke Server Communication ---
 int	OpenConnection(const char *Host, int Port);
 int	Authenticate(int Socket);
 int	GetUserBalance(int Socket);
void	PopulateItemList(int Socket);
 int	Dispense_ItemInfo(int Socket, const char *Type, int ID);
 int	DispenseItem(int Socket, const char *Type, int ID);
 int	Dispense_AlterBalance(int Socket, const char *Username, int Ammount, const char *Reason);
 int	Dispense_SetBalance(int Socket, const char *Username, int Balance, const char *Reason);
 int	Dispense_Give(int Socket, const char *Username, int Ammount, const char *Reason);
 int	Dispense_Refund(int Socket, const char *Username, const char *Item, int PriceOverride);
 int	Dispense_Donate(int Socket, int Ammount, const char *Reason);
 int	Dispense_EnumUsers(int Socket);
 int	Dispense_ShowUser(int Socket, const char *Username);
void	_PrintUserLine(const char *Line);
 int	Dispense_AddUser(int Socket, const char *Username);
 int	Dispense_SetUserType(int Socket, const char *Username, const char *TypeString, const char *Reason);
 int	Dispense_SetItem(int Socket, const char *Type, int ID, int NewPrice, const char *NewName);
// --- Helpers ---
char	*ReadLine(int Socket);
 int	sendf(int Socket, const char *Format, ...);
char	*trim(char *string);
 int	RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage);
void	CompileRegex(regex_t *regex, const char *pattern, int flags);

// === GLOBALS ===
char	*gsDispenseServer = "merlo.ucc.gu.uwa.edu.au";
 int	giDispensePort = 11020;

tItem	*gaItems;
 int	giNumItems;
regex_t	gArrayRegex, gItemRegex, gSaltRegex, gUserInfoRegex, gUserItemIdentRegex;
 int	gbIsAuthenticated = 0;

char	*gsItemPattern;	//!< Item pattern
char	*gsEffectiveUser;	//!< '-u' Dispense as another user
 int	giUIMode = UI_MODE_STANDARD;
 int	gbDryRun = 0;	//!< '-n' Read-only
 int	giMinimumBalance = INT_MIN;	//!< '-m' Minumum balance for `dispense acct`
 int	giMaximumBalance = INT_MAX;	//!< '-M' Maximum balance for `dispense acct`
char	*gsUserName;	//!< User that dispense will happen as
char	*gsUserFlags;	//!< User's flag set
 int	giUserBalance=-1;	//!< User balance (set by Authenticate)
 int	giDispenseCount = 1;	//!< Number of dispenses to do
char	*gsTextArgs[MAX_TXT_ARGS];
 int	giTextArgc;

// === CODE ===
void ShowUsage(void)
{
	printf(	"Usage:\n" );
	if( giTextArgc == 0 )
		printf(
			"  == Everyone ==\n"
			"    dispense\n"
			"        Show interactive list\n"
			"    dispense <name>|<index>|<itemid>\n"
			"        Dispense named item (<name> matches if it is a unique prefix)\n"
			);
	if( giTextArgc == 0 || strcmp(gsTextArgs[0], "give") == 0 )
		printf(
			"    dispense give <user> <ammount> \"<reason>\"\n"
			"        Give money to another user\n"
			);
	
	if( giTextArgc == 0 || strcmp(gsTextArgs[0], "donate") == 0 )
		printf(
			"    dispense donate <ammount> \"<reason>\"\n"
			"        Donate to the club\n"
			);
	if( giTextArgc == 0 || strcmp(gsTextArgs[0], "iteminfo") == 0 )
		printf(
			"    dispense iteminfo <itemid>\n"
			"        Get the name and price for an item\n"
			);
//	if( giTextArgc == 0 || strcmp(gsTextArgs[0], "enumitems") == 0 )
//		printf(
//			"    dispense enumitems\n"
//			"        List avaliable items\n"
//			);
	if( giTextArgc == 0 )
		printf("  == Coke members == \n");
	if( giTextArgc == 0 || strcmp(gsTextArgs[0], "acct") == 0 )
		printf(
			"    dispense acct [<user>]\n"
			"        Show user balances\n"
			"    dispense acct <user> [+-]<ammount> \"<reason>\"\n"
			"        Alter a account value\n"
			"    dispense acct <user> =<ammount> \"<reason>\"\n"
			"        Set an account balance\n"
			);
	if( giTextArgc == 0 || strcmp(gsTextArgs[0], "refund") == 0 )
		printf(
			"    dispense refund <user> <itemid> [<price>]\n"
			"        Refund an item to a user (with optional price override)\n"
			"        Item IDs can be seen in the cokelog (in the brackets after the item name)\n"
			"        e.g. coke:6 for a coke, snack:33 for slot 33 of the snack machine\n"
			);
	if( giTextArgc == 0 || strcmp(gsTextArgs[0], "slot") == 0 )
		printf(
			"    dispense slot <itemid> <price> <name>\n"
			"        Rename/Re-price a slot\n"
			);
	if( giTextArgc == 0 )
		printf("  == Dispense administrators ==\n");
	if( giTextArgc == 0 || strcmp(gsTextArgs[0], "user") == 0 )
		printf(
			"    dispense user add <user>\n"
			"        Create new account\n"
			"    dispense user type <user> <flags> <reason>\n"
{'fraction_non_alphanumeric': 0.1409572284705689, 'fraction_numerical': 0.009440932599950743, 'mean_word_length': 3.6161424782114437, 'pattern_counts': {'":': 0, '<': 105, '<?xml version=': 0, '>': 87, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '303835', 'n_tokens_mistral': 4839, 'n_tokens_neox': 4521, 'n_words': 1310}