mirror of
				https://github.com/arsenetar/send2trash.git
				synced 2025-09-11 18:08:16 +00:00 
			
		
		
		
	Move mac/win to subpackages & fix #64
- Move macOS and Windows implementations to sub packagese to improve organization - Fix #64 in legacy windows implementation by mapping results to standard error codes
This commit is contained in:
		
							parent
							
								
									2a88b82104
								
							
						
					
					
						commit
						d37197c4f7
					
				@ -9,9 +9,9 @@ import sys
 | 
				
			|||||||
from .exceptions import TrashPermissionError  # noqa: F401
 | 
					from .exceptions import TrashPermissionError  # noqa: F401
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if sys.platform == "darwin":
 | 
					if sys.platform == "darwin":
 | 
				
			||||||
    from .plat_osx import send2trash
 | 
					    from .mac import send2trash
 | 
				
			||||||
elif sys.platform == "win32":
 | 
					elif sys.platform == "win32":
 | 
				
			||||||
    from .plat_win import send2trash
 | 
					    from .win import send2trash
 | 
				
			||||||
else:
 | 
					else:
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        # If we can use gio, let's use it
 | 
					        # If we can use gio, let's use it
 | 
				
			||||||
 | 
				
			|||||||
@ -11,10 +11,10 @@ from sys import version_info
 | 
				
			|||||||
macos_ver = tuple(int(part) for part in mac_ver()[0].split("."))
 | 
					macos_ver = tuple(int(part) for part in mac_ver()[0].split("."))
 | 
				
			||||||
if version_info >= (3, 6) and macos_ver >= (10, 9):
 | 
					if version_info >= (3, 6) and macos_ver >= (10, 9):
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        from .plat_osx_pyobjc import send2trash
 | 
					        from .modern import send2trash
 | 
				
			||||||
    except ImportError:
 | 
					    except ImportError:
 | 
				
			||||||
        # Try to fall back to ctypes version, although likely problematic still
 | 
					        # Try to fall back to ctypes version, although likely problematic still
 | 
				
			||||||
        from .plat_osx_ctypes import send2trash
 | 
					        from .legacy import send2trash
 | 
				
			||||||
else:
 | 
					else:
 | 
				
			||||||
    # Just use the old version otherwise
 | 
					    # Just use the old version otherwise
 | 
				
			||||||
    from .plat_osx_ctypes import send2trash  # noqa: F401
 | 
					    from .legacy import send2trash  # noqa: F401
 | 
				
			||||||
@ -9,8 +9,8 @@ from __future__ import unicode_literals
 | 
				
			|||||||
from ctypes import cdll, byref, Structure, c_char, c_char_p
 | 
					from ctypes import cdll, byref, Structure, c_char, c_char_p
 | 
				
			||||||
from ctypes.util import find_library
 | 
					from ctypes.util import find_library
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .compat import binary_type
 | 
					from ..compat import binary_type
 | 
				
			||||||
from .util import preprocess_paths
 | 
					from ..util import preprocess_paths
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Foundation = cdll.LoadLibrary(find_library("Foundation"))
 | 
					Foundation = cdll.LoadLibrary(find_library("Foundation"))
 | 
				
			||||||
CoreServices = cdll.LoadLibrary(find_library("CoreServices"))
 | 
					CoreServices = cdll.LoadLibrary(find_library("CoreServices"))
 | 
				
			||||||
@ -5,8 +5,8 @@
 | 
				
			|||||||
# http://www.hardcoded.net/licenses/bsd_license
 | 
					# http://www.hardcoded.net/licenses/bsd_license
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from Foundation import NSFileManager, NSURL
 | 
					from Foundation import NSFileManager, NSURL
 | 
				
			||||||
from .compat import text_type
 | 
					from ..compat import text_type
 | 
				
			||||||
from .util import preprocess_paths
 | 
					from ..util import preprocess_paths
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_op_result(op_result):
 | 
					def check_op_result(op_result):
 | 
				
			||||||
@ -11,10 +11,10 @@ from platform import version
 | 
				
			|||||||
if int(version().split(".", 1)[0]) >= 6:
 | 
					if int(version().split(".", 1)[0]) >= 6:
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        # Attempt to use pywin32 to use IFileOperation
 | 
					        # Attempt to use pywin32 to use IFileOperation
 | 
				
			||||||
        from .plat_win_modern import send2trash
 | 
					        from .modern import send2trash
 | 
				
			||||||
    except ImportError:
 | 
					    except ImportError:
 | 
				
			||||||
        # use SHFileOperation as fallback
 | 
					        # use SHFileOperation as fallback
 | 
				
			||||||
        from .plat_win_legacy import send2trash
 | 
					        from .legacy import send2trash
 | 
				
			||||||
