#!/usr/local/bin/php
<?php

# arczip - A script for parsing (loading) or serialising an archive of RDF
#          graphs to/from a persistent ARC2 storage.
#
# Usage: ./arczip ( parse [ --keep ] [ --test ] [ --reset ] [ --list ]
#                 | serialise [ --graph <uri> ]
#                   [ --output (rdfxml|turtle|ntriples) ] )
#                 [ --verbose ] <arc2-uri> <archive>
#
# Example: ./arczip parse arc2://user:pass@localhost/db/store_name file.zip
#
# URL: http://www.wasab.dk/morten/blog/archives/2008/01/08/named-graph-exchange
#
# Revision: $ bzr-revision-id $
#
# Changelog
# ---------
# Version 1.5 - 2007.01.24 (morten@mfd-consult.dk):
# - Fixed incorrect file: URIs.
# Version 1.4 - 2007.01.14 (morten@mfd-consult.dk):
# - Fixed hardcoded manifest extension, added generatorAgent, added --list.
# Version 1.3 - 2007.01.09 (morten@mfd-consult.dk):
# - Added --reset option and statistics to manifest.
# Version 1.2 - 2007.01.09 (morten@mfd-consult.dk):
# - Improved loading and documented --verbose option.
# Version 1.1 - 2007.01.08 (morten@mfd-consult.dk):
# - Added --test option, graph label verification.
# Version 1.0 - 2007.01.06 (morten@mfd-consult.dk):
# - First release based on redzip.
#
# Copyright 2005-2007, Morten Frederiksen, mfd-consult.dk
# Licensed under the Eiffel Forum License, version 2.
#

include_once('arc2/ARC2.php');

# Process based on command line arguments, if present.
if (isset($_SERVER['argv']))
    
arczip_process($_SERVER['argv']);

# Parse or serialise based on arguments.
function arczip_process($_argv=array()) {
  global 
$verbose;

    
# Look for options.
    
$list false;
    
$reset false;
    
$verbose false;
    
$keep false;
    
$test false;
    
$graph '';
    
$output 'ntriples';
    
$argv = array(array_shift($_argv));
    
$last '';
    foreach (
$_argv as $v) {
        if (
'--keep'==$v)
            
$keep true;
        elseif (
'--verbose'==$v)
            
$verbose true;
        elseif (
'--test'==$v)
            
$test $verbose true;
        elseif (
'--reset'==$v)
            
$reset $keep true;
        elseif (
'--list'==$v)
            
$list $test true;
        elseif (
'--graph'==$last) {
            
$last '';
            
$graph $v;
        } elseif (
'--output'==$last) {
            
$last '';
            
$output $v;
      } elseif (
''!=$last) {
          
arczip_help();
          exit;
      } elseif (
'--'==substr($v02)) {
              
$last $v;
        } else {
            
$argv[] = $v;
            
$last '';
        }
    }
    
    
# Check arguments.
    
if (sizeof($argv)!=4
            
|| 'parse'!=$argv[1] && 'serialise'!=$argv[1]
            || 
'parse'==$argv[1] && !file_exists($argv[3])) {
        
arczip_help();
        exit;
    }
    
$operation $argv[1];
    
$arc2uri $argv[2];
    
$archive $argv[3];

    
# Configure ARC2 storage.
    
if (!$list) {
        
$config parse_storage_uri($arc2uri);
        
$store ARC2::getStore($config);
        if (!
$store->isSetUp()) 
            
$store->setUp();
        if (
$e $store->getErrors())
            die(
__FILE__ ' on line ' __LINE__ ': '.join(' '$e)."\n");
    }

    if (
'parse'==$operation)
        
arczip_parse($store$archive$keep$test$reset$list);
    elseif (
'serialise'==$operation)
        
arczip_serialise($store$archive$graph$output);
    else
        
arczip_help();
        
}

