#!/usr/bin/perl

# these are turned off for distribution
# use strict;
# use warnings;
$|++;

use locale;
use POSIX qw/locale_h strftime/;
use File::Basename;
use File::Find::Rule;
use File::Path qw/mkpath/;
use Date::Calc qw/Delta_Days/;
use Locale::gettext;
textdomain("clamtk");
setlocale( LC_MESSAGES, "" );

use Gtk2;
use Gtk2::SimpleList;
use Glib qw/TRUE FALSE/;
Gtk2->init;

use constant COLUMN_FILE   => 0;
use constant COLUMN_TYPE   => 1;
use constant COLUMN_SIZE   => 2;
use constant COLUMN_STATUS => 3;
use constant NUM_COLUMNS   => 4;

my $VERSION   = '2.32';
my $virus_log = '';
my $ren       = "None";
my ( $detect_broken, $save_log, $hidden, $showall ) = (0) x 4;
my $follow_symlinks = 0;
my @virus;
my $count       = 0;   # keeps track of the viruses (e.g., $virus[$count]{full})
my $num_scanned = 0;   # counts number of files scanned
my $num_so_far  = 0;   # number of viruses
my %found;
my $start_time;
my ( $q_state, $l_state );
my ( @files,   @quoted );
my $directory = $ENV{HOME} || glob "~";
my $c_dir     = "$directory/.clamtk";
my $v_dir     = "$c_dir/viruses";
my $l_dir     = "$c_dir/history";
my ( $a_tooltip, $authenticate );
my %dirs_scanned;
my $scan_pid = '';
my $SCAN;
my $toolbar      = '';
my $hide_toolbar = 1;
my $top_label;
my $gtk2_version;

if ( !Gtk2->check_version( 2, 6, 0 ) ) {
    $gtk2_version = 1;
}
else {
    $gtk2_version = 0;
}

# maintenance subroutine variables below
my ( $new_slist, $new_hlist );
my @q_files = ();
my $q_label;
my @h_files = ();
my $h_label;

if ( $> == 0 ) {
    $authenticate = 'gtk-yes';
    $a_tooltip    = gettext("Check for signature updates");
}
else {
    $authenticate = 'gtk-no';
    $a_tooltip    = gettext("You must be root to install updates");
}

my $command = '';

# important clamav paths
my $FRESHPATH =
    ( -e '/usr/bin/freshclam' )       ? '/usr/bin/freshclam'
  : ( -e '/usr/local/bin/freshclam' ) ? '/usr/local/bin/freshclam'
  :                                     die "freshclam not found!\n";
my $SIGPATH =
    ( -e '/usr/bin/sigtool' )       ? '/usr/bin/sigtool'
  : ( -e '/usr/local/bin/sigtool' ) ? '/usr/local/bin/sigtool'
  :                                   die "sigtool not found!\n";
my $CLAMPATH =
    ( -e '/usr/bin/clamscan' )       ? '/usr/bin/clamscan'
  : ( -e '/usr/local/bin/clamscan' ) ? '/usr/local/bin/clamscan'
  :                                    die "clamscan not found!\n";
$command .= $CLAMPATH;

# the $INFO_*'s are for parsing ClamAV 0.90 information
my $INFO_DAILY = '';
my $INFO_MAIN  = '';
my $INFO_DATE  = '';
my $INFO_PATH =
  ( -e '/var/lib/clamav/daily.inc/daily.info' )
  ? '/var/lib/clamav/daily.inc/daily.info'
  : ( -e '/var/clamav/daily.inc/daily.info' )
  ? '/var/clamav/daily.inc/daily.info'
  : ( -e '/usr/share/clamav/daily.inc/daily.info' )
  ? '/usr/share/clamav/daily.inc/daily.info'
  : ( -e '/usr/local/share/clamav/daily.inc/daily.info' )
  ? '/usr/local/share/clamav/daily.inc/daily.info'
  : '';

my $INFO_MAIN =
  ( -e '/var/lib/clamav/main.inc/main.info' )
  ? '/var/lib/clamav/main.inc/main.info'
  : ( -e '/var/clamav/main.inc/main.info' ) ? '/var/clamav/main.inc/main.info'
  : ( -e '/usr/share/clamav/main.inc/main.info' )
  ? '/usr/share/clamav/main.inc/main.info'
  : ( -e '/usr/local/share/clamav/main.inc/main.info' )
  ? '/usr/local/share/clamav/main.inc/main.info'
  : '';

my $RARPATH =
    ( -e '/usr/bin/unrar' )       ? '/usr/bin/unrar'
  : ( -e '/usr/local/bin/unrar' ) ? '/usr/local/bin/unrar'
  :                                 '';

$command .= " --unrar=$RARPATH" if ($RARPATH);
my $ZIPPATH =
    ( -e '/usr/bin/unzip' )       ? '/usr/bin/unzip'
  : ( -e '/usr/local/bin/unzip' ) ? '/usr/local/bin/unzip'
  :                                 '';

$command .= " --unzip=$ZIPPATH" if ($ZIPPATH);
my $FILE =
    ( -e '/usr/bin/file' )       ? '/usr/bin/file'
  : ( -e '/usr/local/bin/file' ) ? '/usr/local/bin/file'
  :                                die gettext("\"file\" command not found!\n");

# virus definitions path
my $DEFPATH =
    ( -e '/var/lib/clamav/daily.cvd' )   ? '/var/lib/clamav/daily.cvd'
  : ( -e '/var/clamav/daily.cvd' )       ? '/var/clamav/daily.cvd'
  : ( -e '/usr/share/clamav/daily.cvd' ) ? '/usr/share/clamav/daily.cvd'
  : ( -e '/usr/local/share/clamav/daily.cvd' )
  ? '/usr/local/share/clamav/daily.cvd'
  : ( -e '/var/lib/clamav/daily.cvd.rpmnew' )
  ? '/var/lib/clamav/daily.cvd.rpmnew'
  : ( -e '/var/clamav/daily.cvd.rpmnew' ) ? '/var/clamav/daily.cvd.rpmnew'
  : ( -e '/usr/share/clamav/daily.cvd.rpmnew' )
  ? '/usr/share/clamav/daily.cvd.rpmnew'
  : ( -e '/usr/local/share/clamav/daily.cvd.rpmnew' )
  ? '/usr/local/share/clamav/daily.cvd.rpmnew'
  : '';
my $MAINPATH =
    ( -e '/var/lib/clamav/main.cvd' )   ? '/var/lib/clamav/main.cvd'
  : ( -e '/var/clamav/main.cvd' )       ? '/var/clamav/main.cvd'
  : ( -e '/usr/share/clamav/main.cvd' ) ? '/usr/share/clamav/main.cvd'
  : ( -e '/usr/local/share/clamav/main.cvd' )
  ? '/usr/local/share/clamav/main.cvd'
  : ( -e '/var/lib/clamav/main.cvd.rpmnew' ) ? '/var/lib/clamav/main.cvd.rpmnew'
  : ( -e '/var/clamav/main.cvd.rpmnew' )     ? '/var/clamav/main.cvd.rpmnew'
  : ( -e '/usr/share/clamav/main.cvd.rpmnew' )
  ? '/usr/share/clamav/main.cvd.rpmnew'
  : ( -e '/usr/local/share/clamav/main.cvd.rpmnew' )
  ? '/usr/local/share/clamav/main.cvd.rpmnew'
  : '';

$command .= " --no-summary --block-encrypted ";

if ( !-d $v_dir ) {
    eval { mkpath( $v_dir, 0, oct(777) ); };
    if ($@) {
        $q_state = "disabled";
    }
    else {
        $q_state = "normal";
    }
}
else {
    $q_state = "normal";
}

if ( !-d $l_dir ) {
    eval { mkpath( $l_dir, 0, oct(777) ); };
    if ($@) {
        $l_state = "disabled";
    }
    else {
        $l_state = "normal";
    }
}
else {
    $l_state = "normal";
}

my $window = Gtk2::Window->new();
$window->signal_connect( destroy => sub { Gtk2->main_quit; } );
$window->set_default_size( 630, 325 );
$window->set_title("ClamTk Virus Scanner");
$window->set_border_width(5);
$window->set_position('center-always');

