#!/usr/bin/perl
#  $Id$
# -----------------------------------------------------------------------------
#  Restore node configuration (flat files only, no database)
# -----------------------------------------------------------------------------
#  Author: Andrey Fomenko
#  Modified by: Alexey Tsibulnik
#  QA by:  
#  Copyright: videoNEXT Network solutions, Inc.
# -----------------------------------------------------------------------------

use NextCAM::Conf qw(GetCfgs GetDefaultCfg WriteCfg);
use Log::Log4perl "get_logger";
use Log::Log4perl::Level;
use strict;
use warnings;

# -------------------- standard environment -----------------------------------
my $APL = $ENV{APL};
open (ECF,"$APL/base/etc/env.conf") || die("Cannot read base/etc/env.conf");
map {$ENV{$1}=$2 if /^(\w+)=(\S+)/} grep {/^\w+=\S+/} <ECF>;
close ECF;

require "$APL/common/bin/logger.patrol";

# -----------------------------------------------------------------------------
# Globals
my $APL_CONF = $ENV{APL_CONF} || '$APL/var/conf';
my $APL_USR = $ENV{APL_USR} || 'apl';
my $APL_HTTPD_GRP = $ENV{APL_HTTPD_GRP} || 'apache';
my $BACKUP_LOCATION = "$APL/var/backup/node";
my $BACKUP_FILE = $ARGV[0];
my $STAGING_AREA = "/tmp/restore-node$$";
my $nErrors = 0;
my $Log = get_logger('NEXTCAM::CONF::NODERESTORE');

sub logger {
    my ($level, $msg) = @_;
    $Log->log($level, $msg);
    print "[".get_ts()."]: $msg\n";
    $nErrors++ if $level == $ERROR;
} # sub logger

sub get_ts {
    my ($tsec,$tmin,$thour)=(gmtime(time))[0..2];
    return sprintf("%02s:%02s:%02s",$thour,$tmin,$tsec);
} # sub get_ts

sub prepare {
    die "Must be run under $APL_USR only" unless $ENV{USER} eq $APL_USR;
    die "No backup file specified" unless $BACKUP_FILE;
    my $file = "$BACKUP_LOCATION/$BACKUP_FILE";
    die "Specified backup file doesn't exist: $file\n" unless -f $file;
    logger($INFO, "Node backup recovery starts");
    # create staging area dir and unpack file
    system("mkdir $STAGING_AREA 2>&1") == 0 or die "Failed to create staging area";
    system("tar xzf $file -C $STAGING_AREA 1>/dev/null 2>&1") == 0 or die "Failed to unpack backup file";
}

# restores device configuration files *.conf. Files to recover are filtered against
# corresponding template using 'GetDefaultCfg' call. Devices which aren't present
# in backup will be deleted
sub restore_dev {
    my $err = $nErrors;
    my %def_confs;
    my %devids;
    my %cfgs = GetCfgs;
    foreach my $conffile (<$STAGING_AREA/conf/*.conf>) {
	open(CFG, $conffile) or next;
	my $cfg = \%{{map {/(\w+)=(.*)/} grep {/^\w+=.*$/} <CFG>}};
	my $dev_type = $cfg->{DEVICETYPE};	# skip invalid cfg
	next unless defined $dev_type;
	my $def_cfg = GetDefaultCfg(uc($dev_type));	# Default configuration from template
	if(defined $def_cfg) {
	    foreach(keys %$def_cfg) { # search for params missing in backuped conf
		next if /OBJID|DEVID|DEVICETYPE/i;
	        $cfg->{$_} = $def_cfg->{$_} unless exists $cfg->{$_};
	    }
	    foreach(keys %$cfg) { # remove depreciated parameters
		delete $cfg->{$_} unless exists $def_cfg->{$_};
	    }
	}
	# write configuration to $APL_CONF/conf
	eval { WriteCfg($cfg) };
	logger($ERROR, "Unable to write configuration for device $cfg->{DEVID}") if $@;
	$devids{$cfg->{DEVID}} = 1;
    }
    # for devices missing in backup change their location to '@garbage'
    foreach my $dev (keys %cfgs) {
	next unless defined $cfgs{$dev}{DEVID};
	next if $cfgs{$dev}{DEVICETYPE} =~ /NEXTCAM/i;
	unless(exists $devids{$dev}) {
	    $cfgs{$dev}{LOCATION} = '@garbage';
	    eval { WriteCfg($cfgs{$dev}) };
	    logger($ERROR, "Unable to remove configuration for device $dev") if $@;
	    
	}
    }
    # do chown
    if(system("chown $APL_USR:$APL_HTTPD_GRP $APL_CONF/conf/*") != 0) {
	logger($ERROR, "Failed to chown configuration files");
    }
    
    logger($INFO, "Device configuration restore: ".($nErrors-$err?'ERROR':'OK'));
} # sub restore_dev

# restores node configuration
sub restore_node {
    my $err = $nErrors;
    # Restore only UNI of the node
    open(CONF, "$STAGING_AREA/node/conf") or 
	logger($ERROR, "Failed to open $STAGING_AREA/node/conf for reading");
    my ($new_uni) = map {$1 if /^UNI=(.+)/i} grep {/^UNI=.+/i} <CONF>;
    close CONF;
    open CONF, "+<$APL_CONF/node/conf" or 
	logger($ERROR, "Failed to open $APL_CONF/node/conf for write/read");
    my %conf = map{/(^\w+)=(.+)/} grep {/^\w+=.+/} <CONF>;
    $conf{UNI} = $new_uni;
    seek CONF, 0, 0;
    print CONF "$_=$conf{$_}\n" foreach keys %conf;
    close CONF;
    if(system("chown $APL_USR:$APL_HTTPD_GRP $APL_CONF/node/conf") != 0) {
	logger($ERROR, "Failed to chown $APL_CONF/node/conf");
    }
    chmod 0660, "$APL_CONF/node/conf";
    logger($INFO, "Node configuration restore: ".($nErrors-$err?'ERROR':'OK'));
} # sub restore_node

# restores Storage Manager configuration
sub restore_sm {
    my $err = $nErrors;
    if(system("rm -fr $APL_CONF/sm/{rejected,unassigned,wheels}") != 0) {
	logger($ERROR, "Failed to remove $APL_CONF/sm");
    }
    if(system("cp -pr $STAGING_AREA/sm/{rejected,unassigned,wheels} $APL_CONF/sm") != 0) {
	logger($ERROR, "Failed to copy sm configuration");
    }
    if(system("chown -R $APL_USR:$APL_HTTPD_GRP $APL_CONF/sm") != 0) {
	logger($ERROR, "Failed to chown $APL_CONF/sm");
    }
    
    logger($INFO, "Storage manager configuration restore: ".($nErrors-$err?'ERROR':'OK'));
} # sub restore_sm

# do cleanup and print result
sub finalize {
    system("rm -f $APL_CONF/still/still.*"); # Remove still images
    system("rm -fr $STAGING_AREA") == 0 or $nErrors++;
    logger($INFO, "FINAL: ".($nErrors?"ERROR[s]: $nErrors" : 'SUCCESS'));
    exit $nErrors;
} # sub finalize

# -----------------------------------------------------------------------------
# Main
sub main {
    eval {
	prepare;
    };
    if($@) {
	chomp $@;
	logger($ERROR, "Error occured while preparing to restore: $@");
	exit -1;
    }
    #restore_dev;
    restore_node;
    restore_sm;
    finalize;
}

main();