# Show documentation.
function arczip_help() {
    print 
'Usage: '.$_SERVER['argv'][0].' ( parse [ --keep ] [ --test ] [ --reset ] [ --list ]
                    | serialise [ --graph <uri> ]
                      [ --output (rdfxml|turtle|ntriples) ] )
                    [ --verbose ] <arc2-uri> <archive>

Example: '
.$_SERVER['argv'][0].' parse arc2://user:pass@mysql/db/store file.zip
'
;
}

# Parse archive into storage.
function arczip_parse(&$store$archive$keep$test$reset$list) {
  global 
$verbose;

    
# Extract archive into temporary directory.
    
$tmpdir tempdir();
    if (
$verbose) print $_SERVER['argv'][0].': unzipping archive to '.$tmpdir."\n";
    
$tmperr tempnam($tmpdir'unzip.err');
    
$z shell_exec('unzip -d '.$tmpdir.' '.$archive.' 2>'.$tmperr);

    
# Parse manifest doc.
    
if (!preg_match('|(META-INF/rdf-manifest\.(\w+))|'$z$M)) {
        
deltree($tmpdir);
        die(
__FILE__ ' on line ' __LINE__ ': unable to locate rdf-manifest.'."\n");
    }
    list(, 
$manifest$syntax) = $M;
    if (
'nt'==$syntax || 'ttl'==$syntax)
        
$parser ARC2::getTurtleParser();
    elseif (
'rdf'==$syntax)
        
$parser ARC2::getRDFXMLParser();
    else
        
$parser ARC2::getRDFParser();
    if (
$verbose) print $_SERVER['argv'][0].': parsing manifest: ' $manifest "\n";
    
$parser->parse('file:/'.$tmpdirfile_get_contents($tmpdir.$manifest));
    if (
$e $parser->getErrors()) {
        
deltree($tmpdir);
        die(
__FILE__ ' on line ' __LINE__ ': '.join(' '$e)."\n");
    }
    
$index $parser->getSimpleIndex();
    if (
$verbose) print $_SERVER['argv'][0].': manifest: ' sizeof(ARC2::getTriplesFromIndex($index)) . ' statements' "\n";

    
# Reset storage?
    
if ($reset && !$test) {
        
$store->reset();
        if (
$e $store->getErrors()) {
            
deltree($tmpdir);
            die(
__FILE__ ' on line ' __LINE__ ', while resetting storage: '.join(' '$e)."\n");
        }
    }

    
# List?
    
if ($list) {
        if (isset(
$index['file:/'.$tmpdir.$manifest]['http://www.w3.org/2000/01/rdf-schema#comment']))
            print 
$_SERVER['argv'][0].': content: ' current($index['file:/'.$tmpdir.$manifest]['http://www.w3.org/2000/01/rdf-schema#comment']) . "\n";
        if (isset(
$index['file:/'.$tmpdir.$manifest]['http://webns.net/mvcb/generatorAgent']))
            print 
$_SERVER['argv'][0].': generator: ' current($index['file:/'.$tmpdir.$manifest]['http://webns.net/mvcb/generatorAgent']) . "\n";
    }
        
    
# Iterate over graphs referenced from manifest doc with rdfs:seeAlso.
    
if (!isset($index['file:/'.$tmpdir.$manifest])
            || !isset(
$index['file:/'.$tmpdir.$manifest]['http://www.w3.org/2000/01/rdf-schema#seeAlso'])) {
        
deltree($tmpdir);
        die(
__FILE__ ' on line ' __LINE__ ': No graphs found in manifest'."\n");
    }
    foreach (
$index['file:/'.$tmpdir.$manifest]['http://www.w3.org/2000/01/rdf-schema#seeAlso'] as $graph) {
        
# Find label (name) for graph.
        
if (!isset($index[$graph]['http://www.w3.org/2000/01/rdf-schema#label'])) {
          print 
$_SERVER['argv'][0].': label not found for '.$graph.', skipping'."\n";
          continue;
        }
        
$label array_shift($index[$graph]['http://www.w3.org/2000/01/rdf-schema#label']);
        
# Truncate context?
        
if (!$keep && !$test) {
            
$store->query('DELETE FROM <'.$label.'>');
            if (
$e $store->getErrors()) {
                
deltree($tmpdir);
                die(
__FILE__ ' on line ' __LINE__ ', while truncating '.$label.': '.join(' '$e)."\n");
            }
        }
        
# List?
        
if ($list) {
            if (isset(
$index[$graph]['http://www.w3.org/2000/01/rdf-schema#comment']))
              print 
$_SERVER['argv'][0].': '.$label.': '.current($index[$graph]['http://www.w3.org/2000/01/rdf-schema#comment'])."\n";
            else
              print 
$_SERVER['argv'][0].': '.$label."\n";
          continue;
        }
        
# Parse and load.
      
if ($verbose) print $_SERVER['argv'][0].': parsing and loading graph: '.$label."\n";
      if (
$test) continue;
      
$doc file_get_contents(preg_replace('|^file:/|'''$graph));
        
$r $store->insert($doc$labeltrue);
        if (
$e $store->getErrors()) {
            
deltree($tmpdir);
            die(
__FILE__ ' on line ' __LINE__ ', while inserting '.$label.': '.join(' '$e)."\n");
        }
      if (
$verbose) print $_SERVER['argv'][0].': '.$r['t_count'].' triples were loaded'."\n";
    }
    
    if (
$verbose) print $_SERVER['argv'][0].': done'."\n";
    
deltree($tmpdir);
}

