From 2a67be4892ef3600cb4e6cb4bd6e0456d3f6e3df Mon Sep 17 00:00:00 2001
From: Timm Friebe
Date: Sun, 21 Jun 2026 19:10:25 +0200
Subject: [PATCH 1/2] Refactor I/O exceptions
---
composer.json | 2 +-
src/main/php/peer/ftp/FtpConnection.class.php | 4 ++--
src/main/php/peer/ftp/FtpDir.class.php | 16 ++++++++--------
src/main/php/peer/ftp/FtpDownload.class.php | 4 ++--
src/main/php/peer/ftp/FtpEntry.class.php | 16 ++++++++--------
src/main/php/peer/ftp/FtpFile.class.php | 2 +-
src/main/php/peer/ftp/FtpUpload.class.php | 4 ++--
.../peer/ftp/collections/FtpCollection.class.php | 8 ++++----
.../peer/ftp/collections/FtpElement.class.php | 4 ++--
.../php/peer/ftp/server/FtpProtocol.class.php | 6 +++---
.../server/storage/FilesystemStorage.class.php | 6 +++---
.../peer/ftp/unittest/IntegrationTest.class.php | 4 ++--
12 files changed, 38 insertions(+), 38 deletions(-)
diff --git a/composer.json b/composer.json
index 657b2ea0..46c7356b 100755
--- a/composer.json
+++ b/composer.json
@@ -6,7 +6,7 @@
"description" : "FTP protocol support for the XP Framework",
"keywords": ["module", "xp"],
"require" : {
- "xp-framework/core": "^12.0 | ^11.0 | ^10.0",
+ "xp-framework/core": "^12.11 | ^11.11",
"xp-framework/logging": "^11.0 | ^10.0 | ^9.1",
"xp-framework/networking": "^11.0 | ^10.0 | ^9.3",
"xp-framework/io-collections": "^10.0 | ^9.0 | ^8.0",
diff --git a/src/main/php/peer/ftp/FtpConnection.class.php b/src/main/php/peer/ftp/FtpConnection.class.php
index 07774f4f..37137ed1 100755
--- a/src/main/php/peer/ftp/FtpConnection.class.php
+++ b/src/main/php/peer/ftp/FtpConnection.class.php
@@ -268,7 +268,7 @@ public function expect($r, $codes) {
* @param string name the directory's name
* @param string options default NULL
* @return string[] list or NULL if nothing can be found
- * @throws io.IOException
+ * @throws io.OperationFailed
*/
public function listingOf($name, $options= null) {
with ($transfer= $this->transferSocket()); {
@@ -293,7 +293,7 @@ public function listingOf($name, $options= null) {
}
} else { // Unexpected response
$transfer->close();
- throw new \io\IOException('Listing '.$this->name.$name.' failed ('.$code.': '.$message.')');
+ throw new \io\OperationFailed('Listing '.$this->name.$name.' failed ('.$code.': '.$message.')');
}
}
}
diff --git a/src/main/php/peer/ftp/FtpDir.class.php b/src/main/php/peer/ftp/FtpDir.class.php
index 0d7cb74f..f68582d1 100755
--- a/src/main/php/peer/ftp/FtpDir.class.php
+++ b/src/main/php/peer/ftp/FtpDir.class.php
@@ -1,6 +1,6 @@
connection->listingOf($this->name, '-al'))) {
- throw new IOException('Cannot list "'.$this->name.'"');
+ throw new OperationFailed('Cannot list "'.$this->name.'"');
}
return new FtpEntryList($list, $this->connection, $this->name);
@@ -46,7 +46,7 @@ public function entries() {
/**
* Delete this entry
*
- * @throws io.IOException in case of an I/O error
+ * @throws io.OperationFailed in case of an I/O error
*/
public function delete() {
$this->connection->expect($this->connection->sendCommand('RMD %s', $this->name), [250]);
@@ -57,7 +57,7 @@ public function delete() {
*
* @param string name
* @return peer.ftp.FtpEntry entry or NULL if nothing was found
- * @throws io.IOException in case listing fails
+ * @throws io.OperationFailed in case listing fails
* @throws peer.ProtocolException in case listing yields an unexpected result
*/
protected function findEntry($name) {
@@ -196,7 +196,7 @@ public function getDir($name) {
* Create a new directory
*
* @param string name
- * @throws io.IOException if directory cannot be created
+ * @throws io.OperationFailed if directory cannot be created
* @throws peer.ProtocolException in case the created directory cannot be located or is a file
*/
protected function makeDir($name) {
@@ -217,7 +217,7 @@ protected function makeDir($name) {
* @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
+ * @throws io.OperationFailed in case the directory could not be created
*/
public function newDir($name) {
if ($e= $this->findEntry($name)) {
@@ -238,7 +238,7 @@ public function newDir($name) {
* @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
+ * @throws io.OperationFailed in case the directory could not be created
*/
public function dir($name) {
if (!($e= $this->findEntry($name))) {
diff --git a/src/main/php/peer/ftp/FtpDownload.class.php b/src/main/php/peer/ftp/FtpDownload.class.php
index c118d2bb..00573795 100755
--- a/src/main/php/peer/ftp/FtpDownload.class.php
+++ b/src/main/php/peer/ftp/FtpDownload.class.php
@@ -66,7 +66,7 @@ public function size() {
protected function doTransfer() {
try {
$chunk= $this->socket->readBinary();
- } catch (\io\IOException $e) {
+ } catch (\io\OperationFailed $e) {
$this->listener && $this->listener->failed($this, $e);
$this->close();
throw $e;
@@ -81,7 +81,7 @@ protected function doTransfer() {
try {
$this->out->write($chunk);
- } catch (\io\IOException $e) {
+ } catch (\io\OperationFailed $e) {
$this->listener && $this->listener->failed($this, $e);
$this->close();
throw $e;
diff --git a/src/main/php/peer/ftp/FtpEntry.class.php b/src/main/php/peer/ftp/FtpEntry.class.php
index c76e1ee8..f522919b 100755
--- a/src/main/php/peer/ftp/FtpEntry.class.php
+++ b/src/main/php/peer/ftp/FtpEntry.class.php
@@ -45,7 +45,7 @@ public function getConnection() { return $this->connection; }
* Checks whether this entry exists.
*
* @return bool TRUE if the file exists, FALSE otherwise
- * @throws io.IOException in case of an I/O error
+ * @throws io.OperationFailed in case of an I/O error
*/
public function exists() {
$r= $this->connection->sendCommand('SIZE %s', $this->name);
@@ -73,7 +73,7 @@ public function exists() {
*
*
* @param string to the new name
- * @throws io.IOException in case of an I/O error
+ * @throws io.OperationFailed in case of an I/O error
*/
public function rename($to) {
$target= ('/' === $to[0] ? $to : dirname($this->name).'/'.$to);
@@ -81,7 +81,7 @@ public function rename($to) {
$this->connection->expect($this->connection->sendCommand('RNFR %s', $this->name), [350]);
$this->connection->expect($this->connection->sendCommand('RNTO %s', $target), [250]);
} catch (\peer\ProtocolException $e) {
- throw new \io\IOException('Could not rename '.$this->name.' to '.$to.': '.$e->getMessage());
+ throw new \io\OperationFailed('Could not rename '.$this->name.' to '.$to.': '.$e->getMessage());
}
}
@@ -90,7 +90,7 @@ public function rename($to) {
*
* @param peer.ftp.FtpDir to the new location
* @param string name default NULL the new name - if omitted, will stay the same
- * @throws io.IOException in case of an I/O error
+ * @throws io.OperationFailed in case of an I/O error
*/
public function moveTo(FtpDir $to, $name= null) {
try {
@@ -101,7 +101,7 @@ public function moveTo(FtpDir $to, $name= null) {
$name ? $name : basename($this->name)
), [250]);
} catch (\peer\ProtocolException $e) {
- throw new \io\IOException('Could not rename '.$this->name.' to '.$to->getName().': '.$e->getMessage());
+ throw new \io\OperationFailed('Could not rename '.$this->name.' to '.$to->getName().': '.$e->getMessage());
}
}
@@ -109,7 +109,7 @@ public function moveTo(FtpDir $to, $name= null) {
* Change this entry's permissions
*
* @param int to the new permissions
- * @throws io.IOException in case of an I/O error
+ * @throws io.OperationFailed in case of an I/O error
*/
public function changePermissions($to) {
$this->connection->expect($this->connection->sendCommand('SITE CHMOD %s %d', $this->name, $to));
@@ -118,7 +118,7 @@ public function changePermissions($to) {
/**
* Delete this entry
*
- * @throws io.IOException in case of an I/O error
+ * @throws io.OperationFailed in case of an I/O error
*/
public abstract function delete();
@@ -253,7 +253,7 @@ public function getDate() {
* Get last modified date. Uses the "MDTM" command internally.
*
* @return util.Date or NULL if the server does not support this
- * @throws io.IOException in case the connection is closed
+ * @throws io.OperationFailed in case the connection is closed
*/
public function lastModified() {
$r= $this->connection->sendCommand('MDTM %s', $this->name);
diff --git a/src/main/php/peer/ftp/FtpFile.class.php b/src/main/php/peer/ftp/FtpFile.class.php
index 17f01331..c6497b49 100755
--- a/src/main/php/peer/ftp/FtpFile.class.php
+++ b/src/main/php/peer/ftp/FtpFile.class.php
@@ -15,7 +15,7 @@ public function isFile(): bool { return true; }
/**
* Delete this entry
*
- * @throws io.IOException in case of an I/O error
+ * @throws io.OperationFailed in case of an I/O error
*/
public function delete() {
$this->connection->expect($this->connection->sendCommand('DELE %s', $this->name), [250]);
diff --git a/src/main/php/peer/ftp/FtpUpload.class.php b/src/main/php/peer/ftp/FtpUpload.class.php
index 443bab81..19d53e01 100755
--- a/src/main/php/peer/ftp/FtpUpload.class.php
+++ b/src/main/php/peer/ftp/FtpUpload.class.php
@@ -1,7 +1,7 @@
in->read(8192);
$this->socket->write($chunk);
- } catch (IOException $e) {
+ } catch (OperationFailed $e) {
$this->listener && $this->listener->failed($this, $e);
$this->close();
throw $e;
diff --git a/src/main/php/peer/ftp/collections/FtpCollection.class.php b/src/main/php/peer/ftp/collections/FtpCollection.class.php
index 10d71a6e..c77a2790 100755
--- a/src/main/php/peer/ftp/collections/FtpCollection.class.php
+++ b/src/main/php/peer/ftp/collections/FtpCollection.class.php
@@ -171,19 +171,19 @@ public function setOrigin(IOCollection $origin) {
* Gets input stream to read from this element
*
* @return io.streams.InputStream
- * @throws io.IOException
+ * @throws io.OperationFailed
*/
public function getInputStream() {
- throw new \io\IOException('Cannot read from a directory');
+ throw new \io\OperationFailed('Cannot read from a directory');
}
/**
* Gets output stream to read from this element
*
* @return io.streams.OutputStream
- * @throws io.IOException
+ * @throws io.OperationFailed
*/
public function getOutputStream() {
- throw new \io\IOException('Cannot write to a directory');
+ throw new \io\OperationFailed('Cannot write to a directory');
}
}
\ No newline at end of file
diff --git a/src/main/php/peer/ftp/collections/FtpElement.class.php b/src/main/php/peer/ftp/collections/FtpElement.class.php
index 13f41e20..0e88902b 100755
--- a/src/main/php/peer/ftp/collections/FtpElement.class.php
+++ b/src/main/php/peer/ftp/collections/FtpElement.class.php
@@ -109,7 +109,7 @@ public function setOrigin(\io\collections\IOCollection $origin) {
* Gets input stream to read from this element
*
* @return io.streams.InputStream
- * @throws io.IOException
+ * @throws io.OperationFailed
*/
public function getInputStream() {
return new FtpInputStream($this->file);
@@ -119,7 +119,7 @@ public function getInputStream() {
* Gets output stream to read from this element
*
* @return io.streams.OutputStream
- * @throws io.IOException
+ * @throws io.OperationFailed
*/
public function getOutputStream() {
return new FtpOutputStream($this->file);
diff --git a/src/main/php/peer/ftp/server/FtpProtocol.class.php b/src/main/php/peer/ftp/server/FtpProtocol.class.php
index 2e10acec..914eddba 100755
--- a/src/main/php/peer/ftp/server/FtpProtocol.class.php
+++ b/src/main/php/peer/ftp/server/FtpProtocol.class.php
@@ -152,7 +152,7 @@ public function eol($type) {
* @param string text
* @param array lines default NULL lines of a multiline response
* @return int number of bytes written
- * @throws io.IOException
+ * @throws io.OperationFailed
*/
public function answer($sock, $code, $text, $lines= null) {
if (is_array($lines)) {
@@ -745,7 +745,7 @@ public function onDele($socket, $params) {
try {
$entry->delete();
- } catch (\io\IOException $e) {
+ } catch (\io\OperationFailed $e) {
$this->answer($socket, 450, $params.': ', $e->getMessage());
return;
}
@@ -927,7 +927,7 @@ public function onPasv($socket, $params) {
$this->datasock[$key]->create();
$this->datasock[$key]->bind();
$this->datasock[$key]->listen();
- } catch (\io\IOException $e) {
+ } catch (\io\OperationFailed $e) {
$this->answer($socket, 425, 'Cannot open passive connection '.$e->getMessage());
unset($this->datasock[$key]);
return;
diff --git a/src/main/php/peer/ftp/server/storage/FilesystemStorage.class.php b/src/main/php/peer/ftp/server/storage/FilesystemStorage.class.php
index 1639e40d..d63bb910 100755
--- a/src/main/php/peer/ftp/server/storage/FilesystemStorage.class.php
+++ b/src/main/php/peer/ftp/server/storage/FilesystemStorage.class.php
@@ -74,7 +74,7 @@ public function realname($clientId, $uri) {
*/
public function setBase($clientId, $uri= null) {
if (!is_dir($path= $this->realname($clientId, $uri))) {
- throw new \io\IOException($uri.': not a directory');
+ throw new \io\OperationFailed($uri.': not a directory');
}
$this->base[$clientId]= DIRECTORY_SEPARATOR.ltrim(
str_replace($this->root, '', $path),
@@ -134,13 +134,13 @@ public function create($clientId, $uri, $type) {
switch ($type) {
case ST_ELEMENT:
if (false === touch($path)) {
- throw new \io\IOException('File '.$path.' could not be created');
+ throw new \io\OperationFailed('File '.$path.' could not be created');
}
break;
case ST_COLLECTION:
if (false === mkdir($path)) {
- throw new \io\IOException($path.' could not be created');
+ throw new \io\OperationFailed($path.' could not be created');
}
break;
}
diff --git a/src/test/php/peer/ftp/unittest/IntegrationTest.class.php b/src/test/php/peer/ftp/unittest/IntegrationTest.class.php
index 6396f613..54c8ece8 100755
--- a/src/test/php/peer/ftp/unittest/IntegrationTest.class.php
+++ b/src/test/php/peer/ftp/unittest/IntegrationTest.class.php
@@ -1,7 +1,7 @@
getFile('index.html')->in();
Assert::equals("\n", Streams::readAll($s));
- } catch (IOException $e) {
+ } catch (OperationFailed $e) {
$this->fail('Round '.($i + 1), $e, null);
}
}
From 69958dca6234e713402218327d68b755c98eafa7 Mon Sep 17 00:00:00 2001
From: Timm Friebe
Date: Sun, 21 Jun 2026 19:12:48 +0200
Subject: [PATCH 2/2] FileNotFoundException -> NotFound
---
src/main/php/peer/ftp/FtpDir.class.php | 10 +++++-----
.../php/peer/ftp/unittest/IntegrationTest.class.php | 6 +++---
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/src/main/php/peer/ftp/FtpDir.class.php b/src/main/php/peer/ftp/FtpDir.class.php
index f68582d1..0a26e02d 100755
--- a/src/main/php/peer/ftp/FtpDir.class.php
+++ b/src/main/php/peer/ftp/FtpDir.class.php
@@ -1,6 +1,6 @@
findEntry($name))) {
- throw new FileNotFoundException('File "'.$name.'" not found');
+ throw new NotFound('File "'.$name.'" not found');
} else if ($e instanceof FtpDir) {
throw new \lang\IllegalStateException('File "'.$name.'" is a directory');
}
@@ -180,12 +180,12 @@ public function hasDir($name) {
*
* @param string name
* @return peer.ftp.FtpDir the instance
- * @throws io.FileNotFoundException in case the directory was not found
+ * @throws io.NotFound in case the directory was not found
* @throws lang.IllegalStateException in case the directory exists but is a file
*/
public function getDir($name) {
if (!($e= $this->findEntry($name))) {
- throw new FileNotFoundException('Directory "'.$name.'" not found');
+ throw new NotFound('Directory "'.$name.'" not found');
} else if ($e instanceof FtpFile) {
throw new \lang\IllegalStateException('Directory "'.$name.'" is a file');
}
diff --git a/src/test/php/peer/ftp/unittest/IntegrationTest.class.php b/src/test/php/peer/ftp/unittest/IntegrationTest.class.php
index 54c8ece8..8a132471 100755
--- a/src/test/php/peer/ftp/unittest/IntegrationTest.class.php
+++ b/src/test/php/peer/ftp/unittest/IntegrationTest.class.php
@@ -1,7 +1,7 @@
rootDir()->hasDir(':DOES_NOT_EXIST'));
}
- #[Test, Expect(FileNotFoundException::class)]
+ #[Test, Expect(NotFound::class)]
public function getNonExistantDir() {
$conn= $this->connection()->connect();
$conn->rootDir()->getDir(':DOES_NOT_EXIST');
@@ -207,7 +207,7 @@ public function nonExistantFile() {
Assert::false($conn->rootDir()->getDir('htdocs')->hasFile(':DOES_NOT_EXIST'));
}
- #[Test, Expect(FileNotFoundException::class)]
+ #[Test, Expect(NotFound::class)]
public function getNonExistantFile() {
$conn= $this->connection()->connect();
$conn->rootDir()->getDir('htdocs')->getFile(':DOES_NOT_EXIST');