#!/usr/bin/php -q
<?php

require_once('constants.ini');
require_once($nusoap_include);
require_once('forum_upload.ini');
require_once('script_functions.ini');

// -------------------------------------------------------------------------
// defaults and definitions
//

$progname = basename($argv[0]);

$soap = NULL;
$soap_session_ser = NULL;

$stdin = fopen("php://stdin", "w");
$stderr = fopen("php://stderr", "w");

$uploading_user = trim(`whoami`);

$n_messages_uploaded = 0;
$n_documents_uploaded = 0;

$documents_language = "English";

// -------------------------------------------------------------------------
// functions
//

function read_message_file($fname) {

	global $stderr;

	$data = file_get_contents($fname);
	if (!$data)
		return(NULL);

	$message = array("attachments" => array());
	while(true) {
		$nl_pos = strpos($data, "\n");
		if ($nl_pos === false) {
			fwrite($stderr, "$progname: error in $fname: no message body\n");
			return(NULL);
		}
		$header_line = substr($data, 0, $nl_pos);
		$data = substr($data, $nl_pos+1);
		if (strlen($header_line) == 0)
			break;
		$parts = split(":", $header_line, 2);
		if (count($parts) < 2) {
			fwrite($stderr, "$progname: error in $fname: " . 
			                "bad header line \"$header_line\"\n");
			return(NULL);
		}
		$key = strtolower(trim($parts[0]));
		$value = trim($parts[1]);
		if ($key == "attachment")
			$message["attachments"][] = array("fname" => $value);
		if ($key == "message id") {
			if (array_key_exists("orig_id", $message)) {
				fwrite($stderr, "$progname: error in $fname: " . 
				                "duplicate message ID header\n");
				return(NULL);
			}
			if (!check_int($value)) {
				fwrite($stderr, "$progname: error in $fname: bad message ID\n");
				return(NULL);
			}
			$message["orig_id"] = intval($value);
		}
		if ($key == "from") {
			if (array_key_exists("from", $message)) {
				fwrite($stderr, "$progname: error in $fname: " . 
				                "duplicate from header\n");
				return(NULL);
			}
			$message["from"] = $value;
		}
		if ($key == "subject") {
			if (array_key_exists("subject", $message)) {
				fwrite($stderr, "$progname: error in $fname: " . 
				                "duplicate subject header\n");
				return(NULL);
			}
			$message["subject"] = $value;
		}
		if ($key == "date") {
			if (array_key_exists("date", $message)) {
				fwrite($stderr, "$progname: error in $fname: " . 
				                "duplicate date header\n");
				return(NULL);
			}
			if (!check_int($value)) {
				fwrite($stderr, "$progname: error in $fname: bad date\n");
				return(NULL);
			}
			$message["date"] = intval($value);
		}
		if ($key == "followup to") {
			if (array_key_exists("followup_to", $message)) {
				fwrite($stderr, "$progname: error in $fname: " . 
				                "duplicate followup to header\n");
				return(NULL);
			}
			if (!check_int($value)) {
				fwrite($stderr, "$progname: error in $fname: " . 
				                "bad followup to ID\n");
				return(NULL);
			}
			$message["followup_to"] = intval($value);
		}
	}

	if (strlen(trim($data)) == 0) {
		fwrite($stderr, "$progname: error in $fname: empty body\n");
		return(NULL);
	}
	$message["body"] = str_replace("\n", "\r\n", $data);

	if (!array_key_exists("orig_id", $message)) {
		fwrite($stderr, "$progname: error in $fname: " . 
		                "no message ID in header\n");
		return(NULL);
	}

	if (!array_key_exists("from", $message)) {
		fwrite($stderr, "$progname: error in $fname: no from in header\n");
		return(NULL);
	}

	if (!array_key_exists("subject", $message)) {
		fwrite($stderr, "$progname: error in $fname: no subject in header\n");
		return(NULL);
	}

	if (!array_key_exists("date", $message)) {
		fwrite($stderr, "$progname: error in $fname: no date in header\n");
		return(NULL);
	}

	return($message);

} // end read_message_file()

function error_clean_up() {

	global $n_attachments;
	global $stderr;
	global $progname;
	global $n_messages_uploaded;
	global $n_documents_uploaded;

	if ($n_attachments > 0)
		soap_log_out();

	forum_close();

	fwrite($stderr, "$progname: bailing out\n");
	fwrite($stderr, "uploaded $n_messages_uploaded forum messages\n");
	fwrite($stderr, "uploaded $n_documents_uploaded attachments\n");

	return;

} // end error_clean_up()

