/*

  watchdog.cc

  background watching subroutines for xlogmaster.cc
  Copyright (C) 1998 Georg C. F. Greve
  This is a GNU program
  
  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.
  
  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  
  
  Contact: 
           mailto:xlogmaster-bugs@gnu.org
           http://www.gnu.org/software/xlogmaster/
  Secondary sources:
           http://porter.desy.de/~greve/xlogmaster/
           http://www.fusebox.hanse.de/xlogmaster/
 	 
*/

/*{{{  Header  */

#include "../config.h"
#include "sysinc.H"
#include "../import/import.h"
#include "logclass.H"
#include "watchdog.H"
#include "extern.H"

/*}}}*/

/*{{{  Logfile handling (only class0 filters)  */
/*{{{  activate()  */
void 
activate()
{
  if ( display_logs == DISABLED ) return;
  /* speedup fadedown for activated logfile */
  entry[active]->fadestep = -1 * ( fade_base / 3 );
  
  if ( entry[active]->filterclass & CLASS1_FILTER ){
/*{{{  There are CLASS1 filters for this one...  */
    
    if ( entry[active]->got == -1 ){
      file_error(entry[active]->filename);
      return;
    }

    /* Freeze for output: */
    gtk_text_freeze (GTK_TEXT(textwindow));
    
    if ( entry[active]->mode == TAIL_FILE ){
      output_text(textwindow, entry[active]->buffer, entry[active]->length);
    }  else {
      // for cat the data stays in the read buffer... we don't keep a history
      // anyways.
      output_text(textwindow, entry[active]->rbuffer, entry[active]->length);
    }

    /* And now unfreeze again */
    gtk_text_thaw(GTK_TEXT(textwindow));
    
    gtk_adjustment_set_value(GTK_TEXT(textwindow)->vadj,
			     GTK_TEXT(textwindow)->vadj->upper - 
			     GTK_TEXT(textwindow)->vadj->page_size);

    entry[active]->active = TRUE;

/*}}}*/
  } else {

    if ( entry[active]->mode == TAIL_FILE ){
/*{{{  Startup of "tail" mode  */
      entry[active]->fd = open(entry[active]->filename, O_RDONLY);
      if ( entry[active]->fd == -1 ){
	file_error(entry[active]->filename);
	deactivate();
	return;
      }
      
      glong got;
      fstat( entry[active]->fd,  &status );
      /*
	We need to check whether the file is shorter than maxtext
	bytes because some systems (like FreeBSD) allow a negative
	index for files...
      */
      if ( maxtext < status.st_size )
	{
	  /* if the file is bigger than maxtext we should
	     read maxtext bytes 
	  */
	  lseek(entry[active]->fd, -maxtext, SEEK_END);
	  got = read(entry[active]->fd, read_buffer, maxtext);
	}
      else
	{
	  /*
	    otherwise read everything there is
	  */
	  lseek(entry[active]->fd, 0, SEEK_SET);
	  got = read(entry[active]->fd, read_buffer, status.st_size );
	}
      
      /* Freeze for output: */
      gtk_text_freeze (GTK_TEXT(textwindow));
      
      output_text(textwindow, read_buffer, got);
      
      /* And now unfreeze again */
      gtk_text_thaw(GTK_TEXT(textwindow));
      
      gtk_adjustment_set_value(GTK_TEXT(textwindow)->vadj,
			       GTK_TEXT(textwindow)->vadj->upper - 
			       GTK_TEXT(textwindow)->vadj->page_size);
      
      // Memorize size of logfile...
      entry[active]->last_size = status.st_size;

      entry[active]->tag = gtk_timeout_add (entry[active]->interval*100,
					     *tail_interrupt,
					     (gpointer*) active);
      
      entry[active]->active = TRUE;

/*}}}*/
    } else if ( entry[active]->mode == CAT_FILE ){
/*{{{  Startup of "cat" mode  */
      entry[active]->fd = open(entry[active]->filename, O_RDONLY);
      if ( entry[active]->fd == -1 ){
	file_error(entry[active]->filename);
	deactivate();
	return;
      }
      
      long got = read(entry[active]->fd, read_buffer, maxtext);
      
      close(entry[active]->fd);
      
      /* Freeze for output: */
      gtk_text_freeze (GTK_TEXT(textwindow));
      
      output_text(textwindow, read_buffer, got);
      
      /* And now unfreeze again */
      gtk_text_thaw(GTK_TEXT(textwindow));
      
      gtk_adjustment_set_value(GTK_TEXT(textwindow)->vadj,
			       GTK_TEXT(textwindow)->vadj->lower);
      
      entry[active]->tag = gtk_timeout_add (entry[active]->interval*100,
					     *cat_interrupt,
					     (gpointer*) active);

      entry[active]->active = TRUE;  
 
/*}}}*/
    }

  }
  /*
    Set title to button text:
  */
  gtk_window_set_title (GTK_WINDOW (window), entry[active]->buttontext );  
  
  /*
    If entry is not in alert mode right now, color it's
    button in PRELIGHT color !
  */  
  entry[active]->style = gtk_style_copy(stdstyle);
  if ( entry[active]->alert_tag == 0 )
    {
      entry[active]->style->bg[GTK_STATE_NORMAL].red = prelight.red;
      entry[active]->style->bg[GTK_STATE_NORMAL].green = prelight.green;
      entry[active]->style->bg[GTK_STATE_NORMAL].blue = prelight.blue;
    }  
  gtk_widget_set_style(entry[active]->button, entry[active]->style);
  gtk_style_unref(entry[active]->style);
}
/*}}}*/
/*{{{  deactivate()  */
void 
deactivate()
{
  /*
    We need to disable interrupt-routines...
  */
  entry[active]->active = FALSE;

  if ( ! ( entry[active]->filterclass & CLASS1_FILTER ) ){
    if ( entry[active]->tag != 0 ){
      gtk_timeout_remove(entry[active]->tag);
      entry[active]->tag=0;
    }
    if ( entry[active]->mode == TAIL_FILE )
      close(entry[active]->fd);
  }

  /*
    We always want to clear the textdisplay:
  */
  gtk_text_freeze (GTK_TEXT(textwindow));
  gtk_text_set_point (GTK_TEXT(textwindow),0);
  gtk_text_forward_delete (GTK_TEXT(textwindow),
			   gtk_text_get_length(GTK_TEXT(textwindow)));
  gtk_text_thaw(GTK_TEXT(textwindow));
  gtk_adjustment_set_value(GTK_TEXT(textwindow)->vadj, 0);


  /*
    If entry is not in alert mode right now, color it's
    button in NORMAL color !
  */  
  if ( entry[active]->alert_tag == 0 )
    {
      entry[active]->style = gtk_style_copy(stdstyle);
      gtk_widget_set_style(entry[active]->button, entry[active]->style);
      gtk_style_unref(entry[active]->style);
    }
}
/*}}}*/
/*{{{  tail_interrupt  */
int tail_interrupt(gpointer data){ 
  // if the size didn't change we don't need to do anything...
  fstat( entry[active]->fd,  &status );
  if ( status.st_size == entry[active]->last_size ) return TRUE;
  /* if file shrunk there was probably a logfile rotation
     so just try to reopen the new one */ 
  if ( status.st_size < entry[active]->last_size )
    if ( reopen_logfile(active) == FALSE ) return TRUE;

  long got = read(entry[active]->fd, read_buffer, maxtext);

  while ( got > 0 ){
    /* Freeze for these actions: */
    gtk_text_freeze (GTK_TEXT(textwindow));
    
    /*
      More than maxtext bytes ? Then delete some...
    */
    long text_length = gtk_text_get_length(GTK_TEXT(textwindow));
    gfloat old_value = ( GTK_TEXT(textwindow)->vadj->upper - 
			 GTK_TEXT(textwindow)->vadj->value);
    if ( got + text_length > maxtext ){
      gtk_text_set_point (GTK_TEXT(textwindow),0);
      gtk_text_forward_delete (GTK_TEXT(textwindow), 
			       (text_length+got) - maxtext );
      gtk_text_set_point (GTK_TEXT(textwindow),
			  gtk_text_get_length(GTK_TEXT(textwindow)));
    }
    
    /*
      Now append the data we just read...
    */

    output_text(textwindow, read_buffer, got);

    /* And now unfreeze again */
    gtk_text_thaw(GTK_TEXT(textwindow));
    
    /* Now put scrollbar on same position relative to end */
    gtk_adjustment_set_value(GTK_TEXT(textwindow)->vadj,
			     GTK_TEXT(textwindow)->vadj->upper - 
			     old_value);
    
    got = read(entry[active]->fd, read_buffer, maxtext);
  }

  fstat( entry[active]->fd,  &status );
  entry[active]->last_size = status.st_size;

  return TRUE;
}
/*}}}*/
/*{{{  cat_interrupt  */
int cat_interrupt(gpointer data){
  entry[active]->fd = open(entry[active]->filename, O_RDONLY);
  if ( entry[active]->fd == -1 ){
    deactivate();
    file_error(entry[active]->filename);
    return FALSE;
  }
  long got = read(entry[active]->fd, read_buffer, maxtext);
  close( entry[active]->fd );
  
  /* Freeze textwindow: */
  gtk_text_freeze (GTK_TEXT(textwindow));
  /* Memorize old position */
  gfloat old_value = ( GTK_TEXT(textwindow)->vadj->upper - 
		       GTK_TEXT(textwindow)->vadj->value);
  /* Clear window: */
  gtk_text_set_point (GTK_TEXT(textwindow),0);
  gtk_text_forward_delete (GTK_TEXT(textwindow), 
			   gtk_text_get_length(GTK_TEXT(textwindow)));
  
  if ( got > 0 ) output_text(textwindow, read_buffer, got);

  /* And now unfreeze again */
  gtk_text_thaw(GTK_TEXT(textwindow));
  
  /* Now put scrollbar on same position relative to end */
  gtk_adjustment_set_value(GTK_TEXT(textwindow)->vadj,
			   GTK_TEXT(textwindow)->vadj->upper - 
			   old_value);

  return TRUE;
}
/*}}}*/
/*}}}*/

