/* function definition to parse a string instead of a file
*/
#include "types.h"
extern BotExpenseEntry *g_LastExpenseEntry = NULL;
extern GString *g_filename = NULL;
BotExpenseEntry *bot_get_last_expense()
{
return g_LastExpenseEntry;
}
void bot_set_last_expense(BotExpenseEntry *entry)
{
if(g_LastExpenseEntry) {
free_bot_expense_entry(g_LastExpenseEntry);
}
g_LastExpenseEntry = entry;
}
GString *bot_get_filename()
{
return g_filename;
}
void bot_set_filename(GString *fn)
{
if(g_filename) {
g_string_free(g_filename, true);
}
g_filename = fn;
}
/* Parse a string with Flex+Bison instead of a file / stdout */
int grammar_parse_string(const GString *buf) {
int res;
yy_scan_string(buf->str);
res = yyparse();
yylex_destroy();
return res;
}
int bot_get_current_entry_id
(
const GString* filename
)
{
/* TODO this counts by char, probably more efficient ways exist */
char c;
FILE *fp;
unsigned int count = 0;
if((fp = fopen(filename->str, "r"))) {
for (c = getc(fp); c != EOF; c = getc(fp)) {
if (c == '\n') {
count++;
}
}
fclose(fp);
}
return count;
}
/* Create a BotExpenseEntry with data fed from the parser.
* Returns NULL if error getting local time.
*/
BotExpenseEntry *create_bot_expense_entry
(
const BotExpenseEntry *previous,
const BotCommand cmd,
const double value,
GString* user,
GString* notes
)
{
unsigned int id;
double signedValue = value;
BotExpenseEntry *entry = malloc(sizeof *entry); // TODO this should be freed once it's written to registry
struct tm *lt = malloc(sizeof *lt);
time_t t = time(NULL);
/* get the local time */
localtime_r(&t, lt);
if(lt == NULL) {
fprintf(stderr, "Unable to get localtime.\n");
return NULL;
}
if(cmd == BOT_CMD_OUT) {
signedValue = -value;
}
if(previous == NULL) {
id = 1;
} else {
id = previous->id + 1;
}
entry->id = id;
entry->time = *lt;
entry->value = signedValue;
entry->user = user;
entry->notes = notes;
entry->_serialized = false;
return entry;
}
/* FP should not be null. This assumes that it has been initialized with an header line as well
* Returns NULL in case 'entry' is uninitialized.
*/
GString *serialize_bot_expense_entry_csv
(
BotExpenseEntry *entry
)
{
/* will be automatically expanded by g_string_printf */
GString *tstr = g_string_new(NULL);
GString *result = g_string_new(NULL);
if(entry == NULL) {
fprintf(stderr, "BotExpenseEntry 'entry' is NULL. Initialize it before calling 'serialize_bot_expense_entry_csv'\n");
return NULL;
}
/* Time in ISO 8061 */
g_string_printf(tstr,
"%d-%02d-%02d %02d:%02d:%02d",
entry->time.tm_year + 1900,
entry->time.tm_mon + 1,
entry->time.tm_mday,
entry->time.tm_hour,
entry->time.tm_min,
entry->time.tm_sec);
g_string_printf(result,
"%u,%s,%.2f,%s,%s\n",
entry->id,
tstr->str,
entry->value,
entry->user->str,
entry->notes->str
);
entry->_serialized = true;
return result;
}
/* returns the number of bytes written, following the convention of fprintf.
* Returns negative in case of error
*/
int write_bot_expense_entry
(
const GString *filename,
BotExpenseEntry *entry,
bool needsHeaders
)
{
FILE *fp;
int result;
GString *line = serialize_bot_expense_entry_csv(entry);
if(line == NULL) {
return -1;
}
if((fp = fopen(filename->str, "a"))) {
if(needsHeaders) {
fprintf(fp, "ID,Time,Value,User,Notes\n");
}
/* line is terminated with '\n' */
result = fprintf(fp, "%s", line->str);
fclose(fp);
return result;
fprintf(stderr, "Error opening %s\n", filename->str);
}
fprintf(stderr, "Error opening %s\n", filename->str);
return -1;
}
/* whole routine to write an expense to a CSV file */
int write_update_to_csv
(
const GString *filename,
const BotCommand cmd,
const double value,
GString* user,
GString* notes
)
{
int result;
unsigned int id;
BotExpenseEntry *entry;
BotExpenseEntry *previous = bot_get_last_expense();
bool needsHeaders = false;
entry = create_bot_expense_entry(previous, cmd, value, user, notes);
if(entry == NULL) {
return -1;
}
if(previous == NULL) { /* first message received by the bot; needs to get latest ID */
id = bot_get_current_entry_id(filename);
if(id == 0) { /* file is empty; initialize with headers */
needsHeaders = true;
}
}
result = write_bot_expense_entry(filename, entry, needsHeaders);
if(result > 0) { /* write successful */
bot_set_last_expense(entry);
}
return result;
}
/* To be called after it has been serialized */
void free_bot_expense_entry
(
BotExpenseEntry *entry
)
{
if(!entry->_serialized) {
/* TODO could fail here instead */
fprintf(stderr, "WARNING: freeing an entry that wasn't serialized!\n");
}
/* Also free underlying string, we don't need it anymore */
g_string_free(entry->user, TRUE);
g_string_free(entry->notes, TRUE);
free(entry);
}