#!/usr/bin/perl

package FCC::updater;

################################################
#                                              #
#   FCC Updater                                #
#                                              #
#      (C) 2018 Domero                         #
#                                              #
################################################

use strict;
no strict 'refs';
use warnings;
use Exporter;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);

$VERSION     = '1.01';
@ISA         = qw(Exporter);
@EXPORT      = qw(updater_takeloop c_updatefile c_versioncheck update_version update_node);
@EXPORT_OK   = qw();

use JSON;
use gfio 1.10;
use Crypt::Ed25519;
use FCC::global;

################################################################################
# Globals
my $UPDATEMODE  = 0;
my $PERLPATH    = '../install/modules';
#my $PERLPATH    = '/perl/domero';
my $UPDATEFILE  = "updates.fcc";
my $UPDATEPATH  = "updates";
my $UPDATELIST  = [];
my $UPDATES     = {};
my $CURVERSION  = -e 'current.version' ? gfio::content('current.version') : '';

################################################################################
# used from coinbase.pm
my $CBKEY;
my $OUT;        # Out Message
my $BJSON;      # Broadcast Json
my $OUTJSON;    # Out Json

################################################################################

1;

################################################################################

sub init {
  ($CBKEY,$OUT,$BJSON,$OUTJSON)=@_;
  if(!-d $UPDATEPATH){ mkdir($UPDATEPATH) }
  load_updatelist();
  check_updatelist(1);
}

################################################################################

sub c_versioncheck {
  my ($client,$k) = @_;
  #if(!update_version($k->{currentversion})){ update_node($client) }
  #else { &$OUTJSON($client,{command=>'uptodate'}) }
  &$OUTJSON($client,{command=>'uptodate'})
}

sub update_version {
  my($curversion)=@_;
  return $CURVERSION eq $curversion ? 1 : 0
}

sub update_node {
  my ($client)=@_;
  my $updated=[];
  print " UPDATELIST: ".encode_json($UPDATELIST)."\n";
  for my $line ( @$UPDATELIST ){ push @$updated,$line->[0] }
  &$OUTJSON($client,{command=>'prepareupdate',currentversion=>$CURVERSION,files=>$updated});
}

sub updater_takeloop {
  ($UPDATEMODE)=@_;
  if($UPDATEMODE){ print "\n*** UPDATEMODE: $UPDATEMODE\n\n" }
  if($UPDATEMODE == 1){ return (load_updatelist() ? 2 : 0 ) }
  elsif($UPDATEMODE == 2){ check_updatelist() }
  return 0 # kill the loop
}

################################################################################
# Mode 1
sub load_updatelist {
  print" * Loading Updatelist\n";
  $UPDATELIST = -e $UPDATEFILE ? [split(/\r?\n/,gfio::content($UPDATEFILE))] : [];
  $UPDATES = {};
  for ( my $l = 0; $l <= $#$UPDATELIST; $l++ ) {
    $UPDATELIST->[$l] = [ split(/\|/,$UPDATELIST->[$l]) ];
    my($file) = @{$UPDATELIST->[$l]};
    $UPDATES->{$file}=$UPDATELIST->[$l];
  }
  1 + $#$UPDATELIST
}

sub save_updatelist {
  print" * Saving Updatelist\n";
  my $data="";
  for ( my $l = 0; $l <= $#$UPDATELIST; $l++ ) {
     $data .= join('|',@{$UPDATELIST->[$l]}) . "\n"
  }
  gfio::create($UPDATEFILE,$data);
}