// -------------------------------------------------------------------------
// command line stuff
//

if ($argc == 1) {
?>

usage: <?php echo $progname; ?> <forum ID> <doc group ID> <forum dir> [upload note]

<forum ID> is the ID of the forum to which messages will be uploaded.

<doc group ID> is the ID of the document group to which attachments will be 
uploaded.  It must be valid and in the same group as the forum if there are 
attachments to upload.

<forum dir> is a directory containing files with forum messages, one per 
file.  Each file should contain a header block, a newline, and the message 
body.  The header block should contain key/value pairs, one per line, of 
the form "Key: Value".  Allowed header keys are:

    Message ID (required): A unique integer identifier for this message.  
                           This is only to identify messages before 
                           uploading; this ID will not appear in GForge.

    From (required)

    Subject (required)

    Date (required): Date of posting, in seconds since the epoch.

    Followup To: The Message ID of the message to which this message is a 
                 follow-up.

    Attachment: The name of a file in the attachments directory (see below) 
                to attach to the message.  This is the only field that may 
                appear more than once.

Attachments should be stored in a subdirectory to the forum directory given 
on the command line.  This directory should have subdirectories named after 
the message IDs, in which the attachments should appear.  So if a message 
with ID 137 lists an attachment named "bohr.png", this file should appear 
as <forum dir>/attachments/137/bohr.png.

If an upload note is given on the command line, it is stored in 
forum_upload.note in the GForge database; this is for bookkeeping only and 
will not appear on the web site.

Current configuration:

<?php
	print "    domain: $domain\n";
	print "    nusoap: $nusoap_include\n";
	print "\n";
	exit(1);
}

if ($argc < 4) {
	command_line_error("not enough arguments");
	exit(1);
}

if ($argc > 5) {
	command_line_error("too many arguments");
	exit(1);
}

if (!check_int($argv[1])) {
	command_line_error("bad forum ID \"" . $argv[1] . "\"");
	exit(1);
}
$forum_id = intval($argv[1]);

$doc_group_id_str = $argv[2];

$forum_dir = $argv[3];

if ($argc == 5)
	$upload_note = $argv[4];
else
	$upload_note = NULL;

// -------------------------------------------------------------------------
// basic checks
//

if (!is_dir($forum_dir)) {
	command_line_error("can't find directory \"$forum_dir\"");
	exit(1);
}

$db = pg_connect("dbname=gforge user=gforge");
if (!$db) {
	error_clean_up();
	exit(1);
}

$query = "SELECT forum_group_list.forum_name AS forum_name, " . 
         "       groups.group_id AS group_id, " . 
         "       groups.group_name AS group_name, " . 
         "       groups.unix_group_name AS unix_group_name " . 
         "  FROM forum_group_list, groups " . 
         " WHERE group_forum_id = $forum_id " . 
         "   AND forum_group_list.group_id = groups.group_id";
$res = pg_query($db, $query);
if (!$res) {
	error_clean_up();
	exit(1);
}
if (pg_num_rows($res) == 0) {
	error_clean_up();
	exit(1);
}
$arr = pg_fetch_array($res);
$unix_group_name = $arr["unix_group_name"];
$group_id = $arr["group_id"];
$forum_name = $arr["forum_name"];
print "uploading to forum ID $forum_id: \"$forum_name\"\n";
print "group is \"" . $arr["group_name"] . "\" ($unix_group_name)\n";
pg_free_result($res);

// -------------------------------------------------------------------------
// read the messages
//

print "reading messages...\n";

$messages = array();

$dp = opendir($forum_dir);
if (!$dp) {
	error_clean_up();
	exit(1);
}
while($fname = readdir($dp)) {
	if (strcmp(substr($fname, 0, 1), ".") == 0)
		continue;
	$full_fname = "$forum_dir/$fname";
	if (is_dir($full_fname))
		continue;
	$message = read_message_file($full_fname);
	if (is_null($message)) {
		error_clean_up();
		exit(1);
	}
	if (array_key_exists($message["orig_id"], $messages)) {
		fwrite($stderr, "$progname: duplicate message ID " . 
		                $message["orig_id"] . "\n");
		error_clean_up();
		exit(1);
	}
	$messages[$message["orig_id"]] = $message;
}
closedir($dp);

