<?php
/*
* Copyright (c) 2009-2010 ATMAIL. All rights reserved
* See http://atmail.com/license.php for license agreement
*/

/*
* A5 DB to A6 CALDAV
* Migration
*
* Usage : Migrates all compatable data from a5.6 database to a6 caldav.
*/

// threshold of total events before a child will further thread itself
$CALENDAR_UIDS_STORAGE = "./calendar.uids";
@unlink("./calendar.uids");

// *********** INIT / SETUP ************* //
define('APP_ROOT', dirname(__FILE__) . DIRECTORY_SEPARATOR);
set_include_path(APP_ROOT . "../../library/" . PATH_SEPARATOR . APP_ROOT . "../../library/Atmail" . PATH_SEPARATOR . APP_ROOT . "../../application/models" . PATH_SEPARATOR . APP_ROOT . "../../" . PATH_SEPARATOR . APP_ROOT . "../../library/Atmail/CalDav" . PATH_SEPARATOR . "library/" . PATH_SEPARATOR . get_include_path());
require_once 'Zend/Loader/Autoloader.php';
$loader = Zend_Loader_Autoloader::getInstance();
$loader->setFallbackAutoloader(true);
$loader->suppressNotFoundWarnings(false);

//Error reporting
error_reporting(E_ALL & ~E_NOTICE);
ini_set('display_startup_errors', 1);
ini_set('display_errors', 1);

// uncomment to enable IMAP debugging
define('ATMAIL_IMAP_ERR', '/tmp/atmail-imap-err');

mb_internal_encoding('UTF-8');
iconv_set_encoding("internal_encoding", "UTF-8");
iconv_set_encoding("input_encoding", "BASE-64");
iconv_set_encoding("output_encoding", "UTF-8");

//Load general functions
Zend_Loader::loadFile('Atmail/General.php', null, true);
Zend_Loader::loadFile('Atmail/RenderHelpers.php', null, true);
Zend_Loader::loadFile('Atmail/Exception.php', null, true);
Zend_Loader::loadFile('Atmail/Utility.php', null, true);

require_once 'lib/migrate.php';
require_once 'lib/stats.php';

$overallTotal = 0;
$overallTime = microtime_float();

function displayHelp($options)
{
?>
	This utility will migrate all your data from an existing
	Atmail 5 database to your current Atmail 6 calendar server.

	***** Please DO NOT interrupt/disown this process once started! *****
	This process can take minutes to hours depending on host speed, 
	memory and calendar data density from Atmail 5.

	It is recommended to use a LOCAL sql connection.

	Example with minimum arguments :
	php <?php echo $_SERVER['argv'][0]; ?> --a5_db atmail5database

	Usage: <?php echo $_SERVER['argv'][0]; ?> <options>
		--help, -help, -h, or -? :	to get this help.
		--version : to return the version of this file.
<?php
}

$options = array(
	array("force", "skip any sql errors and continue", "false"),
	array("a5_db", "Atmail 5 database tablename", "atmail5"),
	array("a5_host", "Atmail 5 database host", "localhost"),
	array("a5_user", "Atmail 5 database username", "root"),
	array("a5_pass", "Atmail 5 database password", ""),
	array("a6_db", "Atmail 6 database tablename", "atmail6"),
	array("a6_host", "Atmail 6 database host", "localhost"),
	array("a6_user", "Atmail 6 database username", "root"),
	array("a6_pass", "Atmail 6 database password", ""),
	array("a6_config_file", "Atmail 6 configuration relative path (set to false to specify manual a6 db config)", "../../config/dbconfig.ini"),
	array("a6_override_caldav_url", "Specify the base url to use for target caldav server", "false"),
	array("userCacheSize", "Amount of users to cache when verifying entries", "20000"),
	array("verbose", "verbose output", "false"),
	array("skipNonPersonal", "skip all non-personal calendars", "false"),
	array("dryRun", "Dont actually do any migration", "false"),
	array("noSemaphoreSupport", "Dont use any semaphore functions (time and orphand functions will be disabled/errornous)", "false"),
	array("singleUser", "Migrate the user specified", ""),
	array("cleardb", "Clear the atmail 6 calendar database before begining", "false"),
	
	);

