- 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.
335 lines
7.7 KiB
PHP
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);
|