diff --git a/.hgtags b/.hgtags index 6938dd7..a012386 100644 --- a/.hgtags +++ b/.hgtags @@ -1,2 +1,3 @@ 48c2103380f5e7deca49364f44fb31ded9942bb7 1.0.0 a7e04d8e47e161daaa1f031a7c1e98c52fa269ac 1.0.1 +de5f43fcce5e776eb28306951939afb9946cbd3d 1.1.0 diff --git a/CHANGES b/CHANGES index 754dbe9..0b2be57 100644 --- a/CHANGES +++ b/CHANGES @@ -1,14 +1,22 @@ +Changes +======= + +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 \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..d935839 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include CHANGES \ No newline at end of file diff --git a/README b/README index 8dc302d..0e780d5 100644 --- a/README +++ b/README @@ -2,23 +2,19 @@ Send2Trash -- Send files to trash on all platforms ================================================== -This is a Python 2 package. The Python 3 package is at http://pypi.python.org/pypi/Send2Trash3k . +This is a Python 3 package. The Python 2 package is at http://pypi.python.org/pypi/Send2Trash . 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, it moves the file to the first folder it finds that looks like a trash (so far, it's known to work on Ubuntu). +``ctypes`` is used to access native libraries, so no compilation is necessary. + Installation ------------ Download the source from http://hg.hardcoded.net/send2trash and install it with:: ->>> sudo python setup.py install - -On Windows, you'll need Visual Studio 2008 to compile it. Note that the install you'll get will not be a "universal" package. If you install it on OS X, only the "osx" module will be compiled, and if you install it on Windows, only the "win" module will be compiled. - -To have a cross-platform package you can ship around, you'll have compile the package on both platforms and merge the results so that both compiled modules are in the same package. - -There's no package available on PyPI because packages produced by setuptools/distribute are all crappy (god I hate packaging). That's, I think, because I look at ``sys.platform`` to determine which of the extension module (if any) has to be built. However, when I create a ``sdist``, only the module of the platform I build it on is included in the source package. I don't know how to go around that and configuring that ``setup()`` function is hell. Just download the source from the repo. +>>> python setup.py install Usage ----- diff --git a/modules/send2trash_osx.c b/modules/send2trash_osx.c deleted file mode 100644 index c995f2c..0000000 --- a/modules/send2trash_osx.c +++ /dev/null @@ -1,45 +0,0 @@ -/* Copyright 2010 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 -*/ - -#define PY_SSIZE_T_CLEAN -#include "Python.h" -#include - -static PyObject* send2trash_osx_send(PyObject *self, PyObject *args) -{ - UInt8 *utf8_chars; - FSRef fp; - OSStatus op_result; - - if (!PyArg_ParseTuple(args, "es", "utf-8", &utf8_chars)) { - return NULL; - } - - FSPathMakeRefWithOptions(utf8_chars, kFSPathMakeRefDoNotFollowLeafSymlink, &fp, NULL); - op_result = FSMoveObjectToTrashSync(&fp, NULL, kFSFileOperationDefaultOptions); - PyMem_Free(utf8_chars); - if (op_result != noErr) { - PyErr_SetString(PyExc_OSError, GetMacOSStatusCommentString(op_result)); - return NULL; - } - return Py_None; -} - -static PyMethodDef TrashMethods[] = { - {"send", send2trash_osx_send, METH_VARARGS, ""}, - {NULL, NULL, 0, NULL} -}; - -PyMODINIT_FUNC -init_send2trash_osx(void) -{ - PyObject *m = Py_InitModule("_send2trash_osx", TrashMethods); - if (m == NULL) { - return; - } -} - diff --git a/modules/send2trash_win.c b/modules/send2trash_win.c deleted file mode 100644 index e3ca11a..0000000 --- a/modules/send2trash_win.c +++ /dev/null @@ -1,68 +0,0 @@ -/* Copyright 2010 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 -*/ - -#define PY_SSIZE_T_CLEAN -#include "Python.h" - -#define WINDOWS_LEAN_AND_MEAN -#include "windows.h" -#include "shlobj.h" - -/* WARNING: If the filepath is not fully qualified, Windows deletes the file - rather than sending it to trash. - */ - -static PyObject* send2trash_win_send(PyObject *self, PyObject *args) -{ - SHFILEOPSTRUCTW op; - PyObject *filepath; - Py_ssize_t len; - WCHAR filechars[MAX_PATH+1]; - int r; - - if (!PyArg_ParseTuple(args, "O", &filepath)) { - return NULL; - } - - if (!PyUnicode_Check(filepath)) { - PyErr_SetString(PyExc_TypeError, "Unicode filename required"); - return NULL; - } - - len = PyUnicode_GET_SIZE(filepath); - memcpy(filechars, PyUnicode_AsUnicode(filepath), sizeof(WCHAR)*len); - filechars[len] = '\0'; - filechars[len+1] = '\0'; - - op.hwnd = 0; - op.wFunc = FO_DELETE; - op.pFrom = (LPCWSTR)&filechars; - op.pTo = NULL; - op.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT; - r = SHFileOperationW(&op); - - if (r != 0) { - PyErr_Format(PyExc_OSError, "Couldn't perform operation. Error code: %d", r); - return NULL; - } - - return Py_None; -} - -static PyMethodDef TrashMethods[] = { - {"send", send2trash_win_send, METH_VARARGS, ""}, - {NULL, NULL, 0, NULL} -}; - -PyMODINIT_FUNC -init_send2trash_win(void) -{ - PyObject *m = Py_InitModule("_send2trash_win", TrashMethods); - if (m == NULL) { - return; - } -} \ No newline at end of file diff --git a/send2trash/__init__.py b/send2trash/__init__.py index 9457f4a..d6fa7f0 100644 --- a/send2trash/__init__.py +++ b/send2trash/__init__.py @@ -7,8 +7,8 @@ import sys if sys.platform == 'darwin': - from plat_osx import send2trash + from .plat_osx import send2trash elif sys.platform == 'win32': - from plat_win import send2trash + from .plat_win import send2trash else: - from plat_other import send2trash + from .plat_other import send2trash diff --git a/send2trash/plat_osx.py b/send2trash/plat_osx.py index 3b99f4f..ba58b6f 100644 --- a/send2trash/plat_osx.py +++ b/send2trash/plat_osx.py @@ -4,9 +4,41 @@ # which should be included with this package. The terms are also available at # http://www.hardcoded.net/licenses/bsd_license -import _send2trash_osx +from ctypes import cdll, byref, Structure, c_char, c_char_p +from ctypes.util import find_library + +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, unicode): - path = unicode(path, 'utf-8') - _send2trash_osx.send(path) + if not isinstance(path, bytes): + 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) diff --git a/send2trash/plat_other.py b/send2trash/plat_other.py index dd6e6d1..9ca7cc7 100644 --- a/send2trash/plat_other.py +++ b/send2trash/plat_other.py @@ -4,7 +4,7 @@ # 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 + import sys import os import os.path as op @@ -61,8 +61,8 @@ def move_without_conflict(src, dst): os.rename(src, destpath) def send2trash(path): - if not isinstance(path, unicode): - path = unicode(path, sys.getfilesystemencoding()) + if not isinstance(path, str): + path = str(path, sys.getfilesystemencoding()) try: move_without_conflict(path, TRASH_PATH) except OSError: diff --git a/send2trash/plat_win.py b/send2trash/plat_win.py index 3aada5f..d3607be 100644 --- a/send2trash/plat_win.py +++ b/send2trash/plat_win.py @@ -4,12 +4,52 @@ # which should be included with this package. The terms are also available at # http://www.hardcoded.net/licenses/bsd_license +from ctypes import windll, Structure, byref, c_uint +from ctypes.wintypes import HWND, UINT, LPCWSTR, BOOL import os.path as op -import _send2trash_win + +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 send2trash(path): - if not isinstance(path, unicode): - path = unicode(path, 'mbcs') + if not isinstance(path, str): + path = str(path, 'mbcs') if not op.isabs(path): path = op.abspath(path) - _send2trash_win.send(path) + fileop = SHFILEOPSTRUCTW() + fileop.hwnd = 0 + fileop.wFunc = FO_DELETE + fileop.pFrom = LPCWSTR(path + '\0') + 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: + msg = "Couldn't perform operation. Error code: %d" % result + raise OSError(msg) + diff --git a/setup.py b/setup.py index 3e427d3..f6b309c 100644 --- a/setup.py +++ b/setup.py @@ -2,22 +2,6 @@ import sys import os.path as op from setuptools import setup -from distutils.extension import Extension - -exts = [] - -if sys.platform == 'darwin': - exts.append(Extension( - '_send2trash_osx', - [op.join('modules', 'send2trash_osx.c')], - extra_link_args=['-framework', 'CoreServices'], - )) -if sys.platform == 'win32': - exts.append(Extension( - '_send2trash_win', - [op.join('modules', 'send2trash_win.c')], - extra_link_args = ['shell32.lib'], - )) CLASSIFIERS = [ 'Development Status :: 5 - Production/Stable', @@ -26,24 +10,23 @@ CLASSIFIERS = [ 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', - 'Programming Language :: Python :: 2', - 'Programming Language :: Objective C', - 'Programming Language :: C', + 'Programming Language :: Python :: 3', 'Topic :: Desktop Environment :: File Managers', ] +LONG_DESCRIPTION = open('README', 'rt').read() + '\n\n' + open('CHANGES', 'rt').read() + setup( - name='Send2Trash', - version='1.0.2', + name='Send2Trash3k', + version='1.1.0', author='Hardcoded Software', author_email='hsoft@hardcoded.net', packages=['send2trash'], scripts=[], - ext_modules = exts, url='http://hg.hardcoded.net/send2trash/', license='BSD License', description='Send file to trash natively under Mac OS X, Windows and Linux.', - long_description=open('README').read(), + long_description=LONG_DESCRIPTION, classifiers=CLASSIFIERS, zip_safe=False, ) \ No newline at end of file