###############################################################################
## OCSINVENTORY-NG
## Copyleft Pascal DANEK 2005
## Web : http://ocsinventory.sourceforge.net
##
## This code is open source and may be copied and modified as long as the source
## code is always made freely available.
## Please refer to the General Public Licence http://www.gnu.org/ or Licence.txt
################################################################################
# Function by hook:
# -download_prolog_reader, download_message, download
# -download_inventory_handler
# -download_end_handler, begin, done, clean, finish, period, download, execute,
#   check_signature and build_package
package Ocsinventory::Agent::Modules::Download;

use strict;

use Fcntl qw/:flock/;
use XML::Simple;
use LWP::UserAgent;
use Compress::Zlib;
use Digest::MD5;
use File::Path;
use Socket;

# Can be missing. By default, we use MD5
# You have to install it if you want to use SHA1 digest
eval{ require Digest::SHA1 };

#Global vars
my $ua;
my $download_config;
my @prior_pkgs;


sub new {

    my $name="download";   #Set the name of your module here

    my (undef,$context) = @_;
    my $self = {};

    # Create a special logger for the module
    $self->{logger} = new Ocsinventory::Logger ({
        config => $context->{config},
    });

    # We use the common object for the module
    $self->{common} = $context->{common};

    $self->{context} = $context;
    $self->{logger}->{header}="[$name]";
    $self->{structure} = {
        name => $name,
        start_handler => $name."_start_handler",
        prolog_writer => undef,
        prolog_reader => $name."_prolog_reader",
        inventory_handler => $name."_inventory_handler",
        end_handler => $name."_end_handler"
    };

    $self->{settings} = {
        https_port => '443',
        # Time to wait between scheduler periods, scheduling cycles and fragments downloads
        frag_latency_default     => 10,
        period_latency_default     => 0,
        cycle_latency_default     => 10,
        max_error_count            => 30,
        # Number of loops for one period
        period_lenght_default     => 10,
    };

    $self->{messages} = {
        # Errors
        code_success                     => 'SUCCESS',
        success_already_setup                => 'SUCCESS_ALREADY_SETUP',
        err_bad_id                     => 'ERR_BAD_ID',
        err_download_info                 => 'ERR_DOWNLOAD_INFO',
        err_bad_digest                     => 'ERR_BAD_DIGEST',
        err_download_pack                 => 'ERR_DOWNLOAD_PACK',
        err_build                     => 'ERR_BUILD',
        err_execute                     => 'ERR_EXECUTE',
        err_clean                     => 'ERR_CLEAN',
        err_timeout                    => 'ERR_TIMEOUT',
    };

    # Special hash for packages
    $self->{packages}= {};

    bless $self;
}

sub download_start_handler {
    my $self = shift;
    my $logger = $self->{logger};
    my $common = $self->{context}->{common};
    my $config = $self->{context}->{config};

    $logger->debug("Calling download_start_handler");

    # Disabling module if local mode
    if ($config->{stdout} || $config->{local}) {
        $self->{disabled} = 1;
        $logger->info("Agent is running in local mode...disabling module");
    }

    # If we cannot load prerequisite, we disable the module
    if ($common->can_load('LWP')) {
        my $lwp_version = $LWP::VERSION;
        $lwp_version=$self->{common}->convertVersion($lwp_version,3);
        if ($lwp_version > 583) {  #Newer LWP version
            unless ($common->can_load('LWP::Protocol::https')) {
            $self->{disabled} = 1;
            $logger->error("LWP::Protocol::https perl module is missing !!");
            $logger->error("Humm my prerequisites are not OK...disabling module :( :(");
            }
        } else {
            unless ($common->can_load('Crypt::SSLeay')) {
            $self->{disabled} = 1;
            $logger->error("Crypt::SSLeay perl module is missing !!");
            $logger->error("Humm my prerequisites are not OK...disabling module :( :(");
            }
        }
    } else {
        $self->{disabled} = 1;
        $logger->error("LWP perl module is missing !!");
        $logger->error("Humm my prerequisites are not OK...disabling module :( :(");
    }
}