################################################################################
# Mode 2
sub check_updatelist {
  my($nopost)=@_;
  print" * Checking Updatelist\n";
  my $updated=[];
  my $fileshash='';
  for ( my $l = 0; $l <= $#$UPDATELIST; $l++ ) {
    my ($file,$prevhash) = @{$UPDATELIST->[$l]};
    if (-f "$PERLPATH/$file") {
      my $data=gfio::content("$PERLPATH/$file");
      my $curhash = securehash($data);
      print "$PERLPATH/$file :: ".($prevhash ? "PREV : $prevhash":"")." CUR: $curhash\n";
      $fileshash.=$curhash;
      if (!$prevhash || $curhash ne $prevhash) {
        print "Update File : $file : $curhash\n";
        push @$updated,$file;
        # update the json file
        my $update    = $file; $update=~s/[\/\.]/_/gs;
        my $size      = length($data);
        my $b64data   = encode_base64($data);
        my $sign      = $file.$size.$b64data;
        my $signature = octhex(Crypt::Ed25519::sign($sign,hexoct($FCCSERVERKEY),hexoct($CBKEY)));
        my $verify    = Crypt::Ed25519::verify($sign,hexoct($FCCSERVERKEY),hexoct($signature));
        if(!$verify){
          print "ERROR VERIFYING SIGNATURE for $file!!!\n";
        }else{
          $UPDATELIST->[$l][1] = $curhash; # updates hash value
          gfio::create("$UPDATEPATH/$update.json",encode_json({ command => 'updatefile', file => $file, size => $size, data => $b64data, signature => $signature }));
        }
      }
    } else {
     ## Stupid ERROR ?? ##
    }
  }
  # Save New Version Hash
  my $vhash=securehash($fileshash);
  if($CURVERSION ne $vhash){
    $CURVERSION=$vhash;
    gfio::create('current.version',$CURVERSION);
    save_updatelist();
  }
  # Send New Version Files
  print " Have Updates ".(1+$#$updated)."\n";
  if(!defined $nopost && $#$updated > -1){
    save_updatelist();
    &$BJSON({command=>'prepareupdate',currentversion=>$CURVERSION,files=>$updated});
    return 1
  }
}

################################################################################
# NODE.PM
#
#  sub cfcc_prepareupdate {
#   my ($client,$k) = @_;
#   $UPDATEFILES=$k->{files};
#   my $dir=$INC{'gfio.pm'}; $dir =~ s/\\/\//g;
#   my @sdir=split(/\//,$dir); pop @sdir;
#   $UPDATEDIR=join("/",@sdir);
#   print " * Shutting down active tasks for updates\n"
#  }
#
#  serverloop:
#    if ($UPDATEMODE) {
#     my @tl=keys %$TRANSLIST;
#     if (($#{$TRANSDISTLIST}<0) && ($#tl<0) && !$VOTING && !$TRANSDIST) {
#       if (!$UPDATETIME || ($ctm-$UPDATETIME>=1)) {
#         $UPDATETIME=$ctm;
#         my $file=shift @$UPDATEFILES;
#         outfcc({ command => 'updatefile', file => $file })
#       }
#     }
#   }
#
################################################################################
# Node Requests

sub c_updatefile {
  my($client,$k)=@_;
  my $update=$k->{file};
  if(defined $update && defined $UPDATES->{$update}){
    $update =~ s/[\/\.]/_/gs;
    if(-e "$UPDATEPATH/$update.json"){
      my $data=gfio::content("$UPDATEPATH/$update.json");
      my $obj=decode_json($data);
#      print "FILE: $obj->{file}\n";
#      print "SIZE: $obj->{size}\n";
#      print "DATA: ".substr($obj->{data},0,32)."..".substr($obj->{data},-32)."\n";
#      print "SIGN: $obj->{signature}\n";
#      if (Crypt::Ed25519::verify($obj->{file}.$obj->{size}.$obj->{data},hexoct($FCCSERVERKEY),hexoct($obj->{signature}))) {
#        print "Signature OK\n";
        print "[$client->{ip}:$client->{port}] : Update $update, Message Size: ".length($data)." bytes\n";
        &$OUT($client,$data)
#      }else{
#        print "Signature ERROR\n";
#      }
    }else{
      print "[$client->{ip}:$client->{port}] : Error Updating $UPDATEPATH/$update.json\n";
    }
  } else {
    print "Illegal File Request: ".encode_json($k)."\n";
    # ILLEGAL FILE REQUEST
  }
}


################################################################################
#  
#  sub cfcc_updatefile {
#   my ($client,$k) = @_;
#   my $sign=$k->{file}.$k->{size}.$k->{data};
#   if (Crypt::Ed25519::verify($sign,hexoct($FCCSERVERKEY),hexoct($k->{signature}))) {
#     my $decoded=decode_base64($k->{data});
#     if (length($decoded) == $k->{size}) {
#       gfio::create("$UPDATEDIR/".$k->{file},$decoded);
#       print " * Updated $k->{file}\n"
#     }
#   }
#   if ($#UPDATEFILES<0) {
#     gfio::create('update.fcc',1);
#     killserver("Restarting for updates")
#   }
#  }
# 
################################################################################
# EOF FCC::updater (C) 2018 Domero #############################################
