#!/usr/bin/perl
# fedora-extras version

use strict;

# apparently you aren't supposed to leave warnings
# on when you distribute programs...
# use warnings;
$|++;

use Cwd;
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 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.17';
my $virus_log = '';
my ( $key, $value );
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} || getcwd();
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 $toolbar      = '';
my $hide_toolbar = 1;

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

BEGIN {
    my $encoding = setlocale(LC_CTYPE);
    $ENV{LC_ALL} = $encoding;
}

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

my $command;

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;
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 "\"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'
    : '';
my $MAINPATH =
      ( -e '/var/lib/clamav/main.cvd' ) ? '/var/lib/clamav/main.cvd'
    : ( -e '/var/clamav/main.cvd' ) ? '/var/clamav/main.cvd'
    : '';

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

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

if ( !-d $l_dir ) {
    eval { mkpath( $l_dir, 0, 0777 ); };
    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(0);

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

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

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

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

my @option_entries = (
    [   "SaveToLog", undef, "Save To Log", "F1", "Save a record of this scan",
        \&default_check, FALSE
    ],
    [   "ScanHidden",             undef,
        "Scan Hidden Files (.*)", "F2",
        "Scan the hidden files",  \&default_check,
        FALSE
    ],
    [   "DisplayAll",                undef,
        "Display All Files",         "F3",
        "Display all files scanned", \&default_check,
        FALSE
    ],
    [   "DetectBroken",              undef,
        "Detect Broken Executables", "F4",
        "Detect Broken Executables", \&default_check,
        FALSE
    ],
    [   "FollowLinks",           undef,
        "Follow Symbolic Links", "F5",
        "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,
        "No Action",                        undef,
        "Take no action on infected files", NO_ACTION
    ],
    [   "Quarantine",                              undef,
        "Quarantine Infected Files",               undef,
        "Automatically quarantine infected files", Q_ACTION
    ],
    [   "Delete",                                         undef,
        "Delete Infected Files",                          undef,
        "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='ViewHistories'/>
	  <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, "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, "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, "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, "Clear the display", "" );
$toolbar->insert( $scan_clear, -1 );

my $scan_stop = Gtk2::ToolButton->new_from_stock('gtk-stop');
$scan_stop->signal_connect(
    'clicked' => sub { kill 15, $scan_pid if ($scan_pid); @quoted = (); } );
$scan_stop->set_tooltip( $tt, "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, "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
my $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);
if ( !Gtk2->check_version( 2, 6, 0 ) ) {
    $slist->set(
        hover_selection => TRUE,
        hover_expand    => TRUE
    );
}

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

my $tooltips = Gtk2::Tooltips->new;
$tooltips->set_tip( $slist, "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(
            "<b>File:</b> $virus[$deref]{full}    <b>Status:</b> $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::MenuItem->new('Quarantine this file');
        $quar_pop->signal_connect(
            activate => sub { main_confirm( $deref, "q" ) } );
        $quar_pop->show();
        $menu->append($quar_pop)
            unless dirname( $virus[$deref]{full} ) =~ /^\/(proc|sys)/;

        my $delete_pop = Gtk2::ImageMenuItem->new('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)/;

        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("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("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("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 "Unable to scan $input.\n";
    }

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

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

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

Gtk2->main;

sub about {
    my $message = "ClamTk Virus Scanner\nVersion $VERSION\n"
        . "http://freshmeat.net/projects/clamtk/\nhttp://clamtk.sourceforge.net\n\n"
        . "Copyright (c) 2004-2006 Dave M\n(dave.nerd AT gmail DOT com)\n\n"
        . "This program is free software.\nYou may modify it and/or redistribute it\n"
        . "under the same terms as Perl itself.\n\nPlease read the DISCLAIMER, LICENSE,\n"
        . "and README for more information.\n\nClam AntiVirus is available at http://www.clamav.net .\t\n";

    show_message_dialog( $window, 'info', 'ok', $message );
}

sub create_list {
    my $list = Gtk2::SimpleList->new(
        'File'   => 'markup',
        'Type'   => 'markup',
        'Size'   => 'markup',
        '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" ) {
        my $rule = File::Find::Rule->new;
        $rule->file;
        $rule->readable;
        $rule->maxdepth(1);
        $rule->extras( { follow => 1 } ) if ($follow_symlinks);
        @files = $rule->in($directory);
    }
    elsif ( $option eq "file" ) {
        $dialog = Gtk2::FileChooserDialog->new(
            'Select a File', undef, 'open',
            'gtk-cancel' => 'cancel',
            'gtk-ok'     => 'ok',
        );

        if ( "ok" eq $dialog->run ) {
            $filename = $dialog->get_filename;
            $top_label->set_text("Please wait...");
            $window->queue_draw;
            $dialog->destroy;
            $window->queue_draw;
            push( @files, $filename );
        }
        else {
            $dialog->destroy;
            return;
        }
    }
    elsif ( $option eq "dir" ) {
        $dialog = Gtk2::FileChooserDialog->new(
            '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("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);
            @files = $rule->in($dir);
        }
        else {
            $dialog->destroy;
            return;
        }
    }
    elsif ( $option eq "recur" ) {
        $dialog = Gtk2::FileChooserDialog->new(
            '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("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("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 } );
            @files = $rule->in($cmd_input);
        }
        else {
            @files = $cmd_input;
        }
    }
    else {
        die "Shouldn't reach this.\n";
    }

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

    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("Scanning $too_big...");
            timer();
            $num_scanned++;
            $virus[$count]{full}   = $too_big;
            $virus[$count]{base}   = basename($too_big);
            $virus[$count]{status} = "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();

    # this goes so fast, the stop button is pointless unless
    # there are $lots of files to scan
    defined( my $pid = open( SCAN, "$command @quoted |" ) )
        or die "couldn't fork: $!\n";
    $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);
        if ( length( $virus[$count]{base} ) > 17 ) {
            $virus[$count]{base} = substr( $virus[$count]{base}, 0, 15 );
            $virus[$count]{base} .= "(...)";
        }
        $top_label->set_text("Scanning $virus[$count]{base}...");
        $status =~ s/\s+FOUND$//;
        my $meta = quotemeta($file);

        $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("Files Scanned: $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" );

        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)/ )
                {
                    move_to_quarantine($count);
                    $virus[$count]{status} = "$current_status (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)/ )
                {
                    my $current_status = $virus[$count]{status};
                    unlink( $virus[$count]{full} );
                    $virus[$count]{status} = "$current_status (Deleted)";
                }
            }
        }

        $num_so_far = keys %found;
        $left_status->set_text("Files Scanned: $num_scanned");
        if ( $num_so_far > 0 ) {
            $mid_status->set_markup("<b>Viruses Found: $num_so_far</b>");
        }
        else {
            $mid_status->set_text("Viruses Found: $num_so_far");
        }
        display();
    }
    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++;
    }
    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("Elapsed time: $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();
    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 "ClamAV Signatures: $db_total\n";
            print REPORT "Directories Scanned:\n";
            for my $list (@sorted) {
                print REPORT "$list\n";
            }
            print REPORT
                "\nFound $num_so_far possible viruses ($num_scanned files scanned).\n\n";
        }
        else {
            $top_label->set_text(
                "Could not write to logfile. Check permissions.");
            $save_log = 0;
        }

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

    if ( $num_so_far == 0 ) {
        print REPORT "No viruses found.\n" if ($save_log);
    }
    else {
        if ($save_log) {
            while ( ( $key, $value ) = each %found ) {
                write(REPORT);
            }
        }
    }
    if ($save_log) {
        print REPORT "-" x 76, "\n";
        close(REPORT) if ( fileno(REPORT) );
    }

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

format REPORT =
@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@>>>>>>>>>>>>>>>>>>>>>>>>>>>>
$key,                                                                 $value 
.

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

sub update {
    if ( $> != 0 ) {
        $top_label->set_text("You must be root to install updates.");
        return;
    }

    $top_label->set_text("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 15;

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

sub quarantine_check {
    if ( !-d $v_dir ) {
        $top_label->set_text("No virus directory available.");
        return;
    }
    my @trash;
    opendir( DIR, $v_dir )
        or $top_label->set_text("Unable to open the virus directory.")
        and return;
    @trash = grep { -f "$v_dir/$_" } readdir(DIR);
    closedir(DIR);
    my $del = scalar(@trash);
    if ( !$del ) {
        $top_label->set_text("No items currently quarantined.");
    }
    else {
        $top_label->set_text("$del item(s) currently quarantined.");
    }
}

sub del_quarantined {
    unless ( -e $v_dir ) {
        $top_label->set_text("There is no quarantine directory to empty.");
        return;
    }
    else {
        my @trash;
        opendir( DIR, $v_dir )
            or $top_label->set_text("Unable to open the virus directory.")
            and return;
        @trash = grep { -f "$v_dir/$_" } readdir(DIR);
        closedir(DIR);
        if ( scalar(@trash) == 0 ) {
            $top_label->set_text("There are no quarantined items to delete.");
        }
        else {
            my $del = 0;
            foreach (@trash) {
                unlink and $del++;
            }
            $top_label->set_text("Removed $del item(s).");
        }
    }
}

sub move_to_quarantine {
    my $number   = shift;
    my $basename = $virus[$number]{base};
    if ( not -e $v_dir or not -d $v_dir ) {
        $top_label->set_text("Quarantine directory does not exist.");
        return;
    }
    chmod 0600, $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 {
    my $choice = shift;
    @h_files = ();
    unless ( -e $l_dir ) {
        $top_label->set_text("History directory not found.");
        return;
    }

    my ( $filename, $dialog );
    if ( $choice eq "view" ) {
        @h_files = glob "$l_dir/*.log";
        if ( scalar(@h_files) == 0 ) {
            $top_label->set_text("No history logs are available to view.");
            return;
        }
        chdir($l_dir);
        $dialog = Gtk2::FileChooserDialog->new(
            'Select a History File', undef, 'open',
            'gtk-cancel' => 'cancel',
            'gtk-ok'     => 'ok'
        );

        if ( "ok" eq $dialog->run ) {
            $filename = $dialog->get_filename;
            $dialog->destroy;
        }
        else {
            $dialog->destroy;
            return;
        }
        my $base = basename($filename);

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

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

        open( FILE, "<", $filename )
            or $top_label->set_text("Problems opening $filename...")
            and return;
        my $text;
        {
            local $/ = undef;
            $text = <FILE>;
        }
        close(FILE) or warn "Unable to close FILE $filename! $!\n";

        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' );

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

        $scroll_win->add($textview);
        $new_win->show_all();

    }
    elsif ( $choice eq "delete" ) {
        @h_files = glob "$l_dir/*.log";
        if ( scalar(@h_files) == 0 ) {
            $top_label->set_text("No history logs are available to delete.");
            return;
        }
        if ( defined $new_win ) {

            # why this "iconifies" in the first place, I have NO idea...
            # in the meanwhile, we destroy $new_win and then undef it
            $new_win->deiconify;
        }
        else {
            $new_win = Gtk2::Window->new;
            $new_win->signal_connect(
                destroy => sub { $new_win->destroy; undef $new_win } );
            $new_win->set_default_size( 250, 200 );
            $new_win->set_title("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( 'Histories' => 'text', );
            $s_win->add($new_hlist);

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

            my $pos_quit = Gtk2::Button->new_with_label("Close Window");
            $new_hbox->pack_start( $pos_quit, TRUE, TRUE, 1 );
            $pos_quit->signal_connect(
                clicked => sub { $new_win->destroy; undef $new_win } );
            my $del_single = Gtk2::Button->new_with_label("Delete");
            $new_hbox->pack_start( $del_single, TRUE, TRUE, 1 );
            $del_single->signal_connect(
                clicked => \&history_del_single,
                "del_single"
            );
            my $del_all = Gtk2::Button->new_with_label("Delete All");
            $new_hbox->pack_start( $del_all, TRUE, TRUE, 1 );
            $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 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] ) {
        $q_label->set_text("Unable to delete $h_files[$deref].");
        return;
    }
    splice @{ $new_hlist->{data} }, $deref, 1;
    my $base = basename( $h_files[$deref] );
    $h_label->set_text("Deleted $base.");
    @h_files = glob "$l_dir/*";
}

sub history_del_all {
    return unless (@h_files);
    my $confirm_message = "Really delete all history logs?";
    my $confirm         = Gtk2::MessageDialog->new_with_markup( $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("Could not delete files: @not_del");
        }
        else {
            $h_label->set_text("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" ) {
        $top_label->set_text(
            "Unable to create personal directory - check permissions.");
    }

    date_diff();
    first_run();    # not needed for debian
}

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

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

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

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

    # Number of signatures
    $number = num_of_sigs();

    my $total = "\nBuild: $version\t\n\n"
        . "Signatures: $number\t\n"
        . "($info)\n\n"
        . "GUI Version: $VERSION\n";
    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) {
        $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("Date of ClamAV Signatures: $return");
    }
}

sub date_diff {
    my ( $day1, $month1, $year1 ) = split / /,
        strftime( '%d %m %Y', localtime );
    my ( $day2, $month2, $year2 ) = split / /, cvd_check();
    unless ( $day2 && $month2 && $year2 ) {
        $top_label->set_text("Warning: No virus definitions found!");
        return;
    }
    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 $diff = Delta_Days( $year1, $month1, $day1, $year2, $months{$month2},
        $day2 );
    if ( $diff <= -5 ) {

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

    }
    else {
        return;
    }
}

sub first_run {
    return if ( -e "$c_dir/first_run" || $> != 0 );
    my $warning
        = "This is a rebuild for Fedora Extras.\nUnlike Dag's ClamAV rpms,"
        . " the Extras rpms do not\nautomatically edit freshclam.conf and clamd."
        . "conf under /etc.\nPlease edit those before attempting signature updates.\n";

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

    open( FILE, ">", "$c_dir/first_run" )
        or warn "Couldn't create 'first_run' file...\n";
    close(FILE) or warn "Couldn't close FILE $c_dir first_run! $!\n";
}

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("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("Infected files will be quarantined.");
        if ( @children == 8 ) {
            $danger_ui->hide();
        }
    }
    elsif ( $current->get_name eq "Delete" ) {
        $ren = "Delete";
        $top_label->set_markup(
            "<b>Warning!</b> Files thought to be infected will be deleted!");
        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, "Warning!", "" );
            $toolbar->insert( $danger_ui, -1 );
            $toolbar->show_all;
        }
    }
}

sub danger {
    my $warning = "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";

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

sub num_of_sigs {
    my ( $total, $daily_total, $main_total );
    if ($SIGPATH) {
        $total = `$SIGPATH --info $DEFPATH`;
        ($daily_total) = ( $total =~ /# of signatures: (\d+)/ );
        $total = `$SIGPATH --info $MAINPATH`;
        ($main_total) = ( $total =~ /# of signatures: (\d+)/ );
        chomp( $daily_total, $main_total );
        return ( $daily_total + $main_total );
    }
    else {
        return "Unknown";
    }
}

sub maintenance {
    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("Quarantine");

    my $new_vbox = Gtk2::VBox->new( FALSE, 2 );
    $new_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( 'File' => 'text', );
    $s_win->add($new_slist);

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

    my $pos_quit = Gtk2::Button->new_with_label("Close Window");
    $new_hbox->pack_start( $pos_quit, TRUE, TRUE, 1 );
    $pos_quit->signal_connect( clicked => sub { $new_win->destroy } );
    my $false_pos = Gtk2::Button->new_with_label("False Positive");
    $new_hbox->pack_start( $false_pos, TRUE, TRUE, 1 );
    $false_pos->signal_connect( clicked => \&main_false_pos, "false_pos" );
    my $del_pos = Gtk2::Button->new_with_label("Delete");
    $new_hbox->pack_start( $del_pos, TRUE, TRUE, 1 );
    $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);
    }
    $new_win->set_position('mouse');
    $new_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("Unable to move $base.");
        return;
    }
    splice @{ $new_slist->{data} }, $deref, 1;
    $q_label->set_text("$base moved to home directory.");
    @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("Unable to delete $base.");
        return;
    }

    splice @{ $new_slist->{data} }, $deref, 1;
    $q_label->set_text("Deleted $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("File has been moved or deleted already.");
            return;
        }
        if ( move_to_quarantine($number) ) {
            $top_label->set_text(
                "$virus[$number]{base} has been quarantined.");
            $virus[$number]{status} = "$current_status <b>(Quarantined)</b>";
        }
        else {
            $top_label->set_text(
                "$virus[$number]{base} could not be quarantined.");
            return;
        }
    }
    elsif ( $do_this eq "d" ) {
        if ( not -e $virus[$number]{full} ) {
            $top_label->set_text("File has been moved or deleted already.");
            return;
        }
        my $confirm_message = "Really delete $virus[$number]{base}?";
        my $confirm         = Gtk2::MessageDialog->new_with_markup( $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("Deleted $virus[$number]{base}.");
                $virus[$number]{status} = "$current_status <b>(Deleted)</b>";
            }
            else {
                $top_label->set_text(
                    "Unable to delete $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
    }

    # this is an attempt to clean up some file descriptions
    # shortening the length would be easier, but wouldn't look nice
    $tmp =~ s/(.*?),.*$/$1/;    # remove 1st comma and on
    $tmp =~ s/(MS Windows) PE.*?(\w+ executable).*?/$1 $2/i; # these are long!
    $tmp =~ s/(.*?executable).*$/$1/i;        # remove after word 'executable'
    $tmp =~ s/(.*? text).*$/$1/i;             # good for URL descriptors
    $tmp =~ s/Intel 80386\s//g;
    $tmp =~ s/(DBase \d+ data file) .*/$1/;
    $tmp =~ s/extended-ASCII//;
    $tmp =~ s/symbolic //;
    $tmp =~ s/^(ISO 9660 CD-ROM).*$/$1/;

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

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

sub show_message_dialog {

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

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

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