# Serialise storage into archive.
function arczip_serialise(&$store$archive$graph=''$output='ntriples') {
  global 
$verbose;

    
$extensions = array(
            
'turtle'=>'ttl',
            
'rdfxml'=>'rdf',
            
'ntriples'=>'nt');
    if (!isset(
$extensions[$output]))
        die(
__FILE__ ' on line ' __LINE__ ': Invalid output type, '.$output."\n");

    
# Create temporary directory for file storage.
    
$tmpdir tempdir();
    if (
$verbose) print $_SERVER['argv'][0].': creating archive in '.$tmpdir."\n";
    
    
# Build list of graphs to serialise.
    
if (''!=$graph) {
        
$graphs = array('g' => $graph);
    } else {
        
$graphs $store->query('SELECT DISTINCT ?g WHERE { GRAPH ?g { ?s ?p ?o } }''rows');
        if (
$e $store->getErrors()) {
            
deltree($tmpdir);
            die(
__FILE__ ' on line ' __LINE__ ', while listing graphs: '.join(' '$e)."\n");
        }
    }
    if (
$verbose) print $_SERVER['argv'][0].': serialising '.sizeof($graphs).' graph(s)'."\n";

    
# Iterate through graphs, building manifest on the way.
    
$filenum 0;
    
$triplecount 0;
    
$manifest = array();
    if (
sizeof($graphs))
        
$manifest['META-INF/rdf-manifest.'.$extensions[$output]]['http://www.w3.org/2000/01/rdf-schema#seeAlso'] = array();
    foreach (
$graphs as $graph) {
        
$label $graph['g'];
      if (
$verbose) print $_SERVER['argv'][0].': serialising graph: '.$label."\n";

        
# Extract triples.
        
$index $store->query('CONSTRUCT { ?s ?p ?o } WHERE { GRAPH <'.$label.'> { ?s ?p ?o } }''raw');
        if (
$e $store->getErrors()) {
            
deltree($tmpdir);
            die(
__FILE__ ' on line ' __LINE__ ', while extracting triples from '.$label.': '.join(' '$e)."\n");
        }
        
$triples ARC2::getTriplesFromIndex($index);

        
# Serialise to temporary file.
        
$graph 'graph'.++$filenum.'.'.$extensions[$output];
        if (
'turtle'==$output)
          
$doc $store->toTurtle($index);
        elseif (
'rdfxml'==$output)
          
$doc $store->toRDFXML($index);
      else
          
$doc $store->toNTriples($index);
        if (!(
$fh=fopen($tmpdir.$graph'w'))
                || (
$e $store->getErrors())
                || (
false===fwrite($fh$doc))) {
            
deltree($tmpdir);
            die(
__FILE__ ' on line ' __LINE__ ', while serialising triples from '.$label.': '.join(' '$e)."\n");
        }
        
fclose($fh);

        
# Update manifest.
        
$manifest[$graph]['http://www.w3.org/2000/01/rdf-schema#label'][] = array('val'=>$label'type'=>'iri');
        
$manifest[$graph]['http://www.w3.org/2000/01/rdf-schema#comment'][] = array('val'=>sizeof($triples).' statements''type'=>'literal');
        
$manifest['META-INF/rdf-manifest.'.$extensions[$output]]['http://www.w3.org/2000/01/rdf-schema#seeAlso'][] = array('val'=>$graph'type'=>'iri');
        
$triplecount += sizeof($triples);
    }

    
# Serialise manifest.
    
$manifest['META-INF/rdf-manifest.'.$extensions[$output]]['http://www.w3.org/2000/01/rdf-schema#comment'][] = array('val'=>$filenum ' graphs, ' $triplecount ' statements''type'=>'literal');
    
$manifest['META-INF/rdf-manifest.'.$extensions[$output]]['http://webns.net/mvcb/generatorAgent'][] = array('val'=>'http://bzr.mfd-consult.dk/named-graph-exchange/arczip.php''type'=>'iri');
    if (
$verbose) print $_SERVER['argv'][0].': serialising manifest for ' $filenum ' graphs, ' $triplecount ' statements: META-INF/rdf-manifest.'.$extensions[$output]."\n";
    if (
'turtle'==$output)
        
$triples $store->toTurtle($manifest);
    elseif (
'rdfxml'==$output)
        
$triples $store->toRDFXML($manifest);
    else
      
$triples $store->toNTriples($manifest);
    if (!
mkdir($tmpdir.'META-INF/')
            || !(
$fh=fopen($tmpdir.'META-INF/rdf-manifest.'.$extensions[$output], 'w'))
            || (
$e $store->getErrors())
            || (
false===fwrite($fh$triples))) {
        
deltree($tmpdir);
        die(
__FILE__ ' on line ' __LINE__ ', while serialising triples from '.$label.': '.join(' '$e)."\n");
    }
    
fclose($fh);

    
# Create archive.
    
$cwd getcwd();
    
$z shell_exec('cd '.$tmpdir.' ; zip -r '.$cwd.'/'.$archive.' .');
    
    if (
$verbose) print $_SERVER['argv'][0].': done'."\n";
    
deltree($tmpdir);
}

