#!/usr/bin/env perl

use warnings;
#____________________________________________________________________________
#
#   MusicBrainz -- the open internet music database
#
#   Copyright (C) 1998 Robert Kaye
#
#   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., 675 Mass Ave, Cambridge, MA 02139, USA.
#
#   $Id$
#____________________________________________________________________________

use strict;

use FindBin;
use lib "$FindBin::Bin/../../lib";

use Getopt::Long;
use MusicBrainz::Server::Context;
use DBDefs;
use Sql;

my ($fHelp, $fIgnoreErrors);
my $tmpdir = "/tmp";

GetOptions(
    "help|h"                    => \$fHelp,
    "ignore-errors|i!"  => \$fIgnoreErrors,
    "tmp-dir|t=s"               => \$tmpdir,
);

sub usage
{
    print <<EOF;
Usage: ImportReplicationChanges [options] FILE ...

        --help            show this help
    -i, --ignore-errors   if a table fails to import, continue anyway
    -t, --tmp-dir DIR     use DIR for temporary storage (default: /tmp)

FILE can be any of: a regular file in Postgres "copy" format (as produced
by ExportReplicationChanges --nocompress); a gzip'd or bzip2'd tar file of Postgres
"copy" files (as produced by ExportReplicationChanges); a directory containing
Postgres "copy" files; or a directory containing an "mbdump" directory
containing Postgres "copy" files.

If any "tar" files are named, they are firstly all
decompressed to temporary directories (under the directory named by
--tmp-dir).  These directories are removed on exit.

EOF
}

$fHelp and usage();
@ARGV or usage();

$SIG{'INT'} = sub { die "SIGINT\n" };

my $c = MusicBrainz::Server::Context->new(database => 'READWRITE');
my $sql = Sql->new($c->conn);

for my $arg (@ARGV)
{
    -e $arg or die "'$arg' not found";
    next if -d _;
    -f _ or die "'$arg' is neither a regular file nor a directory";

    next unless $arg =~ /\.tar\.(gz|bz2)$/;

    my $mode = ($1 eq "gz" ? "gzip" : "bzip2");

    my $dir = make_tmp_dir();
    print localtime() . " : tar -C $dir --$mode -xvf $arg\n";
    system "tar -C $dir --$mode -xvf $arg";
    exit $? if $?;
    $arg = $dir;
}

use Time::HiRes qw( gettimeofday tv_interval );
my $t0 = [gettimeofday];
my $totalrows = 0;
my $tables = 0;
my $errors = 0;

print localtime() . " : starting import\n";

printf "%-30.30s %9s %4s %9s\n",
    "Table", "Rows", "est%", "rows/sec",
    ;

ImportReplicationTables();

print localtime() . " : import finished\n";

my $dumptime = tv_interval($t0);
printf "Loaded %d tables (%d rows) in %d seconds\n",
    $tables, $totalrows, $dumptime;

exit($errors ? 1 : 0);

sub ImportTable
{
    my ($table, $file) = @_;

    print localtime() . " : load $table\n";

    my $rows = 0;

    my $t1 = [gettimeofday];
    my $interval;

    my $size = -s($file) || 1;

    my $p = sub {
        my ($pre, $post) = @_;
        no integer;
        printf $pre."%-30.30s %9d %3d%% %9d".$post,
                $table, $rows, int(100 * tell(LOAD) / $size),
                $rows / ($interval||1);
    };

    $| = 1;

    eval
    {
        open(LOAD, "<", $file) or die "open $file: $!";

        $sql->begin;
        my $dbh = $sql->dbh; # issues a ping, must be done before COPY
        $sql->do("COPY $table FROM stdin");

        $p->("", "");

        while (<LOAD>)
        {
                $dbh->pg_putcopydata($_) or die;

                ++$rows;
                unless ($rows & 0xFFF)
                {
                        $interval = tv_interval($t1);
                        $p->("\r", "");
                }
        }

        $dbh->pg_putcopyend() or die;

        $interval = tv_interval($t1);
        $p->("\r", sprintf(" %.2f sec\n", $interval));

        close LOAD
                or die $!;

        $sql->commit;

        die "Error loading data"
                if -f $file and empty($table);

        ++$tables;
        $totalrows += $rows;

        1;
    };

    return 1 unless $@;
    warn "Error loading $file: $@";
    $sql->rollback;

    ++$errors, return 0 if $fIgnoreErrors;
    exit 1;
}

sub empty
{
    my $table = shift;

    my $any = $sql->select_single_value(
        "SELECT 1 FROM $table LIMIT 1",
    );

    not defined $any;
}

sub ImportReplicationTables
{
    $sql->auto_commit;
    eval { $sql->do('ALTER TABLE dbmirror_pendingdata DROP CONSTRAINT "dbmirror_pendingdata_SeqId" CASCADE') };
    $sql->auto_commit;
    eval { $sql->do('ALTER TABLE dbmirror_pending DROP CONSTRAINT "dbmirror_pending_pkey" CASCADE') };
    $sql->auto_commit;
    eval { $sql->do('ALTER TABLE dbmirror_pendingdata DROP CONSTRAINT "dbmirror_pendingdata_pkey" CASCADE') };

    for my $table (('dbmirror_pending', 'dbmirror_pendingdata'))
    {
        my $file = find_file($table);
        $file or print("No data file found for '$table', skipping\n"), die;

        if (not empty($table))
        {
            die "$table already contains data; skipping\n";
            next;
        }

        ImportTable($table, $file);
    }

    $sql->begin;
    $sql->do('ALTER TABLE dbmirror_pending ADD CONSTRAINT dbmirror_pending_pkey PRIMARY KEY (SeqId)');
    $sql->do('ALTER TABLE dbmirror_pendingdata ADD CONSTRAINT dbmirror_pendingdata_pkey PRIMARY KEY (SeqId, IsKey)');
    $sql->do('ALTER TABLE dbmirror_pendingdata ADD CONSTRAINT dbmirror_pendingdata_SeqId FOREIGN KEY (SeqId) REFERENCES dbmirror_pending (SeqId) ON UPDATE CASCADE ON DELETE CASCADE');
    $sql->commit;

    return 1;
}

sub find_file
{
    my $table = shift;

    for my $arg (@ARGV)
    {
        use File::Basename;
        return $arg if -f $arg and basename($arg) eq $table;
        return "$arg/$table" if -f "$arg/$table";
        return "$arg/mbdump/$table" if -f "$arg/mbdump/$table";
    }

    undef;
}

{
    my @tmpdirs;

    END
    {
        use File::Path;
        rmtree(\@tmpdirs);
    }

    sub make_tmp_dir
    {
        for (my $i = 0; ; ++$i)
        {
                my $dir = "$tmpdir/ImportRepl-$$-$i";

                if (mkdir $dir)
                {
                        push @tmpdirs, $dir;
                        return $dir;
                }

                use Errno 'EEXIST';
                next if $! == EEXIST;

                die "Error creating temporary directory ($!)";
        }
    }
}

# vi: set ts=4 sw=4 :
