<commit_before>/**
 * gmenu.c -- goaccess main dashboard
 * Copyright (C) 2009-2014 by <NAME> <<EMAIL>>
 * GoAccess - An Ncurses apache weblog analyzer & interactive viewer
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * A copy of the GNU General Public License is attached to this
 * source distribution for its full text.
 *
 * Visit http://goaccess.prosoftcorp.com for new releases.
 */

#define _XOPEN_SOURCE 700

#include <ctype.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <regex.h>

#ifdef HAVE_LIBGEOIP
#  include <GeoIP.h>
#endif

#include "gdashboard.h"

#include "error.h"
#include "xmalloc.h"
#include "gdns.h"
#include "parser.h"
#include "settings.h"
#include "util.h"

/* *INDENT-OFF* */

static GFind find_t;
/* module's styles */
static const GDashStyle module_style[TOTAL_MODULES] = {
   {COL_WHITE, COL_WHITE, COL_BLACK, COL_RED, COL_WHITE, -1, -1, -1},                 /* VISITORS        */
   {COL_WHITE, COL_WHITE, COL_BLACK, COL_BLACK, -1, COL_BLACK, COL_BLACK, COL_WHITE}, /* REQUESTS        */
   {COL_WHITE, COL_WHITE, COL_BLACK, COL_BLACK, -1, COL_BLACK, COL_BLACK, COL_WHITE}, /* REQUESTS_STATIC */
   {COL_WHITE, COL_WHITE, COL_BLACK, COL_BLACK, -1, COL_BLACK, COL_BLACK, COL_WHITE}, /* NOT FOUND       */
   {COL_WHITE, COL_WHITE, COL_BLACK, COL_BLACK, COL_WHITE, COL_BLACK, -1, -1},        /* HOSTS           */
   {COL_WHITE, COL_WHITE, -1, COL_RED, COL_WHITE, -1, -1, -1},                        /* OS              */
   {COL_WHITE, COL_WHITE, -1, COL_RED, COL_WHITE, -1, -1, -1},                        /* BROWSERS        */
   {COL_WHITE, COL_WHITE, -1, COL_BLACK, -1, -1, -1, -1},                             /* REFERRERS       */
   {COL_WHITE, COL_WHITE, -1, COL_BLACK, -1, -1, -1, -1},                             /* REFERRING_SITES */
   {COL_WHITE, COL_WHITE, -1, COL_BLACK, -1, -1, -1, -1},                             /* KEYPHRASES      */
   #ifdef HAVE_LIBGEOIP
   {COL_WHITE, COL_WHITE, -1, COL_BLACK, -1, -1, -1, -1},                             /* GEO_LOCATION    */
   #endif
   {COL_WHITE, COL_WHITE, -1, COL_BLACK, -1, -1, -1, -1},                             /* STATUS CODES    */
};
/* *INDENT-ON* */

/* reset find indices */
void
reset_find (void)
{
   if (find_t.pattern != NULL && *find_t.pattern != '\0')
      free (find_t.pattern);

   find_t.look_in_sub = 0;
   find_t.module = 0;
   find_t.next_idx = 0;         /* next total index    */
   find_t.next_parent_idx = 0;  /* next parent index   */
   find_t.next_sub_idx = 0;     /* next sub item index */
   find_t.pattern = NULL;
}

/* allocate memory for dash */
GDash *
new_gdash (void)
{
   GDash *dash = xmalloc (sizeof (GDash));
   memset (dash, 0, sizeof *dash);
   dash->total_alloc = 0;

   return dash;
}

/* allocate memory for dash elements */
GDashData *
new_gdata (unsigned int size)
{
   GDashData *data = xcalloc (size, sizeof (GDashData));
   return data;
}

/* free dash and its elements */
void
free_dashboard (GDash * dash)
{
   int i, j;
   for (i = 0; i < TOTAL_MODULES; i++) {
      for (j = 0; j < dash->module[i].alloc_data; j++) {
         free (dash->module[i].data[j].data);
         free (dash->module[i].data[j].bandwidth);
         if (conf.serve_usecs)
            free (dash->module[i].data[j].serve_time);
      }
      free (dash->module[i].data);
   }
   free (dash);
}

