coveralls: 
[![Coverage Status](https://coveralls.io/builds/24817324/badge)](https://coveralls.io/builds/24817324)

Coverage decreased (-0.5%) to 99.078% when pulling **aea8d46794696856b25cd1c4809ce20389c71ce4 on dargueta:ftp-eoferror** into **37b46b4a991a124f4d8e199e111ee21f00c2213b on PyFilesystem:master**.

willmcgugan: Thanks
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b36f9a9..d92e0ec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 
 - `MemFS` now immediately releases all memory it holds when `close()` is called,
   rather than when it gets garbage collected. Closes [issue #308](https://github.com/PyFilesystem/pyfilesystem2/issues/308).
+- `FTPFS` now translates `EOFError` into `RemoteConnectionError`. Closes [#292](https://github.com/PyFilesystem/pyfilesystem2/issues/292)
 
 ## [2.4.8] - 2019-06-12
 
diff --git a/fs/ftpfs.py b/fs/ftpfs.py
index 8e295da..21e98a1 100644
--- a/fs/ftpfs.py
+++ b/fs/ftpfs.py
@@ -77,6 +77,8 @@ def ftp_errors(fs, path=None):
         raise errors.RemoteConnectionError(
             msg="unable to connect to {}".format(fs.host)
         )
+    except EOFError:
+        raise errors.RemoteConnectionError(msg="lost connection to {}".format(fs.host))
     except error_temp as error:
         if path is not None:
             raise errors.ResourceError(

FTPFS: EOFError in listdir
Hello,

I am new to pyfilesystem, so please excuse if i might have missed something. From what i can understand from the documentation pyfilesystem2 translates exceptions from the underlying filesystem into a common set of exceptions.

There seems to be a corner case for ftpfs where it might raise EOFError in listdir if the server forcefully closes the connection during the operation. This exception seems to be coming from the underlying ftplib.
diff --git a/tests/test_ftpfs.py b/tests/test_ftpfs.py
index df6c97a..19ce22e 100644
--- a/tests/test_ftpfs.py
+++ b/tests/test_ftpfs.py
@@ -4,7 +4,6 @@ from __future__ import print_function
 from __future__ import unicode_literals
 
 import socket
-import ftplib
 import os
 import platform
 import shutil
@@ -114,6 +113,20 @@ class TestFTPErrors(unittest.TestCase):
             with ftp_errors(mem_fs):
                 raise error_perm("999 foo")
 
+    def test_manager_with_host(self):
+        mem_fs = open_fs("mem://")
+        mem_fs.host = "ftp.example.com"
+
+        with self.assertRaises(errors.RemoteConnectionError) as err_info:
+            with ftp_errors(mem_fs):
+                raise EOFError
+        self.assertEqual(str(err_info.exception), "lost connection to ftp.example.com")
+
+        with self.assertRaises(errors.RemoteConnectionError) as err_info:
+            with ftp_errors(mem_fs):
+                raise socket.error
+        self.assertEqual(str(err_info.exception), "unable to connect to ftp.example.com")
+
 
 @attr("slow")
 class TestFTPFS(FSTestCases, unittest.TestCase):
@@ -167,7 +180,7 @@ class TestFTPFS(FSTestCases, unittest.TestCase):
 
     def test_ftp_url(self):
         self.assertEqual(self.fs.ftp_url, "ftp://{}:{}@{}:{}".format(self.user, self.pasw, self.server.host, self.server.port))
-        
+
     def test_geturl(self):
         self.fs.makedir("foo")
         self.fs.create("bar")
@@ -251,7 +264,7 @@ class TestFTPFSNoMLSD(TestFTPFS):
     def test_features(self):
         pass
 
-    
+
 @attr("slow")
 class TestAnonFTPFS(FSTestCases, unittest.TestCase):
 

pip install -e .[dev]
parse_log_pytest
requirements.txt
coverage mock pyftpdlib python-coveralls nose pytest
3.6
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
appdirs==1.4.4
attrs==22.2.0
certifi==2021.5.30
charset-normalizer==2.0.12
coverage==6.2
-e git+https://github.com/PyFilesystem/pyfilesystem2.git@c23cfca6f98335aa82db62fc51d36f435b8dfd2c#egg=fs
idna==3.10
importlib-metadata==4.8.3
iniconfig==1.1.1
mock==5.2.0
nose==1.3.7
packaging==21.3
pluggy==1.0.0
py==1.11.0
pyftpdlib==2.0.1
pyparsing==3.1.4
pytest==7.0.1
python-coveralls==2.9.3
pytz==2025.2
PyYAML==6.0.1
requests==2.27.1
six==1.10.0
tomli==1.2.3
typing_extensions==4.1.1
urllib3==1.26.20
zipp==3.6.0

name: pyfilesystem2
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - appdirs==1.4.4
      - attrs==22.2.0
      - charset-normalizer==2.0.12
      - coverage==6.2
      - idna==3.10
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - mock==5.2.0
      - nose==1.3.7
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pyftpdlib==2.0.1
      - pyparsing==3.1.4
      - pytest==7.0.1
      - python-coveralls==2.9.3
      - pytz==2025.2
      - pyyaml==6.0.1
      - requests==2.27.1
      - six==1.10.0
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - zipp==3.6.0
prefix: /opt/conda/envs/pyfilesystem2

tests/test_ftpfs.py::TestFTPErrors::test_manager_with_host
tests/test_ftpfs.py::TestFTPFSClass::test_opener tests/test_ftpfs.py::TestFTPFSClass::test_parse_ftp_time tests/test_ftpfs.py::TestFTPFSClass::test_parse_mlsx tests/test_ftpfs.py::TestFTPFSClass::test_parse_mlsx_type tests/test_ftpfs.py::TestFTPErrors::test_manager
fs/ftpfs.py CHANGELOG.md
fs/ftpfs.py CHANGELOG.md