$arguments = processOptions($_SERVER['argc'], $_SERVER['argv'], $options);
$THREAD_SPLIT_THRESHOLD = $arguments['threadSplitThreshold'];

if (isset($arguments['singleUser']) && !empty($arguments['singleUser'])) {
	$singleUser = $arguments['singleUser'];
	$suffix = substr($singleUser, 0, 1);
	$suffix2 = substr($singleUser, 1, 1);
	if (!in_array($suffix, range('a', 'z'))) {
		$suffix = "other";
	}
	if (!in_array($suffix2, range('a', 'z'))) {
		$suffix2 = "other";
	}
	
	$arguments['noSemaphoreSupport'] = true;
}

// *********** CONNECT DATABASES ************* //
$pdoParams = array(
	PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
	PDO::ATTR_EMULATE_PREPARES => true,
	PDO::ATTR_PERSISTENT => false
	);

$oldParams = array(
	'host'		   => $arguments['a5_host'],
	'username'	   => $arguments['a5_user'],
	'password'	   => $arguments['a5_pass'],
	'dbname'		 => $arguments['a5_db'],
	'driver_options' => $pdoParams
	);

if($arguments['a6_config_file'] != 'false')
{
	$dbConfig = new Zend_Config_Ini(realpath($arguments['a6_config_file']), 'production');
	Zend_Registry::set('dbConfig', $dbConfig);

	$arguments['a6_db'] = $dbConfig->database->params->dbname;
	$arguments['a6_host'] = $dbConfig->database->params->host;
	$arguments['a6_user'] = $dbConfig->database->params->username;
	$arguments['a6_pass'] = $dbConfig->database->params->password;
}
else
{
	$output = "[production]\ndatabase.adapter = pdo_mysql\n\r" 
		. "database.params.host = " . $arguments['a6_host'] . "\n\r"
		. "database.params.username = \"" . $arguments['a6_user'] . "\"\n"
		. "database.params.password = \"" . $arguments['a6_pass'] . "\"\n"
		. "database.params.dbname = \"" . $arguments['a6_db'] . "\"\n"
		. "database.params.configtable = Config\n\n";

	file_put_contents("temp.ini", $output);
	$dbConfig = new Zend_Config_Ini("temp.ini", 'production');
	Zend_Registry::set('dbConfig', $dbConfig);
	unlink("temp.ini");
}

$newParams = array(
	'host'		   => $arguments['a6_host'],
	'username'	   => $arguments['a6_user'],
	'password'	   => $arguments['a6_pass'],
	'dbname'		 => $arguments['a6_db'],
	'driver_options' => $pdoParams
	);

$database = $oldParams['dbname'];
$sql_url = $oldParams['host'];
$sql_user = $oldParams['username'];
$sql_pass = $oldParams['password'];

$oldDb = Zend_Db::Factory('Pdo_Mysql', $oldParams, $pdoParams);

try
{
	$oldDb->getConnection();
}
catch (Exception $e)
{
	die("Could not connect to database " . $arguments['a5_db'] . "\n");	
}

$newDb = Zend_Db::Factory('Pdo_Mysql', $newParams, $pdoParams);

try
{
	$newDb->getConnection();
}
catch (Exception $e)
{
	die("Could not connect to database " . $arguments['a6_db'] . "\n");	
}

checkA5Schema($oldDb);
$oldDb->setFetchMode(Zend_Db::FETCH_OBJ);
$largeSchema = isSchemaTypeLarge($oldDb);

$range = getSchemaRange($oldDb, 'a', 'z', true);
$simple_range = getSchemaRange($oldDb, 'a', 'z', true, true);
$normal_range = getSchemaRange($oldDb, 'a', 'z', false, true);

Zend_Registry::set( 'siteBaseUrl', substr($_SERVER["SCRIPT_NAME"],0,strpos($_SERVER["SCRIPT_NAME"],'index.php')) );

$newDb->query("SET NAMES 'utf8'");
Zend_Db_Table::setDefaultAdapter($newDb);
Zend_Registry::set('dbAdapter', $newDb);

//Get main configuration from database
$config = new Atmail_Config_Mysql($dbConfig, true);
Zend_Registry::set('config', $config);
$locale = new Zend_Locale();
Zend_Registry::set('Zend_Locale', $locale);
session_name('ATMAIL6');