/* allocate memory for holder */
GHolder *
new_gholder (unsigned int size)
{
   GHolder *holder = xmalloc (size * sizeof (GHolder));
   memset (holder, 0, size * sizeof *holder);

   return holder;
}

/* allocate memory for holder items */
static GHolderItem *
new_gholder_item (unsigned int size)
{
   GHolderItem *item = xcalloc (size, sizeof (GHolderItem));
   return item;
}

/* allocate memory for a sub list */
static GSubList *
new_gsublist (void)
{
   GSubList *sub_list = xmalloc (sizeof (GSubList));
   sub_list->head = NULL;
   sub_list->tail = NULL;
   sub_list->size = 0;
   return sub_list;
}

/* allocate memory for a sub list item */
static GSubItem *
new_gsubitem (GModule module, const char *data, int hits, unsigned long long bw)
{
   GSubItem *sub_item = xmalloc (sizeof (GSubItem));
   sub_item->data = data;
   sub_item->hits = hits;
   sub_item->bw = bw;
   sub_item->module = module;
   sub_item->prev = NULL;
   sub_item->next = NULL;
   return sub_item;
}

/* add an item to the end of a given sub list */
void
add_sub_item_back (GSubList * sub_list, GModule module, const char *data,
                   int hits, unsigned long long bw)
{
   GSubItem *sub_item = new_gsubitem (module, data, hits, bw);
   if (sub_list->tail) {
      sub_list->tail->next = sub_item;
      sub_item->prev = sub_list->tail;
      sub_list->tail = sub_item;
   } else {
      sub_list->head = sub_item;
      sub_list->tail = sub_item;
   }
   sub_list->size++;
}

/* delete entire given sub list */
static void
delete_sub_list (GSubList * sub_list)
{
   GSubItem *item = item;
   GSubItem *next = next;

   if (sub_list != NULL && sub_list->size == 0)
      goto clear;
   if (sub_list->size == 0)
      return;

   for (item = sub_list->head; item; item = next) {
      next = item->next;
      free (item);
   }
 clear:
   sub_list->head = NULL;
   sub_list->size = 0;
   free (sub_list);
}

/* free memory allocated in holder for specific module */
void
free_holder_by_module (GHolder ** holder, GModule module)
{
   GSubList *sub_list;
   int j;

   if ((*holder) == NULL)
      return;

   for (j = 0; j < (*holder)[module].holder_size; j++) {
      sub_list = (*holder)[module].items[j].sub_list;
      /* free the sub list */
      if (sub_list != NULL)
         delete_sub_list (sub_list);
      if ((*holder)[module].items[j].data != NULL)
         free ((*holder)[module].items[j].data);
   }
   free ((*holder)[module].items);
   (*holder)[module].holder_size = 0;
   (*holder)[module].idx = 0;
   (*holder)[module].sub_items_size = 0;
}

/* free memory allocated in holder */
void
free_holder (GHolder ** holder)
{
   GSubList *sub_list;
   int i, j;

   if ((*holder) == NULL)
      return;

   for (i = 0; i < TOTAL_MODULES; i++) {
      for (j = 0; j < (*holder)[i].holder_size; j++) {
         sub_list = (*holder)[i].items[j].sub_list;
         /* free the sub list */
         if (sub_list != NULL)
            delete_sub_list (sub_list);
         if ((*holder)[i].items[j].data != NULL)
            free ((*holder)[i].items[j].data);
      }
      free ((*holder)[i].items);
   }
   free (*holder);
   (*holder) = NULL;
}

/* allocate memory for ht raw data */
static GRawData *
new_grawdata (void)
{
   GRawData *raw_data = xmalloc (sizeof (GRawData));
   memset (raw_data, 0, sizeof *raw_data);

   return raw_data;
}

/* allocate memory for raw data items */
static GRawDataItem *
new_grawdata_item (unsigned int size)
{
   GRawDataItem *item = xcalloc (size, sizeof (GRawDataItem));
   return item;
}

/* free memory allocated in raw data */
static void
free_raw_data (GRawData * raw_data)
{
   free (raw_data->items);
   free (raw_data);
}