sub download_prolog_reader{      #Read prolog response

    my ($self,$prolog) = @_;
    my $context = $self->{context};
    my $logger = $self->{logger};
    my $config = $self->{context}->{config};
    my $network = $self->{context}->{network};
    my $common = $self->{common};
    my $settings = $self->{settings};
    my $messages = $self->{messages};
    my $packages = $self->{packages};

    $logger->debug("Calling download_prolog_reader");
    $logger->debug($prolog);
    $prolog = XML::Simple::XMLin( $prolog, ForceArray => ['OPTION', 'PARAM']);
    my $option;
    # Create working directory
    my $opt_dir = $context->{installpath}.'/download';
    mkdir($opt_dir) unless -d $opt_dir;

    # We create a file to tell to download process that we are running
    open SUSPEND, ">$opt_dir/suspend";
    close(SUSPEND);

    # Create history file if needed
    unless(-e "$opt_dir/history"){
        open HISTORY, ">$opt_dir/history" or die("Cannot create history file: $!");
        close(HISTORY);
    }

    # Create lock file if needed
    unless(-e "$opt_dir/lock"){
        open LOCK, ">$opt_dir/lock" or die("Cannot create lock file: $!");
        close(LOCK);
    }

    # Retrieve our options
    for $option (@{$prolog->{OPTION}}){
        if ($option->{NAME} =~/download/i){
            for (@{ $option->{PARAM} } ) {
                # Type of param
                if ($_->{'TYPE'} eq 'CONF'){
                    # Writing configuration
                    open CONFIG, ">$opt_dir/config" or die("Cannot open/create config file ($opt_dir/config)");
                    if (flock(CONFIG, LOCK_EX)){
                        $logger->debug("Writing config file.");
                        print CONFIG XMLout($_, RootName => 'CONF');
                        close(CONFIG);
                        $download_config = $_;
                    } else {
                        $logger->error("Cannot lock config file !!");
                        close(CONFIG);
                        return 0;
                    }

                    # Apply config
                    # ON ?
                    if ($_->{'ON'} == '0'){
                        $logger->info("Download is off.");
                        open LOCK, "$opt_dir/lock" or die("Cannot open lock file: $!");
                        if (flock(LOCK, LOCK_EX|LOCK_NB)){
                            close(LOCK);
                            unlink("$opt_dir/suspend");
                            return 0;
                        } else {
                            $logger->debug("Try to kill current download process...");
                            my $pid = <LOCK>;
                            close(LOCK);
                            $logger->debug("Sending USR1 to $pid...");
                            if (kill("USR1", $pid)){
                                $logger->debug("Success.");
                            } else {
                                $logger->debug("Failed.");
                            }
                            return 0;
                        }
                    }
                    # Maybe a new package to download
                } elsif ($_->{'TYPE'} eq 'PACK'){
                    $packages->{$_->{'ID'}} = {
                        'PACK_LOC' => $_->{'PACK_LOC'},
                        'INFO_LOC' => $_->{'INFO_LOC'},
                        #'ID' => $_->{'ID'},
                        'CERT_PATH' => $_->{'CERT_PATH'},
                        'CERT_FILE' => $_->{'CERT_FILE'},
                        'FORCE' => $_->{'FORCE'}
                    };
                }
            }
        }
    }
    # We are now in download child
    # Connect to server
    if ($context->{network}) {
        $ua = $context->{network}->{ua};
    } else {
        $logger->info("Cannot find network settings to make this module works properly...disabling module");
        $self->{disabled} = 1;
    }

    # Check history file
    unless(open HISTORY, "$opt_dir/history") {
        flock(HISTORY, LOCK_EX);
        unlink("$opt_dir/suspend");
        $logger->error("Cannot read history file: $!");
        return 1;
    }

    chomp(my @done = <HISTORY>);
    close(HISTORY);

    # Package is maybe already handled
    for (keys %$packages){
        my $dir = $opt_dir."/".$_;
        my $fileid = $_;
        my $infofile = 'info';
        my $location = $packages->{$_}->{'INFO_LOC'};

        unless ($packages->{$_}->{'FORCE'} == 1) {
            if($common->already_in_array($fileid, @done)){
                $logger->info("Will not download $fileid. (already in history file)");
                &download_message($fileid, $messages->{success_already_setup},$logger,$context);
                next;
            }
        }

        # Looking for packages status
        unless(-d $dir){
            $logger->debug("Making working directory for $fileid.");
            mkdir($dir) or die("Cannot create $fileid directory: $!");
            open FH, ">$dir/since" or die("Cannot create $fileid since file: $!");;
            print FH time();
            close(FH);
        }

        # Retrieve and writing info file if needed
        unless(-f "$dir/$infofile"){
            # Special value INSTALL_PATH
            $packages->{$_}->{CERT_PATH} =~ s/INSTALL_PATH/$context->{installpath}/;
            $packages->{$_}->{CERT_FILE} =~ s/INSTALL_PATH/$context->{installpath}/;

            # Getting info file
            if ($network->getFile("https","$location/$fileid","info","$dir/info")){
                download_message($fileid, $self->{messages}->{err_download_info},$logger,$context);
                $logger->error("Error download info file !!! Wrong URL or SSL certificate ?");
                next;
            }
        }
    }
    unless(unlink("$opt_dir/suspend")){
        $logger->error("Cannot delete suspend file: $!");
        return 1;
    }
    return 0;
}