// -------------------------------------------------------------------------
// check followups and attachments
//

$n_attachments = false;

foreach ($messages as $msg) {
	if (array_key_exists("followup_to", $msg) && !array_key_exists($msg["followup_to"], $messages)) {
		fwrite($stderr, "$progname: unknown followup (orig ID " . 
		                $msg["followup_to"] . ") to message orig ID " . 
		                $msg["orig_id"] . "\n");
		error_clean_up();
		exit(1);
	}
	$attachment_dir = "$forum_dir/attachments/" . $msg["orig_id"];
	$n_attachments += count($msg["attachments"]);
	foreach ($msg["attachments"] as $attachment) {
		$fname = $attachment["fname"];
		$full_fname = "$attachment_dir/$fname";
		if (!file_exists($full_fname)) {
			fwrite($stderr, "$progname: can't find attachment " . 
			                "\"$full_fname\" to message ID " . 
			                $msg["orig_id"] . "\n");
			error_clean_up();
			exit(1);
		}
		if (!is_readable($full_fname)) {
			fwrite($stderr, "$progname: can't read attachment " . 
			                "\"$full_fname\" to message ID " . 
			                $msg["orig_id"] . "\n");
			error_clean_up();
			exit(1);
		}
	}
}

print count($messages) . " messages to upload\n";
print "$n_attachments attachments to upload\n";

if ($n_attachments > 0) {

	if (!check_int($doc_group_id_str)) {
		command_line_error("bad doc group ID \"" . $doc_group_id_str . "\"");
		error_clean_up();
		exit(1);
	}
	$doc_group_id = intval($doc_group_id_str);

	$query = "SELECT * FROM doc_groups WHERE doc_group = $doc_group_id";
	$res = pg_query($db, $query);
	if (!$res) {
		error_clean_up();
		exit(1);
	}
	if (pg_num_rows($res) == 0) {
		fwrite($stderr, "$progname: can't find document group $doc_group_id\n");
		error_clean_up();
		exit(1);
	}
	$arr = pg_fetch_array($res);
	pg_free_result($res);
	if ($arr["group_id"] != $group_id) {
		fwrite($stderr, "$progname: document group (in group ID " . 
		                $arr["group_id"] . ") and forum (in group " . 
		                "$group_id) in different groups\n");
		error_clean_up();
		exit(1);
	}

	print "uploading attachments to document group ID $doc_group_id: \"" . 
	      $arr["groupname"] . "\"\n";

	$query = "SELECT language_id " . 
	         "  FROM supported_languages " . 
	         " WHERE name = '" . pg_escape_string($documents_language) . "'";
	$res = pg_query($db, $query);
	if (!$res) {
		error_clean_up();
		exit(1);
	}
	if (pg_num_rows($res) == 0) {
		fwrite($stderr, "$progname: can't find language $documents_language\n");
		error_clean_up();
		exit(1);
	}
	$arr = pg_fetch_array($res);
	$language_id = intval($arr["language_id"]);
	pg_free_result($res);

	if (!soap_log_in()) {
		error_clean_up();
		exit(1);
	}

}

// -------------------------------------------------------------------------
// order messages for upload
//

/*****
/*
/* we need to make sure that we upload followups after the messages that 
/* they follow
/*
/* we'll make a list of messages that start with those that aren't followups, 
/* then we'll go through iteratively and schedule messages that are 
/* following messages that are already scheduled
/*
/* we stop when all messages are scheduled or none were scheduled in an 
/* iteration (in the latter case, we have a circular reference or other error)
/*
 *****/

print "ordering messages...\n";

$ordered_msg_orig_ids = array();

// --- first pass ---

foreach (array_keys($messages) as $msg_orig_id) {
	if (!array_key_exists("followup_to", $messages[$msg_orig_id]))
		$ordered_msg_orig_ids[] = $msg_orig_id;
}

// --- subsequent passes ---

while(count($ordered_msg_orig_ids) < count($messages)) {
	$n_scheduled_this_iteration = 0;
	foreach (array_keys($messages) as $msg_orig_id) {
		if (in_array($msg_orig_id, $ordered_msg_orig_ids))
			continue;
		if (in_array($messages[$msg_orig_id]["followup_to"], $ordered_msg_orig_ids)) {
			$ordered_msg_orig_ids[] = $msg_orig_id;
			$n_scheduled_this_iteration++;
		}
	}
	if ($n_scheduled_this_iteration == 0) {
		fwrite($stderr, "$progname: error ordering messages\n");
		error_clean_up();
		exit(1);
	}
}