# I'll leave this here for now (i.e., clam.xpm AND clamtk.png) since
# most packagers won't notice that it's been changed.
if ( -e "/usr/share/pixmaps/clamtk.png" ) {
    $window->set_default_icon_from_file("/usr/share/pixmaps/clamtk.png");
}
elsif ( -e "/usr/share/pixmaps/clam.xpm" ) {
    $window->set_default_icon_from_file("/usr/share/pixmaps/clam.xpm");
}

my $main_vbox = Gtk2::VBox->new( FALSE, 0 );
$window->add($main_vbox);
$main_vbox->show;

my @entries = (
    [ "FileMenu",       undef, gettext("_File") ],
    [ "ViewMenu",       undef, gettext("_View") ],
    [ "OptionsMenu",    undef, gettext("_Options") ],
    [ "ActionsMenu",    undef, gettext("_Actions") ],
    [ "QuarantineMenu", undef, gettext("_Quarantine") ],
    [ "HelpMenu",       undef, gettext("_Help") ],

    [
        "Scan_File",             'gtk-new',
        gettext("Scan a _File"), "<control>F",
        gettext("Scan a file"), sub { getfile('file') }
    ],
    [
        "Scan_Directory",             'gtk-directory',
        gettext("Scan a _Directory"), "<control>D",
        gettext("Scan a Directory"), sub { getfile('dir') }
    ],
    [
        "Recursive_Scan",           'gtk-directory',
        gettext("_Recursive Scan"), "<control>R",
        gettext("Recursively scan a directory"), sub { getfile('recur') }
    ],
    [
        "Exit",           'gtk-quit',
        gettext("E_xit"), "<control>X",
        gettext("Quit this program"), sub { Gtk2->main_quit }
    ],
    [
        "Status",                                      'gtk-edit',
        gettext("_Status"),                            "<control>S",
        gettext("See how many files are quarantined"), \&quarantine_check
    ],
    [
        "Maintenance",                                    'gtk-preferences',
        gettext("_Maintenance"),                          "<control>M",
        gettext("View files that have been quarantined"), \&maintenance,
    ],
    [
        "Empty",
        'gtk-delete',
        gettext("_Empty Quarantine Folder"),
        "<control>E",
        gettext("Delete all files that have been quarantined"),
        \&del_quarantined
    ],
    [
        "SysInfo",                               'gtk-properties',
        gettext("System _Information"),          "<control>I",
        gettext("Status of Antivirus programs"), \&sys_info
    ],
    [
        "UpdateSig",                             $authenticate,
        gettext("_Update Signatures"),           "<control>U",
        gettext("Update your virus signatures"), \&update
    ],
    [
        "About",                          'gtk-about',
        gettext("_About"),                "<control>A",
        gettext("About this program..."), \&about
    ],
);

my @view_entries = (
    [
        "HideToolbar",
        undef,
        gettext("Show/Hide _Toolbar"),
        "<control>T",
        gettext("Hide Toolbar"),
        sub {
            $hide_toolbar ? $toolbar->hide : $toolbar->show;
            $hide_toolbar ^= 1;
        },
        FALSE
    ],
    [
        "ManageHistories",
        undef,
        gettext("Manage _Histories"),
        "<control>H",
        gettext("Select Histories to Delete"),
        sub { history('delete') },
        FALSE
    ],
    [
        "ClearOutput",
        'gtk-clear',
        gettext("Clear _Output"),
        "<control>O",
        gettext("Clear the Display"),
        \&clear_output,
        FALSE
    ],
);

my @option_entries = (
    [
        "SaveToLog", undef, gettext("Save To Log"), "F1",
        gettext("Save a record of this scan"),
        \&default_check, FALSE
    ],
    [
        "ScanHidden",                      undef,
        gettext("Scan Hidden Files (.*)"), "F2",
        gettext("Scan the hidden files"),  \&default_check,
        FALSE
    ],
    [
        "DisplayAll", undef, gettext("Display All Files"),
        "F3", gettext("Display all files scanned"),
        \&default_check, FALSE
    ],
    [
        "DetectBroken",                       undef,
        gettext("Detect Broken Executables"), "F4",
        gettext("Detect Broken Executables"), \&default_check,
        FALSE
    ],
    [
        "FollowLinks",                    undef,
        gettext("Follow Symbolic Links"), "F5",
        gettext("Follow Symbolic Links"), \&default_check,
        FALSE
    ],
);

use constant NO_ACTION => 0;
use constant Q_ACTION  => 1;
use constant D_ACTION  => 2;

my @action_entries = (
    [
        "NoAction",                                  undef,
        gettext("No Action"),                        undef,
        gettext("Take no action on infected files"), NO_ACTION
    ],
    [
        "Quarantine",                                       undef,
        gettext("Quarantine Infected Files"),               undef,
        gettext("Automatically quarantine infected files"), Q_ACTION
    ],
    [
        "Delete",                                                  undef,
        gettext("Delete Infected Files"),                          undef,
        gettext("Automatically delete infected files (CAUTION!)"), D_ACTION
    ],
);

my $ui_info = "<ui>
	<menubar name='MenuBar'>
	 <menu action='FileMenu'>
	  <menuitem action='Scan_File'/>
	  <menuitem action='Scan_Directory'/>
	  <menuitem action='Recursive_Scan'/>
	  <separator/>
	  <menuitem action='Exit'/>
	 </menu>
	  <menu action='ViewMenu'>
	  <menuitem action='HideToolbar'/>
	  <menuitem action='ManageHistories'/>
	  <menuitem action='ClearOutput'/>
	 </menu>
	 <menu action='OptionsMenu'>
	  <menuitem action='SaveToLog'/>
	  <menuitem action='ScanHidden'/>
	  <menuitem action='DisplayAll'/>
	  <menuitem action='DetectBroken'/>
	  <menuitem action='FollowLinks'/>
	 </menu>
	 <menu action='ActionsMenu'>
	  <menuitem action='NoAction'/>
	  <menuitem action='Quarantine'/>
	  <menuitem action='Delete'/>
	 </menu>
	 <menu action='QuarantineMenu'>
	  <menuitem action='Status'/>
	  <menuitem action='Maintenance'/>
	  <menuitem action='Empty'/>
	 </menu>
	 <menu action='HelpMenu'>
	  <menuitem action='SysInfo'/>
	  <menuitem action='UpdateSig'/>
	  <menuitem action='About'/>
	 </menu>
	</menubar>
</ui>";

my $actions = Gtk2::ActionGroup->new("Actions");
$actions->add_actions( \@entries,      undef );
$actions->add_actions( \@view_entries, undef );
$actions->add_toggle_actions( \@option_entries, undef );
$actions->add_radio_actions( \@action_entries, NO_ACTION, \&action_actions );

my $ui = Gtk2::UIManager->new;
$ui->insert_action_group( $actions, 0 );

$window->add_accel_group( $ui->get_accel_group );
$ui->add_ui_from_string($ui_info);
$main_vbox->pack_start( $ui->get_widget("/MenuBar"), FALSE, FALSE, 0 );

# These are the GUI's for scanning and exiting
$toolbar = Gtk2::Toolbar->new;
$toolbar->set_style('icons');
my $tt = Gtk2::Tooltips->new();

# We can set the size of the toolbar stuff here, but
# for now, I like the default
# $toolbar->set_icon_size('menu');
my $scan_file = Gtk2::ToolButton->new_from_stock('gtk-new');
$scan_file->signal_connect( 'clicked' => sub { getfile('file') } );
$scan_file->set_tooltip( $tt, gettext("Scan a file"), "" );
$toolbar->insert( $scan_file, -1 );

my $scan_dir = Gtk2::ToolButton->new_from_stock('gtk-directory');
$scan_dir->signal_connect( 'clicked' => sub { getfile('dir') } );
$scan_dir->set_tooltip( $tt, gettext("Scan a directory"), "" );
$toolbar->insert( $scan_dir, -1 );

my $scan_home = Gtk2::ToolButton->new_from_stock('gtk-home');
$scan_home->signal_connect( 'clicked' => sub { getfile('home') } );
$scan_home->set_tooltip( $tt, gettext("Scan your home directory"), "" );
$toolbar->insert( $scan_home, -1 );

$toolbar->insert( Gtk2::SeparatorToolItem->new, -1 );

my $scan_clear = Gtk2::ToolButton->new_from_stock('gtk-clear');
$scan_clear->signal_connect( 'clicked' => sub { clear_output(); } );
$scan_clear->set_tooltip( $tt, gettext("Clear the display"), "" );
$toolbar->insert( $scan_clear, -1 );

