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