#!/usr/bin/perl

#######################################################################
#
# argonaut-server -- Json-rpc server for deployment
#
# Copyright (C) 2011-2014 FusionDirectory project
#
# Author: Côme BERNIGAUD
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>
#
#######################################################################

#######################################################################
#  The wakeonlan part is taken from FusionInventory
#  Copyright (C) 2011  FusionInventory
#######################################################################

use strict;
use warnings;

use 5.010;

use Argonaut::Libraries::Common qw(:ldap :config :file);
use Argonaut::Libraries::Packages qw(get_packages_info store_packages_file);

use POE qw(Component::Schedule Component::Server::JSONRPC::Http);
use DateTime;
use DateTime::Set;
use Data::Dumper;
use Net::LDAP;
use if (USE_LEGACY_JSON_RPC),     'JSON::RPC::Legacy::Client';
use if not (USE_LEGACY_JSON_RPC), 'JSON::RPC::Client';
use JSON;
use File::Path;
use Log::Handler;
use App::Daemon qw(daemonize);

# where to look for modules files
use Module::Pluggable search_path => 'Argonaut::Server::Modules', sub_name => 'modules', instantiate => 'new', except => 'Argonaut::Server::Modules::Argonaut';
use Argonaut::Server::Modules::Argonaut;
use Argonaut::Server::ModulesPool;

use constant ETH_P_ALL => 0x0003;
use constant PF_PACKET => 17;
use constant SOCK_PACKET => 10;

use English qw(-no_match_vars);
use Socket;

our ($config,$protocol,$server_ip,$server_port);
my ($sslkeyfile,$sslcertfile,$iptool,$delete_finished_tasks,
    $interface,$logdir,$packages_folder,$fetch_packages);
my $logfile = "argonaut-server.log";
my $piddir = "/var/run/argonaut";
my $pidfile = "argonaut-server.pid";

readConfig();

argonaut_create_dir($logdir);

our $log = Log::Handler->create_logger("argonaut-server");

$App::Daemon::pidfile = "$piddir/$pidfile";
$App::Daemon::logfile = "$logdir/$logfile";
$App::Daemon::as_user = "root";

argonaut_create_dir($piddir);

daemonize();

use Log::Log4perl qw(:levels);
$log->add(
    file => {
        filename => "$logdir/$logfile",
        maxlevel => ($App::Daemon::loglevel == $DEBUG?"debug":"info"),
        minlevel => "emergency",
        newline  => 1,
    }
);

sub readConfig {
  $config = argonaut_read_config;
  my $settings = argonaut_get_server_settings($config,$config->{'server_ip'});

  $protocol               = $settings->{'protocol'};
  $server_port            = $settings->{'port'};
  $iptool                 = $settings->{'iptool'};
  $delete_finished_tasks  = ($settings->{'delete_finished_tasks'} eq "TRUE");
  $fetch_packages         = ($settings->{'fetch_packages'} eq "TRUE");
  $interface              = $settings->{'interface'};
  $logdir                 = $settings->{'logdir'};
  $sslkeyfile             = $settings->{'keyfile'};
  $sslcertfile            = $settings->{'certfile'};

  $packages_folder        = "/var/cache/argonaut/packages";
}

sub getIpFromMac {
  my ($mac) = @_;

  my ($ldap,$ldap_base) = argonaut_ldap_handle($config);

  my $mesg = $ldap->search( # perform a search
            base   => $ldap_base,
            filter => "(&(macAddress=$mac)(ipHostNumber=*))",
                        attrs => [ 'ipHostNumber' ]
            );

  $mesg->code && die "Error while searching IP for mac address '$mac' :".$mesg->error;

  if(scalar($mesg->entries)>1) {
    $log->error("Multiple IPs were found for the Mac address '$mac'!");
    die "Multiple IPs were found for the Mac address '$mac'!";
  } elsif(scalar($mesg->entries)<1) {
    $log->error("No IPs were found for the Mac address '$mac'!");
    die "No IPs were found for the Mac address '$mac'!";
  }

  my $ip = ($mesg->entries)[0]->get_value("ipHostNumber");

  $mesg = $ldap->unbind;   # take down session

  return $ip;
}