/**
 * Determine which module should be expanded given the
 * current mouse position.
 */
void
set_module_from_mouse_event (GScrolling * scrolling, GDash * dash, int y)
{
   int module = 0, i;
   int offset = y - MAX_HEIGHT_HEADER - MAX_HEIGHT_FOOTER + 1;
   if (scrolling->expanded) {
      for (i = 0; i < TOTAL_MODULES; i++) {
         /* set current module */
         if (dash->module[i].pos_y == offset) {
            module = i;
            break;
         }
         /* we went over by one module, set current - 1 */
         if (dash->module[i].pos_y > offset) {
            module = i - 1;
            break;
         }
      }
   } else {
      offset += scrolling->dash;
      module = offset / DASH_COLLAPSED;
   }

   if (module >= TOTAL_MODULES)
      module = TOTAL_MODULES - 1;
   else if (module < 0)
      module = 0;

   scrolling->current = module;
}

/* calculate hits percentage */
float
get_percentage (unsigned long long total, unsigned long long hit)
{
   return ((float) (hit * 100) / (total));
}

/* render child nodes */
static char *
render_child_node (const char *data)
{
   char *buf;
   int len = 0;

#ifdef HAVE_LIBNCURSESW
   const char *bend = "\xe2\x94\x9c";
   const char *horz = "\xe2\x94\x80";
#else
   const char *bend = "|";
   const char *horz = "`-";
#endif

   if (data == NULL || *data == '\0')
      return NULL;

   len = snprintf (NULL, 0, " %s%s %s", bend, horz, data);
   buf = xmalloc (len + 3);
   sprintf (buf, " %s%s %s", bend, horz, data);

   return buf;
}

/* get a string of bars given current hits, maximum hit & xpos */
static char *
get_bars (int n, int max, int x)
{
   int w, h, len;

   getmaxyx (stdscr, h, w);
   (void) h;                    /* avoid lint warning */

   if ((len = (n * (w - x) / max)) < 1)
      len = 1;
   return char_repeat (len, '|');
}

/*get largest data's length */
static int
get_max_data_len (GDashData * data, int size)
{
   int i, max = 0, len;
   for (i = 0; i < size; i++) {
      if (data[i].data == NULL)
         continue;
      len = strlen (data[i].data);
      if (len > max)
         max = len;
   }
   return max;
}

/*get largest hit's length */
static int
get_max_hit_len (GDashData * data, int size)
{
   int i, max = 0;
   for (i = 0; i < size; i++) {
      int len = intlen (data[i].hits);
      if (len > max)
         max = len;
   }
   return max;
}

/* get largest hit */
static int
get_max_hit (GDashData * data, int size)
{
   int i, max = 0;
   for (i = 0; i < size; i++) {
      int cur = 0;
      if ((cur = data[i].hits) > max)
         max = cur;
   }
   return max;
}

/* set item's percent in GDashData */
static float
set_percent_data (GDashData * data, int n, int process)
{
   float max = 0.0;
   int i;
   for (i = 0; i < n; i++) {
      data[i].percent = get_percentage (process, data[i].hits);
      if (data[i].percent > max)
         max = data[i].percent;
   }
   return max;
}

/* render module's total */
static void
render_total_label (WINDOW * win, GDashModule * module_data, int y)
{
   char *s;
   int win_h, win_w, total, ht_size;

   total = module_data->holder_size;
   ht_size = module_data->ht_size;

   s = xmalloc (snprintf (NULL, 0, "Total: %d/%d", total, ht_size) + 1);
   getmaxyx (win, win_h, win_w);
   (void) win_h;

   sprintf (s, "Total: %d/%d", total, ht_size);

   wattron (win, COLOR_PAIR (HIGHLIGHT));
   mvwprintw (win, y, win_w - strlen (s) - 2, "%s", s);
   wattroff (win, COLOR_PAIR (HIGHLIGHT));
   free (s);
}

