diff --git a/README.rst b/README.rst index 06ba3a2..b9f80f3 100644 --- a/README.rst +++ b/README.rst @@ -29,7 +29,15 @@ Usage >>> from send2trash import send2trash >>> send2trash('some_file') -When there's a problem ``OSError`` is raised. +On Freedesktop platforms (Linux, BSD, etc.), you may not be able to efficiently +trash some files. In these cases, an exception ``send2trash.TrashPermissionError`` +is raised, so that the application can handle this case. This inherits from +``PermissionError`` (``OSError`` on Python 2). Specifically, this affects +files on a different device to the user's home directory, where the root of the +device does not have a ``.Trash`` directory, and we don't have permission to +create a ``.Trash-$UID`` directory. + +For any other problem, ``OSError`` is raised. .. _PyGObject: https://wiki.gnome.org/PyGObject .. _GIO: https://developer.gnome.org/gio/ diff --git a/send2trash/__init__.py b/send2trash/__init__.py index 8a059a0..3a8c884 100644 --- a/send2trash/__init__.py +++ b/send2trash/__init__.py @@ -6,6 +6,8 @@ import sys +from .exceptions import TrashPermissionError + if sys.platform == 'darwin': from .plat_osx import send2trash elif sys.platform == 'win32': diff --git a/send2trash/exceptions.py b/send2trash/exceptions.py new file mode 100644 index 0000000..4132a6d --- /dev/null +++ b/send2trash/exceptions.py @@ -0,0 +1,25 @@ +import errno +from .compat import PY3 + +if PY3: + _permission_error = PermissionError +else: + _permission_error = OSError + +class TrashPermissionError(_permission_error): + """A permission error specific to a trash directory. + + Raising this error indicates that permissions prevent us efficiently + trashing a file, although we might still have permission to delete it. + This is *not* used when permissions prevent removing the file itself: + that will be raised as a regular PermissionError (OSError on Python 2). + + Application code that catches this may try to simply delete the file, + or prompt the user to decide, or (on Freedesktop platforms), move it to + 'home trash' as a fallback. This last option probably involves copying the + data between partitions, devices, or network drives, so we don't do it as + a fallback. + """ + def __init__(self, filename): + _permission_error.__init__(self, errno.EACCES, "Permission denied", + filename) diff --git a/send2trash/plat_other.py b/send2trash/plat_other.py index b61fe1d..624eb99 100644 --- a/send2trash/plat_other.py +++ b/send2trash/plat_other.py @@ -16,6 +16,7 @@ from __future__ import unicode_literals +import errno import sys import os import os.path as op @@ -28,6 +29,7 @@ except ImportError: from urllib import quote from .compat import text_type, environb +from .exceptions import TrashPermissionError try: fsencode = os.fsencode # Python 3 @@ -134,9 +136,13 @@ def find_ext_volume_global_trash(volume_root): def find_ext_volume_fallback_trash(volume_root): # from [2] Trash directories (1) create a .Trash-$uid dir. trash_dir = op.join(volume_root, TOPDIR_FALLBACK) - # Try to make the directory, if we can't the OSError exception will escape - # be thrown out of send2trash. - check_create(trash_dir) + # Try to make the directory, if we lack permission, raise TrashPermissionError + try: + check_create(trash_dir) + except OSError as e: + if e.errno == errno.EACCES: + raise TrashPermissionError(e.filename) + raise return trash_dir def find_ext_volume_trash(volume_root):