#!/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;
use File::Copy;
use Config::Tiny;
textdomain("clamtk");
setlocale( LC_MESSAGES, "" );
bind_textdomain_codeset( "clamtk", "UTF-8" );

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

my $VERSION   = '3.10';
my $virus_log = '';
my ( $save_log, $hidden, $showall ) = (0) x 3;
my $follow_symlinks = 0;
my @virus;
my $count       = 0;    # keeps track of displayed files
my $num_scanned = 0;    # actual number of files scanned
my $num_so_far  = 0;    # number of viruses
my %found;
my $start_time;
my ( $q_state, $l_state );
my ( @files, @new, @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 $top_label;
my $step       = 0;                # for progressbar; has to be global
my $current    = 0;                # progressbar's current level
my $quarantine = 0;
my $size_set   = 0;
my $stopped    = 0;                # user hit the stop button
my $prefs      = "$c_dir/prefs";
my $Config;                        # for $c_dir/prefs file

# 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'
    : ( -e '/opt/local/bin/freshclam' ) ? '/opt/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'
    : ( -e '/opt/local/bin/sigtool' ) ? '/opt/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'
    : ( -e '/opt/local/bin/clamscan' ) ? '/opt/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 $DAILY_PATH = '';
my $MAIN_PATH  = '';

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

$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");

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

# Create directories for storing histories, preferences, quarantined files
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( 550, 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.
# No big whoop if it stays in, though, since Debian wants an
# xpm file for the .menu file.
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("_Scan") ],
    [ "ViewMenu",       undef, gettext("_View") ],
    [ "OptionsMenu",    undef, gettext("_Options") ],
    [ "QuarantineMenu", undef, gettext("_Quarantine") ],
    [ "HelpMenu",       undef, gettext("_Help") ],

    [   "Scan_File",        'gtk-find',
        gettext("A _File"), "<control>F",
        gettext("Scan a file"), sub { getfile('file') }
    ],
    [   "Quick_Home",             'gtk-go-down',
        gettext("Home (_Quick)"), "<control>Q",
        gettext("Quick Home Scan"), sub { getfile('home') }
    ],
    [   "Full_Home",                'gtk-goto-bottom',
        gettext("Home (Thorough)"), "<control>Z",
        gettext("Full Home Scan"), sub { getfile('full-home') }
    ],
    [   "Scan_Directory",        'gtk-zoom-in',
        gettext("A _Directory"), "<control>D",
        gettext("Scan a Directory"), sub { getfile('dir') }
    ],
    [   "Recursive_Scan",           'gtk-zoom-fit',
        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("Antivirus _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 = (
    [   "ManageHistories",            'gtk-index',
        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
    ],
    [   "LoadPrefs",                      'gtk-revert-to-saved',
        gettext("Load Scan Preferences"), "<control>L",
        gettext("Load Scan Preferences"), \&load_prefs,
        FALSE
    ],
    [   "SavePrefs",                      'gtk-save',
        gettext("Save Scan Preferences"), "<control>P",
        gettext("Save Scan Preferences"), \&save_prefs,
        FALSE
    ],
);

my @option_entries = (
    [   "SaveToLog", undef, gettext("Save To Log"), "F1",
        gettext("Save a record of this scan"),
        sub { $save_log ^= 1; }, FALSE
    ],
    [   "ScanHidden",                      undef,
        gettext("Scan Hidden Files (.*)"), "F2",
        gettext("Scan the hidden files"), sub { $hidden ^= 1; },
        FALSE
    ],
    [   "DisplayAll", undef, gettext("Display All Files"),
        "F3",
        gettext("Display all files scanned"),
        sub { $showall ^= 1; }, FALSE
    ],
    [   "FollowLinks", undef, gettext("Follow Symbolic Links"),
        "F4",
        gettext("Follow Symbolic Links"),
        sub { $follow_symlinks ^= 1; }, FALSE
    ],
    [   "Quarantine",                         'gtk-refresh',
        gettext("Quarantine Infected Files"), "F5",
        gettext("Quarantine Infected Files"), sub { $quarantine ^= 1; },
        FALSE
    ],
    [   "SizeLimit",                undef,
        gettext("No Maximum Size"), "F6",
        gettext("No Maximum Size"), sub { $size_set ^= 1; },
        FALSE
    ],
);

my $ui_info = "<ui>
	<menubar name='MenuBar'>
	 <menu action='FileMenu'>
	  <menuitem action='Scan_File'/>
	  <menuitem action='Scan_Directory'/>
	  <menuitem action='Recursive_Scan'/>
	  <menuitem action='Quick_Home'/>
	  <menuitem action='Full_Home'/>
	  <separator/>
	  <menuitem action='Exit'/>
	 </menu>
	  <menu action='ViewMenu'>
	  <menuitem action='ManageHistories'/>
	  <menuitem action='ClearOutput'/>
	  <menuitem action='LoadPrefs'/>
	  <menuitem action='SavePrefs'/>
	 </menu>
	 <menu action='OptionsMenu'>
	  <menuitem action='SaveToLog'/>
	  <menuitem action='ScanHidden'/>
	  <menuitem action='DisplayAll'/>
	  <menuitem action='FollowLinks'/>
	  <menuitem action='Quarantine'/>
	  <menuitem action='SizeLimit'/>
	 </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 );

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, clearing 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-find');
$scan_file->signal_connect( 'clicked' => sub { getfile('file') } );
$scan_file->set_tooltip( $tt, gettext("Scan a file"), "" );
$toolbar->insert( $scan_file, -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 );

my $scan_dir = Gtk2::ToolButton->new_from_stock('gtk-zoom-in');
$scan_dir->signal_connect( 'clicked' => sub { getfile('dir') } );
$scan_dir->set_tooltip( $tt, gettext("Scan a directory"), "" );
$toolbar->insert( $scan_dir, -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("");
        $stopped = 1;
    }
);
$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 );

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