sub ssl_verify_callback {
    my ($ok, $x509_store_ctx) = @_;
    return $ok;
}

sub download_inventory_handler{          # Adding the ocs package ids to softwares
    my ($self,$inventory) = @_;
    my $context = $self->{context};
    my $logger = $self->{logger};

    $logger->debug("Calling download_inventory_handler");

    my @history;

    # Read download history file
    if ( open PACKAGES, "$context->{installpath}/download/history" ){
        flock(PACKAGES, LOCK_SH);
        while(<PACKAGES>){
            chomp( $_ );
            push @history, { ID => $_ };
        }
    }
    close(PACKAGES);

    # Add it to inventory (will be handled by Download.pm server module
    push @{ $inventory->{xmlroot}->{'CONTENT'}->{'DOWNLOAD'}->{'HISTORY'} },{
        'PACKAGE'=> \@history
    };
}

sub download_end_handler{        # Get global structure

    my $self = shift;
    my $context = $self->{context};
    my $logger = $self->{logger};
    my $common = $self->{common};
    my $settings = $self->{settings};
    my $messages = $self->{messages};
    my $packages = $self->{packages};

    $logger->debug("Calling download_end_handler");

    my $dir = $context->{installpath}."/download";
    my $pidfile = $dir."/lock";

    return 0 unless -d $dir;

    # We have jobs, we do it alone
    my $fork = fork();
    if ($fork>0){
        return 0;
    } elsif ($fork<0){
        return 1;
    } else {
        $SIG{'USR1'} = sub {
            print "Exiting on signal...\n";
            &finish($logger, $context);
        };
        # Go into working directory
        chdir($dir) or die("Cannot chdir to working directory...Abort\n");
    }

    # Maybe an other process is running
    exit(0) if begin($pidfile,$logger);
    # Retrieve the packages to download
    opendir DIR, $dir or die("Cannot read working directory: $!");

    my $end;

    while(1){
        # If agent is running, we wait
        if (-e "suspend") {
            $logger->debug('Found a suspend file... Will wait 10 seconds before retry');
            sleep(10);
            next;
        }

        $end = 1;

        #TODO Uncomment this line #undef $packages;

        # Reading configuration
        open FH, "$dir/config" or die("Cannot read config file: $!");
        if (flock(FH, LOCK_SH)){
            $download_config = XMLin("$dir/config");
            close(FH);
            # If Frag latency is null, download is off
            if ($download_config->{'ON'} eq '0'){
                $logger->info("Option turned off. Exiting.");
                finish($logger, $context);
            }
        } else {
            $logger->error("Cannot read config file :-( . Exiting.");
            close(FH);
            finish($logger, $context);
        }

        # Retrieving packages to download and their priority
        while (my $entry = readdir(DIR)){
            next if $entry !~ /^\d+$/;
            next unless(-d $entry);

            # Clean package if info file does not still exist
            unless(-e "$entry/info"){
                $logger->debug("No info file found for $entry!!");
                clean( $entry, $logger, $context, $messages, $packages );
                next;
            }
            my $info = XML::Simple::XMLin( "$entry/info" ) or next;

            # Check that fileid == directory name
            if ($info->{'ID'} ne $entry){
                $logger->debug("ID in info file does not correspond!!");
                clean( $entry, $logger, $context, $messages, $packages );
                download_message($entry, $messages->{err_bad_id},$logger,$context);
                next;
            }

            # Manage package timeout
            # Clean package if since timestamp is not present
            unless(-e "$entry/since"){
                $logger->debug("No since file found!!");
                clean($entry, $logger, $context,$messages,$packages );
                next;
            } else {
                my $time = time();
                if (open SINCE, "$entry/since"){
                    my $since = <SINCE>;
                    if ($since=~/\d+/){
                        if ((($time-$since)/86400) > $download_config->{TIMEOUT}){
                            $logger->error("Timeout Reached for $entry.");
                            clean($entry, $logger, $context,$messages,$packages );
                            &download_message($entry, $messages->{err_timeout},$logger,$context);
                            close(SINCE);
                            next;
                        } else {
                            $logger->debug("Checking timeout for $entry... OK");
                        }
                    } else {
                        $logger->error("Since data for $entry is incorrect.");
                        clean($entry, $logger, $context, $messages, $packages );
                        &download_message($entry, $messages->{err_timeout},$logger,$context);
                        close(SINCE);
                        next;
                    }
                    close(SINCE);
                } else {
                    $logger->error("Cannot find since data for $entry.");
                    clean($entry, $logger, $context, $messages, $packages );
                    &download_message($entry, $messages->{err_timeout},$logger,$context);
                    next;
                }
            }

            # Building task file if needed
            unless( -f "$entry/task" and -f "$entry/task_done" ){
                open FH, ">$entry/task" or die("Cannot create task file for $entry: $!");

                my $i;
                my $frags = $info->{'FRAGS'};
                # There are no frags if there is only a command
                if ($frags){
                    for ($i=1;$i<=$frags;$i++){
                        print FH "$entry-$i\n";
                    }
                };
                close FH;
                # To be sure that task file is fully created
                open FLAG, ">$entry/task_done" or die ("Cannot create task flag file for $entry: $!");
                close(FLAG);
            }
            # Store info XML descriptions in package attributes
            for (keys %$info){
                $packages->{$entry}->{$_} = $info->{$_}
            }
            $end = 0;
        }
        # Rewind directory
        rewinddir(DIR);
        # Call packages scheduler
        if ($end){
            last;
        } else {
            period($packages,$logger,$context,$self->{messages},$settings);
        }
    }
    $logger->info("No more package to download.");
    finish($logger, $context);
}