=pod
=item getMacFromCn
Search in the ldap for the Mac associated to a host name.
Parameter : the host name
=cut
sub getMacFromCn {
  my ($host, $filter) = @_;

  my ($ldap,$ldap_base) = argonaut_ldap_handle($config);

  my $mesg = $ldap->search( # perform a search
            base   => $ldap_base,
            filter => "(&(cn=$host)(macAddress=*)$filter)",
                        attrs => [ 'macAddress' ]
            );

  $mesg->code && die "Error while searching mac for host '$host' :".$mesg->error;

  if(scalar($mesg->entries)>1) {
    $log->error("Multiple mac addresses were found for the host '$host'!");
    die "Multiple mac addresses were found for the host '$host'!";
  } elsif(scalar($mesg->entries)<1) {
    $log->error("No macs was found for the host '$host'!");
    die "No mac address was found for the host '$host'!";
  }

  my $mac = ($mesg->entries)[0]->get_value("macAddress");

  $mesg = $ldap->unbind;   # take down session

  return $mac;
}

=pod
=item wakeOnLan
Send a wake on lan package to a mac address
Parameter : the mac address
inspired by WakeOnLan.pm file from FusionInventory
=cut
sub wakeOnLan {
    my ($macaddress) = @_;

    $log->info("wake on lan");

    return unless defined $macaddress;

    $macaddress =~ s/://g;

    ###  for LINUX ONLY ###
    if ( eval { socket(SOCKET, PF_PACKET, SOCK_PACKET, getprotobyname('tcp')) or $log->info($!) and 0; }) {

        setsockopt(SOCKET, SOL_SOCKET, SO_BROADCAST, 1)
            or warn "Can't do setsockopt: $ERRNO\n";

        open my $handle, '-|', "$iptool -a $interface"
            or die "Can't run $iptool: $ERRNO";
        while (my $line = <$handle>) {
            next unless $line =~ /$interface \s+ Link \s \S+ \s+ HWaddr \s (\S+)/x;
            my $netMac = $1;
            $log->info("Send magic packet to $macaddress directly on card driver");
            $netMac =~ s/://g;

            my $magic_packet =
                (pack('H12', $macaddress)) .
                (pack('H12', $netMac)) .
                (pack('H4', "0842"));
            $magic_packet .= chr(0xFF) x 6 . (pack('H12', $macaddress) x 16);
            my $destination = pack("Sa14", 0, $interface);
            send(SOCKET, $magic_packet, 0, $destination)
                or warn "Couldn't send packet on $interface: $ERRNO\n";
        }
        close $handle;
        # TODO : For FreeBSD, send to /dev/bpf ....
    } else { # degraded wol by UDP
        if ( eval { socket(SOCKET, PF_INET, SOCK_DGRAM, getprotobyname('udp')); }) {
            my $magic_packet =
                chr(0xFF) x 6 .
                (pack('H12', $macaddress) x 16);
            my $sinbroadcast = sockaddr_in("9", inet_aton("255.255.255.255"));
            $log->info("Send magic packet to $macaddress in UDP mode (degraded wol)");
            send(SOCKET, $magic_packet, 0, $sinbroadcast);
        } else {
            $log->error("Impossible to send magic packet...");
        }
    }
}

sub convert_entry {
    my ($entry,$id) = @_;
    my $res = {};
    $res->{$_} = $entry->{$_} for ('action','data','target','status','substatus','progress','error');
    $res->{id} = $id;
    return $res;
}

sub ldap_authenticate {
  my ($login, $password) = @_;
  if (!defined($login) || !defined($password)) {
    return 0;
  }
  my $ldapinfos = argonaut_ldap_init ($config->{'ldap_configfile'}, 0, $login, 0, $password, 0, $config->{'ldap_tls'});

  if ( $ldapinfos->{'ERROR'} > 0) {
    return 0;
  }

  return 1;
}

sub refresh_task {
  my ($kernel,$heap,$session,$id) = @_;
  if (defined $heap->{tasks}->{$id}->{'handler'}) {
    $heap->{tasks}->{$id} = $heap->{tasks}->{$id}->{'handler'}->update_task($heap->{tasks}->{$id});
  }
  if($heap->{tasks}->{$id}->{progress} >= 100) {
    $kernel->call($session => 'set_task_processed' => $id);
  }
}

