php__phplib/OSInet/Finite_State_Machine/FtpClient.php
Frederic G. MARAND a28ed41155 FSM: PSR-0 conversion, Grapher added.
- converted class names and directories for PSR0
- replaced the basic __autoload() by the canonical PSR0 loader example.
- added a GraphViz-based Grapher class to plot Machine structures.
2012-04-18 00:10:46 +02:00

335 lines
7.7 KiB
PHP

<?php
/**
* An FTP transfer wrapper using the OSInet Finite_State_Machine
*
* @copyright (c) 2007-2012 Ouest Systèmes Informatiques
* @author Frédéric G. MARAND
* @license Licensed under the CeCILL 2.0
*/
namespace OSInet\Finite_State_Machine;
/**
* Save current reporting level while we set it
*/
$_ftpEr = error_reporting(E_ALL | E_STRICT);
/**
* Class implements a finite-state-machine-based FTP client with *very* limited
* functionality, but that can display progress information states, namely:
* - init: not yet set up
* - offline: no link established
* - online: client connected to server
* - live: client connected and logged in
* - active: a data transfer operation is under way
* - unsafe: something failed, disconnect can happen out of our control
*/
class FtpClient extends Machine {
/**
* remote properties
*/
private $fRemoteFile;
private $fRemoteHost;
private $fRemotePass;
private $fRemoteUser;
private $fRemoteWd;
/**
* local properties
*/
private $fLocalWd;
private $fLocalFile;
private $fLocalFp; // file pointer to local file, used in get/put/continue
/**
* FTP properties
*/
private $fFtpCallback; // name of callback function for progress information
private $fFtpGoal; // bytes to be transferred
private $fFtpConnection; // connection
/**
* @return void
*/
public function __construct($values = array()) {
if (!isset($values['source'])) {
throw \Exception('Unspecified machine description.');
}
$this->loadFsm($values['source']);
// print_r($this->fTransitions);
parent::__construct();
// print_r($this);die();
}
/**
* Initialize parameters from an array
*
* @todo should really check names and values.
*
* @param array $params
*
* @return Result
*/
public function setParameters($params) {
$r = new \ReflectionClass(get_class($this));
$dp = $r->getDefaultProperties();
foreach ($params as $name => $value) {
$field_name = "f$name";
if (array_key_exists($field_name, $dp)) {
$this->$field_name = $value;
}
else {
echo "Skipping invalid parameter $name.\n";
}
}
$ret = $this->applyEvent('CheckParameters');
return $ret;
}
/**
* ============ utility functions ============
*/
/**
* format a boolean value as a string
*
* @param boolean $val
* @return string
*/
static function stringFromBoolean($val)
{
return $val ? 'true' : 'false';
}
/**
* Enter description here...
*
* @param unknown_type $status
* @return unknown
*/
static function stringFromFtp($status)
{
switch ($status)
{
case FTP_FINISHED: $ret = 'FTP_FINISHED'; break;
case FTP_MOREDATA: $ret = 'FTP_MOREDATA'; break;
case FTP_FAILED: $ret = 'FTP_MOREDATA'; break;
default: $ret = 'FTP_INVALID_STATUS'; break;
}
return $ret;
}
/**
* ============ event handlers ============
*/
/**
* implement change remote directory
* @return string (boolean)
*/
protected function eventChdir()
{
$ret = ftp_chdir($this->fFtpConnection, $this->fRemoteWd);
return self::stringFromBoolean($ret);
}
/**
* does the instance have all necessary info for a FTP transfer ?
*
* @return string (boolean)
*/
public function eventCheckParameters()
{
$ret = isset($this->fRemoteHost)
&& isset($this->fRemoteUser)
&& isset($this->fRemotePass)
&& isset($this->fRemoteWd)
&& isset($this->fRemoteFile)
&& isset($this->fLocalWd)
&& isset($this->fLocalFile)
&& isset($this->fFtpCallback)
;
$ret = self::stringFromBoolean($ret);
// echo func_name() . ", ret = $ret\n";
return $ret;
}
/**
* implementation of Close event
*
* @return string (boolean)
*/
protected function eventClose()
{
$ret = 'true';
// echo "Trying to close socket...";
if (is_resource($this->fFtpConnection))
{
try
{
// echo "Trying to close connection...";
ftp_close($this->fFtpConnection);
// echo "Done.";
}
catch (Exception $e)
{
print_r($e);
$ret = 'false';
}
}
// echo "Socket closed\n";
return $ret;
}
/**
* implementation of Connect event
*
* @return string (boolean)
*/
protected function eventConnect()
{
// echo func_name() . "\n";
$this->fFtpConnection = ftp_connect($this->fRemoteHost); // default port, default timeout
$ret = is_resource($this->fFtpConnection);
return self::stringFromBoolean($ret);
}
/**
* implementation of continue
* @return string FTP_FINISHED | FTP_MOREDATA | FTP_FAILED
*/
protected function eventContinue()
{
// echo func_name();
if ($this->fFtpCallback)
{
call_user_func($this->fFtpCallback, $this, 'pre');
}
$ret = ftp_nb_continue($this->fFtpConnection);
if ($ret == FTP_FINISHED)
{
fclose($this->fLocalFp);
}
if ($this->fFtpCallback)
call_user_func($this->fFtpCallback, $this, 'post');
$ret = self::stringFromFtp($ret);
return $ret;
}
/**
* implementation of get
*
* @return string FTP_FINISHED | FTP_MOREDATA | FTP_FAILED
* @throws Exception fail creating local file
*/
protected function eventGet()
{
// echo func_name() . "\n";
$this->fLocalFp = fopen($this->fLocalFile, "wb");
if (!is_resource($this->fLocalFp))
{
$ret = self::stringFromFtp(FTP_FAILED);
// throw new Exception(func_name() . ": could not create local file $this->fLocalFile");
return $ret;
}
$this->fFtpGoal = ftp_size($this->fFtpConnection, $this->fRemoteFile);
$ret = ftp_nb_fget($this->fFtpConnection, $this->fLocalFp,
$this->fRemoteFile, FTP_BINARY);
if ($ret == FTP_FINISHED)
{
fclose($this->fLocalFp);
}
call_user_func($this->fFtpCallback, $this, 'post');
// echo func_name() . " => $ret\n"; flush();
return self::stringFromFtp($ret);
}
/**
* implementation of login
* @return string (boolean)
*/
protected function eventLogin()
{
// echo func_name() . "\n";
$ret = ftp_login($this->fFtpConnection, $this->fRemoteUser, $this->fRemotePass);
return self::stringFromBoolean($ret);
}
/**
* handler must be implemented, but does nothing
* @return void
*/
protected function eventProgress()
{
return; // the FSM needs nothing for this
}
/**
* close connection if it hasn't been done, to prevent connection
* lingering on the server if avoidable
* @return void
*/
public function __destruct()
{
// echo func_name() . PHP_EOL;
if ($this->isEventAllowed('Close'))
{
$this->applyEvent('Close');
}
if(is_resource($this->fLocalFp))
try
{
echo "Trying to close file...";
fclose($this->fLocalFp);
echo "Done\n";
}
catch (Exception $e)
{
print_r($e);
}
// echo "End of " . func_name() . "\n";
}
/**
* Make sure name can be resolved
* ignore hosts that don't resolve in DNS or hosts file
*
* @param string $host
* @return boolean
*/
public function isHostValid($host = null)
{
$ret = is_array(gethostbynamel($host));
return $ret;
}
/**
* If a transfer is under way, return the progress percentile, otherwise 0.0
*
* @return float
*/
public function getProgress()
{
if ((!$this->fState == 'active') || (!is_resource($this->fLocalFp)))
{
$ret = 0.0;
}
else
{
$pos = ftell($this->fLocalFp);
$ret = $pos / $this->fFtpGoal;
}
return $ret;
}
}
/**
* Restore previous reporting level
*/
error_reporting($_ftpEr);