* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . **/ require_once ('qcEvents/Hookable.php'); require_once ('qcEvents/Interface/Stream/Consumer.php'); /** * FTP Client Stream * ----------------- * FTP Client Implementation (RFC 969) * This Stream is implemented independant of the underlying Stream. * It can be anything from a standard-compilant TCP-Socket to a pipe. Feel free! * * @see https://tools.ietf.org/html/rfc959 * * @class qcEvents_Stream_FTP_Client * @extends qcEvents_Hookable * @implements qcEvents_Interface_Stream_Consumer * @package qcEvents * @revision 01 * @author Bernd Holzmueller * * @todo CDUP - CHANGE TO PARENT DIRECTORY * @todo SMNT - STRUCTURE MOUNT SMNT * @todo REIN - REINITIALIZE * @todo PORT - DATA PORT * @todo STOR - STORE STOR * @todo STOU - STORE UNIQUE * @todo APPE - APPEND (with create) APPE * @todo ALLO - ALLOCATE ALLO [ R ] * @todo REST - RESTART REST * @todo RNFR - RENAME FROM RNFR * @todo RNTO - RENAME TO RNTO * @todo ABOR - ABORT * @todo DELE - DELETE DELE * @todo RMD - REMOVE DIRECTORY RMD * @todo MKD - MAKE DIRECTORY MKD * @todo LIST - LIST LIST [ ] * @todo SITE - SITE PARAMETERS SITE * @todo SYST - SYSTEM **/ class qcEvents_Stream_FTP_Client extends qcEvents_Hookable implements qcEvents_Interface_Stream_Consumer { /* FTP-Protocol states */ const STATE_CONNECTING = 0; const STATE_CONNECTED = 1; const STATE_AUTHENTICATING = 2; const STATE_AUTHENTICATED = 3; const STATE_DISCONNECTED = 4; /* Representation-Types */ const TYPE_ASCII = 0; const TYPE_EBCDIC = 1; const TYPE_IMAGE = 2; /* Structure types */ const STRUCTURE_FILE = 0; const STRUCTURE_RECORD = 1; const STRUCTURE_PAGE = 2; /* File-Transfer modes */ const MODE_STREAM = 0; const MODE_BLOCK = 1; const MODE_COMPRESSED = 2; /* Current protocol-state */ private $State = qcEvents_Stream_FTP_Client::STATE_CONNECTING; /* Entire Input-Buffer */ private $Buffer = ''; /* Buffer for current response */ private $rBuffer = ''; private $Stream = null; private $StreamCallback = null; private $Command = null; private $CommandQueue = array (); // {{{ noop /** * Just do nothing, but send something over the wire * * @param callable $Callback (optional) * @param mixed $Private (optional) * * @access public * @return void **/ public function noop (callable $Callback = null, $Private = null) { return $this->runFTPCommand ('NOOP', null, function () use ($Callback, $Private) { $this->___raiseCallback ($Callback, $Private); }); } // }}} // {{{ authenticate /** * Try to authenticate this FTP-Stream * * @param string $Username * @param string $Password * @param string $Account (optional) * @param callable $Callback (optional) * @param mixed $Private (optional) * * The callback will be raised in the form of * * function (qcEvents_Stream_FTP_Client $Self, bool $Status, string $Username, string $Account = null, mixed $Private = null) { } * * @access public * @return void **/ public function authenticate ($Username, $Password, $Account = null, callable $Callback = null, $Private = null) { return $this->runFTPCommand ( 'USER', $Username, function (qcEvents_Stream_FTP_Client $Self, $Status, $Code, $Response) use ($Username, $Password, $Account, $Callback, $Private) { // A USER-Call here would result in 230, 530, 500, 501, 421 // A PASS-Call here would result in 202, 230, 530, 500, 501, 503, 421 // A ACCT-Call here would result in 202, 230, 530, 500, 501, 503, 421 // Update the state $this->State = ($Status ? self::STATE_AUTHENTICATED : self::STATE_CONNECTED); // Fire the callback if ($Status) $this->___callback ('ftpAuthenticated', $Username, $Account); $this->___raiseCallback ($Callback, $Status, $Username, $Account, $Private); }, null, function (qcEvents_Stream_FTP_Client $Self, $Code, $Response) use ($Password, $Account) { // A USER-Call here would result in 331 or 332 // A PASS-Call here would result in 332 // A ACCT-Call will never get here // Update our state $this->State = self::STATE_AUTHENTICATING; // Write out the password if it is required if ($Code == 331) return $this->writeCommand ('PASS', $Password); // Check for a protocoll-violation if ($Code != 332) return $this->raiseProtocolError (); // Check if we have an account available if ($Account === null) return false; return $this->writeCommand ('ACCT', $Account); } ); } // }}} // {{{ changeDirectory /** * Change working-directory on server * * @param string $Path The path of the directory to change to * @param callable $Callback (optional) A callback to raise once the operation is complete * @param mixed $Private (optional) A private parameter to pass to the callback * * The callback will be raised in the form of * * function (qcEvents_Stream_FTP_Client $Self, bool $Status, mixed $Private = null) { } * * @access public * @return void **/ public function changeDirectory ($Path, callable $Callback = null, $Private = null) { return $this->runFTPCommand ( 'CWD', $Path, function (qcEvents_Stream_FTP_Client $Self, $Status, $Code, $Response) use ($Callback, $Private) { $this->___raiseCallback ($Callback, $Status, $Private); } ); } // }}} // {{{ getWorkingDirectory /** * Retrive the current working-directory from FTP-Server * * @param callable $Callback * @param mixed $Private (optional) * * The callback will be raised in the form of * * function (qcEvents_Stream_FTP_Client $Self, string $Path, mixed $Private = null) { } * * If there was an error $Path is FALSE. * * @access public * @return void **/ public function getWorkingDirectory (callable $Callback, $Private = null) { return $this->runFTPCommand ( 'PWD', null, function (qcEvents_Stream_FTP_Client $Self, $Status, $Code, $Response) use ($Callback, $Private) { if ($Status) { $Path = $Response; if ($Path [0] == '"') $Path = substr ($Path, 1, strpos ($Path, '"', 2) - 1); $this->___callback ('ftpWorkingDirectory', $Path); } else $Path = false; $this->___raiseCallback ($Callback, $Path, $Private); } ); } // }}} // {{{ getStatus /** * Retrive the status of FTP-Server or a file on that server * * @param string $Path (optional) Pathname for file * @param callable $Callback * @param mixed $Private (optional) * * The callback will be raised in the form of * * function (qcEvents_Stream_FTP_Client $Self, string $Status = null, mixed $Private = null) { } * * @access public * @return void **/ public function getStatus ($Path = null, callable $Callback, $Private = null) { return $this->runFTPCommand ( 'STAT', $Path, function (qcEvents_Stream_FTP_Client $Self, $Status, $Code, $Response) use ($Callback, $Private) { $this->___raiseCallback ($Callback, ($Status ? $Response : null), $Private); } ); } // }}} // {{{ getFilenames /** * Retrive all filenames existant at a given path or the current one * * @param string $Path (optional) The path to use, if NULL the current one will be used * @param callable $Callback (optional) A callback to be raised once the operation was completed * @param mixed $Private (optional) A private parameter to pass to the callback * * The callback will be raised in the form of * * function (qcEvents_Stream_FTP_Client $Self, array $Files = null, mixed $Private = null) { } * * If there was an error during the execution, $Files will be NULL. * * @access public * @return void **/ public function getFilenames ($Path = null, callable $Callback, $Private = null) { return $this->runFTPDataCommandBuffered ( 'NLST', $Path, self::TYPE_ASCII, self::STRUCTURE_FILE, self::MODE_STREAM, // Callback to be raised once the transfer was completed function (qcEvents_Stream_FTP_Client $Self, $Status, $Buffer, $Code, $Response) use ($Callback, $Private) { if ($Status) $Files = explode ("\r\n", substr ($Buffer, 0, -2)); else $Files = null; $this->___raiseCallback ($Callback, $Files, $Private); } ); } // }}} // {{{ retriveFileStream /** * Download a file from the server, return a stream-handle for further processing * * @param string $Filename Path of the file to download * @param callable $Callback A callback to raise once the stream is ready * @param mixed $Private (optional) Any private parameter to pass to the callback * @param callable $finalCallback (optional) A callback to raise once the download was completed * @param mixed $finalPrivate (optional) Any private parameter to pass to the callback * * The callback will be raised in the form of * * function (qcEvents_Stream_FTP_Client $Self, string $Filename, qcEvents_Interface_Stream $Stream = null, mixed $Private = null) { } * * The final callback will be raised in the form of * * function (qcEvents_Stream_FTP_Client $Self, string $Filename, bool $Status, qcEvents_Interface_Stream $Stream = null, mixed $Private = null) { } * * @access public * @return void **/ public function retriveFileStream ($Filename, callable $Callback, $Private = null, callable $finalCallback = null, $finalPrivate = null) { $Input = null; return $this->runFTPDataCommandStream ( 'RETR', $Filename, self::TYPE_IMAGE, self::STRUCTURE_FILE, self::MODE_STREAM, function (qcEvents_Stream_FTP_Client $Self, $Status, $Code, $Response) use ($Filename, $finalCallback, $finalPrivate, &$Input) { $this->___raiseCallback ($finalCallback, $Filename, $Status, $Input, $finalPrivate); }, null, function (qcEvents_Stream_FTP_Client $Self, qcEvents_Interface_Stream $Stream = null) use ($Filename, $Callback, $Private, &$Input) { $Input = $Stream; $this->___raiseCallback ($Callback, $Filename, $Stream, $Private); } ); } // }}} // {{{ downloadFile /** * Download file from server and write to filesystem * * @param string $remotePath Path of file on server * @param string $localPath Local path to write the file to * @param callable $Callback (optional) A callback to raise once the operation is complete * @param mixed $Private (optional) Private data to pass to the callback * * @access public * @return void **/ public function downloadFile ($remotePath, $localPath, callable $Callback = null, $Private = null) { return $this->retriveFileStream ( $remotePath, // Pipe the stream to a local file once it is ready function (qcEvents_Stream_FTP_Client $Self, $Filename, qcEvents_Interface_Stream $Stream = null) use ($localPath) { if ($Stream) $Stream->pipe (new qcEvents_File ($Stream->getEventBase (), $localPath, false, true)); }, null, // Just forward the result once it is completed function (qcEvents_Stream_FTP_Client $Self, $Filename, $Status, qcEvents_Interface_Stream $Stream = null) use ($Callback, $Private) { $this->___raiseCallback ($Callback, $Status, $Private); } ); } // }}} // {{{ downloadFileBuffered /** * Download file from server and write to filesystem * * @param string $remotePath Path of file on server * @param callable $Callback A callback to raise once the operation is complete * @param mixed $Private (optional) Private data to pass to the callback * * The callback will be raised in the form of * * function (qcEvents_Stream_FTP_Client $Self, string $remotePath, string $Content, bool $Status, mixed $Private = null) { } * * @access public * @return void **/ public function downloadFileBuffered ($remotePath, callable $Callback, $Private = null) { $Buffer = ''; return $this->retriveFileStream ( $remotePath, // Pipe the stream to a local file once it is ready function (qcEvents_Stream_FTP_Client $Self, $Filename, qcEvents_Interface_Stream $Stream = null) use (&$Buffer) { if (!$Stream) return; $Stream->addHook ('eventReadable', function (qcEvents_Interface_Stream $Stream) use (&$Buffer) { $Buffer .= $Stream->read (); }); }, null, // Just forward the result once it is completed function (qcEvents_Stream_FTP_Client $Self, $Filename, $Status, qcEvents_Interface_Stream $Stream = null) use (&$Buffer, $Callback, $Private) { // Reset the buffer if download failed if (!$Status) $Buffer = null; // Raise the callback $this->___raiseCallback ($Callback, $Filename, $Buffer, $Status, $Private); } ); } // }}} // {{{ close /** * Close this event-interface * * @param callable $Callback (optional) Callback to raise once the interface is closed * @param mixed $Private (optional) Private data to pass to the callback * * @access public * @return void **/ public function close (callable $Callback = null, $Private = null) { // Try to close gracefully if (!$this->StreamCallback && $this->Stream && ($this->State != self::STATE_CONNECTING)) return $this->runFTPCommand ('QUIT', null, function () use ($Callback, $Private) { if ($this->Stream) return $this->Stream->close (function () use ($Callback, $Private) { $this->___callback ('eventClosed'); $this->___raiseCallback ($Callback, $Private); }); $this->___callback ('eventClosed'); $this->___raiseCallback ($Callback, $Private); }); $this->___raiseCallback ($this->StreamCallback [0], false, $this->StreamCallback [1]); $this->StreamCallback = null; $this->___callback ('eventClosed'); $this->___raiseCallback ($Callback, $Private); } // }}} // {{{ consume /** * Consume a set of data * * @param mixed $Data * @param qcEvents_Interface_Source $Source * * @access public * @return void **/ public function consume ($Data, qcEvents_Interface_Source $Source) { // Append to internal buffer $this->Buffer .= $Data; unset ($Data); // Check if we have received complete lines $s = 0; while (($e = strpos ($this->Buffer, "\r\n", $s)) !== false) { // Peek the next complete line $Line = substr ($this->Buffer, $s, $e - $s); // Move pointer to end-of-line $s = $e + 2; // Check if this is a final response $Code = substr ($Line, 0, 3); if (($Line [3] == ' ') && (($iCode = intval ($Code)) == $Code) && ((strlen ($this->rBuffer) < 3) || ($Code == substr ($this->rBuffer, 0, 3)))) { $this->processFTPResponse ($iCode, rtrim (substr ($this->rBuffer, 4) . substr ($Line, 4))); $this->rBuffer = ''; // Just append to buffer } else $this->rBuffer .= trim ($Line) . "\n"; } // Truncate the input-buffer if ($s > 0) $this->Buffer = substr ($this->Buffer, $s); } // }}} // {{{ processFTPResponse /** * Process a received FTP-Response * * @param int $Code The response-code from the server * @param string $Response The text-response from the server * * @access private * @return void **/ private function processFTPResponse ($Code, $Response) { // Check if we are receiving a HELO from FTP if ($this->State == self::STATE_CONNECTING) { // Check the code if (($Code >= 100) && ($Code < 200)) return; // Change our state if ($Code < 400) { $this->State = self::STATE_CONNECTED; $this->___callback ('ftpConnected'); } else { $this->State = self::STATE_DISCONNECTED; $this->___callback ('ftpDisconnected'); } // Fire the callback if ($this->StreamCallback) { $this->___raiseCallback ($this->StreamCallback [0], ($Code < 400), $this->StreamCallback [1]); $this->StreamCallback = null; } return; } // Look for an active command if (!$this->Command) { $this->raiseProtocolError (); return; } // Check for an intermediate reply if ((($Code >= 100) && ($Code < 200)) || (($Code >= 300) && ($Code < 400))) { // Check if the command is prepared for this if (!$this->Command [4]) return $this->ftpFinishCommand (false, $Code, $Response); // Fire a callback for this if ($this->___raiseCallback ($this->Command [4], $Code, $Response, $this->Command [5]) === false) return $this->ftpFinishCommand (false, $Code, $Response); return; } return $this->ftpFinishCommand ((($Code >= 200) && ($Code < 300)), $Code, $Response); } // }}} // {{{ ftpFinishCommand /** * Finish the current command that is being processed and move to the next one * * @param bool $Status The overall Status-Indicator * @param int $Code The last response-code received from the server * @param stirng $Response The last text-response received from the server * * @access private * @return void **/ private function ftpFinishCommand ($Status, $Code, $Response) { // Peek and free the current command $Command = $this->Command; $this->Command = null; // Raise the callback $this->___raiseCallback ($Command [2], $Status, $Code, $Response, $Command [3]); // Move to next command $this->dispatchFTPCommand (); } // }}} // {{{ raiseProtocolError /** * A protocol-error was detected on the wire * * @access private * @return bool Always FALSE **/ private function raiseProtocolError () { $this->___callback ('ftpProtocolError'); $this->close (); return false; } // }}} // {{{ runFTPCommand /** * Enqueue an FTP-Command for execution * * @param string $Command The actual command to run * @param mixed $Parameters Any parameter for that command * @param callable $finalCallback A callback to run once the operation was completed * @param mixed $finalPrivate (optional) Any private parameter to pass to that callback * @param callable $intermediateCallback (optional) A callback to run whenever an intermediate response was received * @param mixed $intermediatePrivate (optional) Any private parameter to pass to that callback * * The final callback will be raised in the form of * * function (qcEvents_Stream_FTP_Client $Self, bool $Status, int $Code, string $Response, mixed $Private = null) { } * * The intermediate callback will be raised in the form of * * function (qcEvents_Stream_FTP_Client $Self, int $Code, string $Response, mixed $Private = null) { } * * If the intermediate callback returns FALSE the entire operation will be canceled * * @access private * @return void **/ private function runFTPCommand ($Command, $Parameters, callable $finalCallback, $finalPrivate = null, callable $intermediateCallback = null, $intermediatePrivate = null) { // Append the command to our queue $this->CommandQueue [] = array ($Command, $Parameters, $finalCallback, $finalPrivate, $intermediateCallback, $intermediatePrivate); // Try to issue the command $this->dispatchFTPCommand (); } // }}} // {{{ runFTPDataCommandStream /** * Setup a data-connection and run a given command on that * * @param string $Command The command to issue once the connection was established * @param mixed $Parameters Parameters to pass to the previous command * @param enum $Type (optional) Character Representation-Type * @param enum $Structure (optional) File-Structure-Type * @param enum $Mode (optional) Transfer-Mode * @param callable $finalCallback A callback to raise once the whole operation was completed * @param mixed $finalPrivate (optional) Any parameter to pass to the final callback * @param callable $intermediateCallback A callback to raise once the data-connection is ready * @param mixed $intermediatePrivate (optional) Any parameter to pass to the intermediate callback * * The final callback will be raised in the form of * * function (qcEvents_Stream_FTP_Client $Self, bool $Status, int $Code, string $Response, mixed $Private = null) { } * * The intermediate callback will be raised in the form of * * function (qcEvents_Stream_FTP_Client $Self, qcEvents_Interface_Stream $Stream, mixed $Private = null) { } * * @access private * @return void **/ private function runFTPDataCommandStream ($Command, $Parameters, $Type = self::TYPE_ASCII, $Structure = self::STRUCTURE_FILE, $Mode = self::MODE_STREAM, callable $finalCallback, $finalPrivate = null, callable $intermediateCallback, $intermediatePrivate = null) { // Prepare parameters static $tMap = array ( self::TYPE_ASCII => 'A', self::TYPE_EBCDIC => 'E', self::TYPE_IMAGE => 'I', ); static $sMap = array ( self::STRUCTURE_FILE => 'F', self::STRUCTURE_RECORD => 'R', self::STRUCTURE_PAGE => 'P', ); static $mMap = array ( self::MODE_STREAM => 'S', self::MODE_BLOCK => 'B', self::MODE_COMPRESSED => 'C', ); $Type = (isset ($tMap [$Type]) ? $tMap [$Type] : self::TYPE_ASCII); $Structure = (isset ($sMap [$Structure]) ? $sMap [$Structure] : self::STRUCTURE_FILE); $Mode = (isset ($mMap [$Mode]) ? $mMap [$Mode] : self::MODE_STREAM); // Prepare the connection-handler $Handler = null; $Handler = function (qcEvents_Stream_FTP_Client $Self, $Status, $Code, $Response) use (&$Handler, &$Type, &$Structure, &$Mode, $Command, $Parameters, $finalCallback, $finalPrivate, $intermediateCallback, $intermediatePrivate) { // Check if we failed if ($Status === false) return $this->___raiseCallback ($finalCallback, false, $Code, $Response, $finalPrivate); // Check if we have to setup connection-parameters if ($Type !== null) { $pCommand = 'TYPE'; $Parameter = $Type; $Type = null; } elseif ($Structure !== null) { $pCommand = 'STRU'; $Parameter = $Structure; $Structure = null; } elseif ($Mode !== null) { $pCommand = 'MODE'; $Parameter = $Mode; $Mode = null; } else $pCommand = null; // Proceed with connection-setup if ($pCommand !== null) { // Push into normal command-queue if we do this for the first time if ($Status === null) return $this->runFTPCommand ($pCommand, $Parameter, $Handler); array_unshift ( $this->CommandQueue, array ($pCommand, $Parameter, $Handler, null, null, null) ); // or enter the connection } else array_unshift ( $this->CommandQueue, array ('PASV', null, function (qcEvents_Stream_FTP_Client $Self, $Status, $Code, $Response) use ($Command, $Parameters, $finalCallback, $finalPrivate, $intermediateCallback, $intermediatePrivate) { // Make sure the command was successfull if (!$Status || (($s = strpos ($Response, '(')) === false) || (($e = strpos ($Response, ')', $s)) === false)) return $this->___raiseCallback ($finalCallback, false, $Code, $Response, $finalPrivate); // Parse the destination $Host = explode (',', substr ($Response, $s + 1, $e - $s - 1)); $IP = $Host [0] . '.' . $Host [1] . '.' . $Host [2] . '.' . $Host [3]; $Port = (intval ($Host [4]) << 8) | intval ($Host [5]); // Create a socket to this connection $Socket = new qcEvents_Socket ($this->Stream->getEventBase ()); // Try to connect to destination and forward the handle to our caller upon completion $Socket->connect ($IP, $Port, $Socket::TYPE_TCP, false, function (qcEvents_Socket $Socket, $Status) use ($intermediateCallback, $intermediatePrivate) { if (!$Status) $Socket = null; $this->___raiseCallback ($intermediateCallback, $Socket, $intermediatePrivate); }); // Issue the original command array_unshift ( $this->CommandQueue, array ( $Command, $Parameters, function (qcEvents_Stream_FTP_Client $Self, $Status, $Code, $Response) use ($Socket, $finalCallback, $finalPrivate) { // Check if the socket is still connected if ($Socket->isConnected ()) return $Socket->addHook ('eventClosed', function () use ($Status, $Code, $Response, $finalCallback, $finalPrivate) { $this->___raiseCallback ($finalCallback, $Status, $Code, $Response, $finalPrivate); }); $this->___raiseCallback ($finalCallback, $Status, $Code, $Response, $finalPrivate); }, null, function (qcEvents_Stream_FTP_Client $Self, $Code, $Response) { /* Just let this pass */ }, null) ); // Try to issue the command $this->dispatchFTPCommand (); }, null, null, null) ); // Try to issue the command $this->dispatchFTPCommand (); }; // Invoke the handler return $Handler ($this, null, 0, ''); } // }}} // {{{ runFTPDataCommandBuffered /** * Run an FTP-Command that retrives it results via a data-stream and return the result as whole * * @param string $Command The command to issue once the connection was established * @param mixed $Parameters Parameters to pass to the previous command * @param enum $Type (optional) Character Representation-Type * @param enum $Structure (optional) File-Structure-Type * @param enum $Mode (optional) Transfer-Mode * @param callable $Callback (optional) A callback to raise once the operation was completed * @param mixed $Private (optional) Any private parameter to pass to that parameter * * The callback will be raised in the form of * * function (qcEvents_Stream_FTP_Client $Self, bool $Status, string $Buffer = null, int $Code, string $Response, mixed $Private = null) { } * * @access private * @return void **/ private function runFTPDataCommandBuffered ($Command, $Parameters, $Type = null, $Structure = null, $Mode = null, callable $Callback, $Private = null) { // Create a local buffer $Buffer = ''; // Run the command with a "normal" stream return $this->runFTPDataCommandStream ( $Command, $Parameters, $Type, $Structure, $Mode, // Callback to be raised once the transfer was completed function (qcEvents_Stream_FTP_Client $Self, $Status, $Code, $Response) use ($Callback, $Private, &$Buffer) { $this->___raiseCallback ($Callback, $Status, $Buffer, $Code, $Response, $Private); }, null, // Callback to be raised once the data-stream is ready function (qcEvents_Stream_FTP_Client $Self, qcEvents_Interface_Stream $Stream = null) use (&$Buffer) { // Check if the stream is really ready if (!$Stream) { $Buffer = null; return; } // Register our reader-thread $Stream->addHook ('eventReadable', function (qcEvents_Interface_Stream $Stream) use (&$Buffer) { $Buffer .= $Stream->read (); }); } ); } // }}} // {{{ dispatchFTPCommand /** * Try to write the next command in queue to the wire * * @access private * @return void **/ private function dispatchFTPCommand () { if ($this->Command || (count ($this->CommandQueue) == 0)) return; $this->Command = array_shift ($this->CommandQueue); $this->writeCommand ($this->Command [0], $this->Command [1]); } // }}} // {{{ writeCommand /** * Write a given command to the wire * * @param string $Command * @param mixed $Parameters * * @access private * @return void **/ private function writeCommand ($Command, $Parameters) { // Start the commandline with the command $Commandline = $Command; // Check wheter to append some parameters if ($Parameters) { if (!is_array ($Parameters)) $Parameters = array ($Parameters); # TODO: What if a parameter contains a space? $Commandline .= ' ' . implode (' ', $Parameters); } // Terminate the commandline $Commandline .= "\r\n"; // Write out the command $this->Stream->write ($Commandline); } // }}} // {{{ initStreamConsumer /** * Setup ourself to consume data from a stream * * @param qcEvents_Interface_Source $Source * @param callable $Callback (optional) Callback to raise once the pipe is ready * @param mixed $Private (optional) Any private data to pass to the callback * * The callback will be raised in the form of * * function (qcEvents_Interface_Stream_Consumer $Self, bool $Status, mixed $Private = null) { } * * @access public * @return callable **/ public function initStreamConsumer (qcEvents_Interface_Stream $Source, callable $Callback = null, $Private = null) { if ($this->StreamCallback) $this->___raiseCallback ($this->StreamCallback [0], false, $this->StreamCallback [1]); // Update our internal state $this->State = self::STATE_CONNECTING; $this->Buffer = ''; $this->rBuffer = ''; $this->Command = null; $this->CommandQueue = array (); $this->Stream = $Source; if ($Callback) $this->StreamCallback = array ($Callback, $Private); else $this->StreamCallback = null; } // }}} // {{{ deinitConsumer /** * Callback: A source was removed from this consumer * * @param qcEvents_Interface_Source $Source * @param callable $Callback (optional) Callback to raise once the pipe is ready * @param mixed $Private (optional) Any private data to pass to the callback * * The callback will be raised in the form of * * function (qcEvents_Interface_Consumer $Self, bool $Status, mixed $Private = null) { } * * @access public * @return void **/ public function deinitConsumer (qcEvents_Interface_Source $Source, callable $Callback = null, $Private = null) { if ($this->StreamCallback) { $this->___raiseCallback ($this->StreamCallback [0], false, $this->StreamCallback [1]); $this->StreamCallback = null; } $this->___raiseCallback ($Callback, true, $Private); } // }}} protected function ftpConnected () { } protected function ftpDisconnected () { } protected function ftpAuthenticated ($Username, $Account = null) { } protected function ftpWorkingDirectory ($Path) { } protected function ftpProtocolError () { } protected function eventReadable () { } protected function eventClosed () { } } ?>