my $scan_stop = Gtk2::ToolButton->new_from_stock('gtk-stop');
$scan_stop->signal_connect(
    'clicked' => sub {
        @quoted = ();
        kill 15, $scan_pid if ($scan_pid);
        $top_label->set_text( gettext("Please wait...") );
        waitpid( $scan_pid, 0 );

        # this close returns the stupid readline() error.
        # not sure how to fix it yet, besides commenting
        # out 'use warnings' :) it's the only way to immediately
        # stop the $SCAN so far...
        close($SCAN);    # or warn "Unable to close scanner! $!\n";
        $top_label->set_text("");
    }
);
$scan_stop->set_tooltip( $tt, gettext("Stop scanning now"), "" );
$toolbar->insert( $scan_stop, -1 );

my $scan_exit = Gtk2::ToolButton->new_from_stock('gtk-quit');
$scan_exit->signal_connect( 'clicked' => sub { Gtk2->main_quit } );
$scan_exit->set_tooltip( $tt, gettext("Quit"), "" );
$toolbar->insert( $scan_exit, -1 );

my $danger_ui;
$main_vbox->pack_start( $toolbar, FALSE, FALSE, 1 );

# This is the top label where scanning messages are displayed
$top_label = Gtk2::Label->new();
$main_vbox->pack_start( $top_label, FALSE, FALSE, 3 );
$top_label->set_justify('center');

# This scrolled window holds the slist
my $scrolled_win = Gtk2::ScrolledWindow->new;
$scrolled_win->set_shadow_type('etched_in');
$scrolled_win->set_policy( 'automatic', 'automatic' );
$main_vbox->pack_start( $scrolled_win, TRUE, TRUE, 0 );

my $slist = create_list();
$scrolled_win->add($slist);
$scrolled_win->grab_focus();
$slist->get_selection->set_mode('single');
$slist->set_rules_hint(TRUE);
$slist->set_headers_clickable(TRUE);

# can't be reorderable - messes up the \&row_clicked function
$slist->set_reorderable(FALSE);
map { $_->set_fixed_width(146) } $slist->get_columns;
map { $_->set_sizing('fixed') } $slist->get_columns;

if ($gtk2_version) {
    $slist->set(
        hover_selection => TRUE,
        hover_expand    => TRUE
    );
}

my $tooltips = Gtk2::Tooltips->new;
$tooltips->set_tip( $slist,
    gettext("Select a file and right-click for options...") );
$tooltips->disable;

# this anonymous sub handles the row_clicked feature
$slist->get_selection->signal_connect(
    changed => sub {
        my @sel   = $slist->get_selected_indices;
        my $deref = $sel[0];
        defined $deref or return;
        $top_label->set_markup(
            sprintf gettext("<b>File:</b> %s    <b>Status:</b> %s"),
            $virus[$deref]{full},
            $virus[$deref]{status}
        );
    }
);

# below: the right-click functionality. also uses 'sub confirm'.
$slist->signal_connect(
    button_press_event => sub {
        my ( $widget, $event ) = @_;
        return FALSE unless $event->button == 3;
        my @sel   = $slist->get_selected_indices;
        my $deref = $sel[0];
        defined $deref or return;

        my $menu = Gtk2::Menu->new();
        my $quar_pop =
          Gtk2::ImageMenuItem->new( gettext('Quarantine this file') );
        my $quar_image = Gtk2::Image->new_from_stock( 'gtk-refresh', 'menu' );
        $quar_pop->set_image($quar_image);
        $quar_pop->signal_connect(
            activate => sub { main_confirm( $deref, "q" ) } );
        $quar_pop->show();
        $menu->append($quar_pop)
          unless dirname( $virus[$deref]{full} ) =~ /^\/(proc|sys|dev)/;

        my $delete_pop =
          Gtk2::ImageMenuItem->new( gettext('Delete this file') );
        my $del_image = Gtk2::Image->new_from_stock( 'gtk-delete', 'menu' );
        $delete_pop->set_image($del_image);
        $delete_pop->signal_connect(
            activate => sub { main_confirm( $deref, "d" ) } );
        $delete_pop->show();
        $menu->append($delete_pop)
          unless dirname( $virus[$deref]{full} ) =~ /^\/(proc|sys|dev)/;

        my $cancel_pop =
          Gtk2::ImageMenuItem->new_from_stock( 'gtk-cancel', undef );
        $cancel_pop->signal_connect( activate => sub { return; } );
        $cancel_pop->show();
        $menu->append($cancel_pop);
        $menu->popup( undef, undef, undef, undef, $event->button,
            $event->time );
        return TRUE;
    }
);

# bottom_box keeps track of # scanned, # of viruses, and time
my $bottom_box = Gtk2::HBox->new( FALSE, 0 );
$main_vbox->pack_start( $bottom_box, FALSE, FALSE, 2 );

my $left_status = Gtk2::Label->new( gettext("Files Scanned: ") );
$left_status->set_alignment( 0.0, 0.5 );
$bottom_box->pack_start( $left_status, TRUE, TRUE, 4 );

my $mid_status = Gtk2::Label->new( gettext("Viruses Found: ") );
$mid_status->set_alignment( 0.0, 0.5 );
$bottom_box->pack_start( $mid_status, TRUE, TRUE, 0 );

my $right_status = Gtk2::Label->new( gettext("Ready") );
$right_status->set_alignment( 0.0, 0.5 );
$bottom_box->pack_start( $right_status, TRUE, TRUE, 0 );

$window->show_all();

# This is to combine any and all startup checks
startup_prefs();

if (@ARGV) {
    my $input = $ARGV[0];

    # safety net
    unless ( -d $input || -f $input ) {
        die sprintf gettext("Unable to scan %s\n"), $input;
    }

    # doesn't like the end slash ('/')
    $input =~ s/\/$//;

    # it's either a full path...
    if ( $input =~ /^\// ) {
        getfile( 'cmd-scan', $input );
    }

    # or it's not.
    else {
        my $top = glob "~";
        $top .= "/$input";
        getfile( 'cmd-scan', $top );
    }
}

Gtk2->main;

sub about {
    my $about_win = Gtk2::Window->new;
    $about_win->signal_connect( destroy => sub { $about_win->destroy } );
    $about_win->set_default_size( 285, 185 );
    $about_win->set_title("About ClamTk");
    $about_win->set_position('center-always');

    my $tbox = Gtk2::VBox->new;
    $about_win->add($tbox);
    my $top_label = Gtk2::Label->new;
    $tbox->pack_start( $top_label, FALSE, FALSE, 0 );
    $top_label->set_markup(
        "<span size = \"x-large\" weight = \"heavy\">\nClamTk $VERSION\n</span>"
    );

    my $mid_label =
      Gtk2::Label->new("ClamTk is a graphical frontend\nfor Clam Antivirus.");
    $mid_label->set_justify('center');
    $tbox->pack_start( $mid_label, FALSE, FALSE, 0 );

    my $copyright_label = Gtk2::Label->new;
    $tbox->pack_start( $copyright_label, FALSE, FALSE, 0 );
    $copyright_label->set_markup(
        "<span size = \"small\">\n(C) 2004-2007 Dave M.</span>");
    my $url_label = Gtk2::Label->new('http://freshmeat.net/projects/clamtk');
    $tbox->pack_start( $url_label, FALSE, FALSE, 0 );

    my $hrow = Gtk2::HButtonBox->new;
    $tbox->pack_start( $hrow, FALSE, FALSE, 0 );
    my $credits_button = Gtk2::Button->new_from_stock('Credits');
    $hrow->add($credits_button);
    $credits_button->signal_connect( 'clicked' => \&credits_window );
    my $close_button = Gtk2::Button->new_from_stock('gtk-close');
    $hrow->add($close_button);
    $close_button->grab_focus;
    $close_button->signal_connect( 'clicked' => sub { $about_win->destroy; } );
    $about_win->show_all;
}