/*{{{  shared routines  */
/* try to reopen logfile */
int reopen_logfile(int i ){
  close(entry[i]->fd);
  entry[i]->fd = open(entry[i]->filename, O_RDONLY);
 
  if ( entry[active]->fd > -1 ){
    // now we need to go to the beginning of the file:
    lseek(entry[i]->fd, 0, SEEK_SET);
    // everything clear... return
    return TRUE;
  }

  /* we got a file opening error */

  entry[i]->got = -1;

  if ( i == active ) deactivate();

  if ( entry[i]->filterclass & CLASS1_FILTER  ){
    gtk_timeout_remove(entry[i]->wd_tag);
    entry[i]->wd_tag = 0;
    if ( entry[i]->buffer != NULL ) delete entry[i]->buffer;
    entry[i]->buffer = NULL;
    if ( entry[i]->rbuffer != NULL ) delete entry[i]->rbuffer;
    entry[i]->rbuffer = NULL;
    entry[i]->length = 0;
  }

  file_error(entry[i]->filename);
  return FALSE;
}
/*}}}*/

/*{{{  Watchdog (background log watching) on/off  */
/*
  Startup routine for background log watching
  neeeds some more niceness and should probably
  be split up eventually...
*/
void 
start_watchdog()
{
  int i;
  for ( i = 0 ; i < syslogs ; i++ )
    {
      
      if ( entry[i]->filterclass & CLASS1_FILTER ){
	// we need this buffer ONLY for the tail mode !
	if ( entry[i]->mode == TAIL_FILE ){
	  entry[i]->buffer = new char[maxtext];
	}
	entry[i]->rbuffer = new char[entry[i]->chunk];
	entry[i]->length = 0;
	
	entry[i]->fd = open(entry[i]->filename, O_RDONLY);
	
	if ( entry[i]->mode == TAIL_FILE )
	  {
	    
	    fstat( entry[i]->fd,  &status );
	    
	    /*
	      We need to check whether the file is shorter than maxtext
	      bytes because some systems (like FreeBSD) allow a negative
	      index for files...
	    */
	    if ( maxtext < status.st_size )
	      {
		/* if the file is bigger than maxtext we should
		   read maxtext bytes 
		*/
		lseek(entry[i]->fd, -maxtext, SEEK_END);
		entry[i]->length = read(entry[i]->fd, entry[i]->buffer, maxtext);
	      }
	    else
	      {
		/*
		  otherwise read everything there is
		*/
		lseek(entry[i]->fd, 0, SEEK_SET);
		entry[i]->length = read(entry[i]->fd, entry[i]->buffer, status.st_size );
	      }
	    
	    // Memorize size of logfile...
	    entry[i]->last_size = status.st_size;
	    
	    
	  }
	else
	  {
	    
	    entry[i]->length = read(entry[i]->fd, entry[i]->rbuffer, maxtext);  
	    close(entry[i]->fd);
	    
	  }
	
	
	entry[i]->wd_tag = gtk_timeout_add (entry[i]->interval*100,
					    *watchdog_interrupt,
					    (gpointer*) i);
	
      } 
      else
	{
	  
	  
	  if ( entry[i]->buffer != NULL ) delete entry[i]->buffer;
	  entry[i]->buffer = NULL;
	  if ( entry[i]->rbuffer != NULL ) delete entry[i]->rbuffer;
	  entry[i]->rbuffer = NULL;
	  
	  
	  
	  
	}

    }
}
void 
stop_watchdog()
{
  int i;
  for ( i = 0 ; i < syslogs ; i++ )
    if ( entry[i]->filterclass & CLASS1_FILTER ){
      gtk_timeout_remove(entry[i]->wd_tag);
      entry[i]->wd_tag = 0;
     
      if ( entry[i]->mode == TAIL_FILE )
	close(entry[i]->fd);
      
      if ( entry[i]->buffer != NULL ) delete entry[i]->buffer;
      entry[i]->buffer = NULL;
      if ( entry[i]->rbuffer != NULL ) delete entry[i]->rbuffer;
      entry[i]->rbuffer = NULL;
      entry[i]->length = 0;
    } 
}
/*}}}*/

