FTP API enhancements

  Status: implemented,  Thu Oct 18 10:02:14 2007
  • Created: 2007-10-13 16:53:48+0200
  • Categories: peer.ftp
  • Author: friebe

Scope of Change

The peer.ftp API will be enhanced in the following ways:

  • There will be a new method peer.ftp.FtpConnection::rootDir()
  • There will be a new method peer.ftp.FtpConnection::sendCommand()
  • The peer.ftp.FtpEntry class will be abstract and there will be a new class peer.ftp.FtpFile extending it.
  • There will be several new methods in peer.ftp.FtpDir
  • There will be a new class peer.ftp.FtpEntryList
  • There will be several new methods in peer.ftp.FtpEntry
  • There will methods in peer.ftp.FtpFile to up- and download which will work using the io.streams API


Rationale

Prevent having to use raw FTP functions, make common use-cases easier.

Functionality



FtpEntry base class

At the moment, an FtpEntry represents a file, and an FtpDir (which extends FtpEntry) represents a directory. This poses several limitations on what can be in the FtpEntry class and makes it harder to distinguish them from each other.

New layout:

  abstract class FtpEntry extends Object {
// Common functionality like name, size, permissions, owner, ...
}

class FtpFile extends FtpEntry {
// File specific functionality - up & downloads
}

class FtpDir extends FtpEntry {
// Directory specific - getDir(), makeDir(), getFile(), hasFile(), ...
}

FtpEntryList class

  class FtpEntryList extends Object implements IteratorAggregate {

/**
* Returns an iterator for use in foreach()
*
* @see php://language.oop5.iterations
* @return php.Iterator
*/
public function getIterator() {
// ...
}

/**
* Returns the number of elements in this list.
*
* @return int
*/
public function size() {
// ...
}

/**
* Tests if this list has no elements.
*
* @return bool
*/
public function isEmpty() {
// ...
}

/**
* Returns all elements in this list as an array.
*
* @return peer.ftp.FtpEntry[] an array of all entries
* @throws peer.SocketException in case of an I/O error
*/
public function asArray() {
// ...
}
}

New methods

peer.ftp.FtpConnection:

  class FtpConnection extends Object {
// ...

/**
* Returns an FtpDir instance representing this connection's
* root directory
*
* @return peer.ftp.FtpDir the instance
* @throws peer.SocketException in case of an I/O error
*/
public function rootDir() {
// ...
}

/**
* Sends a raw command to the FTP server and returns the server's
* response (unparsed) as an array of strings.
*
* Accepts a command which will be handled as format-string for
* further arbitrary arguments, e.g.:
*
* $c->sendCommand('CLNT %s', $clientName);
*
* @param string command
* @param string* args
* @return string[] result
* @throws peer.SocketException in case of an I/O error
*/
public function sendCommand($command) {
// ...
}

// ...
}

peer.ftp.FtpEntry:

  abstract class FtpEntry {
// ...

/**
* Checks whether this entry exists.
*
* @return bool TRUE if the file exists, FALSE otherwise
* @throws peer.SocketException in case of an I/O error
*/
public function exists() {
// ...
}

/**
* Rename this entry
*
* @param string to the new name
* @throws io.IOException if an entry by the new name already exists
* @throws peer.SocketException in case of an I/O error
*/
public function rename($to) {
// ...
}

/**
* Change this entry's permissions
*
* @param int to the new permissions
* @throws io.IOException if an entry by the new name already exists
* @throws peer.SocketException in case of an I/O error
*/
public function changePermissions($to) {
// ...
}

/**
* Delete this entry
*
* @throws peer.SocketException in case of an I/O error
*/
public abstract function delete();

// ...
}

peer.ftp.FtpDir:

  class FtpDir extends FtpEntry {
// ...

/**
* Returns a list of entries
*
* @return peer.ftp.FtpEntryList
* @throws peer.SocketException in case of an I/O error
*/
public function entries() {
// ...
}

/**
* Checks whether a file by the given name exists in this
* directory.
*
* @param string name
* @return bool TRUE if the file exists, FALSE otherwise
* @throws peer.SocketException in case of an I/O error
*/
public function hasFile($name) {
// ...
}

/**
* Returns an FtpFile instance representing a file in this
* directory.
*
* @param string name
* @return peer.ftp.FtpFile the instance
* @throws io.FileNotFoundException in case the file was not found
* @throws peer.SocketException in case of an I/O error
*/
public function getFile($name) {
// ...
}

/**
* Creates a file in this directory and returns an FtpFile instance
* representing it.
*
* @param string name
* @return peer.ftp.FtpFile the instance
* @throws io.IOException in case the file already exists
* @throws peer.SocketException in case of an I/O error
*/
public function newFile($name) {
// ...
}

/**
* Returns an FtpFile instance representing a file in this
* directory.
*
* Note: Same as getFile() but does not throw exceptions if the file
* does not exist but will return an FtpFile in any case.
*
* @param string name
* @return peer.ftp.FtpFile the instance
* @throws peer.SocketException in case of an I/O error
*/
public function file($name) {
// ...
}

/**
* Checks whether a subdirectory by the given name exists in this
* directory.
*
* @param string name
* @return bool TRUE if the file exists, FALSE otherwise
* @throws peer.SocketException in case of an I/O error
*/
public function hasDir($name) {
// ...
}

/**
* Returns an FtpDir instance representing a subdirectory in this
* directory.
*
* @param string name
* @return peer.ftp.FtpDir the instance
* @throws io.FileNotFoundException in case the file was not found
* @throws peer.SocketException in case of an I/O error
*/
public function getDir($name) {
// ...
}

/**
* Creates a subdirectory in this directory and returns an FtpDir
* instance representing it.
*
* @param string name
* @return peer.ftp.FtpDir the created instance
* @throws lang.IllegalStateException in case the directory already exists
* @throws io.IOException in case the directory could not be created
*/
public function newDir($name) {
// ...
}

/**
* Returns an FtpDir instance representing a subdirectory in this
* directory.
*
* Note: Same as getDir() but does not throw exceptions if the
* directory does not exist but will create it and thus return an
* FtpDir in any case.
*
* @param string name
* @return peer.ftp.FtpDir the instance
* @throws lang.IllegalStateException in case the directory exists and is a file
* @throws io.IOException in case the directory could not be created
*/
public function dir($name) {
// ...
}

// ...
}