sub credits_window {
    my $l_win = Gtk2::Window->new;
    $l_win->signal_connect( destroy => sub { $l_win->destroy } );
    $l_win->set_title('Credits');
    $l_win->set_position('center-always');
    $l_win->set_border_width(10);

    my $l_box = Gtk2::VBox->new;
    $l_win->add($l_box);

    my $notebook = Gtk2::Notebook->new;
    $l_box->pack_start( $notebook, FALSE, FALSE, 0 );
    $notebook->set_show_border(FALSE);

    for ( "Written by", "Community" ) {

        my $l_scroll = Gtk2::ScrolledWindow->new( undef, undef );
        $l_scroll->set_shadow_type('etched-out');
        $l_scroll->set_policy( 'automatic', 'automatic' );
        $l_scroll->set_size_request( 285, 185 );

        my $l_view = Gtk2::TextView->new();
        $l_view->set( editable => FALSE );
        $l_view->set_cursor_visible(FALSE);
        $l_view->set_indent(5);
        my $l_content =
          $_ eq "Written by"
          ? 'Dave M. <dave.nerd@gmail.com>'
          : "Translations:\n"
          . "Karel Hudan, Czech (cs_CZ)\n"
          . "Jimmy Christensen, Danish (da_DK)\n"
          . "Ronny Steiner, German (de_DE)\n"
          . "Mariano Rojo, Spanish (es_ES)\n"
          . "Alain Bernard, French (fr_FR)\n"
          . "Edoardo Tosca, Italian (it_IT)\n"
          . "Tobia Fasciati, Italian (it_IT)\n"
          . "Robert Tomasik, Polish (pl_PL)\n"
          . "Bruno Diniz, Portugese (pt_BR)\n"
          . "Vitaly Lipatov, Russian (ru_RU)\n"
          . "Tao Wei, Chinese (zh_CN)\n"
          . "\nWebsite:\n"
          . "Edoardo Tosca\n"
          . "\nIcon Design:\n"
          . "Gerald Ganson\n";
        my $l_buffer = $l_view->get_buffer();
        $l_buffer->set_text($l_content);

        $l_scroll->add($l_view);
        $notebook->append_page( $l_scroll, $_ );
    }

    my $lh_row = Gtk2::HButtonBox->new;
    $l_box->pack_start( $lh_row, FALSE, FALSE, 0 );

    my $l_close = Gtk2::Button->new_from_stock('gtk-close');
    $lh_row->add($l_close);
    $l_close->signal_connect( 'clicked' => sub { $l_win->destroy; } );
    $l_close->grab_focus();

    $l_win->show_all;
}

sub create_list {
    my $list = Gtk2::SimpleList->new(
        gettext('File')   => 'markup',
        gettext('Type')   => 'markup',
        gettext('Size')   => 'markup',
        gettext('Status') => 'markup',
    );

    return $list;
}

sub getfile {
    my ($option) = shift;
    my $cmd_input = shift;

    # $option will be either "home", "file", "dir", "recur", or "cmd-scan"
    Gtk2->main_iteration while ( Gtk2->events_pending );
    clear_output();
    chdir($directory) or chdir("/tmp");

    my ( $filename, $dir, $dialog );
    if ( $option eq "home" ) {
        $top_label->set_text( gettext("Please wait...") );
        my $rule = File::Find::Rule->new;
        $rule->file;
        $rule->readable;
        $rule->maxdepth(1);
        $rule->extras( { follow => 1 } ) if ($follow_symlinks);
        Gtk2->main_iteration while ( Gtk2->events_pending );
        @files = $rule->in($directory);
    }
    elsif ( $option eq "file" ) {
        $dialog = Gtk2::FileChooserDialog->new(
            gettext('Select Files'), undef, 'open',
            'gtk-cancel' => 'cancel',
            'gtk-ok'     => 'ok',
        );
        $dialog->set_select_multiple(TRUE);

        if ( "ok" eq $dialog->run ) {
            $top_label->set_text( gettext("Please wait...") );
            $window->queue_draw;
            Gtk2->main_iteration while ( Gtk2->events_pending );
            @files = $dialog->get_filenames;
            $window->queue_draw;
            $dialog->destroy;
            $window->queue_draw;
            Gtk2->main_iteration while ( Gtk2->events_pending );
        }
        else {
            $dialog->destroy;
            return;
        }
    }
    elsif ( $option eq "dir" ) {
        $dialog = Gtk2::FileChooserDialog->new(
            gettext('Select a Directory (directory scan)'), undef,
            'select-folder',
            'gtk-cancel' => 'cancel',
            'gtk-ok'     => 'ok',
        );

        if ( "ok" eq $dialog->run ) {
            $dir = $dialog->get_filename;
            $top_label->set_text( gettext("Please wait...") );
            $window->queue_draw;
            $dialog->destroy;
            $window->queue_draw;
            $dir ||= $directory;
            my $rule = File::Find::Rule->new;
            $rule->file;
            $rule->readable;
            $rule->maxdepth(1);
            $rule->extras( { follow => 1 } ) if ($follow_symlinks);
            Gtk2->main_iteration while ( Gtk2->events_pending );
            @files = $rule->in($dir);
        }
        else {
            $dialog->destroy;
            return;
        }
    }
    elsif ( $option eq "recur" ) {
        $dialog = Gtk2::FileChooserDialog->new(
            gettext('Select a Directory (recursive scan)'), undef,
            'select-folder',
            'gtk-cancel' => 'cancel',
            'gtk-ok'     => 'ok'
        );

        if ( "ok" eq $dialog->run ) {
            $dir = $dialog->get_filename;
            $top_label->set_text( gettext("Please wait...") );
            $window->queue_draw;
            $dialog->destroy;
            $window->queue_draw;
            $dir ||= $directory;
            my $rule = File::Find::Rule->new;
            $rule->file;
            $rule->readable;
            $rule->extras( { follow => 1 } ) if ($follow_symlinks);
            @files = $rule->in($dir);
        }
        else {
            $dialog->destroy;
            return;
        }
    }
    elsif ( $option eq 'cmd-scan' ) {
        if ( -d $cmd_input ) {
            $top_label->set_text( gettext("Please wait...") );
            $window->queue_draw;
            my $rule = File::Find::Rule->new;
            $rule->file;
            $rule->readable;
            $rule->maxdepth(1);

            # normally only allowed if selected, but this is cmd_line,
            # so we'll automatically include it
            $rule->extras( { follow => 1 } );
            Gtk2->main_iteration while ( Gtk2->events_pending );
            @files = $rule->in($cmd_input);
        }
        else {
            @files = $cmd_input;
        }
    }
    else {
        die gettext("Shouldn't reach this."), "\n";
    }

    # start the timer - replaces the "Ready"
    $start_time = time;
    $right_status->set_text( gettext("Elapsed time: ") );

    if ( $option eq 'file' ) {
        scan(@files);
    }
    else {
        if ( $hidden == 0 ) {
            @files = grep { basename($_) !~ /^\./ } @files;
        }
        my @new;
        my @large;
        foreach my $foo (@files) {
            if ( -s $foo >= 20_000_000 ) {
                push( @large, $foo );
            }
            else {
                push( @new, $foo );
            }
        }
        if (@large) {
            foreach my $too_big (@large) {
                $top_label->set_text( sprintf gettext("Scanning %s..."),
                    $too_big );
                timer();
                $num_scanned++;
                $virus[$count]{full}   = $too_big;
                $virus[$count]{base}   = basename($too_big);
                $virus[$count]{status} = gettext("Not scanned (size)");
                $virus[$count]{type}   = type($too_big);
                $virus[$count]{size}   = size($too_big);
                display();
                next;
            }
        }
        if ( scalar(@new) ) {
            scan(@new);
        }
    }
    clean_up();
}