/*{{{  watchdog_interrupt  */
/*
  as the name suggests this is the interrupt routine that does the background
  log watching... it is responsible for keeping the buffers up-to date and
  it even displays the text if data (the number of the entry) matches active.
*/
int 
watchdog_interrupt(gpointer data)
{ 
  int i = (int) data;

  if ( entry[i]->mode == TAIL_FILE ){
    if ( entry[i]->fd == -1 ){
      watchdog_file_error(i);
      return TRUE;
    }

    /* tail file */
    fstat( entry[i]->fd,  &status );
    if ( status.st_size == entry[i]->last_size ) return TRUE;
    /* if file shrunk there was probably a logfile rotation
       so just try to reopen the new one */ 
    if ( status.st_size < entry[i]->last_size )
      if ( reopen_logfile(i) == FALSE ) return TRUE;

  } else {

    /* cat file */
    entry[i]->fd = open(entry[i]->filename, O_RDONLY);    
    if ( entry[i]->fd == -1 ){
      /* we got a file opening error */
      watchdog_file_error(i);
      return TRUE;      
    } 
    
  }
  
  long got = read(entry[i]->fd, entry[i]->rbuffer, entry[i]->chunk);

  if ( entry[i]->mode == CAT_FILE ) 
    close( entry[i]->fd );

  while ( got > 0 ){
    long overlap;

    if ( entry[i]->mode == TAIL_FILE ){

      overlap= ( got + entry[i]->length ) - maxtext;
      if ( overlap < 0 ) overlap = 0;
      if ( overlap > 0 ){
	for ( long y = overlap ; y < entry[i]->length ; y++ )
	  entry[i]->buffer[ y - overlap] = entry[i]->buffer[ y ];
      }
      
      long target = entry[i]->length - overlap;
      for ( long z = 0 ; z < got ; z++ )
	entry[i]->buffer[ z + target ] = entry[i]->rbuffer[z];
      entry[i]->length += got;
      if ( entry[i]->length > maxtext ) entry[i]->length = maxtext;

    } else {
      
      entry[i]->length = got;
      
    }
    
    /*
      Now search for Class 1 Filters and take according actions
    */
/*{{{  Class1 Filter stuff  */
    glong start = 0;
    glong stop = 0;
    
    while ( start < got &&
	    entry[i]->filter != NULL )
      {
	
	stop = match_all( entry[i]->filter, 
			  CLASS1_FILTER, 
			  entry[i]->rbuffer, 
			  start, 
			  got);
	
	gint action = 0;
	gint x = 0;
	while ( entry[i]->filter[x] != NULL )
	  {
	    
	    if ( entry[i]->filter[x]->regex_status >= 0 )
	      {
		action |= entry[i]->filter[x]->mode;
	      }
	    
	    x++;
	  }
	
	/*
	  mask out things that are not CLASS1_FILTERS,
	  afterwards actions has the bit for every applicable
	  CLASS1_FILTER set.
	*/
	action &= CLASS1_MASK;
	
	/*
	  if action is not 0 we need to do something about
	  that line, so here is our routine for that:
	*/
	
	if ( action != 0 )
	  {
	    gchar* line = NULL;

	    /*
	      These filters need the line that triggered the match
	    */
	    if ( action & NOTICE ||
		 action & EXECUTE )
	      {
		line = new gchar[(stop-start)+5];
		
		gint a = 0;
		gint b = start;
		while ( b < stop )
		  {

		    if ( entry[i]->rbuffer[b] != '\r' &&
			 entry[i]->rbuffer[b] != '\n' )
		      {
			line[a++] = entry[i]->rbuffer[b++];
		      }
		    else
		      {
			b++;
		      }
		    
		  }
		line[a] = 0;
		
	      }

	    if ( action & NOTICE )
	      {
		notice_alert(entry[i]->buttontext , line);
		play(NOTICE);
	      }

	    if ( action & ALERT )
	      {
		trigger_alert(i);
		play(ALERT);
	      }

	    if ( action & UNICONIFY )
	      {
		/* This maps the window, which also de-iconifies it according to ICCCM. */
		gdk_window_show (GTK_WIDGET (window)->window);
		gdk_window_raise (GTK_WIDGET (window)->window);
		play(UNICONIFY);
	      }

	    if ( action & EXECUTE )
	      {
		x = 0;
		while (  entry[i]->filter[x] != NULL )
		  {
		    
		    if ( entry[i]->filter[x]->regex_status >= 0 
			 && entry[i]->filter[x]->mode & EXECUTE )
		      {
			execute_program(i, x , line);
		      }
		    
		    x++;
		  }
	      }

	    /* 
	       now the line can be freed again
	    */
	    if ( line != NULL ) delete line;
	  }

	start = stop + 1;
      }
/*}}}*/
    /*
      Alright - the CLASS1 Filter stuff is ready now...
    */


    /*
      Is the log active ? Then display it ...
    */
    if ( entry[i]->active == TRUE ){
      if ( entry[i]->mode == TAIL_FILE )
	append_text(entry[i]->rbuffer, got);
      else
	replace_text(entry[i]->rbuffer, got);

    }
    
    if ( entry[i]->mode == TAIL_FILE ) got = read(entry[i]->fd, entry[i]->rbuffer, entry[i]->chunk);
    else got = 0;
  }
  
  if ( entry[i]->mode == TAIL_FILE ){
    fstat( entry[i]->fd,  &status );
    entry[i]->last_size = status.st_size;
  }
  
  return TRUE;
}
void append_text(char* buffer, long amount){
  /* Freeze for these actions: */
  gtk_text_freeze (GTK_TEXT(textwindow));
  
  /*
    More than maxtext bytes ? Then delete some...
  */
  long text_length = gtk_text_get_length(GTK_TEXT(textwindow));
  gfloat old_value = ( GTK_TEXT(textwindow)->vadj->upper - 
		       GTK_TEXT(textwindow)->vadj->value);
  if ( amount + text_length > maxtext ){
    gtk_text_set_point (GTK_TEXT(textwindow),0);
    gtk_text_forward_delete (GTK_TEXT(textwindow), 
			     (text_length+amount) - maxtext );
    gtk_text_set_point (GTK_TEXT(textwindow),
			gtk_text_get_length(GTK_TEXT(textwindow)));
  }
  
  /*
    Now append the data we just read...
  */
  
  output_text(textwindow, buffer, amount);
  
  /* And now unfreeze again */
  gtk_text_thaw(GTK_TEXT(textwindow));
  
  /* Now put scrollbar on same position relative to end */
  gtk_adjustment_set_value(GTK_TEXT(textwindow)->vadj,
			   GTK_TEXT(textwindow)->vadj->upper - 
			   old_value);
}
void replace_text(char* buffer, long amount){
  /* Freeze textwindow: */
  gtk_text_freeze (GTK_TEXT(textwindow));
  /* Memorize old position */
  gfloat old_value = ( GTK_TEXT(textwindow)->vadj->upper - 
		       GTK_TEXT(textwindow)->vadj->value);
  /* Clear window: */
  gtk_text_set_point (GTK_TEXT(textwindow),0);
  gtk_text_forward_delete (GTK_TEXT(textwindow), 
			   gtk_text_get_length(GTK_TEXT(textwindow)));
  
  if ( amount > 0 ) output_text(textwindow, buffer, amount);
  
  /* And now unfreeze again */
  gtk_text_thaw(GTK_TEXT(textwindow));
  
  /* Now put scrollbar on same position relative to end */
  gtk_adjustment_set_value(GTK_TEXT(textwindow)->vadj,
			   GTK_TEXT(textwindow)->vadj->upper - 
			   old_value);
}
/*}}}*/
/*{{{  error subroutines for watchdog  */
/*
  This routine handles file access errors during watchdog startup
  and interrupts.

  The parameter is the number of the entry that caused the problems
 
  - If the number equals te active logfile, deactivate is called
  - the watchdog interrupt for this entry is being removed
  - the buffers are freed
  and a file error dialog box is being popped up
*/
void 
watchdog_file_error(gint i)
{
  entry[i]->got = -1;      
  if ( i == active ) deactivate();
  if ( entry[i]->wd_tag != 0 ) gtk_timeout_remove(entry[i]->wd_tag);
  entry[i]->wd_tag = 0;
  if ( entry[i]->buffer != NULL ) delete entry[i]->buffer;
  entry[i]->buffer = NULL;
  if ( entry[i]->rbuffer != NULL ) delete entry[i]->rbuffer;
  entry[i]->rbuffer = NULL;
  entry[i]->length = 0;

  file_error(entry[i]->filename);
}
/*}}}*/