my $top_frame = Gtk2::Frame->new( gettext("Information") );
$main_vbox->pack_start( $top_frame, FALSE, FALSE, 0 );

# This is the top label where scanning messages are displayed
$top_label = Gtk2::Label->new();
$top_frame->add($top_label);
$top_label->set_justify('center');
$top_label->set_ellipsize('middle');

# 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(250) } $slist->get_columns;
map { $_->set_sizing('fixed') } $slist->get_columns;

$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);
        if ( $virus[$deref]{full}
            =~ /\.(thunderbird|mozilla-thunderbird|evolution)/ )
        {
            $top_label->set_text(
                "Possible email file - please manually quarantine or delete it."
            );
        }
        else {
            $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);
        if ( $virus[$deref]{full}
            =~ /\.(thunderbird|mozilla-thunderbird|evolution)/ )
        {
            $top_label->set_text(
                "Possible email file - please manually quarantine or delete it."
            );
        }
        else {
            $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 $save_pop
            = Gtk2::ImageMenuItem->new_from_stock( 'gtk-save-as', undef );
        $save_pop->signal_connect(
            activate => sub {
                my $save_dialog = Gtk2::FileChooserDialog->new(
                    gettext('Save As...'), undef, 'save',
                    'gtk-cancel' => 'cancel',
                    'gtk-ok'     => 'ok',
                );
                $save_dialog->set_do_overwrite_confirmation(TRUE);

                if ( "ok" eq $save_dialog->run ) {
                    my $tmp = $save_dialog->get_filename;
                    $save_dialog->destroy();
                    move( $virus[$deref]{full}, $tmp )
                        or do {
                        show_message_dialog( $window, 'error', 'close',
                            gettext("Could not save that file.") );
                        return TRUE;
                        };
                    show_message_dialog( $window, 'info', 'close',
                        gettext("File saved.") );
                }
                else {
                    $save_dialog->destroy();
                }
            }
        );
        $save_pop->show();
        $menu->append($save_pop);

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

my $stats_box = Gtk2::Frame->new( gettext("Status") );
$main_vbox->pack_start( $stats_box, FALSE, FALSE, 2 );

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

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

my $pb = Gtk2::ProgressBar->new;
$main_vbox->pack_start( $pb, FALSE, FALSE, 0 );
$pb->set_fraction(0);

$window->show_all();

# This is to combine any and all startup checks
if ( !-e $prefs ) {
    save_prefs();
}

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 = Gtk2::AboutDialog->new;
    $about->set_authors("Dave M, dave.nerd <at> gmail.com");
    $about->set_version($VERSION);
    my @translators = (
        'Karel Hudan, Czech (cs_CZ)',
        'Jimmy Christensen, Danish (da_DK)',
        'Ronny Steiner, German (de_DE)',
        'Mariano Rojo, Spanish (es_ES)',
        'Alain Bernard, French (fr_FR)',
        'David Garcia Rojo, French (fr_FR)',
	'Viale Fabrice, French (fr_FR)',
        'Román Pena, Galician (gl_ES)',
        'Edoardo Tosca, Italian (it_IT)',
        'Tobia Fasciati, Italian (it_IT)',
        'Alessandro Volturno, Italian (it_IT)',
        'Gina C, Korean (ko_KR)',
	'Rob van den Berg, Dutch (nl_NL)',
        'Robert Tomasik, Polish (pl_PL)',
        'Bruno Diniz, Portugese (pt_BR)',
        'Veronica B., Romanian (ro_RO)',
        'Vitaly Lipatov, Russian (ru_RU)',
        'Martin McDowell, Slovene (sl_SI)',
        'Petter Viklund, Swedish (sv_SE)',
        'Tao Wei, Chinese (zh_CN)',
    );
    my @artists
        = ( 'Edoardo Tosca (Website Design)', 'Gerald Ganson (Icon Design)',
        );
    my $t_list = join "\n", @translators;
    $about->set_translator_credits($t_list);
    my $a_list = join "\n", @artists;
    $about->set_artists($a_list);
    my $logo
        = -e '/usr/share/pixmaps/clamtk.png' ? '/usr/share/pixmaps/clamtk.png'
        : 'usr/share/pixmaps/clamtk.xpm'     ? '/usr/share/pixmaps/clamtk.xpm'
        :                                      '';
    my $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file($logo);
    $about->set_logo($pixbuf);
    $about->set_website('http://clamtk.sf.net');
    $about->set_comments(
        "ClamTk is a GUI front-end for the ClamAV antivirus using gtk2-perl."
    );
    $about->set_license( "ClamTk, (c) 2004-2008. All rights reserved.\n\n"
            . "This program is free software; you can redistribute it\n"
            . "and/or modify it under the same terms as Perl itself." );
    $about->run;
    $about->destroy;
}

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

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

    # disable File::Find warnings for this scope.
    # probably should NOT do this.
    no warnings 'File::Find';

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

    my ( $filename, $dir, $dialog );
    Gtk2->main_iteration while ( Gtk2->events_pending );

    my $rule = File::Find::Rule->new;
    $rule->file;
    $rule->exists;
    $rule->readable;
    $rule->maxdepth(1)
        unless ( $option eq 'recur' or $option eq 'full-home' );
    if ($follow_symlinks) {
        $rule->extras(
            { follow => 1, follow_skip => 2, dangling_symlinks => 0 } );
    }

    my $title
        = ( $option eq 'file' ) ? 'Select File'
        : ( $option eq 'dir' )   ? 'Select a Directory (directory scan)'
        : ( $option eq 'recur' ) ? 'Select a Directory (recursive scan)'
        :                          '';

    if ( $option eq 'home' ) {
        $top_label->set_text( gettext("Please wait...") );
        Gtk2->main_iteration while ( Gtk2->events_pending );
        @files = $rule->in($directory);
    }
    elsif ( $option eq 'file' ) {
        $dialog = Gtk2::FileChooserDialog->new(
            gettext($title), 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 'full-home' ) {
        $top_label->set_text( gettext("Please wait...") );
        Gtk2->main_iteration while ( Gtk2->events_pending );
        @files = $rule->in($directory);
    }
    elsif ( $option eq 'dir' or $option eq 'recur' ) {
        $dialog = Gtk2::FileChooserDialog->new(
            gettext($title), undef,
            'select-folder',
            'gtk-cancel' => 'cancel',
            'gtk-ok'     => 'ok',
        );
        if ( "ok" eq $dialog->run ) {
            $dir = $dialog->get_filename;
            if ( $dir =~ m#^/(proc|sys|dev)# ) {
                $dialog->destroy;
                clean_up();
                return;
            }
            $top_label->set_text( gettext("Please wait...") );
            Gtk2->main_iteration while ( Gtk2->events_pending );
            $window->queue_draw;
            $dialog->destroy;
            $window->queue_draw;
            $dir ||= $directory;
            Gtk2->main_iteration while ( Gtk2->events_pending );
            @files = $rule->in($dir);
        }
        else {
            $dialog->destroy;
            return;
        }
    }
    elsif ( $option eq 'cmd-scan' ) {
        if ( -d $cmd_input ) {
            if ( $cmd_input =~ m#^/(proc|sys|dev)# ) {
                clean_up();
                return;
            }
            $top_label->set_text( gettext("Please wait...") );
            $window->queue_draw;

            # toggle the DisplayAll switch to show all files (for save-as)
            my $change_on = $actions->get_action( $option_entries[2]->[0] );
            $change_on->set_active(TRUE);
            Gtk2->main_iteration while ( Gtk2->events_pending );
            @files = $rule->in($cmd_input);
        }
        else {
            $top_label->set_text( gettext("Please wait...") );
            $window->queue_draw;
            Gtk2->main_iteration while ( Gtk2->events_pending );
            @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: ") );

    # Attempt to remove dangling symlinks
    my $index = 0;
    while ( $index <= $#files ) {
        if ( -l $files[$index] && !-e $files[$index] ) {
            splice( @files, $index, 1 );
        }
        else {
            $index++;
        }
    }

    # only a single file
    if ( $option eq 'file' ) {
        scan(@files);
    }
    else {
        if ( $hidden == 0 && $option ne 'recur' && $option ne 'full-home' ) {
            @files = grep { basename($_) !~ /^\./ } @files;
        }

        if ( scalar(@files) == 1 ) {
            $step = 1;
        }
        elsif ( scalar(@files) > 1 ) {
            $step = 1 / scalar(@files);
        }
        else {
            $step = 1;
        }

        if ( !$size_set ) {
            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 );
                    $num_scanned++;
                    timer();
                    $virus[$count]{full}   = $too_big;
                    $virus[$count]{base}   = basename($too_big);
                    $virus[$count]{status} = gettext("Not scanned (size)");
                    display();
                    next;
                }
            }
        }
        else {
            @new = @files;
        }

        if (@new) {
            my @send;
            while ( my $t = pop(@new) ) {
                last if ($stopped);
                push( @send, $t );
                if ( scalar(@send) == 255 ) {
                    scan(@send);
                    @send = ();
                }
                else {
                    next;
                }
            }
            scan(@send) if (@send);
        }
    }
    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]{full} = $file;

        # do not show files in archives - we just want the end-result.
        # it still scans and we still show the result.
        next if ( $virus[$count]{full} =~ /\/tmp\/clamav/ );

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

        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",
            "Files number limit exceeded", "handler error",
            "Broken.Executable",           "Oversized.Zip" );

        if ( $status !~ /$clean_words/ ) {    # a virus
            $found{ $virus[$count]{full} } = $virus[$count]{status};
            my $current_status = $virus[$count]{status};
            if ($quarantine) {

                # ignore tmp directory files, normally for unzipping
                if (dirname( $virus[$count]{full} )
                    !~ /\/tmp\/clamav/

                    # ignore directories /proc, /sys and /dev
                    and dirname( $virus[$count]{full} ) !~ /^\/(proc|sys|dev)/

                    # ignore email directories until we can parse those
                    and $virus[$count]{full}
                    !~ /.(thunderbird|mozilla-thunderbird|evolution)/
                    )

                {
                    move_to_quarantine($count);
                    $virus[$count]{status}
                        = "$current_status " . gettext("(Quarantined)");
                }
            }

        }

        $num_so_far = keys %found;
        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 );
        }
        $num_scanned++;
        display();

        # resize hack below
        my ( $w, $h ) = $window->get_size;
        unless ( $w == 550 && $h == 325 ) {
            $window->resize( 550, 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 );
        }
    }
    if ( !@new ) {
        close($SCAN);    # or warn "Unable to close scanner! $!\n";
        $pb->set_text( gettext("Percent complete: 100") );
    }
    Gtk2->main_iteration while ( Gtk2->events_pending );
}