sub scan {
    my @get = @_;
    @quoted = map { quotemeta($_) } @get;
    timer();

    my $pid = open( $SCAN, "$command @quoted |" );
    defined($pid) or die gettext("couldn't fork: "), "$!\n";
    my $scan_count = 0;
    $top_label->set_text( sprintf gettext("Scanning %s..."),
        $get[$scan_count] );
    $scan_pid = $pid;    # this is for the 'stop button'
    while (<$SCAN>) {
        Gtk2->main_iteration while Gtk2->events_pending;
        my ( $file, $status ) = split /:/;

        chomp($file)   if ( defined $file );
        chomp($status) if ( defined $status );
        next unless ( -e $file && $status );
        next if ( $status =~ /module failure/ );

        $dirs_scanned{ dirname($file) } = 1
          unless ( dirname($file) =~ /\/tmp\/clamav/
            || dirname($file) eq "." );

        $virus[$count]{base} = basename($file);

        $status =~ s/\s+FOUND$//;

        $virus[$count]{type} = type($file);
        $virus[$count]{full} = $file;
        $virus[$count]{size} = size($file);

        # ignore files in archives - we just want the end-result.
        # we still allow it to scan; clamav will show the result
        # this method doesn't count it, though...
        next if ( $virus[$count]{full} =~ /\/tmp\/clamav/ );
        $num_scanned++;

        $virus[$count]{status} = $status;

        $left_status->set_text( sprintf gettext("Files Scanned: %d"),
            $num_scanned );
        timer();

        # clean_words mean no viruses... haven't seen any others than this
        my $clean_words = join( '|',
            "OK",            "Zip module failure", "RAR module failure",
            "Encrypted.RAR", "Encrypted.Zip",      "Empty file",
            "Excluded",      "Input/Output error" );

        if ( $status !~ /$clean_words/ ) {    # a virus
            $found{ $virus[$count]{full} } = $virus[$count]{status};
            my $current_status = $virus[$count]{status};
            if ( $ren eq "Quarantine" ) {
                if (    dirname( $virus[$count]{full} ) !~ /\/tmp\/clamav/
                    and dirname( $virus[$count]{full} ) !~ /^\/(proc|sys|dev)/ )
                {
                    move_to_quarantine($count);
                    $virus[$count]{status} =
                      "$current_status" . gettext("(Quarantined)");
                }
            }
            elsif ( $ren eq "Delete" ) {

            # Can't use the "--remove" option with clamscan; the way this scans,
            # the file is already deleted before it's (sub) displayed...
                if (    dirname( $virus[$count]{full} ) !~ /\/tmp\/clamav/
                    and dirname( $virus[$count]{full} ) !~ /^\/(proc|sys|dev)/ )
                {
                    my $current_status = $virus[$count]{status};
                    unlink( $virus[$count]{full} );
                    $virus[$count]{status} =
                      "$current_status" . gettext("Deleted)");
                }
            }
        }

        $num_so_far = keys %found;
        $left_status->set_text( sprintf gettext("Files Scanned: %d"),
            $num_scanned );
        if ( $num_so_far > 0 ) {
            $mid_status->set_markup(
                sprintf gettext("<b>Viruses Found: %d</b>"), $num_so_far );
        }
        else {
            $mid_status->set_text( sprintf gettext("Viruses Found: %d"),
                $num_so_far );
        }
        display();

        # resize hack below
        my ( $w, $h ) = $window->get_size;
        unless ( $w == 630 && $h == 325 ) {
            $window->resize( 630, 325 );
        }
        $scan_count++;
        if ( defined( $quoted[$scan_count] ) ) {
            Gtk2->main_iteration while ( Gtk2->events_pending );
            $top_label->set_text(
                sprintf gettext("Scanning %s..."),
                basename( $get[$scan_count] )
            );
            Gtk2->main_iteration while ( Gtk2->events_pending );
        }

    }
    close($SCAN);    # or warn "Unable to close scanner! $!\n";
}

sub display {
    timer();
    use encoding 'utf8';
    $virus[$count]{type}   =~ s/\s+$//;
    $virus[$count]{status} =~ s/\s+$//;
    $virus[$count]{status} =~ s/^\s//;
    if (
        (
               $virus[$count]{status} ne "OK"
            && $virus[$count]{status} ne "Empty file"
        )
        || $showall
      )
    {
        push @{ $slist->{data} },
          [
            $virus[$count]{base}, $virus[$count]{type},
            $virus[$count]{size}, $virus[$count]{status}
          ];
        $count++;
    }
    map { $_->set_fixed_width(146) } $slist->get_columns;
    map { $_->set_sizing('fixed') } $slist->get_columns;
    map { $_->set_resizable(TRUE) } $slist->get_columns;
    timer();
}

sub timer {
    Gtk2->main_iteration while ( Gtk2->events_pending );
    my $now     = time;
    my $seconds = $now - $start_time;
    my $s       = sprintf "%02d", ( $seconds % 60 );
    my $m       = sprintf "%02d", ( $seconds - $s ) / 60;
    $right_status->set_text( sprintf gettext("Elapsed time: %s"), "$m:$s" );
    $window->queue_draw;
}

sub clean_up {
    $count ||= 0;

    # highlight the quarantined or deleted files
    for ( 0 .. $#virus ) {
        if ( $virus[$_]{status} =~ /(\(Quarantined\)|\(Deleted\))/ ) {
            main_slist_delete($_);
        }
    }

    $tooltips->enable;

    my $db_total = num_of_sigs();
    my $REPORT;    # filehandle for histories log
    if ($save_log) {
        my ( $mon, $day, $year ) = split / /, strftime( '%b %d %Y', localtime );
        $virus_log = "$mon-$day-$year" . ".log";

        # sort the directories scanned for display
        my @sorted = sort { length $a <=> length $b } keys %dirs_scanned;
        if ( open $REPORT, ">>$l_dir/$virus_log" ) {
            print $REPORT "\nClamTk, v$VERSION\n", scalar localtime, "\n";
            print $REPORT sprintf gettext("ClamAV Signatures: %d\n"), $db_total;
            print $REPORT gettext("Directories Scanned:\n");
            for my $list (@sorted) {
                print $REPORT "$list\n";
            }
            printf $REPORT gettext(
                "\nFound %d possible %s (%d %s scanned).\n\n"), $num_so_far,
              $num_so_far == 1 ? gettext("virus") : gettext("viruses"),
              $num_scanned,
              $num_scanned == 1 ? gettext("file") : gettext("files");
        }
        else {
            $top_label->set_text(
                gettext("Could not write to logfile. Check permissions.") );
            $save_log = 0;
        }

    }
    $db_total =~ s/(\w+)\s+$/$1/;
    $top_label->set_text( sprintf gettext("Scanning complete (%d signatures)"),
        $db_total );
    $left_status->set_text( sprintf gettext("Files Scanned: %d"),
        $num_scanned );
    if ( $num_so_far == 0 ) {
        $mid_status->set_text( sprintf gettext("Viruses Found: %d"),
            $num_so_far );
    }
    $right_status->set_text( gettext("Ready") );
    $window->queue_draw;

    if ( $num_so_far == 0 ) {
        print $REPORT gettext("No viruses found.\n") if ($save_log);
    }
    else {
        if ($save_log) {
            while ( my ( $key, $value ) = each %found ) {
                if ( length($key) > 33 ) {
                    substr( $key, 33 ) = '...';
                }
                if ( length($value) > 33 ) {
                    $value = substr( $value, 33, "..." );
                    substr( $value, 33 ) = '...';
                }
                printf $REPORT "%-38s %38s\n", $key, $value;
            }
        }
    }
    if ($save_log) {
        print $REPORT "-" x 77, "\n";
        close($REPORT) if ( fileno($REPORT) );
    }

    # reset things
    $count        = 0;
    $num_scanned  = 0;
    $num_so_far   = 0;
    %found        = ();
    %dirs_scanned = ();
    @files        = ();
    @quoted       = ();
}

sub clear_output {
    return if ( scalar(@files) > 0 );
    @{ $slist->{data} } = ();
    $window->resize( 630, 325 );
    $window->queue_draw;
    $left_status->set_text( gettext("Files Scanned: ") );
    $mid_status->set_text( gettext("Viruses Found: ") );
    $right_status->set_text( gettext("Ready") );
    $top_label->set_text("");
    $tooltips->disable;
    map { $_->set_fixed_width(146) } $slist->get_columns;
    map { $_->set_sizing('fixed') } $slist->get_columns;
}

sub update {
    if ( $> != 0 ) {
        show_message_dialog( $window, 'info', 'close',
            gettext("You must be root to install updates.") );
        return;
    }

    $top_label->set_text( gettext("Please wait, checking for updates...") );
    Gtk2->main_iteration while Gtk2->events_pending;
    $main_vbox->queue_draw;
    $window->queue_draw;
    my @result;

    eval {
        local $SIG{ALRM} = sub { die "failed" };
        alarm 35;

        @result = `$FRESHPATH --stdout`;
        alarm 0;
    };
    if ( $@ && $@ eq "failed" ) {
        $top_label->set_text(
            gettext("Unable to retrieve updates. Try again later.") );
        return;
    }
    my $showthis;
    if ( !@result ) {
        $top_label->set_text(
            gettext("Unable to retrieve updates. Try again later.") );
    }
    else {
        foreach my $line (@result) {
            if ( $line =~ /Database updated .(\d+) signatures/ ) {
                $showthis =
                  sprintf gettext(
                    "Your virus signatures have been updated (%d signatures)."
                  ), $1;
                $top_label->set_text($showthis);
                last;
            }
        }
        $top_label->set_text( gettext("Your virus signatures are up-to-date.") )
          if ( !$showthis );
    }
    $window->queue_draw;
}

