/* * Copyright (c) 2011- Osmo Antero. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License (GPL3), or any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Library General Public License 3 for more details. * * You should have received a copy of the GNU Library General Public * License 3 along with this program; if not, see /usr/share/common-licenses/GPL file * or . */ #include "timer.h" #include "log.h" #include "support.h" #include "utility.h" #include /* Parsing of timer commands: Syntax: action := comment | ("start" | "stop" | "pause") command comment := '#'...\n command := action_prep data action_prep := "after" | "at" | "if" | "on" data := time_notation | filesize | word time_notation := (##:##:## | #hour #min #sec) time_suffix time_suffix := "am" | "pm" | "" filesize := # ("bytes" | "kb"|"kib" | "mb"|"mib" | "gb"|"gib" | "tb"|"tib") word := ("silence" | "voice" | "sound" | "audio") time_notation signal_threshold signal_threshold := #dB | #[1.1, 100]% | #[0, 1.0] Note: # means an integer or floating point number. Comments begin with '#' and ends at newline \n. In simplified form: start | stop | pause (at|after|if|on) ##:##:## (am|pm|) | ### (bytes|kb|mb|gb|tb) | (silence|voice|sound|audio) ## seconds (##dB | ##% | ##) The words "voice", "sound" and "audio" has exactly same meaning! The file size units: https://wiki.ubuntu.com/UnitsPolicy Some examples: start at 10:10 pm stop after 20.5 min start if voice start if sound 5s 10% # this is a comment stop at 08:00:30 am stop after 6 min 20 sec stop after 5 MB stop if silence 7 s -20 db # duration is 7 seconds, threshold is -20dB stop if silence 7 s 30% # duration is 7 seconds, threshold is 30 (=0.3) stop if silence 7 s 0.3 # duration is 7 seconds, threshold is 0.3 (=30%) stop after silence | 5GB | 35 min pause if silence 10s 7% start if audio -19dB stop if silence 10 sec -18 db | 20 GB | 10 pm stop if silence 10 16 Usage: Call parser_parse_actions(txt), where txt is the command text. GList *parser_parse_actions(gchar *txt); It will return a pointer to g_timer_list. This is a GList of TimerRec records. */ #define MAX_TOKEN_LEN 128 typedef enum {TOK_NONE, TOK_NUMERIC, TOK_TIME, TOK_TEXT} TokenType; typedef struct { TokenType type; gchar tok[MAX_TOKEN_LEN+1]; } TokenRec; typedef struct { gchar *buf; gchar *pos; gint len; gchar back_ch; guint line_no; } ParserRec; typedef struct { gchar *label; gchar *translation; } LangRec; LangRec g_transtable[] = { // Example: start at 10:30 pm {"start", NULL}, // Example: stop after 200 kb {"stop", NULL}, // Start/stop/pause at, after, if, on // Example: start at 10:30 pm {"at", NULL}, // Example: stop after 1 hour 20 min {"after", NULL}, // Example: stop if 2GB {"if", NULL}, // Example: start on voice | 14:00 pm {"on", NULL}, // Start/pause on "voice". // Example: start on voice | 14:00 pm {"voice", NULL}, // Start/pause on "audio" // Example: start on audio | 14:00 pm {"audio", NULL}, // Start/pause on "sound" // Example: start on sound | 14:00 pm {"sound", NULL}, // Time: hour // Example: stop after 1 hour | 1 h {"hour", NULL}, // Time: hour // Example: stop after 1 hour | 1 h {"h", NULL}, // Time: minutes // Example: stop after 20 minutes | 20 min | 20 m {"minutes", NULL}, // Time: minutes // Example: stop after 20 minutes | 20 min | 20 m {"min", NULL}, // Time: minutes // Example: stop after 20 minutes | 20 min | 20 m {"m", NULL}, // Time: seconds // Example: pause after 60 seconds | 60 sec | 60 s {"seconds", NULL}, // Time: seconds // Example: pause after 60 seconds | 60 sec | 60 s {"sec", NULL}, // Time: seconds // Example: pause after 60 seconds | 60 sec | 60 s {"s", NULL}, // Example: pause if 2000 bytes | 2000 byte {"bytes", NULL}, // Example: pause if 2000 bytes | 2000 byte {"bytes", NULL}, // "|" or "or" // Example: pause if silence -20 dB | 2 GB or 10:20 pm {"or", NULL}, // Clock time; ante meridiem, before midday // Example: start at 09:00 am {"am", NULL}, // Clock time; post meridiem, after midday // Example: stop at 09:00 pm {"pm", NULL}, }; // Global variables to this module static ParserRec g_parser; static TokenRec g_curtoken; static TokenRec g_backtoken; static GList *g_timer_list = NULL; static void parser_init(gchar *txt); static void parser_clear(); static void parser_free_node(TimerRec *tr); static void parser_parse_action(); static void parser_parse_data(); static gboolean match_lang(gchar *tok, gchar *text); static gboolean match_word(gchar *l_word, gchar *word, ...); static LangRec *get_translated_str(gchar *label); static gchar parser_get_ch(); static gchar parser_get_ch_ex(); static void parser_put_back_ch(gchar ch); static void parser_get_token(); static TimerRec *parser_get_last(); static TimerRec *parser_add_action(gchar action); static void parser_fix_list(); static void parser_print_error(gchar *msg); static gchar *parser_type_to_str(gint type) __attribute__((unused)); void parser_module_init() { LOG_DEBUG("Init timer-parser.c.\n"); g_timer_list = NULL; parser_init(NULL); } void parser_module_exit() { LOG_DEBUG("Clean up timer-parser.c.\n"); parser_clear(); // Free list parser_free_list(); } GList *parser_parse_actions(gchar *txt) { parser_clear(); parser_init(txt); // 'S'tart... | S'T'op..., 'P'ause... parser_parse_action(); parser_fix_list(); // Return the list head return g_timer_list; } static void parser_init(gchar *txt) { // Init parser record if (txt) { g_parser.buf = g_utf8_strdown(txt, -1); g_parser.len = g_utf8_strlen(g_parser.buf, -1); } else { g_parser.buf = NULL; g_parser.len = 0; } g_parser.pos = g_parser.buf; g_parser.back_ch = '\0'; g_parser.line_no = 1; *g_curtoken.tok = '\0'; *g_backtoken.tok = '\0'; // Free the existing TimerRec list parser_free_list(); } static void parser_clear() { // Clear parser record if (g_parser.buf) g_free(g_parser.buf); parser_init(NULL); } static void parser_print_error(gchar *msg) { LOG_ERROR("Timer command, line %d: %s.\n", g_parser.line_no, msg); } static gboolean match_lang(gchar *tok, gchar *text) { // Check if the given token (tok) matches the text (in plain english or translated language) gboolean ret = !g_strcmp0(tok, text); if (ret) return TRUE; LangRec *lang_rec = get_translated_str(text); ret = FALSE; if (lang_rec) { ret = !g_strcmp0(tok, lang_rec->label); if (!ret) ret = !g_strcmp0(tok, lang_rec->translation); } return ret; } static gboolean match_word(gchar *l_word, gchar *word, ...) { // Check if l_word matches one of the given words (in plain english or translated language). va_list args; va_start(args, word); gboolean found = FALSE; gchar *text = word; while (text) { if (match_lang(l_word, text)) { found = TRUE; break; } text = va_arg(args, gchar *); } va_end(args); return found; } static void parser_put_back_ch(gchar ch) { // Put back a character ParserRec *p = &g_parser; p->back_ch = ch; } static gchar parser_get_ch() { // Get next character ParserRec *p = &g_parser; if (!p->buf) return '\0'; // Check back buf gchar ch = '\0'; if (p->back_ch != '\0') { ch = p->back_ch; p->back_ch = '\0'; return ch; } gchar *end = g_utf8_offset_to_pointer(p->buf, p->len - 1); if (p->pos > end) return '\0'; if (*p->pos == '\0') return '\0'; if (*p->pos == '\n') { // Count lines p->line_no++; } // The char ch = *p->pos; // Advance head p->pos++; return ch; } static void parser_remove_space() { // Remove white spaces from the text gchar ch = parser_get_ch(); while (g_unichar_isspace(ch) && ch != '\0') { ch = parser_get_ch(); } parser_put_back_ch(ch); } static void parser_remove_comment() { // Remove characters until \n gchar ch = parser_get_ch(); while (ch != '\0' && ch != '\n') { ch = parser_get_ch(); } } static gchar parser_get_ch_ex() { // Get next char, remove spaces and comments // Remove spaces parser_remove_space(); gchar ch = '\0'; LBL_1: ch = parser_get_ch(); if (ch == '\0') return ch; if (g_unichar_isspace(ch)) { // Remove spaces parser_remove_space(); goto LBL_1; } else if (ch == '#') { // Remove EOL comment parser_remove_comment(); goto LBL_1; } return ch; } static TokenRec parser_get_token_ex() { // Get next token from the text. // Return token. TokenRec t; *t.tok = '\0'; t.type = TOK_NONE; gchar ch = parser_get_ch_ex(); if (ch == '\0') return t; // An integer or decimal number: +-0-9, 0-9.0-9 // Ot time value of format ##:##:## gint i = 0; while ((g_unichar_isdigit(ch) || g_utf8_strchr(".:+-", 4, ch)) && ch != '\0') { if (i >= MAX_TOKEN_LEN) break; t.tok[i++] = ch; ch = parser_get_ch(); } // Got a numeric token? if (i > 0) { // Put last ch back parser_put_back_ch(ch); // 0-terminate t.tok[i] = '\0'; // Time notation hh:mm:ss? if (g_utf8_strchr(t.tok, g_utf8_strlen(t.tok, -1), ':')) t.type = TOK_TIME; else // Got numeric value t.type = TOK_NUMERIC; return t; } // Is it a "|" (OR token)? if (ch == '|') { g_utf8_strncpy(t.tok, "|", 1); t.type = TOK_TEXT; return t; } // Is it a "%" token? else if (ch == '%') { g_utf8_strncpy(t.tok, "%", 1); t.type = TOK_TEXT; return t; } // Is it a letter, a character? // Read a string. i = 0; // Note: we do NOT use ">", "<", ">=", "<=", "=", "==" tokens here. We merely get rid of them. // Just in case user writes "stop if filezize >= 100 MB". This is illegal syntax but we make our best to interpret it like "stop if 100 MB". while ((g_unichar_isalpha(ch) || g_utf8_strchr("><=", -1, ch)) && ch != '\0') { if (i >= MAX_TOKEN_LEN) break; t.tok[i++] = ch; ch = parser_get_ch(); } if (i > 0) { // Put last ch back parser_put_back_ch(ch); // 0-terminate t.tok[i] = '\0'; t.type = TOK_TEXT; return t; } return t; } static void parser_put_token_back() { // Put token back g_backtoken = g_curtoken; } static void parser_get_token() { // Get next token. Check back buffer. if (*g_backtoken.tok) { g_curtoken = g_backtoken; *g_backtoken.tok = '\0'; return; } // Set the current token g_curtoken = parser_get_token_ex(); // puts(g_curtoken.tok); } static LangRec *get_translated_str(gchar *label) { // Get LangRec for the given text label guint siz = sizeof(g_transtable) / sizeof(g_transtable[0]); guint i = 0; for (i=0; i< siz; i++) { if (!g_strcmp0(label, g_transtable[i].label)) return &g_transtable[i]; if (!g_strcmp0(label, g_transtable[i].translation)) return &g_transtable[i]; } return NULL; } static gboolean starts_with(gchar *s, gchar *what) { // Test if string s starts with "what" (has that prefix) if (!(s && what)) return FALSE; const gchar *p = g_strstr_len(s, -1, what); // The "s" starts with "what", at 1.st byte. return (p == s); } static double tok_to_num(gchar *tok) { // Convert tok to double return atof(tok); } static void normalize_time(TimerRec *tr) { // Normalize decimal values to real hours/minutes/seconds // Eg. 2.5h means 2h 30minutes 0seconds // Convert to 24 hours clock if (!g_strcmp0(tr->label, "pm") && tr->val[0] <= 12) { tr->val[0] += 12.0; } if (tr->val[0] > 24.0) tr->val[0] = 24.0; gdouble secs = tr->val[0]*3600.0 + tr->val[1]*60.0 + tr->val[2]; // Convert back to hour/min/sec tr->val[0] = (guint)(secs / 3600); secs = secs - (tr->val[0]*3600); tr->val[1] = (guint)(secs / 60); tr->val[2] = secs - (tr->val[1]*60); } static void parser_parse_data() { // Get the actual TimerRec record (last one) TimerRec *tr = parser_get_last(); // Safety counter guint loop_count = 0; // State for some intricat values guint state = 0; gboolean seconds_set = FALSE; gboolean threshold_set = FALSE; while (1) { if (loop_count++ > 500) { // Insane loop break; } if (g_curtoken.type == TOK_NONE) { break; } gdouble val = 0.0; gint tok_type = -1; // Numeric values for clock time/duration, file size or threshold (in dB, % or plain value [0, 1.0]) if (g_curtoken.type == TOK_NUMERIC) { // Default data type // tr->data_type = 't'; tok_type = g_curtoken.type; val = tok_to_num(g_curtoken.tok); // Next token parser_get_token(); } // Clock time of format hh:mm:ss else if (g_curtoken.type == TOK_TIME) { // hh // hh:mm // hh:mm:ss tr->data_type = 't'; tok_type = g_curtoken.type; // Split the time string on ":" // eg. 10:20:30 (=10 hours, 20 minutes, 30 seconds) gchar **args = g_strsplit_set(g_curtoken.tok, ":", -1); guint i =0; for (i=0; args[i]; i++) { if (i < 3) { tr->val[i] = tok_to_num(args[i]); } } // Free the string list g_strfreev(args); // Next token parser_get_token(); state = 3; } // Token is string/text // am | pm // 11 am // 07 pm if (match_word(g_curtoken.tok, "am", NULL) || match_word(g_curtoken.tok, "pm", NULL)) { g_utf8_strncpy(tr->label, g_curtoken.tok ,-1); if (tok_type == TOK_NUMERIC) { tr->val[0] = val; } tr->data_type = 't'; state = 3; } // filesize: bytes | kb | mb | gb | tb // 123 mb // 14.5 gb else if (match_word(g_curtoken.tok, "bytes", "byte", NULL)) { tr->data_type = 'f'; tr->val[0] = val; g_utf8_strncpy(tr->label, "bytes" , -1); } else if (starts_with(g_curtoken.tok, "kb") || starts_with(g_curtoken.tok, "kib")) { tr->data_type = 'f'; tr->val[0] = val * 1E3; g_utf8_strncpy(tr->label, "kb" , -1); } else if (starts_with(g_curtoken.tok, "mb") || starts_with(g_curtoken.tok, "mib")) { tr->data_type = 'f'; tr->val[0] = val * 1E6; g_utf8_strncpy(tr->label, "mb" , -1); } else if (starts_with(g_curtoken.tok, "gb") || starts_with(g_curtoken.tok, "gib")) { tr->data_type = 'f'; tr->val[0] = val * 1E9; g_utf8_strncpy(tr->label, "gb" , -1); } else if (starts_with(g_curtoken.tok, "tb") || starts_with(g_curtoken.tok, "tib")) { tr->data_type = 'f'; tr->val[0] = val * 1E12; g_utf8_strncpy(tr->label, "tb" , -1); } // hours, minutes, seconds else if (match_word(g_curtoken.tok, "h", NULL) || starts_with(g_curtoken.tok, "ho")) { // h, hour, hours, horas // 'd' = time duration tr->data_type = 'd'; tr->val[0] = val; state = 3; } else if (match_word(g_curtoken.tok, "m", NULL) || starts_with(g_curtoken.tok, "mi")) { // m, min, minute, minutes // 'd' = time duration tr->data_type = 'd'; tr->val[1] = val; state = 3; } else if (match_word(g_curtoken.tok, "s", NULL) || starts_with(g_curtoken.tok, "se")) { // s, sec, second, seconds if (str_length0(tr->threshold_unit) < 1) { /* == '\0'*/ // Save threshold value (eg. 0.4) // "stop if silence 0.4 5s" => "stop if silence 5s 0.4" tr->threshold = tr->val[2]; } // 'd' = time duration tr->data_type = 'd'; tr->val[2] = val; state = 3; } // "silence" else if (match_word(g_curtoken.tok, "silence", NULL)) { tr->data_type = 'x'; g_utf8_strncpy(tr->label, "silence" , -1); state = 2; } // "voice" | "audio" | "sound" else if (match_word(g_curtoken.tok, "voice", NULL)) { tr->data_type = 'x'; g_utf8_strncpy(tr->label, "voice" , -1); state = 2; } else if (match_word(g_curtoken.tok, "audio", NULL)) { tr->data_type = 'x'; g_utf8_strncpy(tr->label, "audio" , -1); state = 2; } else if (match_word(g_curtoken.tok, "sound", NULL)) { tr->data_type = 'x'; g_utf8_strncpy(tr->label, "sound" , -1); state = 2; } // threshold dB (-100dB - -5dB) else if (match_word(g_curtoken.tok, "db", "decibel", NULL) || starts_with(g_curtoken.tok, "decib")) { // TODO: Check if "silence", "voice" | "audio" | "sound" token detected g_utf8_strncpy(tr->threshold_unit, "db", -1); tr->threshold = val; } // Threshold % (0 - 100%) else if (match_word(g_curtoken.tok, "%", NULL)) { g_utf8_strncpy(tr->threshold_unit, "%", -1); tr->threshold = val; } // "start", "stop", "pause" else if (match_word(g_curtoken.tok, "start", "stop", "pause", "|", NULL)) { // Put token back parser_put_token_back(); // Return from this loop break; } else { if (state == 2) { // silence|voice 10 0.4 (take 10) if (tok_type == TOK_NUMERIC) { tr->val[2] = val; // duration 10s } state = 3; seconds_set = TRUE; // Put token back parser_put_token_back(); } else if (state == 3) { // silence|voice 7 30 # take 30 (=30%) // silence|voice 7 0.3 # take 0.3 (=30%) if (tok_type == TOK_NUMERIC) { tr->threshold = val; if (tr->threshold > 1.0) { // Value between [1%, 100%], assume it's a %-value g_utf8_strncpy(tr->threshold_unit, "%", -1); } else { // Value between [0, 1.0] is a plain number *(tr->threshold_unit) = '\0'; } } state = 0; threshold_set = TRUE; // Put token back parser_put_token_back(); } else if (tok_type == TOK_NUMERIC) { if (val != 0.0) { // Set default tr->data_type = 't'; tr->val[0] = val; } // Put token back parser_put_token_back(); break; } } // Next token parser_get_token(); } // while... if (!g_strcmp0(tr->label, "silence") || !g_strcmp0(tr->label, "voice") || !g_strcmp0(tr->label, "sound") || !g_strcmp0(tr->label, "audio")) { tr->data_type = 'x'; } // We got time value? if (tr->data_type == 't' || tr->data_type == 'd') { // Normalize hours/minutes/seconds. Eg. 2.5 h becomes 2 h 30 minutes. normalize_time(tr); } // start if voice 0.5 --> start if voice 0 sec 0.5 (0.5 is a threshold level, not seconds!) if (seconds_set == TRUE && threshold_set == FALSE && tr->val[2] <= 1.00) { tr->threshold = tr->val[2]; tr->val[2] = 0.0; } } static void parse_parse_line() { // start | stop | pause at|after|if|on 10:10:12 am/pm | 100 bytes/kb/mb/gb/tb | silence/voice/sound // Get next token parser_get_token(); // Get last TimerRec TimerRec *tr = parser_get_last(); // Remove action preposition; "at" | "after" | "if" | "on" gboolean got_action_prep = FALSE; if (match_word(g_curtoken.tok, "at", NULL)) { got_action_prep = TRUE; } else if (match_word(g_curtoken.tok, "after", NULL)) { tr->action_prep = 'a'; // 'a' = after got_action_prep = TRUE; } else if (match_word(g_curtoken.tok, "if", NULL)) { got_action_prep = TRUE; } else if (match_word(g_curtoken.tok, "on", NULL)) { got_action_prep = TRUE; } // Consumed token? if (got_action_prep) { // Get next token parser_get_token(); } while (1) { // Parse data parser_parse_data(); // Next token parser_get_token(); // It is "|" or "or"? if (!g_strcmp0(g_curtoken.tok, "|") || match_word(g_curtoken.tok, "or", NULL)) { // Add new record parser_add_action('X'); // 'X' = unknown at the moment // Next token parser_get_token(); } else { // Put back parser_put_token_back(); return; } } } static void parser_parse_action() { // Get first token parser_get_token(); while (g_curtoken.type != TOK_NONE) { if (match_word(g_curtoken.tok, "start", NULL)) { // "start ..." parser_add_action('S'); // 'S' = start recording // Parse rest of the "start ..." line parse_parse_line(); } else if (match_word(g_curtoken.tok, "stop", NULL)) { // "stop ..." parser_add_action('T'); // 'T' = sTop recording // Parse rest of the "stop ..." line parse_parse_line(); } else if (match_word(g_curtoken.tok, "pause", NULL)) { // "pause ..." parser_add_action('P'); // 'P' = Pause recording // Parse rest of the "pause ..." line parse_parse_line(); } else { // Unknown token gchar *msg = g_strdup_printf("Unknown token: %s.\n", g_curtoken.tok); parser_print_error(msg); g_free(msg); } // Get next token parser_get_token(); } } static gchar *parser_type_to_str(gint type) { // Convert type to text (for debugging) switch (type) { case TOK_NUMERIC: return "TOK_NUMERIC"; case TOK_TIME: return "TOK_TIME"; case TOK_TEXT: return "TOK_TEXT"; default: return "UNKNOWN TOKEN"; } } // ========================================== static void parser_free_node(TimerRec *tr) { // Free TimerRec node if (tr) g_free(tr); } static TimerRec *timer_new_rec(gchar action) { // Create new TimerRec node TimerRec *tr = g_malloc0(sizeof(TimerRec)); tr->action = action; tr->day_of_year = -1; return tr; } static TimerRec *parser_add_action(gchar action) { // Add new TimerRec node to g_timer_list TimerRec *tr = tr = timer_new_rec(action); g_timer_list = g_list_append(g_timer_list, tr); return tr; } static TimerRec *parser_get_last() { // Return last TimerRec node from g_timer_list TimerRec *tr = NULL; if (!g_timer_list) { tr = timer_new_rec('X'); // X = Unknown action g_timer_list = g_list_append(g_timer_list, tr); } GList *last = g_list_last(g_timer_list); return (TimerRec*)last->data; } static void parser_fix_list() { // Fix OR'ed commands. // Eg. "stop if 100 MB | 1 h 20 min | silence" becomes three separate nodes. // It becomes: // "stop if 100 MB" // "stop if 1 h 20 min" // "stop if silence" // // Replace 'X' with previous node's action char. // Replace action_prep with previous node's action_prep. GList *item = g_list_first(g_timer_list); gchar last_action = '\0'; gchar last_prep = '\0'; while (item) { TimerRec *tr = (TimerRec*)item->data; if (last_action != '\0' && tr->action == 'X') { tr->action = last_action; } if (last_action != tr->action) { last_prep = '\0'; } last_action = tr->action; if (last_prep != '\0' && tr->action_prep == '\0') { tr->action_prep = last_prep; } last_prep = tr->action_prep; item = g_list_next(item); } } const gchar *parser_get_action_name(gchar action) { switch (action) { case 'S': return "Start recording"; case 'c': case 'C': return "Continue recording"; case 'T': return "Stop recording"; case 'p': case 'P': return "Pause recording"; default: return "Unknown timer command"; } } void parser_print_rec(TimerRec *tr) { gchar *action_str = NULL; switch (tr->action) { case 'S': action_str = "Start"; break; case 'T': action_str = "sTop"; break; case 'P': action_str = "Pause"; break; } LOG_MSG("action:%c (%s)\n", tr->action, action_str); // Commented out: // LOG_MSG("\taction preposition (if/after/on/etc.):%c\n", tr->action_prep); // Is it silence/voice/sound/audio command? if (!g_strcmp0(tr->label, "silence") || !g_strcmp0(tr->label, "voice") || !g_strcmp0(tr->label, "sound") || !g_strcmp0(tr->label, "audio")) { LOG_MSG("\tlabel: %s, delay:%3.1f %3.1f %3.1f threshold:%3.3f %s\n", tr->label, tr->val[0], tr->val[1], tr->val[2], tr->threshold, tr->threshold_unit); } switch (tr->data_type) { case 'd': LOG_MSG("\t%c, time duration: %3.1f %3.1f %3.1f\n", tr->data_type, tr->val[0], tr->val[1], tr->val[2]); break; case 't': LOG_MSG("\t%c, clock time: %3.1f %3.1f %3.1f\n", tr->data_type, tr->val[0], tr->val[1], tr->val[2]); break; case 'f': LOG_MSG("\t%c, filesize: %3.1f (from %s)\n", tr->data_type, tr->val[0], tr->label); break; case 'x': ; break; default: LOG_MSG("\tUnknown data type in timer command.\n"); } } void parser_print_list(GList *list) { LOG_MSG("---------------------------\n"); g_list_foreach(list, (GFunc)parser_print_rec, NULL); } void parser_free_list() { // Free the TimerRec list g_list_foreach(g_timer_list, (GFunc)parser_free_node, NULL); g_list_free(g_timer_list); g_timer_list = NULL; }