thekid/rfc — Issue #140: RFC 0140: FTP API enhancements
Author: thekid
Created: 2011-10-09 15:21:47+00:00
State: open

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:

```php

```

FtpEntryList class
----

```php

```

New methods
----
peer.ftp.FtpConnection:

```php
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:

```php

```

peer.ftp.FtpDir:

```php

```

Examples
----

Checking for entry existance:

```php
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:

```php
connect();

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

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

Exceptions for makeDir():

```php
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:

```php
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:

```php
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:

```php
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:

```php
connect();

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

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

Entry iteration:

```php
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!