# Schedule the packages
sub period{
    my ($packages,$logger,$context,$messages,$settings) = @_ ;

    my $period_lenght_default = $settings->{period_lenght_default} ;
    my $frag_latency_default= $settings->{frag_latency_default} ;
    my $cycle_latency_default= $settings->{cycle_latency_default} ;
    my $period_latency_default= $settings->{period_latency_default} ;

    my $i;

    #Serching packages with the priority 0
    for (keys %$packages) {
        if ($packages->{$_}->{'PRI'} eq "0") {
            push (@prior_pkgs,$_);
        }
    }

    $logger->debug("New period. Nb of cycles: ".
    (defined($download_config->{'PERIOD_LENGTH'})?$download_config->{'PERIOD_LENGTH'}:$period_lenght_default));

    for ($i=1;$i<=( defined($download_config->{'PERIOD_LENGTH'})?$download_config->{'PERIOD_LENGTH'}:$period_lenght_default);$i++){
        # Highest priority
        if (@prior_pkgs){
            $logger->debug("Managing ".scalar(@prior_pkgs)." package(s) with absolute priority.");
            for (@prior_pkgs){
                # If done file found, clean package
                if (-e "$_/done"){
                    $logger->debug("done file found!!");
                    done($_,$logger,$context,$messages,$settings,$packages);
                    next;
                }
                download($_,$logger,$context,$messages,$settings,$packages);
                $logger->debug("Now pausing for a fragment latency => ".(
                    defined($download_config->{'FRAG_LATENCY'})?$download_config->{'FRAG_LATENCY'}:$frag_latency_default)
                    ." seconds");
                sleep( defined($download_config->{'FRAG_LATENCY'})?$download_config->{'FRAG_LATENCY'}:$frag_latency_default );
            }
            next;
        }

        # Normal priority

        for (keys %$packages){
            # If done file found, clean package
            if(-e "$_/done"){
                $logger->debug("done file found!!");
                done($_,$logger,$context,$messages,$settings,$packages);
                next;
            }
            next if $i % $packages->{$_}->{'PRI'} != 0;
            download($_,$logger,$context,$messages,$settings,$packages);

            $logger->debug("Now pausing for a fragment latency => ".
            (defined( $download_config->{'FRAG_LATENCY'} )?$download_config->{'FRAG_LATENCY'}:$frag_latency_default)
            ." seconds");

            sleep(defined($download_config->{'FRAG_LATENCY'})?$download_config->{'FRAG_LATENCY'}:$frag_latency_default);
        }

        $logger->debug("Now pausing for a cycle latency => ".(
        defined($download_config->{'CYCLE_LATENCY'})?$download_config->{'CYCLE_LATENCY'}:$cycle_latency_default)
        ." seconds");

        sleep(defined($download_config->{'CYCLE_LATENCY'})?$download_config->{'CYCLE_LATENCY'}:$cycle_latency_default);
    }
    sleep($download_config->{'PERIOD_LATENCY'}?$download_config->{'PERIOD_LATENCY'}:$period_latency_default);
}