/* render dashboard bars (graph) */
static void
render_bars (WINDOW * win, GDashModule * module_data, int y, int *x, int idx,
             int w, int selected)
{
   const GDashStyle *style = module_style;
   GModule module = module_data->module;
   char *bar;

   if (style[module].color_bars == -1)
      return;

   bar = get_bars (module_data->data[idx].hits, module_data->max_hits, *x);
   if (selected) {
      if (conf.color_scheme == STD_GREEN)
         init_pair (1, COLOR_BLACK, COLOR_GREEN);
      else
         init_pair (1, COLOR_BLACK, COLOR_WHITE);

      wattron (win, COLOR_PAIR (HIGHLIGHT));
      mvwhline (win, y, *x, ' ', w);
      mvwprintw (win, y, *x, "%s", bar);
      wattroff (win, COLOR_PAIR (HIGHLIGHT));
   } else {
      mvwprintw (win, y, *x, "%s", bar);
   }
   free (bar);
}

/* render dashboard data */
static void
render_data (WINDOW * win, GDashModule * module_data, int y, int *x, int idx,
             int w, int selected)
{
   char buf[DATE_LEN];
   char *data;

   const GDashStyle *style = module_style;
   GModule module = module_data->module;

   data = substring (module_data->data[idx].data, 0, w - *x);
   if (module == VISITORS)
      convert_date (buf, data, "%Y%m%d", "%d/%b/%Y", DATE_LEN);

   if (selected) {
      if (conf.color_scheme == STD_GREEN)
         init_pair (1, COLOR_BLACK, COLOR_GREEN);
      else
         init_pair (1, COLOR_BLACK, COLOR_WHITE);

      wattron (win, COLOR_PAIR (HIGHLIGHT));
      if (module_data->module == HOSTS && module_data->data[idx].is_subitem)
         mvwhline (win, y, 0, ' ', w);
      else
         mvwhline (win, y, *x, ' ', w);
      mvwprintw (win, y, *x, "%s", module == VISITORS ? buf : data);
      wattroff (win, COLOR_PAIR (HIGHLIGHT));
   } else {
      wattron (win, COLOR_PAIR (style[module].color_hits));
      mvwprintw (win, y, *x, "%s", module == VISITORS ? buf : data);
      wattroff (win, COLOR_PAIR (style[module].color_hits));
   }
   *x += module == VISITORS ? DATE_LEN - 1 : module_data->data_len;
   *x += DASH_SPACE;
   free (data);
}

/* render dashboard usecs */
static void
render_method (WINDOW * win, GDashModule * module_data, int y, int *x, int idx,
               int w, int selected)
{
   const GDashStyle *style = module_style;
   GModule module = module_data->module;

   if (style[module].color_method == -1)
      return;

   if (selected) {
      if (conf.color_scheme == STD_GREEN)
         init_pair (1, COLOR_BLACK, COLOR_GREEN);
      else
         init_pair (1, COLOR_BLACK, COLOR_WHITE);

      wattron (win, COLOR_PAIR (HIGHLIGHT));
      mvwhline (win, y, *x, ' ', w);
      mvwprintw (win, y, *x, "%s", module_data->data[idx].method);
      wattroff (win, COLOR_PAIR (HIGHLIGHT));
   } else {
      wattron (win, A_BOLD | COLOR_PAIR (style[module].color_method));
      mvwprintw (win, y, *x, "%s", module_data->data[idx].method);
      wattroff (win, A_BOLD | COLOR_PAIR (style[module].color_method));
   }
   *x += strlen (module_data->data[idx].method) + DASH_SPACE;
}