Examples



Checking for entry existance:

  $c= new FtpConnection('ftp://user:pass@example.com');
$c->connect();

// Current functionality #1 {{{
if (-1 == ftp_size($c->handle, '/'.$filename)) {
// $filename does not exist
}
// }}}

// Current functionality #2 {{{
if (!($dir= $c->getDir('/'))) {
throw new FileNotFoundException('Dir does not exist');
}
$exists= FALSE;
while ($e= $dir->getEntry()) {
if (!($e instanceof FtpDir) && $e->getName() == $filename) {
$exists= TRUE;
break;
}
}
// }}}

// New functionality {{{
if (!$c->rootDir()->hasFile($filename)) {
// $filename does not exist
}
// }}}

Retrieving the root directory:

  $c= new FtpConnection('ftp://user:pass@example.com');
$c->connect();

// Current functionality {{{
$root= $c->getDir('/');
// }}}

// New functionality {{{
$root= $c->rootDir();
// }}}



Exceptions for makeDir():

  $c= new FtpConnection('ftp://user:pass@example.com');
$c->connect();

// Current functionality {{{
if (FALSE === $c->makeDir(new FtpDir('/admin'))) {
// Failed creating directory "admin"
}
$dir= $c->getDir('/admin');
// }}}

// New functionality {{{
try {
$dir= $c->rootDir()->newDir('admin');
} catch (IllegalStateException $e) {
// Directory "admin" already exists
} catch (IOException $e) {
// Failed creating directory "admin"
}
// }}}

Retrieving a single entry:

  $c= new FtpConnection('ftp://user:pass@example.com');
$c->connect();

// Current functionality {{{
if (!($dir= $c->getDir('admin'))) {
throw new FileNotFoundException('Admin dir does not exist');
}
$entry= NULL;
while ($e= $dir->getEntry()) {
if (!($e instanceof FtpDir) && $e->getName() == $name) {
$entry= $e;
break;
}
}
if (!$entry) {
throw new FileNotFoundException('File not found');
}
// }}}

// New functionality {{{
$entry= $c->rootDir()->getDir('admin')->getFile($name);
// }}}

Downloading entries:

  $c= new FtpConnection('ftp://user:pass@example.com');
$c->connect();

// Current functionality {{{
try {
$c->get('/'.$filename, $local);
} catch (IOException $e) {
// - Remote file did not exist
// - Local file not writeable
// - Other socket error
}
// }}}

// New functionality {{{
try {
$c->rootDir()->getFile($filename)->downloadTo(new FileOutputStream(new File($local)));
} catch (FileNotFoundException $e) {
// Remote file not found
} catch (SocketException $e) {
// Socket error
} catch (IOException $e) {
// Local file not writeable
}
// }}}

Uploading entries:

  $c= new FtpConnection('ftp://user:pass@example.com');
$c->connect();

// Current functionality {{{
try {
$c->put($local, '/'.$filename);
} catch (IOException $e) {
// - Remote file not writeable
// - Local file did not exist
// - Other socket error
}
// }}}

// New functionality {{{
try {
$c->rootDir()->file($filename)->uploadFrom(new FileInputStream(new File($local)));
} catch (FileNotFoundException $e) {
// Local file not found
} catch (SocketException $e) {
// Socket error
} catch (IOException $e) {
// Remote file not writeable
}
// }}}

Retrieving all entries into an array:

  $c= new FtpConnection('ftp://user:pass@example.com');
$c->connect();

// Current functionality {{{
$root= $c->getDir('/');
$entries= array();
while ($entry= $root->getEntry()) {
$entries[]= $entry;
}
// }}}

// New functionality {{{
$entries= $c->rootDir()->entries()->asArray();
// }}}

Entry iteration:

  $c= new FtpConnection('ftp://user:pass@example.com');
$c->connect();

// Current functionality {{{
$root= $c->getDir('/');
$entries= array();
while ($entry= $root->getEntry()) {
// ... Handle entry
}
// }}}

// New functionality {{{
foreach ($c->rootDir()->entries() as $entry) {
// ... Handle entry
}
// }}}

Security considerations

n/a

Speed impact

n/a

Dependencies

Deprecated methods in peer.ftp.FtpConnection:

  public peer.ftp.FtpDir getDir([string $dir= NULL])
  public bool setDir(peer.ftp.FtpDir $f) 
  public bool makeDir(peer.ftp.FtpDir $f)
  public bool put(mixed $arg, [string $remote= NULL], [string $mode= 0]) 
  public bool get(string $remote, mixed $arg, [string $mode= 0])
  public bool delete(string $remote)
  public bool rename(string $src, string $target)
  public array quote(string $command)



Related documents

- http://experiments.xp-framework.net/?arena,ftp The new API

Comments

- friebe, Sun Oct 14 19:26:33 2007 Should the old methods be removed instantly (breaking BC) or should they be available but deprecated?

- friebe, Mon Oct 15 22:02:46 2007 Decided to add methods from old API as deprecated methods. They will be removed in a future release! <EOF>


Table of contents