// Load the CalDavUrl from the DB
if($arguments['a6_override_caldav_url'] != 'false')
{
	$cal_url = $arguments['a6_override_caldav_url'];
}
else
{
	$cal_url = $config->defaultUserSettings['CalDavUrl'];	
}

echo "Using CalDav URL: $cal_url\n";

unset($config);
unset($settings);

include 'application/models/calendar.php';

$no_pass = false;

fwrite(STDOUT, "\n\nPlease DO NOT interrupt/disown this process once started!\n\n\n");
fwrite(STDOUT, "You will find 'orphaned.entries' in your current directory with a list of non-migrated events that did not have an owner\n\n");

$calendars = $range;
$calendaruids = array();
$pids = array();

@unlink("./orphaned.entries");

if($arguments['cleardb'])
{
	if( prompt('You have specified that the Atmail 6 calendar database be cleared. ALL CURRENT ATMAIL 6 CALENDAR DATA WILL BE LOST. Type "yes" if you really want to do this.', 'no') == 'yes')
	{
		fwrite(STDOUT, "Clearing Atmail 6 calendar database...\n");
		$newDb->query("delete from calendarDelegates; delete from calendarExtendedData; delete from calendarObjects; delete from calendars; delete from groupMembers; delete from principals;");
	}
}


function insertUser($cal_url, $Account, $entry_password, $CalName, $oldDb, $CalNameID)
{
	global $arguments, $newDb;

	fwrite(STDOUT, "Creating principals for: $Account\n");
	
	$principal = "principals/users/$Account";
	
	if(!$arguments['dryRun']) {
		$newDb->query(
				"INSERT IGNORE INTO principals (uri, email, displayname) VALUES (?, ?, ?)", array($principal, $Account, $Account)
		);
	}

	$_calendar = new calendar(array('URL' => $cal_url, 'Account' => $Account, 'Password' => $entry_password, 'Calendar' => "calendar", 'home', 'largeInstall' => 0));
    $newcal = $_calendar->make_calendar($Account, $CalName);
	unset($_calendar);

	unset($stmt3);
	$stmt3 = $oldDb->query("select * from Calander_shared where CalNameID = " . $CalNameID . "");
	while($row3 = $stmt3->fetch())
	{
		$UserTo=$row3->UserTo;
		$UserFrom=$row3->UserFrom;
		$Title=$row3->Title;
		$DatePost=$row3->DatePost;
		$CalMessage=$row3->CalMessage;
		$Alert=$row3->Alert;
		$DateStart=$row3->DateStart;
		$DateEnd=$row3->DateEnd;
		$Type=$row3->Type;
		$Importance=$row3->Importance;
		$Task=$row3->Task;
		$Ref=$row3->Ref;
		$Parent=$row3->Parent;
		$Permission=$row3->Permission;
		$EntryID=$row3->EntryID;
		$DateModified=$row3->DateModified;
		$Location=$row3->Location;
		$AllDayEvent=$row3->AllDayEvent;
		$DateAlert=$row3->DateAlert;
		$IsRecurring=$row3->IsRecurring;
		$IsException=$row3->IsException;
		$DateAlertPeriod=$row3->DateAlertPeriod;
		$Availability=$row3->Availability;
		$CalNameID=$row3->CalNameID;

		// caldav glue	
		$entry_username = $Account;
		$_calendar = new calendar(array('migration' => true, 'URL' => $cal_url, 'Account' => $entry_username, 'Password' => $entry_password, 'Calendar' => "calendar", 'home', 'largeInstall' => 0));

		if($Title == "(null)")
			$Title = "";			
		if($CalMessage == "(null)")
			$CalMessage = "";

		$calurl = $newcal->url;

		if(!$arguments['dryRun'])
		{
			$args = array(
				'task' => $Task,
				'title' => $Title,
				'description' => $CalMessage,
				'dateStart' => $DateStart,
				'dateEnd' => $DateEnd,
				'location' => $Location,
				'allDayEvent' => $AllDayEvent,
				'calendarUrl' => $calurl
			);
			$uid = $_calendar->add_record($args);
		}
	}

	unset($_calendar);
	
	return $newuser;
}