sub quarantine_check {
    if ( !-d $v_dir ) {
        show_message_dialog( $window, 'error', 'close',
            gettext("No virus directory available.") );
        return;
    }
    my @trash;
    unless ( opendir( DIR, $v_dir ) ) {
        show_message_dialog( $window, 'error', 'close',
            gettext("Unable to open the virus directory.") );
        return;
    }
    @trash = grep { -f "$v_dir/$_" } readdir(DIR);
    closedir(DIR);
    my $del = scalar(@trash);
    if ( !$del ) {
        show_message_dialog( $window, 'info', 'ok',
            gettext("No items currently quarantined.") );
    }
    else {
        my $notice = sprintf gettext("%d item(s) currently quarantined."), $del;
        show_message_dialog( $window, 'info', 'ok', $notice );
    }
}

sub del_quarantined {
    unless ( -e $v_dir ) {
        show_message_dialog( $window, 'error', 'close',
            gettext("There is no quarantine directory to empty.") );
        return;
    }
    else {
        my @trash;
        unless ( opendir( DIR, $v_dir ) ) {
            show_message_dialog( $window, 'error', 'close',
                gettext("Unable to open the virus directory.") );
        }
        @trash = grep { -f "$v_dir/$_" } readdir(DIR);
        closedir(DIR);
        if ( scalar(@trash) == 0 ) {
            show_message_dialog( $window, 'info', 'close',
                gettext("There are no quarantined items to delete.") );
        }
        else {
            my $del = 0;
            foreach (@trash) {
                unlink "$v_dir/$_" and $del++;
            }
            my $notice = sprintf gettext("Removed %d item(s)."), $del;
            show_message_dialog( $window, 'info', 'close', $notice );
        }
    }
}

sub move_to_quarantine {
    my $number   = shift;
    my $basename = $virus[$number]{base};
    if ( not -e $v_dir or not -d $v_dir ) {
        show_message_dialog( $window, 'info', 'close',
            gettext("Quarantine directory does not exist.") );
        return;
    }
    chmod oct(600), $virus[$number]{full};
    system( "mv", $virus[$number]{full}, "$v_dir/$basename" );
    rename( "$v_dir/$basename", "$v_dir/$basename.VIRUS" );
    if ( not -e $virus[$number]{full} ) {
        return 1;
    }
    else {
        return -1;
    }
}

#------------------history and history files stuff-------------------
sub history {
    @h_files = glob "$l_dir/*.log";
    my $new_win = Gtk2::Window->new;
    $new_win->signal_connect( destroy => sub { $new_win->destroy } );
    $new_win->set_default_size( 250, 200 );
    $new_win->set_title( gettext("Scanning Histories") );

    my $new_vbox = Gtk2::VBox->new;
    $new_win->add($new_vbox);

    my $s_win = Gtk2::ScrolledWindow->new;
    $s_win->set_shadow_type('etched-in');
    $s_win->set_policy( 'automatic', 'automatic' );
    $new_vbox->pack_start( $s_win, TRUE, TRUE, 0 );

    $new_hlist = Gtk2::SimpleList->new( gettext('Histories') => 'text', );
    $s_win->add($new_hlist);

    my $new_hbox = Gtk2::HButtonBox->new;
    $new_vbox->pack_start( $new_hbox, FALSE, FALSE, 0 );

    my $hist_view = Gtk2::Button->new_with_label( gettext("View") );
    $new_hbox->add($hist_view);
    $hist_view->signal_connect( clicked => \&view_box, "viewer" );

    my $pos_quit = Gtk2::Button->new_with_label( gettext("Close Window") );
    $new_hbox->add($pos_quit);
    $pos_quit->signal_connect( clicked => sub { $new_win->destroy } );
    my $del_single = Gtk2::Button->new_with_label( gettext("Delete") );
    $new_hbox->add($del_single);
    $del_single->signal_connect(
        clicked => \&history_del_single,
        "del_single"
    );
    my $del_all = Gtk2::Button->new_with_label( gettext("Delete All") );
    $new_hbox->add($del_all);
    $del_all->signal_connect(
        clicked => \&history_del_all,
        "del_all"
    );

    $h_label = Gtk2::Label->new();
    $new_vbox->pack_start( $h_label, FALSE, FALSE, 2 );

    for my $opt (@h_files) {
        push @{ $new_hlist->{data} }, basename($opt);
    }
    $new_win->set_position('mouse');
    $new_win->show_all;
}

sub view_box {
    my @sel = $new_hlist->get_selected_indices;
    return if ( !@sel );
    my $deref = $sel[0];
    return if ( not exists $h_files[$deref] );

    my $base = basename( $h_files[$deref] );

    my $view_win = Gtk2::Dialog->new( sprintf( gettext("Viewing %s"), $base ),
        undef, [], 'gtk-close' => 'close' );
    $view_win->set_default_response('close');
    $view_win->signal_connect( response => sub { $view_win->destroy } );
    $view_win->set_default_size( 650, 350 );

    my $textview = Gtk2::TextView->new;
    $textview->set( editable => FALSE );

    my $FILE;    # filehandle for histories log
    unless ( open( $FILE, "<", $h_files[$deref] ) ) {
        my $notice = sprintf gettext("Problems opening %s..."),
          $h_files[$deref];
        show_message_dialog( $window, 'error', 'ok', $notice );
        return;
    }
    my $text;
    $text = do {
        local $/ = undef;
        $text = <$FILE>;
    };
    close($FILE)
      or warn sprintf gettext("Unable to close FILE %s! %s\n"),
      $h_files[$deref];

    my $textbuffer = $textview->get_buffer;
    $textbuffer->create_tag( 'mono', family => 'Monospace' );
    $textbuffer->insert_with_tags_by_name( $textbuffer->get_start_iter, $text,
        'mono' );

    my $scroll_win = Gtk2::ScrolledWindow->new;
    $scroll_win->set_border_width(5);
    $scroll_win->set_shadow_type('etched-in');
    $scroll_win->set_policy( 'automatic', 'automatic' );

    $view_win->vbox->pack_start( $scroll_win, TRUE, TRUE, 0 );

    $scroll_win->add($textview);
    $view_win->show_all();
}

sub history_del_single {
    my @sel = $new_hlist->get_selected_indices;
    return if ( !@sel );
    my $deref = $sel[0];
    return if ( not exists $h_files[$deref] );
    unlink $h_files[$deref];
    if ( -e $h_files[$deref] ) {
        my $notice = sprintf gettext("Unable to delete %s!"), $h_files[$deref];
        show_message_dialog( $window, 'error', 'ok', $notice );
        return;
    }
    splice @{ $new_hlist->{data} }, $deref, 1;
    my $base = basename( $h_files[$deref] );
    $h_label->set_text( sprintf gettext("Deleted %s."), $base );
    @h_files = glob "$l_dir/*";
}

sub history_del_all {
    return unless (@h_files);
    my $confirm_message = gettext("Really delete all history logs?");
    my $confirm =
      Gtk2::MessageDialog->new( $window, [qw(modal destroy-with-parent)],
        'question', 'ok-cancel', $confirm_message );

    if ( "cancel" eq $confirm->run ) {
        $confirm->destroy;
        return;
    }
    else {
        $confirm->destroy;
        my @not_del;
        my $size = @h_files;
        foreach (@h_files) {
            unlink($_) or push( @not_del, $_ );
        }
        if ( scalar(@not_del) >= 1 ) {
            $h_label->set_text( sprintf gettext("Could not delete files: %s!"),
                @not_del );
        }
        else {
            show_message_dialog( $window, 'info', 'ok',
                gettext("Successfully removed history logs.") );
        }
        splice @{ $new_hlist->{data} }, 0, $size;
        @h_files = glob "$l_dir/*";
    }
}

#-----------------^history and history files stuff^------------------