else:
 | 
					else:
 | 
				
			||||||
    # use SHFileOperation as fallback
 | 
					    # use SHFileOperation as fallback
 | 
				
			||||||
    from .plat_win_legacy import send2trash  # noqa: F401
 | 
					    from .legacy import send2trash  # noqa: F401
 | 
				
			||||||
@ -6,8 +6,8 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
import os.path as op
 | 
					import os.path as op
 | 
				
			||||||
from .compat import text_type
 | 
					from ..compat import text_type
 | 
				
			||||||
from .util import preprocess_paths
 | 
					from ..util import preprocess_paths
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from ctypes import (
 | 
					from ctypes import (
 | 
				
			||||||
    windll,
 | 
					    windll,
 | 
				
			||||||
@ -53,6 +53,44 @@ FOF_ALLOWUNDO = 64
 | 
				
			|||||||
FOF_NOERRORUI = 1024
 | 
					FOF_NOERRORUI = 1024
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def convert_sh_file_opt_result(result):
 | 
				
			||||||
 | 
					    # map overlapping values from SHFileOpterationW to approximate standard windows errors
 | 
				
			||||||
 | 
					    # ref https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shfileoperationw#return-value
 | 
				
			||||||
 | 
					    # ref https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
 | 
				
			||||||
 | 
					    results = {
 | 
				
			||||||
 | 
					        0x71: 0x50,  # DE_SAMEFILE -> ERROR_FILE_EXISTS
 | 
				
			||||||
 | 
					        0x72: 0x57,  # DE_MANYSRC1DEST -> ERROR_INVALID_PARAMETER
 | 
				
			||||||
 | 
					        0x73: 0x57,  # DE_DIFFDIR -> ERROR_INVALID_PARAMETER
 | 
				
			||||||
 | 
					        0x74: 0x57,  # DE_ROOTDIR -> ERROR_INVALID_PARAMETER
 | 
				
			||||||
 | 
					        0x75: 0x4C7,  # DE_OPCANCELLED -> ERROR_CANCELLED
 | 
				
			||||||
 | 
					        0x76: 0x57,  # DE_DESTSUBTREE -> ERROR_INVALID_PARAMETER
 | 
				
			||||||
 | 
					        0x78: 0x05,  # DE_ACCESSDENIEDSRC -> ERROR_ACCESS_DENIED
 | 
				
			||||||
 | 
					        0x79: 0x6F,  # DE_PATHTOODEEP -> ERROR_BUFFER_OVERFLOW
 | 
				
			||||||
 | 
					        0x7A: 0x57,  # DE_MANYDEST -> ERROR_INVALID_PARAMETER
 | 
				
			||||||
 | 
					        0x7C: 0xA1,  # DE_INVALIDFILES -> ERROR_BAD_PATHNAME
 | 
				
			||||||
 | 
					        0x7D: 0x57,  # DE_DESTSAMETREE -> ERROR_INVALID_PARAMETER
 | 
				
			||||||
 | 
					        0x7E: 0xB7,  # DE_FLDDESTISFILE -> ERROR_ALREADY_EXISTS
 | 
				
			||||||
 | 
					        0x80: 0xB7,  # DE_FILEDESTISFLD -> ERROR_ALREADY_EXISTS
 | 
				
			||||||
 | 
					        0x81: 0x6F,  # DE_FILENAMETOOLONG -> ERROR_BUFFER_OVERFLOW
 | 
				
			||||||
 | 
					        0x82: 0x13,  # DE_DEST_IS_CDROM -> ERROR_WRITE_PROTECT
 | 
				
			||||||
 | 
					        0x83: 0x13,  # DE_DEST_IS_DVD -> ERROR_WRITE_PROTECT
 | 
				
			||||||
 | 
					        0x84: 0x6F9,  # DE_DEST_IS_CDRECORD -> ERROR_UNRECOGNIZED_MEDIA
 | 
				
			||||||
 | 
					        0x85: 0xDF,  # DE_FILE_TOO_LARGE -> ERROR_FILE_TOO_LARGE
 | 
				
			||||||
 | 
					        0x86: 0x13,  # DE_SRC_IS_CDROM -> ERROR_WRITE_PROTECT
 | 
				
			||||||
 | 
					        0x87: 0x13,  # DE_SRC_IS_DVD -> ERROR_WRITE_PROTECT
 | 
				
			||||||
 | 
					        0x88: 0x6F9,  # DE_SRC_IS_CDRECORD -> ERROR_UNRECOGNIZED_MEDIA
 | 
				
			||||||
 | 
					        0xB7: 0x6F,  # DE_ERROR_MAX -> ERROR_BUFFER_OVERFLOW
 | 
				
			||||||
 | 
					        0x402: 0xA1,  # UNKNOWN -> ERROR_BAD_PATHNAME
 | 
				
			||||||
 | 
					        0x10000: 0x1D,  # ERRORONDEST -> ERROR_WRITE_FAULT
 | 
				
			||||||
 | 
					        0x10074: 0x57,  # DE_ROOTDIR | ERRORONDEST -> ERROR_INVALID_PARAMETER
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if result in results.keys():
 | 
				
			||||||
 | 
					        return results[result]
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        return result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def prefix_and_path(path):
 | 
					def prefix_and_path(path):
 | 
				
			||||||
    r"""Guess the long-path prefix based on the kind of *path*.
 | 
					    r"""Guess the long-path prefix based on the kind of *path*.
 | 
				
			||||||
    Local paths (C:\folder\file.ext) and UNC names (\\server\folder\file.ext)
 | 
					    Local paths (C:\folder\file.ext) and UNC names (\\server\folder\file.ext)
 | 
				
			||||||
@ -141,4 +179,5 @@ def send2trash(paths):
 | 
				
			|||||||
    fileop.lpszProgressTitle = None
 | 
					    fileop.lpszProgressTitle = None
 | 
				
			||||||
    result = SHFileOperationW(byref(fileop))
 | 
					    result = SHFileOperationW(byref(fileop))
 | 
				
			||||||
    if result:
 | 
					    if result:
 | 
				
			||||||
        raise WindowsError(result, FormatError(result), paths)
 | 
					        error = convert_sh_file_opt_result(result)
 | 
				
			||||||
 | 
					        raise WindowsError(None, FormatError(error), paths, error)
 | 
				
			||||||
@ -6,8 +6,8 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
import os.path as op
 | 
					import os.path as op
 | 
				
			||||||
from .compat import text_type
 | 
					from ..compat import text_type
 | 
				
			||||||
from .util import preprocess_paths
 | 
					from ..util import preprocess_paths
 | 
				
			||||||
from platform import version
 | 
					from platform import version
 | 
				
			||||||
import pythoncom
 | 
					import pythoncom
 | 
				
			||||||
import pywintypes
 | 
					import pywintypes
 | 
				
			||||||
@ -27,7 +27,10 @@ def send2trash(paths):
 | 
				
			|||||||
    pythoncom.CoInitialize()
 | 
					    pythoncom.CoInitialize()
 | 
				
			||||||
    # create instance of file operation object
 | 
					    # create instance of file operation object
 | 
				
			||||||
    fileop = pythoncom.CoCreateInstance(
 | 
					    fileop = pythoncom.CoCreateInstance(
 | 
				
			||||||
        shell.CLSID_FileOperation, None, pythoncom.CLSCTX_ALL, shell.IID_IFileOperation,
 | 
					        shell.CLSID_FileOperation,
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					        pythoncom.CLSCTX_ALL,
 | 
				
			||||||
 | 
					        shell.IID_IFileOperation,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    # default flags to use
 | 
					    # default flags to use
 | 
				
			||||||
    flags = shellcon.FOF_NOCONFIRMATION | shellcon.FOF_NOERRORUI | shellcon.FOF_SILENT | shellcon.FOFX_EARLYFAILURE
 | 
					    flags = shellcon.FOF_NOCONFIRMATION | shellcon.FOF_NOERRORUI | shellcon.FOF_SILENT | shellcon.FOFX_EARLYFAILURE
 | 
				
			||||||
@ -50,7 +50,9 @@ def testfiles():
 | 
				
			|||||||
    files = list(
 | 
					    files = list(
 | 
				
			||||||
        map(
 | 
					        map(
 | 
				
			||||||
            lambda index: NamedTemporaryFile(
 | 
					            lambda index: NamedTemporaryFile(
 | 
				
			||||||
                dir=op.expanduser("~"), prefix="send2trash_test{}".format(index), delete=False,
 | 
					                dir=op.expanduser("~"),
 | 
				
			||||||
 | 
					                prefix="send2trash_test{}".format(index),
 | 
				
			||||||
 | 
					                delete=False,
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            range(10),
 | 
					            range(10),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
@ -129,7 +131,10 @@ class ExtVol:
 | 
				
			|||||||
            return st.st_dev
 | 
					            return st.st_dev
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def s_ismount(path):
 | 
					        def s_ismount(path):
 | 
				
			||||||
            if op.realpath(path) in (op.realpath(self.trash_topdir), op.realpath(self.trash_topdir_b),):
 | 
					            if op.realpath(path) in (
 | 
				
			||||||
 | 
					                op.realpath(self.trash_topdir),
 | 
				
			||||||
 | 
					                op.realpath(self.trash_topdir_b),
 | 
				
			||||||
 | 
					            ):
 | 
				
			||||||
                return True
 | 
					                return True
 | 
				
			||||||
            return old_ismount(path)
 | 
					            return old_ismount(path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -163,7 +168,17 @@ def test_trash_topdir(gen_ext_vol):
 | 
				
			|||||||
    s2t(gen_ext_vol[2])
 | 
					    s2t(gen_ext_vol[2])
 | 
				
			||||||
    assert op.exists(gen_ext_vol[2]) is False
 | 
					    assert op.exists(gen_ext_vol[2]) is False
 | 
				
			||||||
    assert op.exists(op.join(trash_dir, str(os.getuid()), "files", gen_ext_vol[1])) is True
 | 
					    assert op.exists(op.join(trash_dir, str(os.getuid()), "files", gen_ext_vol[1])) is True
 | 
				
			||||||
    assert op.exists(op.join(trash_dir, str(os.getuid()), "info", gen_ext_vol[1] + INFO_SUFFIX,)) is True
 | 
					    assert (
 | 
				
			||||||
 | 
					        op.exists(
 | 
				
			||||||
 | 
					            op.join(
 | 
				
			||||||
 | 
					                trash_dir,
 | 
				
			||||||
 | 
					                str(os.getuid()),
 | 
				
			||||||
 | 
					                "info",
 | 
				
			||||||
 | 
					                gen_ext_vol[1] + INFO_SUFFIX,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        is True
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    # info relative path (if another test is added, with the same fileName/Path,
 | 
					    # info relative path (if another test is added, with the same fileName/Path,
 | 
				
			||||||
    # then it gets renamed etc.)
 | 
					    # then it gets renamed etc.)
 | 
				
			||||||
    cfg = ConfigParser()
 | 
					    cfg = ConfigParser()
 | 
				
			||||||
@ -175,7 +190,15 @@ def test_trash_topdir_fallback(gen_ext_vol):
 | 
				
			|||||||
    s2t(gen_ext_vol[2])
 | 
					    s2t(gen_ext_vol[2])
 | 
				
			||||||
    assert op.exists(gen_ext_vol[2]) is False
 | 
					    assert op.exists(gen_ext_vol[2]) is False
 | 
				
			||||||
    assert (
 | 
					    assert (
 | 
				
			||||||
        op.exists(op.join(gen_ext_vol[0].trash_topdir, ".Trash-" + str(os.getuid()), "files", gen_ext_vol[1],)) is True
 | 
					        op.exists(
 | 
				
			||||||
 | 
					            op.join(
 | 
				
			||||||
 | 
					                gen_ext_vol[0].trash_topdir,
 | 
				
			||||||
 | 
					                ".Trash-" + str(os.getuid()),
 | 
				
			||||||
 | 
					                "files",
 | 
				
			||||||
 | 
					                gen_ext_vol[1],
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        is True
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -195,6 +218,14 @@ def test_trash_symlink(gen_ext_vol):
 | 
				
			|||||||
    s2t(op.join(sl_dir, gen_ext_vol[1]))
 | 
					    s2t(op.join(sl_dir, gen_ext_vol[1]))
 | 
				
			||||||
    assert op.exists(file_path) is False
 | 
					    assert op.exists(file_path) is False
 | 
				
			||||||
    assert (
 | 
					    assert (
 | 
				
			||||||
        op.exists(op.join(gen_ext_vol[0].trash_topdir, ".Trash-" + str(os.getuid()), "files", gen_ext_vol[1],)) is True
 | 
					        op.exists(
 | 
				
			||||||
 | 
					            op.join(
 | 
				
			||||||
 | 
					                gen_ext_vol[0].trash_topdir,
 | 
				
			||||||
 | 
					                ".Trash-" + str(os.getuid()),
 | 
				
			||||||
 | 
					                "files",
 | 
				
			||||||
 | 
					                gen_ext_vol[1],
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        is True
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    os.remove(sl_dir)
 | 
					    os.remove(sl_dir)
 | 
				
			||||||
 | 
				
			|||||||
@ -9,8 +9,8 @@ from send2trash import send2trash as s2t
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# import the two versions as well as the "automatic" version
 | 
					# import the two versions as well as the "automatic" version
 | 
				
			||||||
if sys.platform == "win32":
 | 
					if sys.platform == "win32":
 | 
				
			||||||
    from send2trash.plat_win_modern import send2trash as s2t_modern
 | 
					    from send2trash.win.modern import send2trash as s2t_modern
 | 
				
			||||||
    from send2trash.plat_win_legacy import send2trash as s2t_legacy
 | 
					    from send2trash.win.legacy import send2trash as s2t_legacy
 | 
				
			||||||
else:
 | 
					else:
 | 
				
			||||||
    pytest.skip("Skipping windows-only tests", allow_module_level=True)
 | 
					    pytest.skip("Skipping windows-only tests", allow_module_level=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user