pax_global_header00006660000000000000000000000064134313464200014512gustar00rootroot0000000000000052 comment=b456dc2d782065f274a0f6be2e0c72d8a1f2546f send2trash-1.5.0/000077500000000000000000000000001343134642000135725ustar00rootroot00000000000000send2trash-1.5.0/.gitignore000066400000000000000000000000721343134642000155610ustar00rootroot00000000000000*.pyc *.egg-info /build /dist .DS_Store /.tox __pycache__ send2trash-1.5.0/.travis.yml000066400000000000000000000002401343134642000156770ustar00rootroot00000000000000language: python python: - "2.7" - "3.4" - "3.5" - "3.6" install: - "pip install tox" script: - "tox -e $(echo py$TRAVIS_PYTHON_VERSION | tr -d .)" send2trash-1.5.0/CHANGES.rst000066400000000000000000000027051343134642000154000ustar00rootroot00000000000000Changes ======= Version 1.5.0 -- 2018/02/16 --------------------------- * More specific error when failing to create XDG fallback trash directory (#20) * Windows: Workaround for long paths (#23) Version 1.4.2 -- 2017/11/17 --------------------------- * Fix incompatibility with Python 3.6 on Windows. (#18) Version 1.4.1 -- 2017/08/07 --------------------------- * Fix crash on Windows introduced in v1.4.0. Oops... (#14) Version 1.4.0 -- 2017/08/07 --------------------------- * Use ``bytes`` instead of ``str`` for internal path handling in ``plat_other``. (#13) Version 1.3.1 -- 2017/07/31 --------------------------- * Throw ``WindowsError`` instead of ``OSError`` in ``plat_win``. (#7) * Fix ``TypeError`` on python 2 in ``plat_other``. (#12) Version 1.3.0 -- 2013/07/19 --------------------------- * Added support for Gnome's GIO. * Merged Python 3 and Python 2 versions in a single codebase. Version 1.2.0 -- 2011/03/16 --------------------------- * Improved ``plat_other`` to follow freedesktop.org trash specification. Version 1.1.0 -- 2010/10/18 --------------------------- * Converted compiled modules to ctypes so that cross-platform compilation isn't necessary anymore. Version 1.0.2 -- 2010/07/10 --------------------------- * Fixed bugs with external volumes in plat_other. Version 1.0.1 -- 2010/04/19 --------------------------- * Fixed memory leak in OS X module. Version 1.0.0 -- 2010/04/07 --------------------------- * Initial Release send2trash-1.5.0/LICENSE000066400000000000000000000027261343134642000146060ustar00rootroot00000000000000Copyright (c) 2017, Virgil Dupras All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Hardcoded Software Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. send2trash-1.5.0/MANIFEST.in000066400000000000000000000000341343134642000153250ustar00rootroot00000000000000include CHANGES.rst LICENSE send2trash-1.5.0/PKG-INFO000066400000000000000000000116271343134642000146760ustar00rootroot00000000000000Metadata-Version: 1.1 Name: Send2Trash Version: 1.5.0 Summary: Send file to trash natively under Mac OS X, Windows and Linux. Home-page: https://github.com/hsoft/send2trash Author: Virgil Dupras Author-email: hsoft@hardcoded.net License: BSD License Description-Content-Type: UNKNOWN Description: ================================================== Send2Trash -- Send files to trash on all platforms ================================================== Send2Trash is a small package that sends files to the Trash (or Recycle Bin) *natively* and on *all platforms*. On OS X, it uses native ``FSMoveObjectToTrashSync`` Cocoa calls, on Windows, it uses native (and ugly) ``SHFileOperation`` win32 calls. On other platforms, if `PyGObject`_ and `GIO`_ are available, it will use this. Otherwise, it will fallback to its own implementation of the `trash specifications from freedesktop.org`_. ``ctypes`` is used to access native libraries, so no compilation is necessary. Send2Trash supports Python 2.7 and up (Python 3 is supported). Installation ------------ You can download it with pip:: pip install Send2Trash or you can download the source from http://github.com/hsoft/send2trash and install it with:: >>> python setup.py install Usage ----- >>> from send2trash import send2trash >>> send2trash('some_file') 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/ .. _trash specifications from freedesktop.org: http://freedesktop.org/wiki/Specifications/trash-spec/ Changes ======= Version 1.5.0 -- 2018/02/16 --------------------------- * More specific error when failing to create XDG fallback trash directory (#20) * Windows: Workaround for long paths (#23) Version 1.4.2 -- 2017/11/17 --------------------------- * Fix incompatibility with Python 3.6 on Windows. (#18) Version 1.4.1 -- 2017/08/07 --------------------------- * Fix crash on Windows introduced in v1.4.0. Oops... (#14) Version 1.4.0 -- 2017/08/07 --------------------------- * Use ``bytes`` instead of ``str`` for internal path handling in ``plat_other``. (#13) Version 1.3.1 -- 2017/07/31 --------------------------- * Throw ``WindowsError`` instead of ``OSError`` in ``plat_win``. (#7) * Fix ``TypeError`` on python 2 in ``plat_other``. (#12) Version 1.3.0 -- 2013/07/19 --------------------------- * Added support for Gnome's GIO. * Merged Python 3 and Python 2 versions in a single codebase. Version 1.2.0 -- 2011/03/16 --------------------------- * Improved ``plat_other`` to follow freedesktop.org trash specification. Version 1.1.0 -- 2010/10/18 --------------------------- * Converted compiled modules to ctypes so that cross-platform compilation isn't necessary anymore. Version 1.0.2 -- 2010/07/10 --------------------------- * Fixed bugs with external volumes in plat_other. Version 1.0.1 -- 2010/04/19 --------------------------- * Fixed memory leak in OS X module. Version 1.0.0 -- 2010/04/07 --------------------------- * Initial Release Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Topic :: Desktop Environment :: File Managers send2trash-1.5.0/README.rst000066400000000000000000000033571343134642000152710ustar00rootroot00000000000000================================================== Send2Trash -- Send files to trash on all platforms ================================================== Send2Trash is a small package that sends files to the Trash (or Recycle Bin) *natively* and on *all platforms*. On OS X, it uses native ``FSMoveObjectToTrashSync`` Cocoa calls, on Windows, it uses native (and ugly) ``SHFileOperation`` win32 calls. On other platforms, if `PyGObject`_ and `GIO`_ are available, it will use this. Otherwise, it will fallback to its own implementation of the `trash specifications from freedesktop.org`_. ``ctypes`` is used to access native libraries, so no compilation is necessary. Send2Trash supports Python 2.7 and up (Python 3 is supported). Installation ------------ You can download it with pip:: pip install Send2Trash or you can download the source from http://github.com/hsoft/send2trash and install it with:: >>> python setup.py install Usage ----- >>> from send2trash import send2trash >>> send2trash('some_file') 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/ .. _trash specifications from freedesktop.org: http://freedesktop.org/wiki/Specifications/trash-spec/ send2trash-1.5.0/Send2Trash.egg-info/000077500000000000000000000000001343134642000172415ustar00rootroot00000000000000send2trash-1.5.0/Send2Trash.egg-info/PKG-INFO000066400000000000000000000116271343134642000203450ustar00rootroot00000000000000Metadata-Version: 1.1 Name: Send2Trash Version: 1.5.0 Summary: Send file to trash natively under Mac OS X, Windows and Linux. Home-page: https://github.com/hsoft/send2trash Author: Virgil Dupras Author-email: hsoft@hardcoded.net License: BSD License Description-Content-Type: UNKNOWN Description: ================================================== Send2Trash -- Send files to trash on all platforms ================================================== Send2Trash is a small package that sends files to the Trash (or Recycle Bin) *natively* and on *all platforms*. On OS X, it uses native ``FSMoveObjectToTrashSync`` Cocoa calls, on Windows, it uses native (and ugly) ``SHFileOperation`` win32 calls. On other platforms, if `PyGObject`_ and `GIO`_ are available, it will use this. Otherwise, it will fallback to its own implementation of the `trash specifications from freedesktop.org`_. ``ctypes`` is used to access native libraries, so no compilation is necessary. Send2Trash supports Python 2.7 and up (Python 3 is supported). Installation ------------ You can download it with pip:: pip install Send2Trash or you can download the source from http://github.com/hsoft/send2trash and install it with:: >>> python setup.py install Usage ----- >>> from send2trash import send2trash >>> send2trash('some_file') 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/ .. _trash specifications from freedesktop.org: http://freedesktop.org/wiki/Specifications/trash-spec/ Changes ======= Version 1.5.0 -- 2018/02/16 --------------------------- * More specific error when failing to create XDG fallback trash directory (#20) * Windows: Workaround for long paths (#23) Version 1.4.2 -- 2017/11/17 --------------------------- * Fix incompatibility with Python 3.6 on Windows. (#18) Version 1.4.1 -- 2017/08/07 --------------------------- * Fix crash on Windows introduced in v1.4.0. Oops... (#14) Version 1.4.0 -- 2017/08/07 --------------------------- * Use ``bytes`` instead of ``str`` for internal path handling in ``plat_other``. (#13) Version 1.3.1 -- 2017/07/31 --------------------------- * Throw ``WindowsError`` instead of ``OSError`` in ``plat_win``. (#7) * Fix ``TypeError`` on python 2 in ``plat_other``. (#12) Version 1.3.0 -- 2013/07/19 --------------------------- * Added support for Gnome's GIO. * Merged Python 3 and Python 2 versions in a single codebase. Version 1.2.0 -- 2011/03/16 --------------------------- * Improved ``plat_other`` to follow freedesktop.org trash specification. Version 1.1.0 -- 2010/10/18 --------------------------- * Converted compiled modules to ctypes so that cross-platform compilation isn't necessary anymore. Version 1.0.2 -- 2010/07/10 --------------------------- * Fixed bugs with external volumes in plat_other. Version 1.0.1 -- 2010/04/19 --------------------------- * Fixed memory leak in OS X module. Version 1.0.0 -- 2010/04/07 --------------------------- * Initial Release Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Topic :: Desktop Environment :: File Managers send2trash-1.5.0/Send2Trash.egg-info/SOURCES.txt000066400000000000000000000006771343134642000211370ustar00rootroot00000000000000.gitignore .travis.yml CHANGES.rst LICENSE MANIFEST.in README.rst setup.py tox.ini Send2Trash.egg-info/PKG-INFO Send2Trash.egg-info/SOURCES.txt Send2Trash.egg-info/dependency_links.txt Send2Trash.egg-info/top_level.txt send2trash/__init__.py send2trash/compat.py send2trash/exceptions.py send2trash/plat_gio.py send2trash/plat_osx.py send2trash/plat_other.py send2trash/plat_win.py tests/__init__.py tests/test_plat_other.py tests/test_plat_win.pysend2trash-1.5.0/Send2Trash.egg-info/dependency_links.txt000066400000000000000000000000011343134642000233070ustar00rootroot00000000000000 send2trash-1.5.0/Send2Trash.egg-info/top_level.txt000066400000000000000000000000131343134642000217650ustar00rootroot00000000000000send2trash send2trash-1.5.0/send2trash/000077500000000000000000000000001343134642000156475ustar00rootroot00000000000000send2trash-1.5.0/send2trash/__init__.py000066400000000000000000000013131343134642000177560ustar00rootroot00000000000000# Copyright 2013 Hardcoded Software (http://www.hardcoded.net) # This software is licensed under the "BSD" License as described in the "LICENSE" file, # which should be included with this package. The terms are also available at # http://www.hardcoded.net/licenses/bsd_license import sys from .exceptions import TrashPermissionError if sys.platform == 'darwin': from .plat_osx import send2trash elif sys.platform == 'win32': from .plat_win import send2trash else: try: # If we can use gio, let's use it from .plat_gio import send2trash except ImportError: # Oh well, let's fallback to our own Freedesktop trash implementation from .plat_other import send2trash send2trash-1.5.0/send2trash/compat.py000066400000000000000000000011141343134642000175010ustar00rootroot00000000000000# Copyright 2017 Virgil Dupras # This software is licensed under the "BSD" License as described in the "LICENSE" file, # which should be included with this package. The terms are also available at # http://www.hardcoded.net/licenses/bsd_license import sys import os PY3 = sys.version_info[0] >= 3 if PY3: text_type = str binary_type = bytes if os.supports_bytes_environ: # environb will be unset under Windows, but then again we're not supposed to use it. environb = os.environb else: text_type = unicode binary_type = str environb = os.environ send2trash-1.5.0/send2trash/exceptions.py000066400000000000000000000017671343134642000204150ustar00rootroot00000000000000import 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) send2trash-1.5.0/send2trash/plat_gio.py000066400000000000000000000013601343134642000200170ustar00rootroot00000000000000# Copyright 2017 Virgil Dupras # This software is licensed under the "BSD" License as described in the "LICENSE" file, # which should be included with this package. The terms are also available at # http://www.hardcoded.net/licenses/bsd_license from gi.repository import GObject, Gio from .exceptions import TrashPermissionError def send2trash(path): try: f = Gio.File.new_for_path(path) f.trash(cancellable=None) except GObject.GError as e: if e.code == Gio.IOErrorEnum.NOT_SUPPORTED: # We get here if we can't create a trash directory on the same # device. I don't know if other errors can result in NOT_SUPPORTED. raise TrashPermissionError('') raise OSError(e.message) send2trash-1.5.0/send2trash/plat_osx.py000066400000000000000000000032201343134642000200470ustar00rootroot00000000000000# Copyright 2017 Virgil Dupras # This software is licensed under the "BSD" License as described in the "LICENSE" file, # which should be included with this package. The terms are also available at # http://www.hardcoded.net/licenses/bsd_license from __future__ import unicode_literals from ctypes import cdll, byref, Structure, c_char, c_char_p from ctypes.util import find_library from .compat import binary_type Foundation = cdll.LoadLibrary(find_library('Foundation')) CoreServices = cdll.LoadLibrary(find_library('CoreServices')) GetMacOSStatusCommentString = Foundation.GetMacOSStatusCommentString GetMacOSStatusCommentString.restype = c_char_p FSPathMakeRefWithOptions = CoreServices.FSPathMakeRefWithOptions FSMoveObjectToTrashSync = CoreServices.FSMoveObjectToTrashSync kFSPathMakeRefDefaultOptions = 0 kFSPathMakeRefDoNotFollowLeafSymlink = 0x01 kFSFileOperationDefaultOptions = 0 kFSFileOperationOverwrite = 0x01 kFSFileOperationSkipSourcePermissionErrors = 0x02 kFSFileOperationDoNotMoveAcrossVolumes = 0x04 kFSFileOperationSkipPreflight = 0x08 class FSRef(Structure): _fields_ = [('hidden', c_char * 80)] def check_op_result(op_result): if op_result: msg = GetMacOSStatusCommentString(op_result).decode('utf-8') raise OSError(msg) def send2trash(path): if not isinstance(path, binary_type): path = path.encode('utf-8') fp = FSRef() opts = kFSPathMakeRefDoNotFollowLeafSymlink op_result = FSPathMakeRefWithOptions(path, opts, byref(fp), None) check_op_result(op_result) opts = kFSFileOperationDefaultOptions op_result = FSMoveObjectToTrashSync(byref(fp), None, opts) check_op_result(op_result) send2trash-1.5.0/send2trash/plat_other.py000066400000000000000000000144561343134642000203740ustar00rootroot00000000000000# Copyright 2017 Virgil Dupras # This software is licensed under the "BSD" License as described in the "LICENSE" file, # which should be included with this package. The terms are also available at # http://www.hardcoded.net/licenses/bsd_license # This is a reimplementation of plat_other.py with reference to the # freedesktop.org trash specification: # [1] http://www.freedesktop.org/wiki/Specifications/trash-spec # [2] http://www.ramendik.ru/docs/trashspec.html # See also: # [3] http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html # # For external volumes this implementation will raise an exception if it can't # find or create the user's trash directory. from __future__ import unicode_literals import errno import sys import os import os.path as op from datetime import datetime import stat try: from urllib.parse import quote except ImportError: # Python 2 from urllib import quote from .compat import text_type, environb from .exceptions import TrashPermissionError try: fsencode = os.fsencode # Python 3 fsdecode = os.fsdecode except AttributeError: def fsencode(u): # Python 2 return u.encode(sys.getfilesystemencoding()) def fsdecode(b): return b.decode(sys.getfilesystemencoding()) # The Python 3 versions are a bit smarter, handling surrogate escapes, # but these should work in most cases. FILES_DIR = b'files' INFO_DIR = b'info' INFO_SUFFIX = b'.trashinfo' # Default of ~/.local/share [3] XDG_DATA_HOME = op.expanduser(environb.get(b'XDG_DATA_HOME', b'~/.local/share')) HOMETRASH_B = op.join(XDG_DATA_HOME, b'Trash') HOMETRASH = fsdecode(HOMETRASH_B) uid = os.getuid() TOPDIR_TRASH = b'.Trash' TOPDIR_FALLBACK = b'.Trash-' + text_type(uid).encode('ascii') def is_parent(parent, path): path = op.realpath(path) # In case it's a symlink if isinstance(path, text_type): path = fsencode(path) parent = op.realpath(parent) if isinstance(parent, text_type): parent = fsencode(parent) return path.startswith(parent) def format_date(date): return date.strftime("%Y-%m-%dT%H:%M:%S") def info_for(src, topdir): # ...it MUST not include a ".." directory, and for files not "under" that # directory, absolute pathnames must be used. [2] if topdir is None or not is_parent(topdir, src): src = op.abspath(src) else: src = op.relpath(src, topdir) info = "[Trash Info]\n" info += "Path=" + quote(src) + "\n" info += "DeletionDate=" + format_date(datetime.now()) + "\n" return info def check_create(dir): # use 0700 for paths [3] if not op.exists(dir): os.makedirs(dir, 0o700) def trash_move(src, dst, topdir=None): filename = op.basename(src) filespath = op.join(dst, FILES_DIR) infopath = op.join(dst, INFO_DIR) base_name, ext = op.splitext(filename) counter = 0 destname = filename while op.exists(op.join(filespath, destname)) or op.exists(op.join(infopath, destname + INFO_SUFFIX)): counter += 1 destname = base_name + b' ' + text_type(counter).encode('ascii') + ext check_create(filespath) check_create(infopath) os.rename(src, op.join(filespath, destname)) f = open(op.join(infopath, destname + INFO_SUFFIX), 'w') f.write(info_for(src, topdir)) f.close() def find_mount_point(path): # Even if something's wrong, "/" is a mount point, so the loop will exit. # Use realpath in case it's a symlink path = op.realpath(path) # Required to avoid infinite loop while not op.ismount(path): path = op.split(path)[0] return path def find_ext_volume_global_trash(volume_root): # from [2] Trash directories (1) check for a .Trash dir with the right # permissions set. trash_dir = op.join(volume_root, TOPDIR_TRASH) if not op.exists(trash_dir): return None mode = os.lstat(trash_dir).st_mode # vol/.Trash must be a directory, cannot be a symlink, and must have the # sticky bit set. if not op.isdir(trash_dir) or op.islink(trash_dir) or not (mode & stat.S_ISVTX): return None trash_dir = op.join(trash_dir, text_type(uid).encode('ascii')) try: check_create(trash_dir) except OSError: return None return trash_dir 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 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): trash_dir = find_ext_volume_global_trash(volume_root) if trash_dir is None: trash_dir = find_ext_volume_fallback_trash(volume_root) return trash_dir # Pull this out so it's easy to stub (to avoid stubbing lstat itself) def get_dev(path): return os.lstat(path).st_dev def send2trash(path): if isinstance(path, text_type): path_b = fsencode(path) elif isinstance(path, bytes): path_b = path elif hasattr(path, '__fspath__'): # Python 3.6 PathLike protocol return send2trash(path.__fspath__()) else: raise TypeError('str, bytes or PathLike expected, not %r' % type(path)) if not op.exists(path_b): raise OSError("File not found: %s" % path) # ...should check whether the user has the necessary permissions to delete # it, before starting the trashing operation itself. [2] if not os.access(path_b, os.W_OK): raise OSError("Permission denied: %s" % path) # if the file to be trashed is on the same device as HOMETRASH we # want to move it there. path_dev = get_dev(path_b) # If XDG_DATA_HOME or HOMETRASH do not yet exist we need to stat the # home directory, and these paths will be created further on if needed. trash_dev = get_dev(op.expanduser(b'~')) if path_dev == trash_dev: topdir = XDG_DATA_HOME dest_trash = HOMETRASH_B else: topdir = find_mount_point(path_b) trash_dev = get_dev(topdir) if trash_dev != path_dev: raise OSError("Couldn't find mount point for %s" % path) dest_trash = find_ext_volume_trash(topdir) trash_move(path_b, dest_trash, topdir) send2trash-1.5.0/send2trash/plat_win.py000066400000000000000000000054001343134642000200350ustar00rootroot00000000000000# Copyright 2017 Virgil Dupras # This software is licensed under the "BSD" License as described in the "LICENSE" file, # which should be included with this package. The terms are also available at # http://www.hardcoded.net/licenses/bsd_license from __future__ import unicode_literals from ctypes import (windll, Structure, byref, c_uint, create_unicode_buffer, addressof) from ctypes.wintypes import HWND, UINT, LPCWSTR, BOOL import os.path as op from .compat import text_type kernel32 = windll.kernel32 GetShortPathNameW = kernel32.GetShortPathNameW shell32 = windll.shell32 SHFileOperationW = shell32.SHFileOperationW class SHFILEOPSTRUCTW(Structure): _fields_ = [ ("hwnd", HWND), ("wFunc", UINT), ("pFrom", LPCWSTR), ("pTo", LPCWSTR), ("fFlags", c_uint), ("fAnyOperationsAborted", BOOL), ("hNameMappings", c_uint), ("lpszProgressTitle", LPCWSTR), ] FO_MOVE = 1 FO_COPY = 2 FO_DELETE = 3 FO_RENAME = 4 FOF_MULTIDESTFILES = 1 FOF_SILENT = 4 FOF_NOCONFIRMATION = 16 FOF_ALLOWUNDO = 64 FOF_NOERRORUI = 1024 def get_short_path_name(long_name): if not long_name.startswith('\\\\?\\'): long_name = '\\\\?\\' + long_name buf_size = GetShortPathNameW(long_name, None, 0) output = create_unicode_buffer(buf_size) GetShortPathNameW(long_name, output, buf_size) return output.value[4:] # Remove '\\?\' for SHFileOperationW def send2trash(path): if not isinstance(path, text_type): path = text_type(path, 'mbcs') if not op.isabs(path): path = op.abspath(path) path = get_short_path_name(path) fileop = SHFILEOPSTRUCTW() fileop.hwnd = 0 fileop.wFunc = FO_DELETE # FIX: https://github.com/hsoft/send2trash/issues/17 # Starting in python 3.6.3 it is no longer possible to use: # LPCWSTR(path + '\0') directly as embedded null characters are no longer # allowed in strings # Workaround # - create buffer of c_wchar[] (LPCWSTR is based on this type) # - buffer is two c_wchar characters longer (double null terminator) # - cast the address of the buffer to a LPCWSTR # NOTE: based on how python allocates memory for these types they should # always be zero, if this is ever not true we can go back to explicitly # setting the last two characters to null using buffer[index] = '\0'. buffer = create_unicode_buffer(path, len(path)+2) fileop.pFrom = LPCWSTR(addressof(buffer)) fileop.pTo = None fileop.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT fileop.fAnyOperationsAborted = 0 fileop.hNameMappings = 0 fileop.lpszProgressTitle = None result = SHFileOperationW(byref(fileop)) if result: raise WindowsError(None, None, path, result) send2trash-1.5.0/setup.cfg000066400000000000000000000000461343134642000154130ustar00rootroot00000000000000[egg_info] tag_build = tag_date = 0 send2trash-1.5.0/setup.py000066400000000000000000000021101343134642000152760ustar00rootroot00000000000000from setuptools import setup CLASSIFIERS = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Topic :: Desktop Environment :: File Managers', ] LONG_DESCRIPTION = open('README.rst', 'rt').read() + '\n\n' + open('CHANGES.rst', 'rt').read() setup( name='Send2Trash', version='1.5.0', author='Virgil Dupras', author_email='hsoft@hardcoded.net', packages=['send2trash'], scripts=[], test_suite='tests', url='https://github.com/hsoft/send2trash', license='BSD License', description='Send file to trash natively under Mac OS X, Windows and Linux.', long_description=LONG_DESCRIPTION, classifiers=CLASSIFIERS, ) send2trash-1.5.0/tests/000077500000000000000000000000001343134642000147345ustar00rootroot00000000000000send2trash-1.5.0/tests/__init__.py000066400000000000000000000000001343134642000170330ustar00rootroot00000000000000send2trash-1.5.0/tests/test_plat_other.py000066400000000000000000000136021343134642000205100ustar00rootroot00000000000000# encoding: utf-8 import codecs import unittest import os from os import path as op import send2trash.plat_other from send2trash.plat_other import send2trash as s2t from send2trash.compat import PY3 from configparser import ConfigParser from tempfile import mkdtemp, NamedTemporaryFile, mktemp import shutil import stat import sys # Could still use cleaning up. But no longer relies on ramfs. HOMETRASH = send2trash.plat_other.HOMETRASH def touch(path): with open(path, 'a'): os.utime(path, None) class TestHomeTrash(unittest.TestCase): def setUp(self): self.file = NamedTemporaryFile( dir=op.expanduser("~"), prefix='send2trash_test', delete=False) def test_trash(self): s2t(self.file.name) self.assertFalse(op.exists(self.file.name)) def tearDown(self): name = op.basename(self.file.name) os.remove(op.join(HOMETRASH, 'files', name)) os.remove(op.join(HOMETRASH, 'info', name+'.trashinfo')) def _filesys_enc(): enc = sys.getfilesystemencoding() # Get canonical name of codec return codecs.lookup(enc).name @unittest.skipIf(_filesys_enc() == 'ascii', 'ASCII filesystem') class TestUnicodeTrash(unittest.TestCase): def setUp(self): self.name = u'send2trash_tést1' self.file = op.join(op.expanduser(b'~'), self.name.encode('utf-8')) touch(self.file) def test_trash_bytes(self): s2t(self.file) assert not op.exists(self.file) def test_trash_unicode(self): s2t(self.file.decode(sys.getfilesystemencoding())) assert not op.exists(self.file) def tearDown(self): if op.exists(self.file): os.remove(self.file) trash_file = op.join(HOMETRASH, 'files', self.name) if op.exists(trash_file): os.remove(trash_file) os.remove(op.join(HOMETRASH, 'info', self.name+'.trashinfo')) # # Tests for files on some other volume than the user's home directory. # # What we need to stub: # * plat_other.get_dev (to make sure the file will not be on the home dir dev) # * os.path.ismount (to make our topdir look like a top dir) # class TestExtVol(unittest.TestCase): def setUp(self): self.trashTopdir = mkdtemp(prefix='s2t') if PY3: trashTopdir_b = os.fsencode(self.trashTopdir) else: trashTopdir_b = self.trashTopdir self.fileName = 'test.txt' self.filePath = op.join(self.trashTopdir, self.fileName) touch(self.filePath) self.old_ismount = old_ismount = op.ismount self.old_getdev = send2trash.plat_other.get_dev def s_getdev(path): from send2trash.plat_other import is_parent st = os.lstat(path) if is_parent(self.trashTopdir, path): return 'dev' return st.st_dev def s_ismount(path): if op.realpath(path) in (op.realpath(self.trashTopdir), op.realpath(trashTopdir_b)): return True return old_ismount(path) send2trash.plat_other.os.path.ismount = s_ismount send2trash.plat_other.get_dev = s_getdev def tearDown(self): send2trash.plat_other.get_dev = self.old_getdev send2trash.plat_other.os.path.ismount = self.old_ismount shutil.rmtree(self.trashTopdir) class TestTopdirTrash(TestExtVol): def setUp(self): TestExtVol.setUp(self) # Create a .Trash dir w/ a sticky bit self.trashDir = op.join(self.trashTopdir, '.Trash') os.mkdir(self.trashDir, 0o777|stat.S_ISVTX) def test_trash(self): s2t(self.filePath) self.assertFalse(op.exists(self.filePath)) self.assertTrue(op.exists(op.join(self.trashDir, str(os.getuid()), 'files', self.fileName))) self.assertTrue(op.exists(op.join(self.trashDir, str(os.getuid()), 'info', self.fileName + '.trashinfo'))) # info relative path (if another test is added, with the same fileName/Path, # then it gets renamed etc.) cfg = ConfigParser() cfg.read(op.join(self.trashDir, str(os.getuid()), 'info', self.fileName + '.trashinfo')) self.assertEqual(self.fileName, cfg.get('Trash Info', 'Path', raw=True)) # Test .Trash-UID class TestTopdirTrashFallback(TestExtVol): def test_trash(self): touch(self.filePath) s2t(self.filePath) self.assertFalse(op.exists(self.filePath)) self.assertTrue(op.exists(op.join(self.trashTopdir, '.Trash-' + str(os.getuid()), 'files', self.fileName))) # Test failure class TestTopdirFailure(TestExtVol): def setUp(self): TestExtVol.setUp(self) os.chmod(self.trashTopdir, 0o500) # not writable to induce the exception def test_trash(self): with self.assertRaises(OSError): s2t(self.filePath) self.assertTrue(op.exists(self.filePath)) def tearDown(self): os.chmod(self.trashTopdir, 0o700) # writable to allow deletion TestExtVol.tearDown(self) # Make sure it will find the mount point properly for a file in a symlinked path class TestSymlink(TestExtVol): def setUp(self): TestExtVol.setUp(self) # Use mktemp (race conditioney but no symlink equivalent) # Since is_parent uses realpath(), and our getdev uses is_parent, # this should work self.slDir = mktemp(prefix='s2t', dir=op.expanduser('~')) os.mkdir(op.join(self.trashTopdir, 'subdir'), 0o700) self.filePath = op.join(self.trashTopdir, 'subdir', self.fileName) touch(self.filePath) os.symlink(op.join(self.trashTopdir, 'subdir'), self.slDir) def test_trash(self): s2t(op.join(self.slDir, self.fileName)) self.assertFalse(op.exists(self.filePath)) self.assertTrue(op.exists(op.join(self.trashTopdir, '.Trash-' + str(os.getuid()), 'files', self.fileName))) def tearDown(self): os.remove(self.slDir) TestExtVol.tearDown(self) if __name__ == '__main__': unittest.main() send2trash-1.5.0/tests/test_plat_win.py000066400000000000000000000024601343134642000201640ustar00rootroot00000000000000# coding: utf-8 import os import sys import unittest from os import path as op from tempfile import gettempdir from send2trash import send2trash as s2t @unittest.skipIf(sys.platform != 'win32', 'Windows only') class TestLongPath(unittest.TestCase): def setUp(self): filename = 'A' * 100 self.dirname = '\\\\?\\' + os.path.join(gettempdir(), filename) self.file = os.path.join( self.dirname, filename, filename, # From there, the path is not trashable from Explorer filename, filename + '.txt') self._create_tree(self.file) def tearDown(self): try: os.remove(self.dirname) except OSError: pass def _create_tree(self, path): dirname = os.path.dirname(path) if not os.path.isdir(dirname): os.makedirs(dirname) with open(path, 'w') as writer: writer.write('Looong filename!') def test_trash_file(self): s2t(self.file) self.assertFalse(op.exists(self.file)) @unittest.skipIf( op.splitdrive(os.getcwd())[0] != op.splitdrive(gettempdir())[0], 'Cannot trash long path from other drive') def test_trash_folder(self): s2t(self.dirname) self.assertFalse(op.exists(self.dirname)) send2trash-1.5.0/tox.ini000066400000000000000000000002341343134642000151040ustar00rootroot00000000000000[tox] envlist = py27,py34,py35,py36 skip_missing_interpreters = True [testenv] commands = python setup.py test [testenv:py27] deps = configparser