# Download a fragment of the specified package
sub download {
    my ($id,$logger,$context,$messages,$settings,$packages) = @_;

    my $error;
    my $proto = $packages->{$id}->{'PROTO'};
    my $location = $packages->{$id}->{'PACK_LOC'};
    my $network = $context->{network};

    # If we find a temp file, we know that the update of the task file has failed for any reason. So we retrieve it from this file
    if (-e "$id/task.temp") {
        unlink("$id/task.temp");
        rename("$id/task.temp","$id/task") or return 1;
    }

    # Retrieve fragments already downloaded
    unless(open TASK, "$id/task"){
        $logger->error("Cannot open $id/task.");
        return 1;
    }
    my @task = <TASK>;

    # Done
    if (!@task){
        $logger->debug("Download of $id... Finished.");
        close(TASK);
        execute($id,$logger,$context,$messages,$settings,$packages);
        return 0;
    }

    my $fragment = shift(@task);

    $logger->debug("Downloading $fragment...");

    # Using proxy if possible
    my $res = $network->getFile(lc($proto),"$location/$id",$fragment,"$id/$fragment");

    # Checking if connected
    unless($res) {
        #Success
        $error = 0;

        # Updating task file
        rename(">$id/task", ">$id/task.temp");
        open TASK, ">$id/task" or return 1;
        print TASK @task;
        close(TASK);
        unlink(">$id/task.temp");
    } else {
        $error++;
        if ($error > $settings->{max_error_count}){
            $logger->error("Error : Max errors count reached");
            finish($logger,$context);
        }
        return 1;
    }
    return 0;
}

# Assemble and handle downloaded package
sub execute{
    my ($id,$logger,$context,$messages,$settings,$packages) = @_;

    my $common = $context->{common};

    my $tmp = $id."/tmp";
    my $exit_code;

    $logger->debug("Execute orders for package $id.");

    if (build_package($id,$logger,$context,$messages,$packages)){
        clean($id,$logger, $context,$messages,$packages);
        return 1;
    } else {
        # First, we get in temp directory
        unless( chdir($tmp) ){
            $logger->error("Cannot chdir to working directory: $!");
            download_message($id, $messages->{err_execute}, $logger,$context);
            clean($id,$logger, $context,$messages,$packages);
            return 1;
        }

        # Executing preorders (notify user, auto launch, etc....
        # $id->{NOTIFY_USER}
        # $id->{NOTIFY_TEXT}
        # $id->{NOTIFY_COUNTDOWN}
        # $id->{NOTIFY_CAN_ABORT}
        # TODO: notification to send through DBUS to the user

        eval{
            # Execute instructions
            if ($packages->{$id}->{'ACT'} eq 'LAUNCH'){
                my $exe_line = $packages->{$id}->{'NAME'};
                $packages->{$id}->{'NAME'} =~ s/^([^ -]+).*/$1/;
                # Exec specified file (LAUNCH => NAME)
                if (-e $packages->{$id}->{'NAME'}){
                    $logger->debug("Launching $packages->{$id}->{'NAME'}...");
                    chmod(0755, $packages->{$id}->{'NAME'}) or die("Cannot chmod: $!");
                    $exit_code = system( "./".$exe_line ) >> 8;
                } else {
                    die();
                }

            } elsif ($packages->{$id}->{'ACT'} eq 'EXECUTE'){
                # Exec specified command EXECUTE => COMMAND
                $logger->debug("Execute $packages->{$id}->{'COMMAND'}...");
                system( $packages->{$id}->{'COMMAND'} ) and die();

            } elsif ($packages->{$id}->{'ACT'} eq 'STORE'){
                # Store files in specified path STORE => PATH
                $packages->{$id}->{'PATH'} =~ s/INSTALL_PATH/$context->{installpath}/;

                # Build it if needed
                my @dir = split('/', $packages->{$id}->{'PATH'});
                my $dir;

                for (@dir){
                    $dir .= "$_/";
                    unless(-e $dir){
                        mkdir($dir);
                        $logger->debug("Create $dir...");
                    }
                }

                $logger->debug("Storing package to $packages->{$id}->{'PATH'}...");
                # Stefano Brandimarte => Stevenson! <stevens@stevens.it>
                system($common->get_path('cp')." -pr * ".$packages->{$id}->{'PATH'}) and die();
            }
        };
        if ($@){
            # Notify success to ocs server
            download_message($id, $messages->{err_execute},$logger,$context);
            chdir("../..") or die("Cannot go back to download directory: $!");
            clean($id,$logger,$context,$messages,$packages);
            return 1;
        } else {
            chdir("../..") or die("Cannot go back to download directory: $!");
            done($id,$logger,$context,$messages,$settings,$packages,(defined($exit_code)?$exit_code:'_NONE_'));
            return 0;
        }
    }
}