sub display {
    timer();
    use encoding 'utf8';
    $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]{status} ];
        $count++;
    }
    map { $_->set_fixed_width(250) } $slist->get_columns;
    map { $_->set_sizing('fixed') } $slist->get_columns;
    map { $_->set_resizable(TRUE) } $slist->get_columns;
    timer();
    if ( $current + $step <= 0 || $current + $step >= 1.0 ) {
        $current = .99;
    }
    else {
        $current += $step;
    }
    $pb->set_fraction($current);
    $pb->set_text( sprintf gettext("Percent complete: %2d"), $current * 100 );
    Gtk2->main_iteration while ( Gtk2->events_pending );
}

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" );
    $left_status->set_text( sprintf gettext("Files Scanned: %d"),
        $num_scanned );
    $window->queue_draw;
    Gtk2->main_iteration while ( Gtk2->events_pending );
}

sub clean_up {
    $pb->set_fraction(1.0);
    $pb->set_text("");
    $count ||= 0;

    # highlight the quarantined or deleted files
    my $utf8_string = gettext("(Quarantined)");
    for ( 0 .. $#virus ) {
        if ( $virus[$_]{status} =~ /$utf8_string/ ) {
            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("Infected files set to be quarantined.\n")
                if ($quarantine);
            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, '...' );
                }
                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_so_far   = 0;
    $num_scanned  = 0;
    %found        = ();
    %dirs_scanned = ();
    @files        = ();
    @new          = ();
    @quoted       = ();
    $current      = 0;
    $stopped      = 0;
}

