Skip to content

Commit

Permalink
Add initial pyobjc version for macOS
Browse files Browse the repository at this point in the history
This is to help with issue #51.  Will not help in the case of python 2 or
older python 3 version < 3.6.
  • Loading branch information
arsenetar committed Apr 14, 2021
1 parent 10c7693 commit 530e9b4
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 50 deletions.
64 changes: 14 additions & 50 deletions send2trash/plat_osx.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,17 @@
# 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(paths):
if not isinstance(paths, list):
paths = [paths]
paths = [
path.encode("utf-8") if not isinstance(path, binary_type) else path
for path in paths
]
for path in paths:
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)
from platform import mac_ver
from sys import version_info

# If macOS is 11.0 or newer try to use the pyobjc version to get around #51
# NOTE: pyobjc only supports python >= 3.6
if version_info >= (3, 6) and int(mac_ver()[0].split(".", 1)[0]) >= 11:
try:
from .plat_osx_pyobjc import send2trash
except ImportError:
# Try to fall back to ctypes version, although likely problematic still
from .plat_osx_ctypes import send2trash
else:
# Just use the old version otherwise
from .plat_osx_ctypes import send2trash # noqa: F401
56 changes: 56 additions & 0 deletions send2trash/plat_osx_ctypes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# 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(paths):
if not isinstance(paths, list):
paths = [paths]
paths = [
path.encode("utf-8") if not isinstance(path, binary_type) else path
for path in paths
]
for path in paths:
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)
29 changes: 29 additions & 0 deletions send2trash/plat_osx_pyobjc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# 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 Foundation import NSFileManager, NSURL
from .compat import text_type


def check_op_result(op_result):
# First value will be false on failure
if not op_result[0]:
# Error is in third value, localized failure reason matchs ctypes version
raise OSError(op_result[2].localizedFailureReason())


def send2trash(paths):
if not isinstance(paths, list):
paths = [paths]
paths = [
path.decode("utf-8") if not isinstance(path, text_type) else path
for path in paths
]
for path in paths:
file_url = NSURL.fileURLWithPath_(path)
fm = NSFileManager.defaultManager()
op_result = fm.trashItemAtURL_resultingItemURL_error_(file_url, None, None)
check_op_result(op_result)
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ deps =
flake8
pytest
pywin32; sys_platform == 'win32'
pyobjc-framework-Cocoa; sys_platform == 'darwin'
commands =
flake8
pytest
Expand Down

0 comments on commit 530e9b4

Please sign in to comment.