# Check package integrity
sub build_package{
    my ($id,$logger,$context,$messages,$packages) = @_;

    my $common = $context->{common};

    my $count = $packages->{$id}->{'FRAGS'};
    my $i;
    my $tmp = "./$id/tmp";

    unless(-d $tmp){
        mkdir("$tmp");
    }
    # No job if no files
    return 0 unless $count;

    # Assemble package
    $logger->info("Building package for $id.");

    for ($i=1;$i<=$count;$i++){
        if (-f "./$id/$id-$i"){
            # We make a tmp working directory
            if ($i==1){
                open PACKAGE, ">$tmp/build.tar.gz" or return 1;
            }
            # We write each fragment in the final package
            open FRAGMENT, "./$id/$id-$i" or return 1;
            my $row;
            while ($row = <FRAGMENT>){
                print PACKAGE $row;
            }
            close(FRAGMENT);
        } else {
            return 1;
        }
    }
    close(PACKAGE);
    #
    if (check_signature($packages->{$id}->{'DIGEST'}, "$tmp/build.tar.gz", $packages->{$id}->{'DIGEST_ALGO'}, $packages->{$id}->{'DIGEST_ENCODE'},$logger)){
        download_message($id, $messages->{err_bad_digest},$logger,$context);
        return 1;
    }

    if ( system( $common->get_path("tar")." -xvzf $tmp/build.tar.gz -C $tmp") ){
        $logger->error("Cannot extract $id with tar, trying with unzip.");
        if ( system( $common->get_path("unzip")." $tmp/build.tar.gz -d $tmp") ){
            $logger->error("Cannot extract $id with unzip.");
            download_message($id,$messages->{err_build},$logger,$context);
            return 1;
        }
    }
    $logger->debug("Building of $id... Success.");
    unlink("$tmp/build.tar.gz") or die ("Cannot remove build file: $!\n");
    return 0;
}

sub check_signature{
    my ($checksum, $file, $digest, $encode,$logger) = @_;

    $logger->info("Checking signature for $file.");

    my $base64;

    # Open file
    unless(open FILE, $file){
        $logger->error("cannot open $file: $!");
        return 1;
    }

    binmode(FILE);
    # Retrieving encoding form
    if ($encode =~ /base64/i){
        $base64 = 1;
        $logger->debug('Digest format: Base 64');
    } elsif ($encode =~ /hexa/i){
        $logger->debug('Digest format: Hexadecimal');
    } else {
        $logger->debug('Digest format: Not supported');
        return 1;
    }

    eval{
        # Check it
        if ($digest eq 'MD5'){
            $logger->debug('Digest algo: MD5');
            if ($base64){
                die unless Digest::MD5->new->addfile(*FILE)->b64digest eq $checksum;
            } else {
                die unless Digest::MD5->new->addfile(*FILE)->hexdigest eq $checksum;
            }
        } elsif ($digest eq 'SHA1'){
            $logger->debug('Digest algo: SHA1');
            if ($base64){
                die unless Digest::SHA1->new->addfile(*FILE)->b64digest eq $checksum;
            } else {
                die unless Digest::SHA1->new->addfile(*FILE)->hexdigest eq $checksum;
            }
        } else {
            $logger->debug('Digest algo unknown: '.$digest);
            die;
        }
    };
    if ($@){
        $logger->debug("Digest checking error !!");
        close(FILE);
        return 1;
    } else {
        close(FILE);
        $logger->debug("Digest OK...");
        return 0;
    }
}