// -------------------------------------------------------------------------
// upload the messages
//

if (!start_upload($uploading_user, $upload_note)) {
	error_clean_up();
	exit(1);
}

print "upload ID is $upload_id\n";

foreach ($ordered_msg_orig_ids as $msg_orig_id) {

	print "uploading orig ID $msg_orig_id...\n";

	// --- attachments first ---

	$attachment_urls = array();
	for($i = 0;$i < count($messages[$msg_orig_id]["attachments"]);$i++) {
		$attachment = $messages[$msg_orig_id]["attachments"][$i];
		print "    uploading attachment " . $attachment["fname"] . "...\n";
		$attachment_dir = "$forum_dir/attachments/" . $msg_orig_id;
		$full_name = "$attachment_dir/" . $attachment["fname"];
		$file_contents = file_get_contents($full_name);
		if ($file_contents === false) {
			error_clean_up();
			exit(1);
		}
		$base64_contents = base64_encode($file_contents);
		$soap_vars = array("session_ser"     => $soap_session_ser, 
		                   "group_id"        => $group_id, 
		                   "doc_group"       => $doc_group_id, 
		                   "title"           => "forum attachment file", 
		                   "description"     => "forum attachment file", 
		                   "language_id"     => $language_id, 
		                   "base64_contents" => $base64_contents, 
		                   "filename"        => $attachment["fname"], 
		                   "file_url"        => "");
		$rv = $soap->call("addDocument", $soap_vars);
		if (!is_int($rv)) {
			fwrite($stderr, "$progname: error uploading attachment\n");
			if (isset($rv["detail"]["soapVal"])) {
				$msg = "$progname: " . $rv["detail"]["soapVal"] . "\n";
				fwrite($stderr, $msg);
			} elseif ($rv === false) {
				fwrite($stderr, "$progname: $soap->error_str\n");
			}
			error_clean_up();
			exit(1);
		}
		$doc_id = $rv;
		$messages[$msg_orig_id]["attachments"][$i]["doc_id"] = $doc_id;
		$n_documents_uploaded++;
		$query = "INSERT INTO forum_upload_attachment (forum_upload, " . 
		         "                                     doc_id) " . 
		         "       VALUES ($upload_id, $doc_id)";
		$res = pg_query($db, $query);
		if (!$res) {
			error_clean_up();
			exit(1);
		}
		$query = "SELECT CURRVAL('forum_upload_attachment_id_seq')";
		$res = pg_query($db, $query);
		if (!$res) {
			error_clean_up();
			exit(1);
		}
		$arr = pg_fetch_array($res);
		$messages[$msg_orig_id]["attachments"][$i]["forum_upload_attachment_id"] = intval($arr[0]);
		$messages[$msg_orig_id]["attachments"][$i]["url"] = "http://$domain/docman/view.php/$group_id/$doc_id/" . urlencode($attachment["fname"]);
	}

	// --- then the message ---

	if (count($messages[$msg_orig_id]["attachments"]) > 0) {
		$messages[$msg_orig_id]["body"] .= "\n";
		$messages[$msg_orig_id]["body"] .= "Attachments:\n";
		$messages[$msg_orig_id]["body"] .= "\n";
		foreach ($messages[$msg_orig_id]["attachments"] as $attachment)
			$messages[$msg_orig_id]["body"] .= $attachment["url"] . "\n";
	}
	if (array_key_exists("followup_to", $messages[$msg_orig_id])) {
		$parent_msg = $messages[$messages[$msg_orig_id]["followup_to"]];
		$parent_msg_nitrc_id = $parent_msg["nitrc_id"];
		$thread_id = $parent_msg["thread_id"];
	} else {
		$parent_msg_nitrc_id = 0;
		$thread_id = 0;
	}
	$nitrc_msg = create_message($forum_id, 
	                            $messages[$msg_orig_id]["subject"], 
	                            $messages[$msg_orig_id]["body"], 
	                            $thread_id, 
	                            $parent_msg_nitrc_id, 
	                            $messages[$msg_orig_id]["date"], 
	                            $messages[$msg_orig_id]["from"]);
	if (is_null($nitrc_msg)) {
		error_clean_up();
		exit(1);
	}
	$n_messages_uploaded++;
	$messages[$msg_orig_id]["nitrc_id"] = $nitrc_msg["msg_id"];
	$messages[$msg_orig_id]["thread_id"] = $nitrc_msg["thread_id"];

}