sub clear_output {
    return if ( scalar(@files) > 0 );
    $pb->set_fraction(0);
    @{ $slist->{data} } = ();
    $window->resize( 550, 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(250) } $slist->get_columns;
    map { $_->set_sizing('fixed') } $slist->get_columns;
}

sub update {
    system("/usr/bin/clamtk-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 60;

        @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;
            }
            elsif ( $line =~ /WARNING: Incremental update failed/ ) {
                $showthis = sprintf gettext(
                    "Unable to retrieve update. Please try again later.");
                $top_label->set_text($showthis);
            }
        }
        $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( 260, 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( 600, 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/*";
    }
}

sub startup_prefs {

    # this is an effort to combine any
    # and all startup things

    # 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."
            )
        );
    }

    num_of_sigs();
    date_diff();
    first_run() if ( not -e "$c_dir/first_run" || $> == 0 );
}

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

    # ClamAV version
    my $ver_info = `$CLAMPATH -V`;
    chomp($ver_info);

    ( $version = $ver_info ) =~ s/^(.*?)\/.*$/$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_DATE, $VERSION;
    show_message_dialog( $window, 'info', 'ok', $total );
}

sub date_diff {
    return unless ($INFO_DATE);
    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 ) = split / /, $INFO_DATE;

    return unless ( $day2 && $month2 && $year2 );

    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 {
    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 num_of_sigs {
    find_defs();
    if ($MAIN_PATH) {
        my $FILE;
        if ( open( $FILE, "<", $MAIN_PATH ) ) {
            while (<$FILE>) {
                if (/ClamAV-VDB:\S+\s+\S+\s+\S+.*?\+\d+:\d+:(\d+)/) {
                    $INFO_MAIN = $1;
                    last;
                }
                close($FILE);
            }
        }
        else {
            $INFO_MAIN = 0;
        }
    }
    else {
        $INFO_MAIN = 0;
    }

    if ($DAILY_PATH) {
        my $FILE;
        if ( open( $FILE, "<", $DAILY_PATH ) ) {
            while (<$FILE>) {
                if (/ClamAV-VDB:(\S+\s+\S+\s+\S+).*\+\d+:\d+:(\d+)/) {
                    $INFO_DATE  = $1;
                    $INFO_DAILY = $2;
                    last;
                }
            }
            close($FILE);
        }
        else {
            $INFO_DATE  = '01 Jan 1970';
            $INFO_DAILY = 0;
        }
    }

    if ( $INFO_MAIN && $INFO_DAILY ) {
        return ( $INFO_MAIN + $INFO_DAILY );
    }
    else {
        return "Unknown";
    }
}

sub find_defs {
    for my $dir_list (
        '/var/lib/clamav',         '/var/clamav',
        '/opt/local/share/clamav', '/usr/share/clamav',
        '/usr/local/share/clamav'
        )
    {
        if ( -e "$dir_list/daily.inc/daily.info" ) {
            $DAILY_PATH = "$dir_list/daily.inc/daily.info";
        }
        if ( -e "$dir_list/main.inc/main.info" ) {
            $MAIN_PATH = "$dir_list/main.inc/main.info";
        }

        # return now if we have both
        return if ( $DAILY_PATH && $MAIN_PATH );
    }

    # If we've reached here, we didn't find {daily,main}.info.
    # There are 3 formats:
    # 1. The .cvd files
    # 2. The {daily,main}.info directories
    # 3. The .cld files
    # Here are the usual directories... no standardization
    for $dir_list (
        '/var/lib/clamav',         '/var/clamav',
        '/opt/local/share/clamav', '/usr/share/clamav',
        '/usr/local/share/clamav'
        )
    {
        if ( -e "$dir_list/daily.cvd" ) {
            $DAILY_PATH = "$dir_list/daily.cvd";
        }
        elsif ( -e "$dir_list/daily.cld" ) {
            $DAILY_PATH = "$dir_list/daily.cld";
        }
        if ( -e "$dir_list/main.cvd" ) {
            $MAIN_PATH = "$dir_list/main.cvd";
        }
        elsif ( -e "$dir_list/main.cld" ) {
            $MAIN_PATH = "$dir_list/main.cld";
        }
    }
}

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;
    $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( gettext("Operation failed.") );
        return;
    }
    splice @{ $new_slist->{data} }, $deref, 1;
    $q_label->set_text( gettext("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( gettext("Operation failed.") );
        return;
    }

    splice @{ $new_slist->{data} }, $deref, 1;
    $q_label->set_text( gettext("Deleted.") );
    @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.") );
            $slist->{data}[$number][0]
                = "<span foreground='#CCCCCC'>$virus[$number]{base}</span>";
            $slist->{data}[$number][1]
                = "<span foreground='#CCCCCC'>$virus[$number]{status}</span>";
            return;
        }
        if ( move_to_quarantine($number) ) {
            $top_label->set_text( gettext("File has been quarantined.") );
            $virus[$number]{status}
                = sprintf gettext("%s <b>(Quarantined)</b>"), $current_status;
        }
        else {
            $top_label->set_text( gettext("File could not be quarantined.") );
            return;
        }
    }
    elsif ( $do_this eq "d" ) {
        if ( not -e $virus[$number]{full} ) {
            $top_label->set_text(
                gettext("File has been moved or deleted already.") );
            $slist->{data}[$number][0]
                = "<span foreground='#CCCCCC'>$virus[$number]{base}</span>";
            $slist->{data}[$number][1]
                = "<span foreground='#CCCCCC'>$virus[$number]{status}</span>";
            return;
        }
        my $confirm_message = gettext("Really delete this file?");
        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( gettext("File has been deleted.") );
                $virus[$number]{status}
                    = sprintf gettext("%s <b>(Deleted)</b>"), $current_status;
            }
            else {
                $top_label->set_text( gettext("File could not be deleted.") );
                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]{status}</span>";
    $window->queue_draw;
}

sub show_message_dialog {

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

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

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

sub load_prefs {
    $Config = Config::Tiny->read($prefs);
    return unless $Config;
    my $changer;
    if ( $Config->{_}->{SaveToLog} ) {
        $changer = $actions->get_action( $option_entries[0]->[0] );
        $changer->set_active(TRUE);
    }
    if ( $Config->{_}->{ScanHidden} ) {
        $changer = $actions->get_action( $option_entries[1]->[0] );
        $changer->set_active(TRUE);
    }
    if ( $Config->{_}->{DisplayAll} ) {
        $changer = $actions->get_action( $option_entries[2]->[0] );
        $changer->set_active(TRUE);
    }
    if ( $Config->{_}->{FollowLinks} ) {
        $changer = $actions->get_action( $option_entries[3]->[0] );
        $changer->set_active(TRUE);
    }
    if ( $Config->{_}->{Quarantine} ) {
        $changer = $actions->get_action( $option_entries[4]->[0] );
        $changer->set_active(TRUE);
    }
    if ( $Config->{_}->{SizeLimit} ) {
        $changer = $actions->get_action( $option_entries[5]->[0] );
        $changer->set_active(TRUE);
    }
    $top_label->set_text( gettext("Loaded your scanning preferences.") );
}

sub save_prefs {
    my $display = shift;
    $Config                     = Config::Tiny->new();
    $Config->{_}->{SaveToLog}   = $save_log;
    $Config->{_}->{ScanHidden}  = $hidden;
    $Config->{_}->{DisplayAll}  = $showall;
    $Config->{_}->{FollowLinks} = $follow_symlinks;
    $Config->{_}->{Quarantine}  = $quarantine;
    $Config->{_}->{SizeLimit}   = $size_set;
    $Config->write($prefs) or do {
        $top_label->set_text( gettext("Unable to save your preferences.") );
        return;
    };

    $top_label->set_text( gettext("Your preferences were saved.") )
        if $display;
}
