mirror of
https://github.com/arsenetar/send2trash.git
synced 2025-05-09 18:29:49 +00:00
Compare commits
No commits in common. "d0e4890a4d3338d95969ff86549d883844861304" and "69a82a5162c0828bb11e88986df27c04eb8d6866" have entirely different histories.
d0e4890a4d
...
69a82a5162
@ -1,6 +0,0 @@
|
|||||||
[build-system]
|
|
||||||
requires = ["setuptools >= 40.6.0", "wheel"]
|
|
||||||
build-backend = "setuptools.build_meta"
|
|
||||||
|
|
||||||
[tool.black]
|
|
||||||
line-length = 120
|
|
@ -35,7 +35,9 @@ class FileOperationProgressSink(DesignatedWrapPolicy):
|
|||||||
# Can detect cases where to stop via flags and condition below, however the operation
|
# Can detect cases where to stop via flags and condition below, however the operation
|
||||||
# does not actual stop, we can resort to raising an exception as that does stop things
|
# does not actual stop, we can resort to raising an exception as that does stop things
|
||||||
# but that may need some additional considerations before implementing.
|
# but that may need some additional considerations before implementing.
|
||||||
return 0 if flags & shellcon.TSF_DELETE_RECYCLE_IF_POSSIBLE else 0x80004005 # S_OK, or E_FAIL
|
return (
|
||||||
|
0 if flags & shellcon.TSF_DELETE_RECYCLE_IF_POSSIBLE else 0x80004005
|
||||||
|
) # S_OK, or E_FAIL
|
||||||
|
|
||||||
def PostDeleteItem(self, flags, item, hrDelete, newlyCreated):
|
def PostDeleteItem(self, flags, item, hrDelete, newlyCreated):
|
||||||
if newlyCreated:
|
if newlyCreated:
|
||||||
@ -56,20 +58,31 @@ class FileOperationProgressSink(DesignatedWrapPolicy):
|
|||||||
def PreMoveItem(self, Flags, Item, DestinationFolder, NewName):
|
def PreMoveItem(self, Flags, Item, DestinationFolder, NewName):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def PostMoveItem(self, Flags, Item, DestinationFolder, NewName, hrMove, NewlyCreated):
|
def PostMoveItem(
|
||||||
|
self, Flags, Item, DestinationFolder, NewName, hrMove, NewlyCreated
|
||||||
|
):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def PreCopyItem(self, Flags, Item, DestinationFolder, NewName):
|
def PreCopyItem(self, Flags, Item, DestinationFolder, NewName):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def PostCopyItem(self, Flags, Item, DestinationFolder, NewName, hrCopy, NewlyCreated):
|
def PostCopyItem(
|
||||||
|
self, Flags, Item, DestinationFolder, NewName, hrCopy, NewlyCreated
|
||||||
|
):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def PreNewItem(self, Flags, DestinationFolder, NewName):
|
def PreNewItem(self, Flags, DestinationFolder, NewName):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def PostNewItem(
|
def PostNewItem(
|
||||||
self, Flags, DestinationFolder, NewName, TemplateName, FileAttributes, hrNew, NewItem,
|
self,
|
||||||
|
Flags,
|
||||||
|
DestinationFolder,
|
||||||
|
NewName,
|
||||||
|
TemplateName,
|
||||||
|
FileAttributes,
|
||||||
|
hrNew,
|
||||||
|
NewItem,
|
||||||
):
|
):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -87,4 +100,6 @@ class FileOperationProgressSink(DesignatedWrapPolicy):
|
|||||||
|
|
||||||
|
|
||||||
def CreateSink():
|
def CreateSink():
|
||||||
return pythoncom.WrapObject(FileOperationProgressSink(), shell.IID_IFileOperationProgressSink)
|
return pythoncom.WrapObject(
|
||||||
|
FileOperationProgressSink(), shell.IID_IFileOperationProgressSink
|
||||||
|
)
|
||||||
|
@ -14,20 +14,20 @@ from send2trash import send2trash
|
|||||||
|
|
||||||
|
|
||||||
def main(args=None):
|
def main(args=None):
|
||||||
parser = ArgumentParser(description="Tool to send files to trash")
|
parser = ArgumentParser(description='Tool to send files to trash')
|
||||||
parser.add_argument("files", nargs="+")
|
parser.add_argument('files', nargs='+')
|
||||||
parser.add_argument("-v", "--verbose", action="store_true", help="Print deleted files")
|
parser.add_argument('-v', '--verbose', action='store_true', help='Print deleted files')
|
||||||
args = parser.parse_args(args)
|
args = parser.parse_args(args)
|
||||||
|
|
||||||
for filename in args.files:
|
for filename in args.files:
|
||||||
try:
|
try:
|
||||||
send2trash(filename)
|
send2trash(filename)
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print("Trashed «" + filename + "»")
|
print('Trashed «' + filename + '»')
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
print(str(e), file=sys.stderr)
|
print(str(e), file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
@ -42,7 +42,10 @@ def check_op_result(op_result):
|
|||||||
|
|
||||||
def send2trash(paths):
|
def send2trash(paths):
|
||||||
paths = preprocess_paths(paths)
|
paths = preprocess_paths(paths)
|
||||||
paths = [path.encode("utf-8") if not isinstance(path, binary_type) else path for path in paths]
|
paths = [
|
||||||
|
path.encode("utf-8") if not isinstance(path, binary_type) else path
|
||||||
|
for path in paths
|
||||||
|
]
|
||||||
for path in paths:
|
for path in paths:
|
||||||
fp = FSRef()
|
fp = FSRef()
|
||||||
opts = kFSPathMakeRefDoNotFollowLeafSymlink
|
opts = kFSPathMakeRefDoNotFollowLeafSymlink
|
||||||
|
@ -18,7 +18,10 @@ def check_op_result(op_result):
|
|||||||
|
|
||||||
def send2trash(paths):
|
def send2trash(paths):
|
||||||
paths = preprocess_paths(paths)
|
paths = preprocess_paths(paths)
|
||||||
paths = [path.decode("utf-8") if not isinstance(path, text_type) else path for path in paths]
|
paths = [
|
||||||
|
path.decode("utf-8") if not isinstance(path, text_type) else path
|
||||||
|
for path in paths
|
||||||
|
]
|
||||||
for path in paths:
|
for path in paths:
|
||||||
file_url = NSURL.fileURLWithPath_(path)
|
file_url = NSURL.fileURLWithPath_(path)
|
||||||
fm = NSFileManager.defaultManager()
|
fm = NSFileManager.defaultManager()
|
||||||
|
@ -103,7 +103,9 @@ def trash_move(src, dst, topdir=None):
|
|||||||
|
|
||||||
counter = 0
|
counter = 0
|
||||||
destname = filename
|
destname = filename
|
||||||
while op.exists(op.join(filespath, destname)) or op.exists(op.join(infopath, destname + INFO_SUFFIX)):
|
while op.exists(op.join(filespath, destname)) or op.exists(
|
||||||
|
op.join(infopath, destname + INFO_SUFFIX)
|
||||||
|
):
|
||||||
counter += 1
|
counter += 1
|
||||||
destname = base_name + b" " + text_type(counter).encode("ascii") + ext
|
destname = base_name + b" " + text_type(counter).encode("ascii") + ext
|
||||||
|
|
||||||
|
@ -105,7 +105,10 @@ def get_short_path_name(long_name):
|
|||||||
def send2trash(paths):
|
def send2trash(paths):
|
||||||
paths = preprocess_paths(paths)
|
paths = preprocess_paths(paths)
|
||||||
# convert data type
|
# convert data type
|
||||||
paths = [text_type(path, "mbcs") if not isinstance(path, text_type) else path for path in paths]
|
paths = [
|
||||||
|
text_type(path, "mbcs") if not isinstance(path, text_type) else path
|
||||||
|
for path in paths
|
||||||
|
]
|
||||||
# convert to full paths
|
# convert to full paths
|
||||||
paths = [op.abspath(path) if not op.isabs(path) else path for path in paths]
|
paths = [op.abspath(path) if not op.isabs(path) else path for path in paths]
|
||||||
# get short path to handle path length issues
|
# get short path to handle path length issues
|
||||||
|
@ -18,7 +18,10 @@ from .IFileOperationProgressSink import CreateSink
|
|||||||
def send2trash(paths):
|
def send2trash(paths):
|
||||||
paths = preprocess_paths(paths)
|
paths = preprocess_paths(paths)
|
||||||
# convert data type
|
# convert data type
|
||||||
paths = [text_type(path, "mbcs") if not isinstance(path, text_type) else path for path in paths]
|
paths = [
|
||||||
|
text_type(path, "mbcs") if not isinstance(path, text_type) else path
|
||||||
|
for path in paths
|
||||||
|
]
|
||||||
# convert to full paths
|
# convert to full paths
|
||||||
paths = [op.abspath(path) if not op.isabs(path) else path for path in paths]
|
paths = [op.abspath(path) if not op.isabs(path) else path for path in paths]
|
||||||
# remove the leading \\?\ if present
|
# remove the leading \\?\ if present
|
||||||
@ -30,11 +33,19 @@ def send2trash(paths):
|
|||||||
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
|
||||||
|
)
|
||||||
# determine rest of the flags based on OS version
|
# determine rest of the flags based on OS version
|
||||||
# use newer recommended flags if available
|
# use newer recommended flags if available
|
||||||
if int(version().split(".", 1)[0]) >= 8:
|
if int(version().split(".", 1)[0]) >= 8:
|
||||||
flags |= 0x20000000 | 0x00080000 # FOFX_ADDUNDORECORD win 8+ # FOFX_RECYCLEONDELETE win 8+
|
flags |= (
|
||||||
|
0x20000000 # FOFX_ADDUNDORECORD win 8+
|
||||||
|
| 0x00080000 # FOFX_RECYCLEONDELETE win 8+
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
flags |= shellcon.FOF_ALLOWUNDO
|
flags |= shellcon.FOF_ALLOWUNDO
|
||||||
# set the flags
|
# set the flags
|
||||||
|
@ -10,5 +10,7 @@ def preprocess_paths(paths):
|
|||||||
if not isinstance(paths, list):
|
if not isinstance(paths, list):
|
||||||
paths = [paths]
|
paths = [paths]
|
||||||
# Convert items such as pathlib paths to strings
|
# Convert items such as pathlib paths to strings
|
||||||
paths = [path.__fspath__() if hasattr(path, "__fspath__") else path for path in paths]
|
paths = [
|
||||||
|
path.__fspath__() if hasattr(path, "__fspath__") else path for path in paths
|
||||||
|
]
|
||||||
return paths
|
return paths
|
||||||
|
45
setup.cfg
45
setup.cfg
@ -1,45 +0,0 @@
|
|||||||
[metadata]
|
|
||||||
name = Send2Trash
|
|
||||||
version = 1.8.0
|
|
||||||
url = https://github.com/arsenetar/send2trash
|
|
||||||
project_urls =
|
|
||||||
Bug Reports = https://github.com/arsenetar/send2trash/issues
|
|
||||||
author = Andrew Senetar
|
|
||||||
author_email = arsenetar@voltaicideas.net
|
|
||||||
license = BSD License
|
|
||||||
license_files = LICENSE
|
|
||||||
description = Send file to trash natively under Mac OS X, Windows and Linux
|
|
||||||
long_description = file:README.rst
|
|
||||||
long_description_content_type = text/x-rst
|
|
||||||
classifiers =
|
|
||||||
Development Status :: 5 - Production/Stable
|
|
||||||
Intended Audience :: Developers
|
|
||||||
License :: OSI Approved :: BSD License
|
|
||||||
Operating System :: MacOS :: MacOS X
|
|
||||||
Operating System :: Microsoft :: Windows
|
|
||||||
Operating System :: POSIX
|
|
||||||
Programming Language :: Python :: 2.7
|
|
||||||
Programming Language :: Python :: 3
|
|
||||||
Programming Language :: Python :: 3.4
|
|
||||||
Programming Language :: Python :: 3.5
|
|
||||||
Programming Language :: Python :: 3.6
|
|
||||||
Programming Language :: Python :: 3.7
|
|
||||||
Programming Language :: Python :: 3.8
|
|
||||||
Programming Language :: Python :: 3.9
|
|
||||||
Programming Language :: Python :: 3.10
|
|
||||||
Topic :: Desktop Environment :: File Managers
|
|
||||||
|
|
||||||
[options]
|
|
||||||
packages = send2trash
|
|
||||||
tests_require = pytest
|
|
||||||
|
|
||||||
[options.extras_require]
|
|
||||||
win32 = pywin32; sys_platform == "win32"
|
|
||||||
objc = pyobjc-framework-Cocoa; sys_platform == "darwin"
|
|
||||||
nativeLib =
|
|
||||||
pywin32; sys_platform == "win32"
|
|
||||||
pyobjc-framework-Cocoa; sys_platform == "darwin"
|
|
||||||
|
|
||||||
[options.entry_points]
|
|
||||||
console_scripts =
|
|
||||||
send2trash = send2trash.__main__:main
|
|
49
setup.py
Normal file
49
setup.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
CLASSIFIERS = [
|
||||||
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"License :: OSI Approved :: BSD License",
|
||||||
|
"Operating System :: MacOS :: MacOS X",
|
||||||
|
"Operating System :: Microsoft :: Windows",
|
||||||
|
"Operating System :: POSIX",
|
||||||
|
"Programming Language :: Python :: 2.7",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.4",
|
||||||
|
"Programming Language :: Python :: 3.5",
|
||||||
|
"Programming Language :: Python :: 3.6",
|
||||||
|
"Programming Language :: Python :: 3.7",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Topic :: Desktop Environment :: File Managers",
|
||||||
|
]
|
||||||
|
|
||||||
|
with open("README.rst", "rt") as f1, open("CHANGES.rst", "rt") as f2:
|
||||||
|
LONG_DESCRIPTION = f1.read() + "\n\n" + f2.read()
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="Send2Trash",
|
||||||
|
version="1.8.0",
|
||||||
|
author="Andrew Senetar",
|
||||||
|
author_email="arsenetar@voltaicideas.net",
|
||||||
|
packages=["send2trash"],
|
||||||
|
scripts=[],
|
||||||
|
test_suite="tests",
|
||||||
|
url="https://github.com/arsenetar/send2trash",
|
||||||
|
license="BSD License",
|
||||||
|
description="Send file to trash natively under Mac OS X, Windows and Linux.",
|
||||||
|
long_description=LONG_DESCRIPTION,
|
||||||
|
long_description_content_type="text/x-rst",
|
||||||
|
classifiers=CLASSIFIERS,
|
||||||
|
extras_require={
|
||||||
|
"win32": ['pywin32; sys_platform == "win32"'],
|
||||||
|
"objc": ['pyobjc-framework-Cocoa; sys_platform == "darwin"'],
|
||||||
|
"nativeLib": [
|
||||||
|
'pywin32; sys_platform == "win32"',
|
||||||
|
'pyobjc-framework-Cocoa; sys_platform == "darwin"',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
project_urls={"Bug Reports": "https://github.com/arsenetar/send2trash/issues"},
|
||||||
|
entry_points={"console_scripts": ["send2trash=send2trash.__main__:main"]},
|
||||||
|
)
|
@ -28,7 +28,9 @@ else:
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def testfile():
|
def testfile():
|
||||||
file = NamedTemporaryFile(dir=op.expanduser("~"), prefix="send2trash_test", delete=False)
|
file = NamedTemporaryFile(
|
||||||
|
dir=op.expanduser("~"), prefix="send2trash_test", delete=False
|
||||||
|
)
|
||||||
file.close()
|
file.close()
|
||||||
assert op.exists(file.name) is True
|
assert op.exists(file.name) is True
|
||||||
yield file
|
yield file
|
||||||
@ -48,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),
|
||||||
)
|
)
|
||||||
@ -58,7 +62,10 @@ def testfiles():
|
|||||||
yield files
|
yield files
|
||||||
filenames = [op.basename(file.name) for file in files]
|
filenames = [op.basename(file.name) for file in files]
|
||||||
[os.remove(op.join(HOMETRASH, "files", filename)) for filename in filenames]
|
[os.remove(op.join(HOMETRASH, "files", filename)) for filename in filenames]
|
||||||
[os.remove(op.join(HOMETRASH, "info", filename + ".trashinfo")) for filename in filenames]
|
[
|
||||||
|
os.remove(op.join(HOMETRASH, "info", filename + ".trashinfo"))
|
||||||
|
for filename in filenames
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_trash(testfile):
|
def test_trash(testfile):
|
||||||
@ -129,7 +136,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.trashTopdir), op.realpath(self.trashTopdir_b),):
|
if op.realpath(path) in (
|
||||||
|
op.realpath(self.trashTopdir),
|
||||||
|
op.realpath(self.trashTopdir_b),
|
||||||
|
):
|
||||||
return True
|
return True
|
||||||
return old_ismount(path)
|
return old_ismount(path)
|
||||||
|
|
||||||
@ -162,8 +172,15 @@ def test_trash_topdir(testExtVol):
|
|||||||
|
|
||||||
s2t(testExtVol[2])
|
s2t(testExtVol[2])
|
||||||
assert op.exists(testExtVol[2]) is False
|
assert op.exists(testExtVol[2]) is False
|
||||||
assert op.exists(op.join(trashDir, str(os.getuid()), "files", testExtVol[1])) is True
|
assert (
|
||||||
assert op.exists(op.join(trashDir, str(os.getuid()), "info", testExtVol[1] + ".trashinfo",)) is True
|
op.exists(op.join(trashDir, str(os.getuid()), "files", testExtVol[1])) is True
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
op.exists(
|
||||||
|
op.join(trashDir, str(os.getuid()), "info", testExtVol[1] + ".trashinfo",)
|
||||||
|
)
|
||||||
|
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()
|
||||||
@ -174,7 +191,17 @@ def test_trash_topdir(testExtVol):
|
|||||||
def test_trash_topdir_fallback(testExtVol):
|
def test_trash_topdir_fallback(testExtVol):
|
||||||
s2t(testExtVol[2])
|
s2t(testExtVol[2])
|
||||||
assert op.exists(testExtVol[2]) is False
|
assert op.exists(testExtVol[2]) is False
|
||||||
assert op.exists(op.join(testExtVol[0].trashTopdir, ".Trash-" + str(os.getuid()), "files", testExtVol[1],)) is True
|
assert (
|
||||||
|
op.exists(
|
||||||
|
op.join(
|
||||||
|
testExtVol[0].trashTopdir,
|
||||||
|
".Trash-" + str(os.getuid()),
|
||||||
|
"files",
|
||||||
|
testExtVol[1],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_trash_topdir_failure(testExtVol):
|
def test_trash_topdir_failure(testExtVol):
|
||||||
@ -194,5 +221,15 @@ def test_trash_symlink(testExtVol):
|
|||||||
os.symlink(op.join(testExtVol[0].trashTopdir, "subdir"), slDir)
|
os.symlink(op.join(testExtVol[0].trashTopdir, "subdir"), slDir)
|
||||||
s2t(op.join(slDir, testExtVol[1]))
|
s2t(op.join(slDir, testExtVol[1]))
|
||||||
assert op.exists(filePath) is False
|
assert op.exists(filePath) is False
|
||||||
assert op.exists(op.join(testExtVol[0].trashTopdir, ".Trash-" + str(os.getuid()), "files", testExtVol[1],)) is True
|
assert (
|
||||||
|
op.exists(
|
||||||
|
op.join(
|
||||||
|
testExtVol[0].trashTopdir,
|
||||||
|
".Trash-" + str(os.getuid()),
|
||||||
|
"files",
|
||||||
|
testExtVol[1],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
os.remove(slDir)
|
os.remove(slDir)
|
||||||
|
@ -14,7 +14,9 @@ if sys.platform != "win32":
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def file():
|
def file():
|
||||||
file = NamedTemporaryFile(dir=op.expanduser("~"), prefix="send2trash_test", delete=False)
|
file = NamedTemporaryFile(
|
||||||
|
dir=op.expanduser("~"), prefix="send2trash_test", delete=False
|
||||||
|
)
|
||||||
file.close()
|
file.close()
|
||||||
# Verify file was actually created
|
# Verify file was actually created
|
||||||
assert op.exists(file.name) is True
|
assert op.exists(file.name) is True
|
||||||
|
Loading…
x
Reference in New Issue
Block a user