#!/usr/bin/perl
#
# Move a collection of eagle-i resources from either a file or repository
# to a workspace graph in a new repository.
#
$Usage = "Usage: move-resources  [--verbose] [--replace] \n".
         "    [--bad-idea-copy-localname] \n".
         "         [--type published|workspace] \n".
         "         { --file <sf> --prefix prefix  |\n".
         "           --source src-url --user src-user:src-password --graph src-graph }\n".
         "         dest-url dest-login:dest-password dest-ng\n".
         "  (options may be abbreviated to first letter, e.g. -f)\n";
#
# NOTE: the --bad-idea-copy-localname option DISABLES the careful URI mapping
# which is half hte purpose of this miserable script. It creates URIs in the
# target repo which have the SAME local-name portion as on the source.
# DO NOT DO THIS unless you are starting with a TOTALLY EMPTY destination repo
# or better yet just DO NOT DO THIS.  Though that could be said of this whole
# misbegotten script.
#
#
# Requires:
#   - Unix environment
#   - curl utility (could be ported to use a perl http client, curl was easier)
#
# $Id: move-resources 5484 2010-11-15 22:08:17Z lcs14 $
# Author: Larry Stone
# Started: May 26 2010
#

use Getopt::Long;

$MDGraph = "http://eagle-i.org/ont/repo/1.0/NG_Metadata";

# URL of destination repo
$destRepo = undef;

# login cred of dest repo
$destUser = undef;

# graph URI of dest repo
$destGraph = undef;

# config: how big a chunk of URIs to upload at a time.
$uriChunk = 100;

# display breakdown of actual status and signal from $? after command fails..
sub statusMessage {
    local ($msg) = @_;
    local ($status, $sig);
    $status = int($? / 256);
    $sig = $? % 256;
    $msg .= ": ";
    $msg .= "status=$status" if $status != 0;
    $msg .= " signal=$sig" if $sig != 0;
    die "${msg}\n";
}