function addCalendarProxy($cal_url, $entry_username, $entry_password, $Account, $newuser)
{
	global $arguments;
	$_calendar = new calendar(array('migration' => true, 'URL' => $cal_url, 'Account' => $entry_username, 'Password' => $entry_password, 'Calendar' => "calendar", 'home', 'largeInstall' => 0));
	if(!$arguments['dryRun'])
	{
		$_calendar->add_proxy($newuser, $Account, 'w');
	}
	unset($_calendar);	
}

function processNonPersonal($oldDb, $cal_url)
{
	global $arguments;
	
	$oldDb->getConnection();
	unset($stmt);
	if (isset($singleUser)) {
		$stmt = $oldDb->query("select count(*) from CalendarNames as cn, CalendarPermissions as cp where cp.Account = ? and CalNameID > 0 and cn.id = cp.CalNameID", $singleUser);
	} else {
		$stmt = $oldDb->query("select count(*) as total from CalendarNames");
	}
	$total = $stmt->fetch();
	$total = $total->total;
	$count = 0;
	$orphans = 0;
	fwrite(STDOUT, "Migrating non personal calendars... (" . $total . ")\n");
	unset($stmt);
	if (isset($singleUser)) {
		$stmt = $oldDb->query("select cn.* from CalendarNames as cn, CalendarPermissions as cp 
			where cp.Account = ? and CalNameID > 0 and cn.id = cp.CalNameID", $singleUser);
	} else {
		$stmt = $oldDb->query("select * from CalendarNames");
	}
	resetStats(null);

	while ($row = $stmt->fetch())
	{
		displayStats($count, $total);
		$count++;

		$CalName=$row->CalName;
		$CalNameID=$row->id;

		if($arguments['verbose'])
		{
			fwrite(STDOUT, "Creating calendar " . $CalName . " " . $CalNameID . "\n");
		}

		unset($stmt2);
		$stmt2 = $oldDb->query("select * from CalendarPermissions where CalNameID = " . $CalNameID);
		$create = true;
		while ($row2 = $stmt2->fetch())
		{
			$CalId=$row2->CalID;
			$Account=$row2->Account;
			$Permissions=$row2->Permissions;
			$Type=$row2->Type;
			$id=$row2->id;
			$Domain=$row2->Domain;

			if($create)
			{
				if($arguments['verbose'])
				{
					fwrite(STDOUT, "Creating calendar '" . $CalName ."' for owner " . $Account . "\n");
					fwrite(STDOUT, "using calendar '" . $cal_url ."'\n");
				}

				unset($stmt3);
				$stmt3 = $oldDb->query("select * from UserSession where Account = ?", $Account);
				$row3 = $stmt3->fetch();

				if(isset($row3->Password))
				{
					$entry_password = $row3->Password;
				}
				else
				{
					if($arguments['verbose'])
					{
						fwrite(STDOUT, "NOTICE: Found orphaned account event. (account = " . $Account . " id = " . $id . ")\n");
					}
					file_put_contents("./orphaned.entires", "Unable to find user/pass " . $Account . " in UserSession from table CalanderNames id = " . $id . "\n");	
					$orphans ++;
					continue;
				}

				$newuser = insertUser($cal_url, $Account, $entry_password, $CalName, $oldDb, $CalNameID);

				$create = false;
			}
			else
			{
				// applying delegation
				// caldav glue
				if($arguments['verbose'])
				{
					fwrite(STDOUT, "Applying delegation to calendar " . $Account . " for user " . $newuser . "\n");
				}

				$entry_username = $newuser;
				if($Account == "All Users")
				{
					fwrite(STDOUT, "NOTICE: All Users migration is not supported at this time.\nThis calendar will be available, but only to the owner. (account = " . $Account . " user = " . $newuser . ")\n");					
				}
				else
				{
					addCalendarProxy($cal_url, $entry_username, $entry_password, $Account, $newuser);
				}
			}
		}
	}

	fwrite(STDOUT, "\n");

	if($orphans)
		fwrite(STDOUT, "Skipped " . $orphans . " orphaned calendars.\n");

	return $total;
}

if(!$arguments['skipNonPersonal'])
{
	$overallTotal += processNonPersonal($oldDb, $cal_url);
}

{
	$count = 0;
	$total = 0;
	$oldDb->getConnection();
	$oldDb->setFetchMode(Zend_Db::FETCH_OBJ);
	
	if (isset($singleUser)) {
		unset($stmt);
		if ($largeSchema) {
			$stmt = $oldDb->query("select count(*) as total from Calander_$suffix$suffix2 where UserFrom = ?", $singleUser);
		} else {
			$stmt = $oldDb->query("select count(*) as total from Calander_$suffix where UserFrom = ?", $singleUser);
		}
		$total_temp = $stmt->fetch();
		$total += $total_temp->total;

	} else {
	
		foreach($simple_range as $current_letter)
		{	
			if ($largeSchema && $current_letter != 'other')
			{
				foreach($normal_range as $current_letter2)
				{
					unset($stmt);
					$stmt = $oldDb->query("select count(*) as total from Calander_$current_letter$current_letter2 where UserFrom in (select Account from UserSession)");
					$total_temp = $stmt->fetch();
					$total += $total_temp->total;
				}
			}
			else
			{
				unset($stmt);
				$stmt = $oldDb->query("select count(*) as total from Calander_$current_letter where UserFrom in (select Account from UserSession)");
				$total_temp = $stmt->fetch();
				$total += $total_temp->total;	
			}
		}
	
		$orphaned = 0;
		foreach($simple_range as $current_letter)
		{	
			if ($largeSchema && $current_letter != 'other')
			{
				foreach($normal_range as $current_letter2)
				{
					unset($stmt);
					$stmt = $oldDb->query("select count(*) as total from Calander_$current_letter$current_letter2 where UserFrom not in (select Account from UserSession)");
					$total_temp = $stmt->fetch();
					$orphaned += $total_temp->total;
				}
			}
			else
			{
				unset($stmt);
				$stmt = $oldDb->query("select count(*) as total from Calander_$current_letter where UserFrom not in (select Account from UserSession)");
				$total_temp = $stmt->fetch();
				$orphaned += $total_temp->total;	
			}
		}
	}
	
	fwrite(STDOUT, "Migrating personal calendars... (" . $total . ")\n");	
	$overallTotal += $total;

	$projectIds = array_merge(range('a','z'), range('0', '9'));
	$pID = false;
	foreach($projectIds as $projectId)
	{
		if(($handle = @fopen('./' . $projectId, 'r')) != false)
		{
			fclose($handle);
		}
		else
		{
			$pID = $projectId;
			touch('./' . $pID);
			break;
		}
	}
		
	if($pID == false)
	{
		die("Unable to find a spare shared memory ident !");
	}
	
	if(!$arguments['noSemaphoreSupport'])
	{
		$semId = rand(0, 10000);
		$shm_key = ftok('.', $pID);
		if ($shm_key == (key_t) -1) 
		{
		    perror("IPC error: ftok"); exit(1);
		}

		$sem_id = sem_get($semId, 1, 0600, -1);
		$shm_id = shmop_open($shm_key, "c", 0644, 100);
		shmop_write($shm_id, str_to_nts("0"), 0);
		shmop_write($shm_id, str_to_nts("0"), 8);
		shmop_close($shm_id);
	}
	else
	{
		$shm_key = null;
	}
	
	resetStats($shm_key);
	

	if (isset($singleUser)) {
		
		$connected = false;
		while(!$connected) {
			try {
				$db = Zend_Db::Factory('Pdo_Mysql', $oldParams, $pdoParams);
				$db->getConnection();
				$connected = true;
			} catch (Exception $e) {
				fwrite(STDOUT, "Connection problems... retrying..\n");
				$connected = false;			
			}
		}
		
		$db->setFetchMode(Zend_Db::FETCH_OBJ);

		$num = getTableCount($db, "Calander_$suffix", $largeSchema);

		if($arguments['verbose']) {
			fwrite(STDOUT, "Processing $num items...\n");
		}

		if($num != 0) {
			if ($largeSchema) {
				unset($stmt);
				$stmt = $db->query("select * from Calander_" . $suffix . $suffix2 . " where UserFrom = ?", $singleUser);				
				process($db, $stmt, $suffix . $suffix2, $cal_url, $database);
			} else {
				unset($stmt);
				$stmt = $db->query("select * from Calander_" . $suffix . " where UserFrom = ?", $singleUser);				
				process($db, $stmt, $suffix, $cal_url, $database);
			}
		}
		
		$db->closeConnection();
		$db = null;
		
	} else {
		
		foreach($simple_range as $current_letter)
		{	
			if($arguments['verbose'])
			{
				fwrite(STDOUT, "Parent: Processing '" . $current_letter . "'...\n");
			}

			{
				$connected = false;
				while(!$connected)
				{
					try
					{
						$db = Zend_Db::Factory('Pdo_Mysql', $oldParams, $pdoParams);
						$db->getConnection();
						$connected = true;
					}
					catch (Exception $e)
					{
						fwrite(STDOUT, "Connection problems... retrying..\n");
						$connected = false;			
					}
				}
				$db->setFetchMode(Zend_Db::FETCH_OBJ);

				$num = getTableCount($db, "Calander_" . $current_letter, $largeSchema);

				if($arguments['verbose'])
				{
					fwrite(STDOUT, "'" . $current_letter . "': Processing " . $num . " items...\n");
				}

				if($num != 0)
				{
					if ($largeSchema && $current_letter != 'other')
					{
						foreach($normal_range as $current_letter2)
						{
							unset($stmt);
							$stmt = $db->query("select * from Calander_" . $current_letter . $current_letter2 . " where UserFrom in (select Account from UserSession)");				
							process($db, $stmt, $current_letter . $current_letter2, $cal_url, $database);
						}
					}
					else
					{
						unset($stmt);
						$stmt = $db->query("select * from Calander_" . $current_letter . " where UserFrom in (select Account from UserSession)");				
						process($db, $stmt, $current_letter, $cal_url, $database);
					}
				}
				
				$db->closeConnection();
				$db = null;
			}
		}
	}

	fwrite(STDOUT, "\n");

	if(!$arguments['noSemaphoreSupport'])
	{	
		sem_acquire($sem_id);
		$shm_id = shmop_open($shm_key, "w", 0644, 100);	
		$count = (int)str_from_mem(shmop_read($shm_id, 0, 8));
		fwrite(STDOUT, $orphaned . " total orphaned entires.\n");
		if(!shmop_delete($shm_id))
		{
			echo "Couldn't mark shared memory block for deletion.";
		}
		shmop_close($shm_id);
		sem_release($sem_id);
		sem_remove($sem_id);
	}
	unlink ('./' . $pID);
}

fwrite(STDOUT, "Finished.\n");
displayElaped("Total migration time for " . $overallTotal . " items is ", $overallTime, 'human');

//$cal_url, $Account, $entry_password, $CalName, $oldDb, $CalNameID)
function processUser($url, $row, $_calendar, $db)
{
	global $arguments;
	
	$UserTo=$row->UserTo;
	$UserFrom=$row->UserFrom;
	$Title=$row->Title;
	$DatePost=$row->DatePost;
	$CalMessage=$row->CalMessage;
	$Alert=$row->Alert;
	$DateStart=$row->DateStart;
	$DateEnd=$row->DateEnd;
	$Type=$row->Type;
	$Importance=$row->Importance;
	$Task=$row->Task;
	$Ref=$row->Ref;
	$Parent=$row->Parent;
	$Permission=$row->Permission;
	$EntryID=$row->EntryID;
	$DateModified=$row->DateModified;
	$Location=$row->Location;
	$AllDayEvent=$row->AllDayEvent;
	$DateAlert=$row->DateAlert;
	$IsRecurring=$row->IsRecurring;
	$IsException=$row->IsException;
	$DateAlertPeriod=$row->DateAlertPeriod;
	$Availability=$row->Availability;
	$CalNameID=$row->CalNameID;
	$id = $row->id;

	$calname = "Private";

	if($CalNameID) 
	{
		$handle = fopen('./temp.calnames', 'a+');
		fwrite($handle, $row->UserFrom . "\n");
		fclose($handle);
	}					

	// caldav glue	
	$entry_username = $UserFrom;
	$entry_username = str_replace("mailto:" , "", $entry_username);
	$entry_username = str_replace("MAILTO:" , "", $entry_username);


	// check if the user exists
	// ASSUME : that the user exists, the user import will have run already.
    $newuser = $entry_username;

	if($Title == "(null)")
		$Title = "";			
	if($CalMessage == "(null)")
		$CalMessage = "";
		
	$user_array = fetchUser($db, $entry_username);
	if($user_array == false)
	{
		unset($user_array);
		unset($row);
		return false;
	}
	$entry_password = $user_array['password'];
	unset($user_array);

	if(!$arguments['dryRun'])
	{
		$args = array(
			'task' => $Task,
			'title' => $Title,
			'description' => $CalMessage,
			'dateStart' => $DateStart,
			'dateEnd' => $DateEnd,
			'location' => $Location,
			'allDayEvent' => $AllDayEvent,
			'calendarUrl' => "/calendars/" . urlencode($entry_username) . "/calendar",
		);
		$_calendar->restartAdapter(array('Account' => $entry_username, 'Password' => $entry_password));
		$uid = $_calendar->add_record($args);
	}

	unset($row);
	
	return true;
}

function process($db, $stmt, $current_letter, $cal_url_in, $database)
{
	global $total;
	global $shm_key;
	global $sem_id;
	global $arguments;
	global $newParams;
	global $oldParams;
	global $pdoParams;
    global $singleUser;

	$newDb = Zend_Db::Factory('Pdo_Mysql', $newParams, $pdoParams);
	try
	{
		$newDb->getConnection();
	}
	catch (Exception $e)
	{
		die("Could not connect to database " . $arguments['a5_db'] . "\n");	
	}

	$newDb->query("SET NAMES 'utf8'");
	Zend_Db_Table::setDefaultAdapter($newDb);
	Zend_Registry::set('dbAdapter', $newDb);

	$found_users = array();

	$url = $cal_url_in;
	$localCount =0;

	$_calendar = new calendar(array('migration' => true, 'URL' => $url, 'Account' => 'blank', 'Password' => 'none', 'Calendar' => "calendar", 'home', 'largeInstall' => 0));
	while ($row = $stmt->fetch())
	{
		$localCount ++;
		if($localCount % 30 == 0)
		{
			if(!$arguments['noSemaphoreSupport'])
			{
				sem_acquire($sem_id);
				$shm_id = shmop_open($shm_key, "w", 0644, 100);
				$count = (int)str_from_mem(shmop_read($shm_id, 0, 8));
			}
			$count += $localCount;
			if(!$arguments['noSemaphoreSupport'])
			{
				shmop_write($shm_id, str_to_nts($count), 0);
				shmop_close($shm_id);
			}

			displayStats($count-1, $total, $shm_key);
			if(!$arguments['noSemaphoreSupport'])
			{
				sem_release($sem_id);
			}

			$localCount = 0;
		}	
		if(!processUser($url, $row, $_calendar, $db))
		{
			if(!$arguments['noSemaphoreSupport'])
			{
				sem_acquire($sem_id);
				$shm_id = shmop_open($shm_key, "w", 0644, 100);
				$orphand = (int)str_from_mem(shmop_read($shm_id, 8, 8));
			}
			$orphand++;
			if(!$arguments['noSemaphoreSupport'])
			{
				shmop_write($shm_id, str_to_nts($orphand), 8);
				shmop_close($shm_id);
				sem_release($sem_id);
			}
			continue;
		}
		
		unset($row);
	}
	unset($stmt);
	$newDb->closeConnection();

	if(!$arguments['noSemaphoreSupport'])
	{
		sem_acquire($sem_id);
		$shm_id = shmop_open($shm_key, "w", 0644, 100);
		$count = (int)str_from_mem(shmop_read($shm_id, 0, 8));
	}
	
	$count += $localCount;
	
	if(!$arguments['noSemaphoreSupport'])
	{
		shmop_write($shm_id, str_to_nts($count), 0);
		shmop_close($shm_id);
	}
	displayStats($count-1, $total, $shm_key);
	
	if(!$arguments['noSemaphoreSupport'])
	{
		sem_release($sem_id);
	}
	$localCount = 0;

}