# Launch a download error to ocs server
sub download_message{
    my ($id, $code,$logger,$context) = @_;

    $logger->debug("Sending message for $id, code=$code.");

    my $xml = {
        'DEVICEID' => $context->{deviceid},
        'QUERY' => 'DOWNLOAD',
        'ID' => $id,
        'ERR' => $code
    };

    # Generate xml
    $xml = XMLout($xml, RootName => 'REQUEST');

    # Compress data
    $xml = Compress::Zlib::compress( $xml );

    my $URI = $context->{servername};

    # Send request
    my $request = HTTP::Request->new(POST => $URI);
    $request->header('Pragma' => 'no-cache', 'Content-type', 'application/x-compress');
    $request->content($xml);
    my $res = $ua->request($request);

    # Checking result
    if ($res->is_success) {
        return 0;
    }else{
        return 1;
    }
}

# At the beginning of end handler
sub begin{
    my ($pidfile,$logger) = @_;

    open LOCK_R, "$pidfile" or die("Cannot open pid file: $!");
    if (flock(LOCK_R,LOCK_EX|LOCK_NB)){
        open LOCK_W, ">$pidfile" or die("Cannot open pid file: $!");
        select(LOCK_W) and $|=1;
        select(STDOUT) and $|=1;
        print LOCK_W $$;
        $logger->info("Beginning work. I am $$.");
        return 0;
    } else {
        close(LOCK_R);
        $logger->error("$pidfile locked. Cannot begin work... :-(");
        return 1;
    }
}

sub done{
    my ($id,$logger,$context,$messages,$settings,$packages,$suffix) = @_;

    my $common = $context->{common};

    my $frag_latency_default = $settings->{frag_latency_default};

    $logger->debug("Package $id... Done. Sending message...");
    # Trace installed package
    open DONE, ">$id/done";
    close(DONE);

    # Read history file
    open HISTORY,"$context->{installpath}/download/history" or warn("Cannot open history file: $!");
    chomp(my @historyIds = <HISTORY>);
    close(HISTORY);

    # Put it in history file
    open HISTORY,">>$context->{installpath}/download/history" or warn("Cannot open history file: $!");
    flock(HISTORY, LOCK_EX);

    if ( $common->already_in_array($id, @historyIds) ){
        $logger->debug("Warning: ID $id has been found in the history file (package was already deployed)");
    } else {
        $logger->debug("Writing $id reference in history file");
        print HISTORY $id,"\n";
    }
    close(HISTORY);

    # Notify success to ocs server
    my $code;
    if ($suffix ne '_NONE_'){
        $code = $messages->{code_success}."_$suffix";
    } else {
        $code = $messages->{code_success};
    }
    unless(download_message($id, $code,$logger,$context)){
        clean($id,$logger,$context,$messages,$packages);
    } else {
        sleep( defined($download_config->{'FRAG_LATENCY'})?$download_config->{'FRAG_LATENCY'}:$frag_latency_default );
    }
    return 0;
}

sub clean{
    my ($id,$logger,$context,$messages,$packages) = @_;

    $logger->info("Cleaning $id package.");

    delete $packages->{$id};

    #If the package is priority 0
    if ((my $index) = grep { $prior_pkgs[$_] eq $id } 0..$#prior_pkgs){
        delete $prior_pkgs[$index];
    }

    unless(File::Path::rmtree($id, 0)){
        $logger->error("Cannot clean $id!! Abort...");
        download_message($id, $messages->{err_clean},$logger,$context);
        die();
    }
    return 0;
}

# At the end
sub finish{
    my ($logger,$context) = @_;

    open LOCK, '>'.$context->{installpath}.'/download/lock';
    $logger->debug("End of work...\n");
    exit(0);
}

1;