# return a new unique URI taht resolves to dest repo.
sub newURI {
    if ($#uriPool < 0) {
        print STDERR "Getting another batch of $uriChunk URIs..\n" if $optDebug;
        @uriPool = `curl $curlExtra -k -s -u "${destUser}" -d format=text/plain -d count=$uriChunk "${destRepo}/repository/new"`;
        &statusMessage("newURI: Failed in curl") unless $? == 0;
        die "No results from request for new URIs from dest repo!" if $#uriPool < 0;
        shift @uriPool;
        #chop @uriPool;
        map {$_ =~ s/\s+//g} @uriPool;
    }
    return shift @uriPool;
}

# returns the URI prefix of the given repo
sub getURIPrefix {
    local ($url, $login) = @_;
    local (@uris, $result);
    $uriCmd= "curl $curlExtra -k -s -u '${login}' -d format=text/plain '${url}/repository/new'";
    print STDERR "Getting prefix from url=$url, login=$login\n  command=$uriCmd\n" if $optDebug;
    @uris = `$uriCmd`;
    &statusMessage("getURIPrefix: Failed in curl, command=$uriCmd") unless $? == 0;
    die "No results from request for new URIs from dest repo!" if $#uris < 0;
    ## get rid of leading 'new' and whitespace
    shift @uris;
    ($result) = $uris[0] =~ m/^(.+\/)/;
    die "Empty result URI prefix, interpreting: $uris[0] !" if length($result) == 0;
    die "Bad URI synax in prefix, interpreting: $uris[0] !" if $result !~ m/^https?:/;
    return $result;
}

GetOptions("help|h" => sub { print "$Usage\n"; exit(1); },
           "version" => sub { print "$0 from release ${project.version} SCM revision ${buildNumber}\n" ; exit(0); },
           "bad-idea-copy-localname" => \$optBadIdea,
           "type|t=s" => \$optType,
           "debug|d" => \$optDebug,
           "verbose|v" => \$optVerbose,
           "curl-verbose" => \$optCurlVerbose,
           "replace|r" => \$optReplace,
           "file|f=s" => \$optFile,
           "prefix|p=s" => \$optPrefix,
           "source|s=s" => \$optRepo,
           "user|u=s" => \$optUser,
           "graph|g=s" => \$optGraph);

die "Missing required destination args, use --help for usage.\n"
    if $#ARGV < 2;
$destRepo = shift;
$destUser = shift;
$destGraph = shift;
chop($destRepo) if $destRepo =~ m#/$#;

$curlExtra = $optCurlVerbose ? "-v" : "";

# return value from main
$retStatus = 0;

# arg sanity checks: must have input from either source repo or file
# also open source stream and set souce prefix
if (length($optFile) > 0) {
    die "Missing required option --prefix, use --help for usage.\n"
        if (length($optPrefix) == 0);
    if (length($optRepo) > 0) {
        die "Illegal to specify both repo and file input, use --help for usage.\n"
    }
    $srcPrefix = $optPrefix;
    open(SRC, "< $optFile") || die("Cannot read input data file: $optFile: $!\n");

} elsif (length($optRepo) > 0) {
    die "Missing required option --user, use --help for usage.\n"
        if (length($optUser) == 0);
    die "Missing required option --graph, use --help for usage.\n"
        if (length($optGraph) == 0);

    $srcPrefix = &getURIPrefix($optRepo, $optUser);
    $srcCmd = "curl $curlExtra -k -s -S -X GET -G -u '${optUser}' -d 'format=text/plain' ".
              "--data-urlencode 'name=${optGraph}' '${optRepo}/repository/graph'";
    if ($optVerbose) {
        ($printme = $srcCmd) =~ s/${optUser}/USER:PASSWORD/;
        print STDERR "Source GET command = $printme\n" ;
        undef $printme;
    }
    open(SRC, "$srcCmd |") || die("Failed to fork curl for input: command=$srcCmd, error=$!\n");
} else {
    die "You must specify the data source from either a file or a graph in a repository, use --help for usage.\n";
}

print STDERR "src prefix=$srcPrefix\n" if $optVerbose;
$destPrefix = &getURIPrefix($destRepo, $destUser);
print STDERR "dest prefix=$destPrefix\n" if $optVerbose;

undef $type;
$type = $optType if length($optType) >0;

$qSrcPrefix = quotemeta($srcPrefix);

$action = $optReplace ? 'replace' : 'add';
$dstCmd = "curl $curlExtra -k -s -S -u '${destUser}' -F 'format=text/plain' ".
           "-F 'content=<-;type=text/plain' ".
           "-F action=$action ".
           (length($type) ? "-F type=$type " : "").
           "-F 'name=${destGraph}' '${destRepo}/repository/graph'";

if ($optVerbose) {
    ($printme = $dstCmd) =~ s/${destUser}/USER:PASSWORD/;
    print STDERR "Destination PUT command = $printme\n";
    undef $printme;
}

# copy graph, while recording subject URLs for later metadata grab.
open(DST, "| ${dstCmd}") || die("Failed to fork curl for output: $!\n");
while (<SRC>) {
    ($sub) = $_ =~ /\s*<([^>]+)>/;
    if ($_ =~ m/<${qSrcPrefix}/) {
        while ($_ =~ m/<${qSrcPrefix}([^>]+)>/) {
            $oldUri = $srcPrefix . $1;
            if ($optBadIdea) {
                $newUri = $destPrefix . $1;
            } else {
                if (defined($uriMap{$oldUri})) {
                    $newUri = $uriMap{$oldUri};
                } else {
                    $newUri = &newURI();
                    $uriMap{$oldUri} = $newUri;
                }
            }
            $qOldUri = $qSrcPrefix . $1;
            $_ =~ s/<${qOldUri}/<${newUri}/g;
        }
        print DST $_;
        ++$dataStatements;
        $subject{$sub} = 1 if length($sub) > 0;
        print STDERR "." if $optVerbose;
    } else {
        print STDERR "Subject does not match indicated prefix, skipping this statement: $_";
    }
}
print STDERR "\n" if $optVerbose;
unless (close(SRC)) {
    warn("error closing resource source stream: $!");
    $retStatus = 1;
}
unless (close(DST)) {
    warn("error closing resource destination stream: $!");
    $retStatus = 2;
}

# Pass 2: copy object metadata - statements in MD graph about subjects we copied.
if (length($optRepo) > 0) {
    $mdSrcCmd = "curl $curlExtra -k -s -S -X GET -G -u '${optUser}' -d 'format=text/plain' ".
              "--data-urlencode 'name=${MDGraph}' '${optRepo}/repository/graph'";
    if ($optVerbose) {
        ($printme = $mdSrcCmd) =~ s/${optUser}/USER:PASSWORD/;
        print STDERR "Source Metadata GET command = $printme\n" ;
        undef $printme;
    }
    open(SRC, "$mdSrcCmd |") || die("Failed to fork curl for MD input: command=$srcCmd, error=$!\n");
    $mdDstCmd = "curl $curlExtra -k -s -u '${destUser}' -F 'format=text/plain' ".
              "-F action=add -F 'content=<-;type=text/plain' ".
              "-F 'name=${MDGraph}' '${destRepo}/repository/graph'";

    if ($optVerbose) {
        ($printme = $mdDstCmd) =~ s/${destUser}/USER:PASSWORD/;
        print STDERR "Metadata Destination PUT command = $printme\n";
        undef $printme;
    }
     
    open(DST, "| ${mdDstCmd}") || die("Failed to fork curl for MD output: $!\n");
    while (<SRC>) {
        ($sub) = $_ =~ /\s*<([^>]+)>/;
        if (defined($subject{$sub})) {
            while ($_ =~ m/<${qSrcPrefix}([^>]+)>/) {
                $oldUri = $srcPrefix . $1;
                if ($optBadIdea) {
                    $newUri = $destPrefix . $1;
                } else {
                    if (defined($uriMap{$oldUri})) {
                        $newUri = $uriMap{$oldUri};
                    } else {
                        $newUri = &newURI();
                        $uriMap{$oldUri} = $newUri;
                    }
                }
                $qOldUri = $qSrcPrefix . $1;
                $_ =~ s/<${qOldUri}/<${newUri}/g;
            }
            print DST $_;
            ++$mdStatements;
            print STDERR "," if $optVerbose;
        }
    }
    unless (close(SRC)) {
        warn("error closing metadata source stream: $!");
        $retStatus = 3;
    }
    unless (close(DST)) {
        warn("error closing metadata destination stream: $!");
        $retStatus = 4;
    }
}

print STDERR "\n" if $optVerbose;
print STDERR "Moved ".(0+$dataStatements)." data statements and ".(0+$mdStatements)." metadata statements.\n";

exit($retStatus);