# parse_storage_uri returns associative array with the following keys in order:
# - db_type (not used)
# - db_host
# - db_name
# - db_user
# - db_pwd
# - store_name
function parse_storage_uri($uri='arc2://mysql/db/rdf') {
    
$uriparts parse_url($uri);
    
$uriparts['path'] = preg_replace('|^/|','',$uriparts['path']);
    list(
$uriparts['path'],$uriparts['store_name']) = split('/',$uriparts['path'].'/');
    return array(
            
'db_type' => @$uriparts['scheme'],
            
'db_host' => @$uriparts['host'],
            
'db_name' => @$uriparts['path'],
            
'db_user' => @$uriparts['user'],
            
'db_pwd' => @$uriparts['pass'],
            
'store_name' => @$uriparts['store_name']);
}

function 
tempdir($dir='/root/'$prefix='tempdir'$mode=0700) {
    if (!(
$dir tempnam($dir$prefix)))
        return 
'';
    
unlink($dir);
    do {
        
$path $dir.mt_rand(09999999).'/';
    } while (!
mkdir($path$mode));
    return 
$path;
}

function 
deltree($dir) {
    if (
is_dir($dir)) {
        
$dh opendir($dir);
        while (
false !== ($item readdir($dh))) {
            if (!
strcmp($item'.') || !strcmp($item'..'))
                continue;       
            
deltree($dir '/' $item);
        }
        
closedir($dh);
        
rmdir($dir);
    } else
        
unlink($dir);
}

?>