Browse Source

New version for FSM 1.7

Frederic G. Marand 17 years ago
parent
commit
30ca3ae5db
2 changed files with 427 additions and 0 deletions
  1. 90 0
      Ftp.xml
  2. 337 0
      Ftp_Client.php

+ 90 - 0
Ftp.xml

@@ -0,0 +1,90 @@
+<?xml version="1.0" ?>
+<!-- $Id: Ftp.xml,v 1.1 2007-06-03 21:27:57 marand Exp $ -->
+<fsm fsm_version="1.3"
+  idle_work="1" allow_actions="1"
+  init="init" final="unsafe"
+  revision="$Revision: 1.1 $">
+  <state id="init">
+    <event name="CheckParameters" type="boolean">
+      <next result="true"         state="offline" />
+      <next result="false"        state="init" />
+      </event>
+    <!--event name="Progress">
+      <next action="CheckParameters" />
+      </event-->
+    </state>
+  <state id="offline">
+    <event name="CheckParameters" type="boolean">
+      <next result="true"         state="offline" />
+      <next result="false"        state="init" />
+      </event>
+    <event name="Connect"         type="boolean">
+      <next result="true"         state="online" />
+      <next result="false"        state="unsafe" />
+      </event>
+    <event name="Progress">
+      <next action="Connect" />
+      </event>
+    </state>
+  <state id="online">
+    <event name="Close" type="boolean">
+      <next result="true"         state="offline" />
+      <next result="false"        state="unsafe" />
+      </event>
+    <event name="Login" type="boolean">
+      <next result="true"         state="live" />
+      <next result="false"        state="unsafe" />
+      </event>
+    <event name="Progress">
+      <next action="Login" />
+      </event>
+    </state>
+  <state id="live">
+    <event name="Close" type="boolean">
+      <next result="true"         state="offline" />
+      <next result="false"        state="unsafe" />
+      </event>
+    <event name="Chdir" type="boolean">
+      <next result="true"         state="ready" />
+      <next result="false"        state="unsafe" />
+      </event>
+    <event name="Get" type="int">
+      <next result="FTP_FINISHED" state="live" />
+      <next result="FTP_FAILED"   state="unsafe" />
+      <next result="FTP_MOREDATA" state="active" />
+      </event>
+    <event name="Put" type="int">
+      <next result="FTP_FINISHED" state="live" />
+      <next result="FTP_FAILED"   state="unsafe" />
+      <next result="FTP_MOREDATA" state="active" />
+      </event>
+    <event name="Progress">
+      <next action="Chdir" />
+      </event>
+    </state>
+  <state id="ready">
+    <event name="Get" type="string">
+      <next result="FTP_FINISHED" state="live" action="Close" />
+      <next result="FTP_MOREDATA" state="active" />
+      <next result="FTP_FAILED"   state="unsafe" />
+      </event>
+    <event name="Progress">
+      <next action="Get" />
+      </event>
+    </state>
+  <state id="active">
+    <event name="close" type="boolean">
+      <next result="true"         state="offline" />
+      <next result="false"        state="unsafe" />
+      </event>
+    <event name="Continue" type="int">
+      <next result="FTP_FINISHED" state="live" action="Close" />
+      <next result="FTP_FAILED"   state="unsafe" />
+      <next result="FTP_MOREDATA" state="active" />
+      </event>
+    <event name="Progress">
+      <next action="Continue" />
+      </event>
+    </state>
+  <state id="unsafe"  type="void" />
+  </fsm>

+ 337 - 0
Ftp_Client.php

@@ -0,0 +1,337 @@
+<?php
+/**
+ * An FTP transfer wrapper using the OSInet Finite_State_Machine
+ *
+ * @copyright  (c) 2007 OSI
+ * @author     Frédéric G. MARAND
+ * @license    Licensed under the CeCILL 2.0
+ * @version    CVS: $Id: Ftp_Client.php,v 1.1 2007-06-03 21:27:57 marand Exp $
+ * @link
+ * @since      Not applicable yet
+ * @package    osinetoffice
+ * @subpackage bo_up_ingram
+ *
+ */
+
+/**
+ * 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 are:
+ * - 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 Ftp_Client extends Finite_State_Machine
+  {
+  /**
+   * Code currently depends on the actual FSM location. Future versions should
+   * probably locate it on their own.
+   */
+  const FTP_FSM = 'e:/src/osinetoffice/lib/Ftp.xml';
+
+  /**
+   * 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
+
+  /**
+   * Initialize parameters from an array
+   *
+   * @todo should really check names and values.
+   * @param array $params
+   * @return fsm_result
+   */
+  public function setParameters($params)
+    {
+    foreach ($params as $name => $value)
+      {
+      $field_name = "f$name";
+      $this->$field_name = $value;
+      }
+    $ret = $this->apply_event('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 ftp_client::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 = ftp_client::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 Ftp_Client::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 = Ftp_Client::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 = Ftp_Client::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 Ftp_Client::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 Ftp_Client::stringFromBoolean($ret);
+    }
+
+  /**
+   * handler must be implemented, but does nothing
+   * @return void
+   */
+  protected function eventProgress()
+    {
+    return; // the FSM needs nothing for this
+    }
+
+  /**
+   * @return void
+   */
+  public function __construct()
+    {
+    $this->load_fsm(Ftp_Client::FTP_FSM);
+    // print_r($this->f_transitions);
+    parent::__construct();
+    // print_r($this);die();
+    }
+
+  /**
+   * 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->is_event_allowed('Close'))
+      {
+      $this->apply_event('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->f_state == '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);