mirror of
https://github.com/arsenetar/send2trash.git
synced 2024-11-01 14:25:59 +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
|
||||
|
||||
if sys.platform == "darwin":
|
||||
from .plat_osx import send2trash
|
||||
from .mac import send2trash
|
||||
elif sys.platform == "win32":
|
||||
from .plat_win import send2trash
|
||||
from .win import send2trash
|
||||
else:
|
||||
try:
|
||||
# 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("."))
|
||||
if version_info >= (3, 6) and macos_ver >= (10, 9):
|
||||
try:
|
||||
from .plat_osx_pyobjc import send2trash
|
||||
from .modern import send2trash
|
||||
except ImportError:
|
||||
# Try to fall back to ctypes version, although likely problematic still
|
||||
from .plat_osx_ctypes import send2trash
|
||||
from .legacy import send2trash
|
||||
else:
|
||||
# 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.util import find_library
|
||||
|
||||
from .compat import binary_type
|
||||
from .util import preprocess_paths
|
||||
from ..compat import binary_type
|
||||
from ..util import preprocess_paths
|
||||
|
||||
Foundation = cdll.LoadLibrary(find_library("Foundation"))
|
||||
CoreServices = cdll.LoadLibrary(find_library("CoreServices"))
|
@ -5,8 +5,8 @@
|
||||
# http://www.hardcoded.net/licenses/bsd_license
|
||||
|
||||
from Foundation import NSFileManager, NSURL
|
||||
from .compat import text_type
|
||||
from .util import preprocess_paths
|
||||
from ..compat import text_type
|
||||
from ..util import preprocess_paths
|
||||
|
||||
|
||||
def check_op_result(op_result):
|
@ -11,10 +11,10 @@ from platform import version
|
||||
if int(version().split(".", 1)[0]) >= 6:
|
||||
try:
|
||||
# Attempt to use pywin32 to use IFileOperation
|
||||
from .plat_win_modern import send2trash
|
||||
from .modern import send2trash
|
||||
except ImportError:
|
||||
# use SHFileOperation as fallback
|
||||
from .plat_win_legacy import send2trash
|
||||
from .legacy import send2trash
|
||||
else:
|
||||
# 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
|
||||
import os.path as op
|
||||
from .compat import text_type
|
||||
from .util import preprocess_paths
|
||||
from ..compat import text_type
|
||||
from ..util import preprocess_paths
|
||||
|
||||
from ctypes import (
|
||||
windll,
|
||||
@ -53,6 +53,44 @@ FOF_ALLOWUNDO = 64
|
||||
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):
|
||||
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)
|
||||
@ -141,4 +179,5 @@ def send2trash(paths):
|
||||
fileop.lpszProgressTitle = None
|
||||
result = SHFileOperationW(byref(fileop))
|
||||
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
|
||||
import os.path as op
|
||||
from .compat import text_type
|
||||
from .util import preprocess_paths
|
||||
from ..compat import text_type
|
||||
from ..util import preprocess_paths
|
||||
from platform import version
|
||||
import pythoncom
|
||||
import pywintypes
|
||||
@ -27,7 +27,10 @@ def send2trash(paths):
|
||||
pythoncom.CoInitialize()
|
||||
# create instance of file operation object
|
||||
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
|
||||
flags = shellcon.FOF_NOCONFIRMATION | shellcon.FOF_NOERRORUI | shellcon.FOF_SILENT | shellcon.FOFX_EARLYFAILURE
|
@ -50,7 +50,9 @@ def testfiles():
|
||||
files = list(
|
||||
map(
|
||||
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),
|
||||
)
|
||||
@ -129,7 +131,10 @@ class ExtVol:
|
||||
return st.st_dev
|
||||
|
||||
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 old_ismount(path)
|
||||
|
||||
@ -163,7 +168,17 @@ def test_trash_topdir(gen_ext_vol):
|
||||
s2t(gen_ext_vol[2])
|
||||
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()), "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,
|
||||
# then it gets renamed etc.)
|
||||
cfg = ConfigParser()
|
||||
@ -175,7 +190,15 @@ def test_trash_topdir_fallback(gen_ext_vol):
|
||||
s2t(gen_ext_vol[2])
|
||||
assert op.exists(gen_ext_vol[2]) is False
|
||||
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]))
|
||||
assert op.exists(file_path) is False
|
||||
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)
|
||||
|
@ -9,8 +9,8 @@ from send2trash import send2trash as s2t
|
||||
|
||||
# import the two versions as well as the "automatic" version
|
||||
if sys.platform == "win32":
|
||||
from send2trash.plat_win_modern import send2trash as s2t_modern
|
||||
from send2trash.plat_win_legacy import send2trash as s2t_legacy
|
||||
from send2trash.win.modern import send2trash as s2t_modern
|
||||
from send2trash.win.legacy import send2trash as s2t_legacy
|
||||
else:
|
||||
pytest.skip("Skipping windows-only tests", allow_module_level=True)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user