#!/usr/bin/perl
#============================================================= -*-perl-*-
#
# BackupPC_fixupBackupSummary: recreate backups file in case
# it was lost.
#
# DESCRIPTION
#  
#   Usage: BackupPC_fixupBackupSummary [clients...]
#
# AUTHOR
#   Craig Barratt  <cbarratt@users.sourceforge.net>
#
# COPYRIGHT
#   Copyright (C) 2005-2015  Craig Barratt
#
#   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, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#========================================================================
#
# Version 3.3.1, released 11 Jan 2015.
#
# See http://backuppc.sourceforge.net.
#
#========================================================================

use strict;
no  utf8;
use lib "/usr/share/backuppc/lib";
use Getopt::Std;
use Data::Dumper;
use Time::ParseDate;

use BackupPC::Lib;
use BackupPC::Attrib qw(:all);
use BackupPC::FileZIO;
use BackupPC::Storage;

die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) );
my $TopDir = $bpc->TopDir();
my $BinDir = $bpc->BinDir();
my %Conf   = $bpc->Conf();
my $Hosts  = $bpc->HostInfoRead();
my @hostList;

our(%backupInfo);
my %opts;

if ( !getopts("l", \%opts) ) {
    print STDERR <<EOF;
usage: $0 [-l]
  Options:
    -l    legacy mode: try to reconstruct backups from LOG
          files for backups prior to BackupPC v3.0.
EOF
    exit(1);
}

if ( !@ARGV ) {
    @hostList = sort(keys(%$Hosts));
} else {
    @hostList = @ARGV;
}

foreach my $host ( @hostList ) {
    my(@Backups, $BkupFromLOG, $BkupFromInfo, $BkupNums, @LogFiles);

    $BkupFromInfo = {};
    $BkupFromLOG  = {};
    if ( !defined($Hosts->{$host}) ) {
        print("$host doesn't exist in BackupPC's host file... skipping\n");
        next;
    }

    my $dir = "$TopDir/pc/$host";
    print("Doing host $host\n");

    if ( !opendir(DIR, $dir) ) {
        print("$host: Can't open $dir... skipping $host\n");
        next;
    }

    #
    # Read the backups file
    #
    @Backups = $bpc->BackupInfoRead($host);

    #
    # Look through the LOG files to get information about
    # completed backups.  The data from the LOG file is
    # incomplete, but enough to get some useful info.
    #
    # Also, try to pick up the new-style of information
    # that is kept in each backup tree.  This info is
    # complete.  This data is only saved after version
    # 2.1.2.
    #
    my @files = readdir(DIR);
    closedir(DIR);
    foreach my $file ( @files ) {
        if ( $opts{l} && $file =~ /^LOG(.\d+\.z)?/ ) {
            push(@LogFiles, $file);
        } elsif ( $file =~ /^(\d+)$/ ) {
            my $bkupNum = $1;
            $BkupNums->{$bkupNum} = 1;

            next if ( !-f "$dir/$bkupNum/backupInfo" );

            #
            # Read backup info
            #
            %backupInfo = ();
            print("    Reading $dir/$bkupNum/backupInfo\n");
            if ( !(my $ret = do "$dir/$bkupNum/backupInfo") ) {
                print("    couldn't parse $dir/$bkupNum/backupInfo: $@\n") if $@;
                print("    couldn't do $dir/$bkupNum/backupInfo: $!\n")
                                                        unless defined $ret;
                print("    couldn't run $dir/$bkupNum/backupInfo\n");
                next;
            }
            if ( !keys(%backupInfo) || !defined($backupInfo{num}) ) {
                print("    $dir/$bkupNum/backupInfo is empty\n");
                next;
            }
            %{$BkupFromInfo->{$backupInfo{num}}} = %backupInfo;
        }
    }

    #
    # Read through LOG files from oldest to newest
    #
    @LogFiles = sort({-M "$dir/$a" <=> -M "$dir/$b"} @LogFiles);
    my $startTime;
    my $fillFromNum;
    foreach my $file ( @LogFiles ) {
        my $f = BackupPC::FileZIO->open("$dir/$file", 0, $file =~ /\.z/);

        if ( !defined($f) ) {
            print("$host: unable to open file $dir/$file\n");
            next;
        }
        print("    Reading $file\n");
        while ( (my $str = $f->readLine()) ne "" ) {
            if ( $str =~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (full|incr|partial) backup started / ) {
                $startTime = parsedate($1);
                next;
            }
            next if ( $str !~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (full|incr|partial) backup (\d+) complete, (\d+) files, (\d+) bytes, (\d+) xferErrs \((\d+) bad files, (\d+) bad shares, (\d+) other\)/ );

            my $type        = $2;
            my $bkupNum     = $3;
            my $nFilesTotal = $4;
            my $sizeTotal   = $5;
            my $xferErrs    = $6;
            my $badFiles    = $7;
            my $badShare    = $8;
            my $endTime     = parsedate($1);
            print("    Got $type backup $bkupNum at $endTime\n");
            next if ( !-d "$dir/$bkupNum" );
            $BkupFromLOG->{$bkupNum} = {
                num            => $bkupNum,
                type           => $type,
                startTime      => $startTime,
                endTime        => $endTime,
                size           => $sizeTotal,
                nFiles         => $nFilesTotal,
                xferErrs       => $xferErrs,
                xferBadFile    => $badFiles,
                xferBadShare   => $badShare,
                nFilesExist    => 0,
                sizeExist      => 0,
                sizeExistComp  => 0,
                tarErrs        => 0,
                compress       => $Conf{CompressLevel},
                noFill         => $type eq "incr" ? 1 : 0,
                level          => $type eq "incr" ? 1 : 0,
                mangle         => 1,
                fillFromNum    => $fillFromNum,
            };
            $fillFromNum = $bkupNum if ( $type eq "full" );
        }
    }

    #
    # Now merge any info from $BkupFromInfo and $BkupFromLOG
    # that is missing from @Backups.
    #
    # First, anything in @Backups overrides the other data
    #
    #
    foreach ( my $i = 0 ; $i < @Backups ; $i++ ) {
        my $bkupNum = $Backups[$i]{num};
        delete($BkupFromLOG->{$bkupNum});
        delete($BkupFromInfo->{$bkupNum});
        delete($BkupNums->{$bkupNum});
    }

    #
    # Now merge in data from the LOG and backupInfo files.
    # backupInfo files override LOG files.
    #
    my $changes;

    foreach my $bkupNum ( keys(%$BkupFromLOG) ) {
        next if ( defined($BkupFromInfo->{$bkupNum}) );
        print("    Adding info for backup $bkupNum from LOG file\n");
        push(@Backups, $BkupFromLOG->{$bkupNum});
        delete($BkupNums->{$bkupNum});
        $changes++;
    }
    foreach my $bkupNum ( keys(%$BkupFromInfo) ) {
        print("    Adding info for backup $bkupNum from backupInfo file\n");
        push(@Backups, $BkupFromInfo->{$bkupNum});
        delete($BkupNums->{$bkupNum});
        $changes++;
    }
    foreach my $bkupNum ( keys(%$BkupNums) ) {
        print("    *** No info for backup number $bkupNum\n");
    }

    if ( $changes ) {
        @Backups = sort({$a->{num} <=> $b->{num}} @Backups);
        # print Dumper \@Backups;
        $bpc->BackupInfoWrite($host, @Backups);
    } else {
        print("    No changes for host $host\n");
    }
}