/* render dashboard usecs */
static void
render_protocol (WINDOW * win, GDashModule * module_data, int y, int *x,
                 int idx, int w, int selected)
{
   const GDashStyle *style = module_style;
   GModule module = module_data->module;

   if (style[module].color_protocol == -1)
      return;

   if (selected) {
      if (conf.color_scheme == STD_GREEN)
         init_pair (1, COLOR_BLACK, COLOR_GREEN);
      else
         init_pair (1, COLOR_BLACK, COLOR_WHITE);
<commit_msg>Fixed minor comment typo in gdashboard.<commit_after>/**
 * gmenu.c -- goaccess main dashboard
 * Copyright (C) 2009-2014 by <NAME> <<EMAIL>>
 * GoAccess - An Ncurses apache weblog analyzer & interactive viewer
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * A copy of the GNU General Public License is attached to this
 * source distribution for its full text.
 *
 * Visit http://goaccess.prosoftcorp.com for new releases.
 */

#define _XOPEN_SOURCE 700

#include <ctype.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <regex.h>

#ifdef HAVE_LIBGEOIP
#  include <GeoIP.h>
#endif

#include "gdashboard.h"

#include "error.h"
#include "xmalloc.h"
#include "gdns.h"
#include "parser.h"
#include "settings.h"
#include "util.h"

/* *INDENT-OFF* */

static GFind find_t;
/* module's styles */
static const GDashStyle module_style[TOTAL_MODULES] = {
   {COL_WHITE, COL_WHITE, COL_BLACK, COL_RED, COL_WHITE, -1, -1, -1},                 /* VISITORS        */
   {COL_WHITE, COL_WHITE, COL_BLACK, COL_BLACK, -1, COL_BLACK, COL_BLACK, COL_WHITE}, /* REQUESTS        */
   {COL_WHITE, COL_WHITE, COL_BLACK, COL_BLACK, -1, COL_BLACK, COL_BLACK, COL_WHITE}, /* REQUESTS_STATIC */
   {COL_WHITE, COL_WHITE, COL_BLACK, COL_BLACK, -1, COL_BLACK, COL_BLACK, COL_WHITE}, /* NOT FOUND       */
   {COL_WHITE, COL_WHITE, COL_BLACK, COL_BLACK, COL_WHITE, COL_BLACK, -1, -1},        /* HOSTS           */
   {COL_WHITE, COL_WHITE, -1, COL_RED, COL_WHITE, -1, -1, -1},                        /* OS              */
   {COL_WHITE, COL_WHITE, -1, COL_RED, COL_WHITE, -1, -1, -1},                        /* BROWSERS        */
   {COL_WHITE, COL_WHITE, -1, COL_BLACK, -1, -1, -1, -1},                             /* REFERRERS       */
   {COL_WHITE, COL_WHITE, -1, COL_BLACK, -1, -1, -1, -1},                             /* REFERRING_SITES */
   {COL_WHITE, COL_WHITE, -1, COL_BLACK, -1, -1, -1, -1},                             /* KEYPHRASES      */
   #ifdef HAVE_LIBGEOIP
   {COL_WHITE, COL_WHITE, -1, COL_BLACK, -1, -1, -1, -1},                             /* GEO_LOCATION    */
   #endif
   {COL_WHITE, COL_WHITE, -1, COL_BLACK, -1, -1, -1, -1},                             /* STATUS CODES    */
};
/* *INDENT-ON* */

/* reset find indices */
void
reset_find (void)
{
   if (find_t.pattern != NULL && *find_t.pattern != '\0')
      free (find_t.pattern);

   find_t.look_in_sub = 0;
   find_t.module = 0;
   find_t.next_idx = 0;         /* next total index    */
   find_t.next_parent_idx = 0;  /* next parent index   */
   find_t.next_sub_idx = 0;     /* next sub item index */
   find_t.pattern = NULL;
}

/* allocate memory for dash */
GDash *
new_gdash (void)
{
   GDash *dash = xmalloc (sizeof (GDash));
   memset (dash, 0, sizeof *dash);
   dash->total_alloc = 0;

   return dash;
}

/* allocate memory for dash elements */
GDashData *
new_gdata (unsigned int size)
{
   GDashData *data = xcalloc (size, sizeof (GDashData));
   return data;
}

/* free dash and its elements */
void
free_dashboard (GDash * dash)
{
   int i, j;
   for (i = 0; i < TOTAL_MODULES; i++) {
      for (j = 0; j < dash->module[i].alloc_data; j++) {
         free (dash->module[i].data[j].data);
         free (dash->module[i].data[j].bandwidth);
         if (conf.serve_usecs)
            free (dash->module[i].data[j].serve_time);
      }
      free (dash->module[i].data);
   }
   free (dash);
}

/* allocate memory for holder */
GHolder *
new_gholder (unsigned int size)
{
   GHolder *holder = xmalloc (size * sizeof (GHolder));
   memset (holder, 0, size * sizeof *holder);

   return holder;
}

/* allocate memory for holder items */
static GHolderItem *
new_gholder_item (unsigned int size)
{
   GHolderItem *item = xcalloc (size, sizeof (GHolderItem));
   return item;
}

/* allocate memory for a sub list */
static GSubList *
new_gsublist (void)
{
   GSubList *sub_list = xmalloc (sizeof (GSubList));
   sub_list->head = NULL;
   sub_list->tail = NULL;
   sub_list->size = 0;
   return sub_list;
}

/* allocate memory for a sub list item */
static GSubItem *
new_gsubitem (GModule module, const char *data, int hits, unsigned long long bw)
{
   GSubItem *sub_item = xmalloc (sizeof (GSubItem));
   sub_item->data = data;
   sub_item->hits = hits;
   sub_item->bw = bw;
   sub_item->module = module;
   sub_item->prev = NULL;
   sub_item->next = NULL;
   return sub_item;
}

/* add an item to the end of a given sub list */
void
add_sub_item_back (GSubList * sub_list, GModule module, const char *data,
                   int hits, unsigned long long bw)
{
   GSubItem *sub_item = new_gsubitem (module, data, hits, bw);
   if (sub_list->tail) {
      sub_list->tail->next = sub_item;
      sub_item->prev = sub_list->tail;
      sub_list->tail = sub_item;
   } else {
      sub_list->head = sub_item;
      sub_list->tail = sub_item;
   }
   sub_list->size++;
}

/* delete entire given sub list */
static void
delete_sub_list (GSubList * sub_list)
{
   GSubItem *item = item;
   GSubItem *next = next;

   if (sub_list != NULL && sub_list->size == 0)
      goto clear;
   if (sub_list->size == 0)
      return;

   for (item = sub_list->head; item; item = next) {
      next = item->next;
      free (item);
   }
 clear:
   sub_list->head = NULL;
   sub_list->size = 0;
   free (sub_list);
}

/* free memory allocated in holder for specific module */
void
free_holder_by_module (GHolder ** holder, GModule module)
{
   GSubList *sub_list;
   int j;

   if ((*holder) == NULL)
      return;

   for (j = 0; j < (*holder)[module].holder_size; j++) {
      sub_list = (*holder)[module].items[j].sub_list;
      /* free the sub list */
      if (sub_list != NULL)
         delete_sub_list (sub_list);
      if ((*holder)[module].items[j].data != NULL)
         free ((*holder)[module].items[j].data);
   }
   free ((*holder)[module].items);
   (*holder)[module].holder_size = 0;
   (*holder)[module].idx = 0;
   (*holder)[module].sub_items_size = 0;
}

/* free memory allocated in holder */
void
free_holder (GHolder ** holder)
{
   GSubList *sub_list;
   int i, j;

   if ((*holder) == NULL)
      return;

   for (i = 0; i < TOTAL_MODULES; i++) {
      for (j = 0; j < (*holder)[i].holder_size; j++) {
         sub_list = (*holder)[i].items[j].sub_list;
         /* free the sub list */
         if (sub_list != NULL)
            delete_sub_list (sub_list);
         if ((*holder)[i].items[j].data != NULL)
            free ((*holder)[i].items[j].data);
      }
      free ((*holder)[i].items);
   }
   free (*holder);
   (*holder) = NULL;
}

/* allocate memory for ht raw data */
static GRawData *
new_grawdata (void)
{
   GRawData *raw_data = xmalloc (sizeof (GRawData));
   memset (raw_data, 0, sizeof *raw_data);

   return raw_data;
}

/* allocate memory for raw data items */
static GRawDataItem *
new_grawdata_item (unsigned int size)
{
   GRawDataItem *item = xcalloc (size, sizeof (GRawDataItem));
   return item;
}

/* free memory allocated in raw data */
static void
free_raw_data (GRawData * raw_data)
{
   free (raw_data->items);
   free (raw_data);
}

/**
 * Determine which module should be expanded given the
 * current mouse position.
 */
void
set_module_from_mouse_event (GScrolling * scrolling, GDash * dash, int y)
{
   int module = 0, i;
   int offset = y - MAX_HEIGHT_HEADER - MAX_HEIGHT_FOOTER + 1;
   if (scrolling->expanded) {
      for (i = 0; i < TOTAL_MODULES; i++) {
         /* set current module */
         if (dash->module[i].pos_y == offset) {
            module = i;
            break;
         }
         /* we went over by one module, set current - 1 */
         if (dash->module[i].pos_y > offset) {
            module = i - 1;
            break;
         }
      }
   } else {
      offset += scrolling->dash;
      module = offset / DASH_COLLAPSED;
   }

   if (module >= TOTAL_MODULES)
      module = TOTAL_MODULES - 1;
   else if (module < 0)
      module = 0;

   scrolling->current = module;
}

/* calculate hits percentage */
float
get_percentage (unsigned long long total, unsigned long long hit)
{
   return ((float) (hit * 100) / (total));
}

/* render child nodes */
static char *
render_child_node (const char *data)
{
   char *buf;
   int len = 0;

#ifdef HAVE_LIBNCURSESW
   const char *bend = "\xe2\x94\x9c";
   const char *horz = "\xe2\x94\x80";
#else
   const char *bend = "|";
   const char *horz = "`-";
#endif

   if (data == NULL || *data == '\0')
      return NULL;

   len = snprintf (NULL, 0, " %s%s %s", bend, horz, data);
   buf = xmalloc (len + 3);
   sprintf (buf, " %s%s %s", bend, horz, data);

   return buf;
}

/* get a string of bars given current hits, maximum hit & xpos */
static char *
get_bars (int n, int max, int x)
{
   int w, h, len;

   getmaxyx (stdscr, h, w);
   (void) h;                    /* avoid lint warning */

   if ((len = (n * (w - x) / max)) < 1)
      len = 1;
   return char_repeat (len, '|');
}

/*get largest data's length */
static int
get_max_data_len (GDashData * data, int size)
{
   int i, max = 0, len;
   for (i = 0; i < size; i++) {
      if (data[i].data == NULL)
         continue;
      len = strlen (data[i].data);
      if (len > max)
         max = len;
   }
   return max;
}

/*get largest hit's length */
static int
get_max_hit_len (GDashData * data, int size)
{
   int i, max = 0;
   for (i = 0; i < size; i++) {
      int len = intlen (data[i].hits);
      if (len > max)
         max = len;
   }
   return max;
}

/* get largest hit */
static int
get_max_hit (GDashData * data, int size)
{
   int i, max = 0;
   for (i = 0; i < size; i++) {
      int cur = 0;
      if ((cur = data[i].hits) > max)
         max = cur;
   }
   return max;
}

/* set item's percent in GDashData */
static float
set_percent_data (GDashData * data, int n, int process)
{
   float max = 0.0;
   int i;
   for (i = 0; i < n; i++) {
      data[i].percent = get_percentage (process, data[i].hits);
      if (data[i].percent > max)
         max = data[i].percent;
   }
   return max;
}

/* render module's total */
static void
render_total_label (WINDOW * win, GDashModule * module_data, int y)
{
   char *s;
   int win_h, win_w, total, ht_size;

   total = module_data->holder_size;
   ht_size = module_data->ht_size;

   s = xmalloc (snprintf (NULL, 0, "Total: %d/%d", total, ht_size) + 1);
   getmaxyx (win, win_h, win_w);
   (void) win_h;

   sprintf (s, "Total: %d/%d", total, ht_size);

   wattron (win, COLOR_PAIR (HIGHLIGHT));
   mvwprintw (win, y, win_w - strlen (s) - 2, "%s", s);
   wattroff (win, COLOR_PAIR (HIGHLIGHT));
   free (s);
}

/* render dashboard bars (graph) */
static void
render_bars (WINDOW * win, GDashModule * module_data, int y, int *x, int idx,
             int w, int selected)
{
   const GDashStyle *style = module_style;
   GModule module = module_data->module;
   char *bar;

   if (style[module].color_bars == -1)
      return;

   bar = get_bars (module_data->data[idx].hits, module_data->max_hits, *x);
   if (selected) {
      if (conf.color_scheme == STD_GREEN)
         init_pair (1, COLOR_BLACK, COLOR_GREEN);
      else
         init_pair (1, COLOR_BLACK, COLOR_WHITE);

      wattron (win, COLOR_PAIR (HIGHLIGHT));
      mvwhline (win, y, *x, ' ', w);
      mvwprintw (win, y, *x, "%s", bar);
      wattroff (win, COLOR_PAIR (HIGHLIGHT));
   } else {
      mvwprintw (win, y, *x, "%s", bar);
   }
   free (bar);
}

/* render dashboard data */
static void
render_data (WINDOW * win, GDashModule * module_data, int y, int *x, int idx,
             int w, int selected)
{
   char buf[DATE_LEN];
   char *data;

   const GDashStyle *style = module_style;
   GModule module = module_data->module;

   data = substring (module_data->data[idx].data, 0, w - *x);
   if (module == VISITORS)
      convert_date (buf, data, "%Y%m%d", "%d/%b/%Y", DATE_LEN);

   if (selected) {
      if (conf.color_scheme == STD_GREEN)
         init_pair (1, COLOR_BLACK, COLOR_GREEN);
      else
         init_pair (1, COLOR_BLACK, COLOR_WHITE);

      wattron (win, COLOR_PAIR (HIGHLIGHT));
      if (module_data->module == HOSTS && module_data->data[idx].is_subitem)
         mvwhline (win, y, 0, ' ', w);
      else
         mvwhline (win, y, *x, ' ', w);
      mvwprintw (win, y, *x, "%s", module == VISITORS ? buf : data);
      wattroff (win, COLOR_PAIR (HIGHLIGHT));
   } else {
      wattron (win, COLOR_PAIR (style[module].color_hits));
      mvwprintw (win, y, *x, "%s", module == VISITORS ? buf : data);
      wattroff (win, COLOR_PAIR (style[module].color_hits));
   }
   *x += module == VISITORS ? DATE_LEN - 1 : module_data->data_len;
   *x += DASH_SPACE;
   free (data);
}

/* render dashboard request method */
static void
render_method (WINDOW * win, GDashModule * module_data, int y, int *x, int idx,
               int w, int selected)
{
   const GDashStyle *style = module_style;
   GModule module = module_data->module;

   if (style[module].color_method == -1)
      return;

   if (selected) {
      if (conf.color_scheme == STD_GREEN)
         init_pair (1, COLOR_BLACK, COLOR_GREEN);
      else
         init_pair (1, COLOR_BLACK, COLOR_WHITE);

      wattron (win, COLOR_PAIR (HIGHLIGHT));
      mvwhline (win, y, *x, ' ', w);
      mvwprintw (win, y, *x, "%s", module_data->data[idx].method);
      wattroff (win, COLOR_PAIR (HIGHLIGHT));
   } else {
      wattron (win, A_BOLD | COLOR_PAIR (style[module].color_method));
      mvwprintw (win, y, *x, "%s", module_data->data[idx].method);
      wattroff (win, A_BOLD | COLOR_PAIR (style[module].color_method));
   }
   *x += strlen (module_data->data[idx].method) + DASH_SPACE;
}

/* render dashboard request protocol */
static void
render_protocol (WINDOW * win, GDashModule * module_data, int y, int *x,
                 int idx, int w, int selected)
{
   const GDashStyle *style = module_style;
   GModule module = module_data->module;

   if (style[module].color_protocol == -1)
      return;

   if (selected) {
      if (conf.color_scheme == STD_GREEN)
         init_pair (1, COLOR_BLACK, COLOR_GREEN);
      else
         init_pair (1, COLOR_BLACK, COLOR_WHITE);

{'fraction_non_alphanumeric': 0.14491211205712717, 'fraction_numerical': 0.008788794287283713, 'mean_word_length': 2.5939543491671806, 'pattern_counts': {'":': 0, '<': 53, '<?xml version=': 0, '>': 143, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1295815', 'n_tokens_mistral': 10854, 'n_tokens_neox': 10596, 'n_words': 2983}