sub startup_prefs {

    # this is an effort to combine any
    # and all startup things, such as the upcoming prefs file

    # theoretically, this next line shouldn't be necessary
    if ( $q_state eq "disabled" || $l_state eq "disabled" ) {
        show_message_dialog( $window, 'error', 'close',
            gettext("Unable to create personal directory - check permissions.")
        );
    }

    if ($INFO_MAIN) {
        if ( open( FILE, "<", $INFO_MAIN ) ) {
            while (<FILE>) {
                if (/ClamAV-VDB:\S+\s+\S+\s+\S+.*?\+\d+:\d+:(\d+)/) {
                    $INFO_MAIN = $1;
                }
                close(FILE);
            }
        }
    }
    else {
        $INFO_MAIN = 0;
    }

    if ($INFO_PATH) {
        if ( open( FILE, "<", $INFO_PATH ) ) {
            while (<FILE>) {
                if (/ClamAV-VDB:(\S+\s+\S+\s+\S+).*?\+\d+:\d+:(\d+)/) {
                    $INFO_DATE  = $1;
                    $INFO_DAILY = $2;
                }
            }
            close(FILE);
        }
        else {
            show_message_dialog(
                $window, 'error', 'close',
                gettext(
                    "Unable to view ClamAV's information file.\nThis will affect
how ClamTk views the number of viruses and version information.\n"
                )
            );
            return;
        }
    }

    date_diff();
    first_run();
}

sub sys_info {
    my ( $info, $return, $version, $number );

    # Signature date info
    if ( $DEFPATH && $MAINPATH ) {
        if ( !$INFO_PATH ) {
            $return = `$SIGPATH --info $DEFPATH 2>&1`;
        }
    }
    else {
        $return = gettext("(No definitions found)");
    }

    if ($INFO_DATE) {
        $info = $INFO_DATE;
    }
    elsif ( $return =~ /^Build time: (\S+\s\S+\s\S{4})/ ) {
        $info = $1;
    }
    else {
        $info = $return;
    }

    # ClamAV version
    my $ver_info = `$CLAMPATH -V`;
    chomp($ver_info);
    ( $version = $ver_info ) =~ s/^(\S+\s+\S+\.\S+(?:\.\d+))\/.*$/$1/;

    # Number of signatures
    $number = num_of_sigs();

    my $total =
      sprintf gettext( "\nBuild: %s\t\n\n"
          . "Signatures: %d\t\n"
          . "(%s)\n\n"
          . "GUI Version: %s\n" ), $version, $number, $info, $VERSION;
    show_message_dialog( $window, 'info', 'ok', $total );
}

sub cvd_check {
    my $return;

    # this path is something to watch
    # (i.e., /var/clamav vs. /var/lib/clamav)
    if ( $DEFPATH && $MAINPATH ) {
        $return = `$SIGPATH --info $DEFPATH 2>&1`;
    }
    else {
        return ( 0, 0, 0 );    #no definitions found
    }

    if ( $return =~ /^Build time: (\d+\s\w+\s\d{4})/ ) {
        return $1;
    }
    else {
        $top_label->set_text( sprintf gettext("Date of ClamAV Signatures: %s"),
            $return );
    }
}

sub date_diff {
    my ( $day1, $month1, $year1 ) = split / /,
      strftime( '%d %m %Y', localtime );
    my %months = (
        'Jan' => 1,
        'Feb' => 2,
        'Mar' => 3,
        'Apr' => 4,
        'May' => 5,
        'Jun' => 6,
        'Jul' => 7,
        'Aug' => 8,
        'Sep' => 9,
        'Oct' => 10,
        'Nov' => 11,
        'Dec' => 12,
    );
    my ( $day2, $month2, $year2 );
    if ( !$INFO_DATE ) {
        ( $day2, $month2, $year2 ) = split / /, cvd_check();
    }
    else {
        ( $day2, $month2, $year2 ) = split / /, $INFO_DATE;
    }

    unless ( $day2 && $month2 && $year2 ) {
        my $message =
          gettext( "Warning: No virus definitions found! If you are"
              . " sure you have definitions installed, please inform the developer"
              . " where your definitions are held so the paths can be added." );
        show_message_dialog( $window, 'warning', 'ok', $message );
        return;
    }
    my $diff =
      Delta_Days( $year1, $month1, $day1, $year2, $months{$month2}, $day2 );
    if ( $diff <= -5 ) {

        $diff *= -1;    # $diff returns a negative number, so...
        my $warning =
          sprintf gettext("Warning:\nYour virus signatures are %d days old!"),
          $diff;
        show_message_dialog( $window, 'warning', 'ok', $warning );
    }
    else {
        return;
    }
}

sub first_run {
    return if ( -e "$c_dir/first_run" || $> != 0 );
    my $warning =
      gettext( "Some distributions do not automatically edit\n"
          . "freshclam.conf and clamd.conf under /etc.\n"
          . "Please edit those before attempting signature updates.\n" );

    show_message_dialog( $window, 'warning', 'ok', $warning );

    my $FILE;    # filehandle to create first_run txt file
    open( $FILE, ">", "$c_dir/first_run" )
      or warn gettext("Couldn't create 'first_run' file...\n");
    close($FILE)
      or warn sprintf gettext("Couldn't close FILE %s! %s\n"),
      "$c_dir/first_run", $!;
}

sub default_check {
    my $value  = shift;
    my $action = $value->get_name;
    if ( $action eq "ScanHidden" ) {
        $hidden ^= 1;
    }
    if ( $action eq "DisplayAll" ) {
        $showall ^= 1;
    }
    if ( $action eq "SaveToLog" ) {
        $save_log ^= 1;
    }
    if ( $action eq "DetectBroken" ) {
        $detect_broken ^= 1;
        if ( $detect_broken == 1 ) {
            $command .= " --detect-broken";
        }
        else {
            $command =~ s/\s+--detect-broken//;
        }
    }
    if ( $action eq "FollowLinks" ) {
        $follow_symlinks ^= 1;
    }
}

sub action_actions {
    my ( $previous, $current ) = @_;
    my @children = $toolbar->get_children;

    # $previous is the old value; $current is the current value :)
    if ( $current->get_name eq "NoAction" ) {
        $ren = "None";
        $top_label->set_text(
            gettext("No action will be taken on infected files.") );
        if ( @children == 8 ) {
            $danger_ui->hide();
        }
    }
    elsif ( $current->get_name eq "Quarantine" ) {
        $ren = "Quarantine";
        $top_label->set_text( gettext("Infected files will be quarantined.") );
        if ( @children == 8 ) {
            $danger_ui->hide();
        }
    }
    elsif ( $current->get_name eq "Delete" ) {
        $ren = "Delete";
        my $warning =
          ( $gtk2_version == 1 )
          ? gettext(
            "<b>Warning!</b> Files thought to be infected will be deleted!")
          : gettext("Warning! Files thought to be infected will be deleted!");
        $top_label->set_text("");
        show_message_dialog( $window, 'warning', 'ok', $warning );
        if ( @children == 8 ) {
            $danger_ui->show();
        }
        else {
            $danger_ui = Gtk2::ToolButton->new_from_stock('gtk-dialog-warning');
            $danger_ui->signal_connect( 'clicked' => sub { danger() } );
            $danger_ui->set_tooltip( $tt, gettext("Warning!"), "" );
            $toolbar->insert( $danger_ui, -1 );
            $toolbar->show_all;
        }
    }
}

sub danger {
    my $warning =
      ( $gtk2_version == 1 )
      ? gettext( "You may wish to consider another Action. Running\n"
          . "ClamTk with the Action set to <b>Delete</b> will delete any\n"
          . "file it suspects as infected without prompting you first.\n\n"
          . "If you choose <b>No Action</b>, you can right-click on the\n"
          . "infected file and quarantine or delete it once the scan\n"
          . "has finished.\n\n"
          . "Selecting <b>Quarantine</b> is another safe choice.\n"
          . "Quarantined files can be managed by selecting\n"
          . "<b>Quarantine</b> and then <b>Maintenance</b>. There you\n"
          . "can decide whether or not to delete the file.\n\n"
          . "<b>No Action</b> and <b>Quarantine</b> are actions that can\n"
          . "help prevent the deletion of false positives.\n" )
      : gettext( "You may wish to consider another Action. Running\n"
          . "ClamTk with the Action set to Delete will delete any\n"
          . "file it suspects as infected without prompting you first.\n\n"
          . "If you choose No Action, you can right-click on the\n"
          . "infected file and quarantine or delete it once the scan\n"
          . "has finished.\n\n"
          . "Selecting Quarantine is another safe choice.\n"
          . "Quarantined files can be managed by selecting\n"
          . "Quarantine and then Maintenance. There you\n"
          . "can decide whether or not to delete the file.\n\n"
          . "No Action and Quarantine are actions that can\n"
          . "help prevent the deletion of false positives.\n" );

    show_message_dialog( $window, 'warning', 'ok', $warning );
}