// --- now update attachment descriptions ---

if ($n_attachments > 0) {

	$rv = $soap->call("getDocuments", array("session_ser" => $soap_session_ser, 
	                                        "group_id"    => $group_id, 
	                                        "doc_group"   => $doc_group_id));
	if (!is_array($rv)) {
		fwrite($stderr, "$progname: unknown error in getDocuments SOAP call\n");
		error_clean_up();
		exit(1);
	}
	if (isset($rv["faultstring"])) {
		$err_msg = "$progname: error in getDocuments SOAP call: " .  
		           $rv["faultstring"];
		fwrite($stderr, $err_msg);
		error_clean_up();
		exit(1);
	}
	if (count($rv) == 0) {
		fwrite($stderr, "$progname: couldn't get statuses for attachments\n");
		error_clean_up();
		exit(1);
	}
	$state_ids = array();
	foreach ($rv as $doc_info) {
		if (!is_array($doc_info) || !array_key_exists("stateid", $doc_info) || !array_key_exists("docid", $doc_info)) {
			fwrite($stderr, "$progname: error getting attachment status\n");
			error_clean_up();
			exit(1);
		}
		$state_ids[$doc_info["docid"]] = $doc_info["stateid"];
}

	foreach ($messages as $msg) {

		for($i = 0;$i < count($msg["attachments"]);$i++) {

			print "updating attachment " . 
			      $msg["attachments"][$i]["fname"] . 
			      " to message " . $msg["nitrc_id"] . "...\n";

			$forum_upload_attachment_id = 
			    $msg["attachments"][$i]["forum_upload_attachment_id"];
			$query = "UPDATE forum_upload_attachment " . 
			         "   SET msg_id = " . $msg["nitrc_id"] . 
			         " WHERE id = $forum_upload_attachment_id";
			$res = pg_query($db, $query);
			if (!$res) {
				error_clean_up();
				exit(1);
			}
			pg_free_result($res);

			$doc_id = $msg["attachments"][$i]["doc_id"];
			if (!array_key_exists($doc_id, $state_ids)) {
				$err_msg = "$progname: missing status for " . 
				           "document ID $doc_id\n";
				fwrite($stderr, $err_msg);
				error_clean_up();
				exit(1);
			}
			$msg_id = $msg["nitrc_id"];
			$new_title = "forum $forum_id message $msg_id attachment " . 
			             strval($i+1);
			$new_description = "attachment \"" . $msg["attachments"][$i]["fname"] . "\" to message ID $msg_id in forum \"$forum_name\"";
			$soap_vars = array("session_ser" => $soap_session_ser, 
			                   "group_id"    => $group_id, 
			                   "doc_group"   => $doc_group_id, 
			                   "doc_id"      => $doc_id, 
			                   "state_id"    => $state_ids[$doc_id],  
			                   "title"       => $new_title, 
			                   "description" => $new_description, 
			                   "language_id" => $language_id, 
			                   "filename"    => $attachment["fname"], 
			                   "file_url"    => "");
			$rv = $soap->call("updateDocument", $soap_vars);
			if ($rv === true) {
				// success
			} elseif ($rv === false) {
				fwrite($stderr, "$progname: $soap->error_str\n");
			} elseif (is_array($rv) && isset($rv["faultstring"])) {
				$err_msg = "$progname: error in getDocuments SOAP call: " .  
				           $rv["faultstring"];
				fwrite($stderr, $err_msg);
				error_clean_up();
				exit(1);
			} else {
				$err_msg = "$progname: unknown error in getDocuments SOAP call";
				fwrite($stderr, $err_msg);
				error_clean_up();
				exit(1);
			}

		}

	}

}

end_upload();

print "uploaded $n_messages_uploaded forum messages\n";
print "uploaded $n_documents_uploaded attachments\n";

// -------------------------------------------------------------------------
// clean up and exit
//

if ($n_attachments > 0)
	soap_log_out();

forum_close();

exit(0);

?>