POE::Session->create(
    inline_states => {
        _start => sub {
            $_[KERNEL]->sig( INT => "sigint", ("sigint"));
            $_[KERNEL]->sig( TERM => "sigint", ("sigterm"));
            $_[KERNEL]->sig( KILL => "sigint", ("sigkill"));
            $_[KERNEL]->sig( HUP => "sighup" );
            $_[HEAP]{tasks} = {};
            $_[HEAP]{id} = 0;

            $_[HEAP]{jsonserver} = POE::Component::Server::JSONRPC::Http->new(
                json    => JSON->new->utf8,
                Port    => $server_port,
                Handler => {
                    'echo' => 'echo',
                    'ping' => 'ping',
                    'action' => 'add',
                    'get_entries' => 'get_entries_by_id',
                    'get_entries_by_id' => 'get_entries_by_id',
                    'get_entries_by_mac' => 'get_entries_by_mac',
                    'remove_entries' => 'remove_entries',
                    'process_entries_now' => 'process_entries',
                    'get_my_id' => 'id_of_mac',
                    'get_host_id' => 'id_of_host',
                    'set_task_substatus' => 'jsonrpc_set_task_substatus',
                    'set_error' => 'set_error',
                    'get_packages' => 'get_packages'
                },
                ($protocol eq 'https')  ? ( SslKey  => $sslkeyfile,
                                            SslCert => $sslcertfile,
                                            Authenticate => \&ldap_authenticate,)
                                        : ()
            );
            $_[HEAP]{modulepool} = Argonaut::Server::ModulesPool->new(
            );
            $_[HEAP]{scheduled_only} = [
                "Deployment.update",
                "Deployment.reinstall",
                "Deployment.memcheck",
                "detect_hardware"
                ];
            #~ $_[KERNEL]->yield(add=>undef,undef,"job_trigger_action_halt",["00:11:22:33:44:55"],{timestamp=>1314107700});
            $log->notice("Argonaut server launched on port $server_port");
            if ($fetch_packages) {
                $_[HEAP]{crawler} = POE::Component::Schedule->add(
                    $_[SESSION],
                    packages_crawler => DateTime::Set->from_recurrence(
                                    after      => DateTime->now,
                                    recurrence => sub {
                                        return $_[0]->add( days => 1 )
                                    },
                                ));
                $_[KERNEL]->yield("packages_crawler");
            }
        },
        echo => sub {
            my ($kernel, $jsonrpc, $id, @params) = @_[KERNEL, ARG0..$#_ ];
            $log->info("echo (usually means server has been pinged)");
            $kernel->post( $jsonrpc => 'result' => $id, @params );
        },
        ping => sub {
            my ($kernel, $session, $heap, $jsonrpc, $id, @params) = @_[KERNEL,SESSION,HEAP, ARG0..$#_ ];
            my ($mac) = @params;
            $log->info("ping $mac");
            $kernel->yield(add => $jsonrpc, $id, 'ping', [$mac], {'args' => []});
        },
        id_of_mac => sub {
            my ($kernel, $heap, $jsonrpc, $id, @params) = @_[KERNEL,HEAP, ARG0..$#_ ];
            my ($mac) = @params;

            $log->info("searching taskid for $mac");

            while (my ($taskid,$entry) = each(%{$heap->{tasks}})) {
                if(($entry->{target} eq $mac)&&($entry->{status} eq "processing")) {
                    $heap->{tasks}->{$taskid}->{substatus} = "";
                    $kernel->post( $jsonrpc => 'result' => $id, $taskid);
                    return;
                }
            }
            $kernel->post( $jsonrpc => 'error' => $id, "Mac address '$mac' was not found in queue");
        },
        id_of_host => sub {
            my ($kernel, $heap, $session, $jsonrpc, $id, @params) = @_[KERNEL,HEAP,SESSION, ARG0..$#_ ];
            my ($host, $filter) = @params;

            $log->info("searching taskid for $host $filter");

            my $mac = getMacFromCn($host, $filter);

            $kernel->call($session => 'id_of_mac' => $jsonrpc, $id, $mac);
        },
        jsonrpc_set_task_substatus => sub {
            my ($kernel, $heap, $session, $jsonrpc, $id, @params) = @_[KERNEL,HEAP,SESSION, ARG0..$#_ ];
            my ($taskid,$substatus,$progress) = @params;

            if(!defined $heap->{tasks}->{$taskid}) {
              $kernel->post( $jsonrpc => 'error' => $id, "This task does not exists");
              return;
            }

            $kernel->call($session => 'set_task_substatus' => $taskid,$substatus,$progress);

            $kernel->post($jsonrpc => 'result' => $id, "ok");
        },
        set_task_substatus => sub {
          my ($kernel, $heap, $session, @params) = @_[KERNEL,HEAP,SESSION, ARG0..$#_ ];
          my ($taskid,$substatus,$progress) = @params;

          if (defined $progress) {
            $log->info("setting task $taskid substatus to '$substatus' ($progress %)");
          } else {
            $log->info("setting task $taskid substatus to '$substatus'");
          }

          $heap->{tasks}->{$taskid}->{substatus} = $substatus;
          if (defined $progress) {
            $heap->{tasks}->{$taskid}->{progress} = $progress;
            if ($progress >= 100) {
              $kernel->call($session => 'set_task_processed' => $taskid);
            }
          }
        },
        set_error => sub {
            my ($kernel, $heap, $session, $jsonrpc, $id, @params) = @_[KERNEL,HEAP,SESSION, ARG0..$#_ ];
            my ($taskid,$error) = @params;

            $log->info("setting task $taskid error to '$error'");

            if(!defined $heap->{tasks}->{$taskid}) {
                $kernel->post( $jsonrpc => 'error' => $id, "This task does not exists");
            } else {
                $heap->{tasks}->{$taskid}->{status} = "error";
                $heap->{tasks}->{$taskid}->{error} = $error;
                $kernel->post( $jsonrpc => 'result' => $id, "ok");
            }
        },
        set_task_handler => sub {
          my ($heap,$taskid,$handler) = @_[HEAP,ARG0 .. $#_];
          $heap->{tasks}->{$taskid}->{handler} = $handler;
        },
        schedule => sub {
          my ($kernel,$session,$heap,$date,$action,$target,$data) = @_[KERNEL,SESSION,HEAP,ARG0 .. $#_];
          my $taskid = $kernel->call($session => 'get_new_task_id');
          $heap->{tasks}->{$taskid} = {
            handle    => POE::Component::Schedule->add($session, action => $date, ($taskid,$action,$target,$data)),
            date      => $date,
            action    => $action,
            target    => $target,
            data      => $data,
            status    => "waiting",
            substatus => "",
            progress  => 0,
            error     => ""
          };
          $log->debug("action $action scheduled on target $target");
        },
        add => sub {
          my ($kernel, $heap, $session, $jsonrpc, $id, @params) = @_[KERNEL,HEAP,SESSION, ARG0..$#_ ];
          my ($action,$targets,$data) = @params;

          if (ref $data ne ref {}) {
            #If data is not an hash reference, make it so to avoid errors.
            $data = {};
          }
          $log->info("adding action $action");

          if( ((! defined $data->{timestamp}) || ($data->{timestamp} eq "")) &&
              (grep {$_ eq $action} @{$heap->{scheduled_only}})) {
              # If the action is scheduled_only and there is no timestamp
              # We schedule it for now
              $data->{timestamp} = time();
          }

          if(defined $data->{timestamp} && ($data->{timestamp} ne "")) {
            $kernel->post( $jsonrpc => 'result' => $id, "OK" ); # asynchronous

            my $date = DateTime->from_epoch(epoch => $data->{timestamp});
            my $datetime;
            if (defined $data->{periodic} && ($data->{periodic} =~ /^(\d+)_(\w+)$/)) {
              my $periodic_nb = $1;
              my $periodic_keyword = $2;
              $datetime = DateTime::Set->from_recurrence (
                after      => $date,
                recurrence => sub {
                  return $_[0]->add( $periodic_keyword => $periodic_nb )
                },
              );
            } else {
              $datetime = DateTime::Set->from_datetimes(dates => [ $date ]);
            }
            foreach my $target (@{$targets}) {
              $kernel->yield(schedule => $datetime->clone, $action, lc($target), $data);
            }
            $log->info("action $action scheduled");
          } else {
            my $errors = "";
            my @results;
            my $taskid = $kernel->call($session => 'get_new_task_id');
            $heap->{tasks}->{$taskid}->{nb_targets} = scalar(@$targets);
            $heap->{tasks}->{$taskid}->{jsonid} = $id;
            $heap->{tasks}->{$taskid}->{jsonsession} = $jsonrpc;
            foreach my $target (@{$targets}) {
              $kernel->yield('action' => $taskid,$action,lc($target),$data);
            }
          }
        },
        get_entries_by_id => sub {
          my ($kernel, $heap, $session, $jsonrpc, $id, @params) = @_[KERNEL, HEAP, SESSION, ARG0..$#_ ];
          my ($ids) = @params;
          my $entries = [];
          foreach my $id (keys(%{$heap->{tasks}})) {
            if ((not defined $heap->{tasks}->{$id}->{jsonid}) && ((!defined $ids) || (grep {$_ == $id} $ids))) {
              refresh_task($kernel,$heap,$session,$id);
              push @{$entries}, convert_entry($heap->{tasks}->{$id},$id);
            }
          }
          $kernel->post( $jsonrpc => 'result' => $id, $entries);
        },
        get_entries_by_mac => sub { # this has not been tested
            my ($kernel, $heap, $session, $jsonrpc, $id, @params) = @_[KERNEL, HEAP, SESSION, ARG0..$#_ ];
            my ($macs) = @params;
            my $entries = [];
            foreach my $id (keys(%{$heap->{tasks}})) {
              if ((not defined $heap->{tasks}->{$id}->{jsonid}) && ((!defined $macs) || (grep {lc($_) eq $heap->{tasks}->{$id}->{'target'}} $macs))) {
                refresh_task($kernel,$heap,$session,$id);
                push @{$entries}, convert_entry($heap->{tasks}->{$id},$id);
              }
            }
            $kernel->post( $jsonrpc => 'result' => $id, $entries);
        },
        remove_entries => sub {
            my ($kernel, $heap, $session, $jsonrpc, $id, @params) = @_[KERNEL, HEAP, SESSION, ARG0..$#_ ];
            my ($ids) = @params;

            foreach my $id (@{$ids}) {
              if ((defined $id) && (defined $heap->{tasks}->{$id})) {
                $kernel->call($session => 'delete_task' => $id);
              }
            }

            $kernel->post( $jsonrpc => 'result' => $id, "ok");
        },
        process_entries => sub {
            my ($kernel, $heap, $jsonrpc, $id, @params) = @_[KERNEL, HEAP,ARG0..$#_ ];
            my ($ids) = @params;

            my $errors = "";
            foreach my $id (@{$ids}) {
                if ((defined $id) && (defined $heap->{tasks}->{$id})) {
                    delete $heap->{tasks}->{$id}->{handle};
                    $kernel->yield(action =>
                        $id,
                        $heap->{tasks}->{$id}->{action},
                        $heap->{tasks}->{$id}->{target},
                        $heap->{tasks}->{$id}->{data});
                } elsif (defined $id) {
                    $errors.="$id unknown\n";
                } else {
                    $errors.="id undefined\n";
                }
            }
            if ($errors ne "") {
                $kernel->post( $jsonrpc => 'error' => $id, $errors);
            } else {
                $kernel->post( $jsonrpc => 'result' => $id, "ok");
            }
        },
        action => sub {
          my ($kernel,$heap,$session,$taskid,$action,$target,$data) = @_[KERNEL,HEAP,SESSION,ARG0 .. $#_];

          $heap->{tasks}->{$taskid}->{action} = $action;

          my $handled = 0;

          my @modules = modules();
          push @modules, Argonaut::Server::Modules::Argonaut->new();
          MODULES: foreach my $module (@modules) {
            # Is this module able to handle this client and action?
            if (eval {$module->handle_client($target,$action)}) {
              $handled = 1;
              # If it is, send him the infos
              my $args = undef;
              if(defined $data->{args}) {
                $args = $data->{args};
              }
              $kernel->post($heap->{modulepool} => "do" => $module, $taskid, $args);
              last MODULES;
            }
          }
          unless ($handled) {
            $kernel->yield('set_task_error' => $taskid, "No client module can handle action $action for target $target");
          }
        },
        set_task_error => sub {
          my ($kernel,$heap,$session,$taskid,$error) = @_[KERNEL,HEAP,SESSION,ARG0 .. $#_];
          if (defined $heap->{tasks}->{$taskid}->{jsonid}) {
            $heap->{tasks}->{$taskid}->{error} .= $error;
            $kernel->call($session => 'send_result' => $taskid);
          } else {
            $log->warning("Error occured : ".$error);
            $heap->{tasks}->{$taskid}->{status} = "error";
            $heap->{tasks}->{$taskid}->{error} = $error;
          }
        },
        set_task_result => sub {
          my ($kernel,$heap,$session,$taskid,$res) = @_[KERNEL,HEAP,SESSION,ARG0 .. $#_];
          $log->debug("Setting task result '$res' for task '$taskid'");
          if (defined $heap->{tasks}->{$taskid}->{jsonid}) {
            push @{$heap->{tasks}->{$taskid}->{result}}, $res;
            $kernel->call($session => 'send_result' => $taskid);
          } else {
            $heap->{tasks}->{$taskid}->{error} = "";
            if (grep {$_ eq $heap->{tasks}->{$taskid}->{action}} @{$heap->{scheduled_only}}) {
              $heap->{tasks}->{$taskid}->{status} = "processing";
            } else {
              $kernel->call($session => 'set_task_processed' => $taskid);
            }
          }
        },
        send_result => sub {
          my ($kernel,$heap,$session,$taskid) = @_[KERNEL,HEAP,SESSION,ARG0 .. $#_];
          if (--$heap->{tasks}->{$taskid}->{nb_targets} == 0) {
            if ($heap->{tasks}->{$taskid}->{error} ne "") {
              $kernel->post(
                $heap->{tasks}->{$taskid}->{jsonsession} => 'error' =>
                $heap->{tasks}->{$taskid}->{jsonid},
                $heap->{tasks}->{$taskid}->{error}
              );
            } else {
              $kernel->post(
                $heap->{tasks}->{$taskid}->{jsonsession} => 'result' =>
                $heap->{tasks}->{$taskid}->{jsonid},
                $heap->{tasks}->{$taskid}->{result}
              );
            }
            $kernel->call($session => 'delete_task' => $taskid);
          }
        },
        delete_task => sub {
          my ($kernel,$heap,$session,$taskid) = @_[KERNEL,HEAP,SESSION,ARG0 .. $#_];
          if (not defined $heap->{tasks}->{$taskid}) {
            return;
          }
          $log->debug("deleting task $taskid");
          if (defined $heap->{tasks}->{$taskid}->{handler}) {
            $kernel->call("".$heap->{tasks}->{$taskid}->{handler} => 'stop');
            delete $heap->{tasks}->{$taskid}->{handler};
          }
          delete $heap->{tasks}->{$taskid};
        },
        set_task_processed => sub {
          my ($kernel,$heap,$session,$taskid) = @_[KERNEL,HEAP,SESSION,ARG0 .. $#_];
          if (not defined $heap->{tasks}->{$taskid}) {
            return;
          }
          if (defined $heap->{tasks}->{$taskid}->{'handler'}) {
            $heap->{tasks}->{$taskid} = $heap->{tasks}->{$taskid}->{'handler'}->task_processed($heap->{tasks}->{$taskid});
          }
          $heap->{tasks}->{$taskid}->{status} = "processed";
          if($delete_finished_tasks) {
            $kernel->call($session => 'delete_task' => $taskid);
          }
        },
        get_new_task_id => sub {
          my $heap = $_[HEAP];
          while (defined $heap->{tasks}->{$heap->{id}}) {
            $heap->{id}++;
            if ($heap->{id}>=2147483648) { # limit to 4 bytes
              $heap->{id} = 0;
            }
          }
          $heap->{tasks}->{$heap->{id}} = {
            'error'       => '',
            'result'      => [],
            'nb_targets'  => 1
          };
          return $heap->{id};
        },
        get_packages => sub {
            my ($kernel, $jsonrpc, $id, @params) = @_[KERNEL, ARG0..$#_ ];
            my ($release,$attrs,$filters,$from,$to) = @params;

            $log->info("get_packages($release,[".join(',',@{$attrs})."],[".join(',',@{$filters})."],$from,$to)");

            eval {
                my $distributions = get_packages_info($packages_folder,undef,$release,$attrs,$filters,$from,$to);
                $kernel->post( $jsonrpc => 'result' => $id, $distributions);
            };
            if($@) {
                $log->error($@);
                $kernel->post( $jsonrpc => 'error' => $id, $@);
            }
        },
        packages_crawler => sub {
            $log->info("Getting Packages files from repositories");
            my $errors = store_packages_file($packages_folder);
            if(@{$errors} > 0) {
              $log->notice("Errors while getting Packages files : ".join(',',@{$errors}));
            }
            $log->info("done");
        },
        load_dump => sub { # this function has not been tested
            my ($kernel,$filename) = @_[KERNEL,ARG0..$#_];

            open (DUMP, $filename) or die "cannot open file";
            while (<DUMP>) {
                my $task = from_json($_);
                if(($task->{status} eq "waiting") && ($task->{data}->{timestamp} > time)) {
                    $kernel->yield(schedule=>$task->{date},$task->{action},$task->{target},$task->{data});
                }
            }
            close(DUMP);
        },
        sighup => sub {
            my ($kernel,$heap) = @_[KERNEL,HEAP];
            $log->notice("reloading config…");
            readConfig();
            $kernel->signal($heap->{jsonserver},"KILL");
            $heap->{jsonserver} = POE::Component::Server::JSONRPC::Http->new(
                Port    => $server_port,
                Handler => { # FIXME this hash should not be duplicated
                    'echo' => 'echo',
                    'ping' => 'ping',
                    'action' => 'add',
                    'get_entries' => 'get_entries_by_id',
                    'get_entries_by_id' => 'get_entries_by_id',
                    'get_entries_by_mac' => 'get_entries_by_mac',
                    'remove_entries' => 'remove_entries',
                    'process_entries_now' => 'process_entries',
                    'get_my_id' => 'id_of_mac',
                    'set_task_substatus' => 'jsonrpc_set_task_substatus',
                    'set_error' => 'set_error',
                    'get_packages' => 'get_packages'
                },
            );
            $kernel->sig_handled();
        },
        sigint => sub {
            my ($kernel,$heap,$signal) = @_[KERNEL,HEAP,ARG0..$#_];
            $log->notice("exiting because of $signal…");
            #here, do something with waiting tasks
            if(scalar(keys(%{$_[HEAP]->{tasks}})) > 0) {
                open (DUMP, ">dump_".time.".json") or die "cannot open file";
                foreach my $task (values(%{$_[HEAP]->{tasks}})) {
                    delete $task->{handle};
                    print DUMP (to_json($task)."\n");
                }
                close(DUMP);
            }
        },
        sigchild => sub {
            $log->notice("child process exiting…");
            #~ delete $_[HEAP]->{bloquant};
        },
        _stop => sub {
            $log->notice("_stop");
        },
    },
);

POE::Kernel->run();

exit 0;

__END__

=head1 NAME

argonaut-server - Dispatching action received from FusionDirectory to the clients

=head1 SYNOPSIS

argonaut-server

=head1 DESCRIPTION

argonaut-server - argonaut-server dispatch actions received from FusionDirectory and send it to the clients. It is modular
and can load various modules at run time.

=head1 BUGS

Please report any bugs, or post any suggestions, to the fusiondirectory mailing list fusiondirectory-users or to
<https://forge.fusiondirectory.org/projects/argonaut-agents/issues/new>

=head1 LICENCE AND COPYRIGHT

This code is part of FusionDirectory <http://www.fusiondirectory.org>

=over 1

=item Copyright (C) 2011-2014 FusionDirectory project

=back

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

=cut