sub num_of_sigs {
    my ( $total, $daily_total, $main_total );
    if ( $INFO_MAIN && $INFO_DAILY ) {
        return ( $INFO_MAIN + $INFO_DAILY );
    }
    elsif ( $SIGPATH && $MAINPATH ) {
        if ($DEFPATH) {
            $total = `$SIGPATH --info $DEFPATH`;
            ($daily_total) = ( $total =~ /ignatures: (\d+)/ );
        }
        else {
            $daily_total = 0;
        }
        if ($MAINPATH) {
            $total = `$SIGPATH --info $MAINPATH`;
            ($main_total) = ( $total =~ /ignatures: (\d+)/ );
        }
        else {
            $main_total = 0;
        }
        chomp( $daily_total, $main_total );
        return ( $daily_total + $main_total );
    }
    else {
        return "Unknown";
    }
}

sub maintenance {
    my $main_win = Gtk2::Window->new;
    $main_win->signal_connect( destroy => sub { $main_win->destroy; } );
    $main_win->set_default_size( 250, 200 );
    $main_win->set_title( gettext("Quarantine") );

    my $new_vbox = Gtk2::VBox->new( FALSE, 2 );
    $main_win->add($new_vbox);

    @q_files = glob "$v_dir/*";
    my $s_win = Gtk2::ScrolledWindow->new;
    $s_win->set_shadow_type('etched-in');
    $s_win->set_policy( 'automatic', 'automatic' );
    $new_vbox->pack_start( $s_win, TRUE, TRUE, 0 );

    $new_slist = Gtk2::SimpleList->new( gettext('File') => 'text', );
    $s_win->add($new_slist);

    my $new_hbox = Gtk2::HButtonBox->new;
    $new_vbox->pack_start( $new_hbox, FALSE, FALSE, 0 );

    my $pos_quit = Gtk2::Button->new_with_label( gettext("Close Window") );
    $new_hbox->add($pos_quit);
    $pos_quit->signal_connect( clicked => sub { $main_win->destroy } );
    my $false_pos = Gtk2::Button->new_with_label( gettext("False Positive") );
    $new_hbox->add($false_pos);
    $false_pos->signal_connect( clicked => \&main_false_pos, "false_pos" );
    my $del_pos = Gtk2::Button->new_with_label( gettext("Delete") );
    $new_hbox->add($del_pos);
    $del_pos->signal_connect( clicked => \&main_del_pos, "false_pos" );

    $q_label = Gtk2::Label->new();
    $new_vbox->pack_start( $q_label, FALSE, FALSE, 2 );

    for my $opt (@q_files) {
        push @{ $new_slist->{data} }, basename($opt);
    }
    $main_win->set_position('mouse');
    $main_win->show_all;
}

sub main_false_pos {
    my @sel = $new_slist->get_selected_indices;
    return if ( !@sel );
    my $deref = $sel[0];
    return if ( not exists $q_files[$deref] );
    my $base = basename( $q_files[$deref] );
    system( "mv", $q_files[$deref], $directory );
    my $new_name = $base;
    $new_name =~ s/.VIRUS$//;
    rename( "$directory/$base", "$directory/$new_name" );

    if ( -e $q_files[$deref] ) {
        $q_label->set_text( sprintf gettext("Unable to move %s."), $base );
        return;
    }
    splice @{ $new_slist->{data} }, $deref, 1;
    $q_label->set_text( sprintf gettext("%s moved to home directory."), $base );
    @q_files = glob "$v_dir/*";
}

sub main_del_pos {
    my @sel = $new_slist->get_selected_indices;
    return if ( !@sel );
    my $deref = $sel[0];
    return if ( not exists $q_files[$deref] );
    my $base = basename( $q_files[$deref] );
    unlink $q_files[$deref];
    if ( -e $q_files[$deref] ) {
        $q_label->set_text( sprintf gettext("Unable to delete %s."), $base );
        return;
    }

    splice @{ $new_slist->{data} }, $deref, 1;
    $q_label->set_text( sprintf gettext("Deleted %s."), $base );
    @q_files = glob "$v_dir/*";
}

sub main_confirm {
    my $number         = shift;
    my $do_this        = shift;
    my $current_status = $virus[$number]{status};

    if ( $do_this eq "q" ) {
        if ( not -e $virus[$number]{full} ) {
            $top_label->set_text(
                sprintf gettext("File has been moved or deleted already.") );
            return;
        }
        if ( move_to_quarantine($number) ) {
            $top_label->set_text( sprintf gettext("%s has been quarantined."),
                $virus[$number]{base} );
            $virus[$number]{status} =
              sprintf gettext("%s <b>(Quarantined)</b>"), $current_status;
        }
        else {
            $top_label->set_text(
                sprintf gettext("%s could not be quarantined."),
                $virus[$number]{base} );
            return;
        }
    }
    elsif ( $do_this eq "d" ) {
        if ( not -e $virus[$number]{full} ) {
            $top_label->set_text(
                gettext("File has been moved or deleted already.") );
            return;
        }
        my $confirm_message = sprintf gettext("Really delete %s?"),
          $virus[$number]{base};
        my $confirm =
          Gtk2::MessageDialog->new( $window, [qw(modal destroy-with-parent)],
            'question', 'ok-cancel', $confirm_message );

        if ( "cancel" eq $confirm->run ) {
            $confirm->destroy;
            return;
        }
        else {
            $confirm->destroy;
            if ( unlink( $virus[$number]{full} ) ) {
                $top_label->set_text( sprintf gettext("Deleted %s."),
                    $virus[$number]{base} );
                $virus[$number]{status} =
                  sprintf gettext("%s <b>(Deleted)</b>"), $current_status;
            }
            else {
                $top_label->set_text( sprintf gettext("Unable to delete %s."),
                    $virus[$number]{base} );
                return;
            }
        }
    }
    main_slist_delete($number);
}

sub main_slist_delete {
    my $number = shift;
    $slist->{data}[$number][0] =
      "<span foreground='#CCCCCC'>$virus[$number]{base}</span>";
    $slist->{data}[$number][1] =
      "<span foreground='#CCCCCC'>$virus[$number]{type}</span>";
    $slist->{data}[$number][2] =
      "<span foreground='#CCCCCC'>$virus[$number]{size}</span>";
    $slist->{data}[$number][3] =
      "<span foreground='#CCCCCC'>$virus[$number]{status}</span>";
    $window->queue_draw;
}

sub type {
    my $file        = shift;
    my $quoted_file = quotemeta($file);
    my $tmp         = `$FILE -b $quoted_file`;
    chomp($tmp);
    if ( $tmp =~ /^ERROR/ ) {
        $tmp = `$FILE -b $file`;
        chomp($tmp);
    }

    # once more - file may have been removed already, so...
    if ( $tmp =~ /^ERROR/ ) {
        $tmp = "unknown (e)"    # leave the 'e' so we know there's an error
    }

    # sometimes `file` doesn't return anything...
    $tmp ||= "unknown";
    return $tmp;
}

# there are shorter ways to do this, but this is easier to read
sub size {
    my $file = shift;
    my $size = -s $file;
    my ( $kb, $mb );
    $kb = 1024;
    $mb = 1_048_576;
    if ($size) {
        if ( $size == 0 ) {
            $size = gettext('empty');
        }
        elsif ( $size < $kb ) {
            $size = gettext('less than 1 KB');
        }
        elsif ( $size < $mb ) {
            $size = $size / $kb;
            $size = sprintf "%.2f", ($size);
            $size .= gettext(' KB');
        }
        else {
            $size = sprintf "%.2f", ( $size / $mb );
            $size .= gettext(' MB');
        }
        return $size;
    }
}

sub show_message_dialog {

    #parent window, type = info, warning, question, etc, button = ok, cancel
    my ( $parent, $type, $button, $message ) = @_;

    my $dialog;
    $dialog =
      ( $gtk2_version == 1 )
      ? Gtk2::MessageDialog->new_with_markup( $parent,
        [qw(modal destroy-with-parent)],
        $type, $button, $message )
      : Gtk2::MessageDialog->new( $parent, [qw(modal destroy-with-parent)],
        $type, $button, $message );

    $dialog->run;
    $dialog->destroy;
    return;
}
