mirror of
https://github.com/arsenetar/send2trash.git
synced 2025-05-08 09:49:52 +00:00
Windows: Workaround for long paths (#23)
By using the short path version of a file, we can manage to move long paths to the trash. Limitations: 1/ If the final short path is longer than what `SHFileOperationW` can handle, it will fail 2/ Still not able to trash long path from another drive, ie: trying to delete C:\temp\foo.txt while the script is running from D:\trash.py
This commit is contained in:
parent
6b0bd46036
commit
020d05979d
@ -7,15 +7,19 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from ctypes import (windll, Structure, byref, c_uint,
|
from ctypes import (windll, Structure, byref, c_uint,
|
||||||
create_unicode_buffer, sizeof, addressof)
|
create_unicode_buffer, addressof)
|
||||||
from ctypes.wintypes import HWND, UINT, LPCWSTR, BOOL
|
from ctypes.wintypes import HWND, UINT, LPCWSTR, BOOL
|
||||||
import os.path as op
|
import os.path as op
|
||||||
|
|
||||||
from .compat import text_type
|
from .compat import text_type
|
||||||
|
|
||||||
|
kernel32 = windll.kernel32
|
||||||
|
GetShortPathNameW = kernel32.GetShortPathNameW
|
||||||
|
|
||||||
shell32 = windll.shell32
|
shell32 = windll.shell32
|
||||||
SHFileOperationW = shell32.SHFileOperationW
|
SHFileOperationW = shell32.SHFileOperationW
|
||||||
|
|
||||||
|
|
||||||
class SHFILEOPSTRUCTW(Structure):
|
class SHFILEOPSTRUCTW(Structure):
|
||||||
_fields_ = [
|
_fields_ = [
|
||||||
("hwnd", HWND),
|
("hwnd", HWND),
|
||||||
@ -28,6 +32,7 @@ class SHFILEOPSTRUCTW(Structure):
|
|||||||
("lpszProgressTitle", LPCWSTR),
|
("lpszProgressTitle", LPCWSTR),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
FO_MOVE = 1
|
FO_MOVE = 1
|
||||||
FO_COPY = 2
|
FO_COPY = 2
|
||||||
FO_DELETE = 3
|
FO_DELETE = 3
|
||||||
@ -39,11 +44,22 @@ FOF_NOCONFIRMATION = 16
|
|||||||
FOF_ALLOWUNDO = 64
|
FOF_ALLOWUNDO = 64
|
||||||
FOF_NOERRORUI = 1024
|
FOF_NOERRORUI = 1024
|
||||||
|
|
||||||
|
|
||||||
|
def get_short_path_name(long_name):
|
||||||
|
if not long_name.startswith('\\\\?\\'):
|
||||||
|
long_name = '\\\\?\\' + long_name
|
||||||
|
buf_size = GetShortPathNameW(long_name, None, 0)
|
||||||
|
output = create_unicode_buffer(buf_size)
|
||||||
|
GetShortPathNameW(long_name, output, buf_size)
|
||||||
|
return output.value[4:] # Remove '\\?\' for SHFileOperationW
|
||||||
|
|
||||||
|
|
||||||
def send2trash(path):
|
def send2trash(path):
|
||||||
if not isinstance(path, text_type):
|
if not isinstance(path, text_type):
|
||||||
path = text_type(path, 'mbcs')
|
path = text_type(path, 'mbcs')
|
||||||
if not op.isabs(path):
|
if not op.isabs(path):
|
||||||
path = op.abspath(path)
|
path = op.abspath(path)
|
||||||
|
path = get_short_path_name(path)
|
||||||
fileop = SHFILEOPSTRUCTW()
|
fileop = SHFILEOPSTRUCTW()
|
||||||
fileop.hwnd = 0
|
fileop.hwnd = 0
|
||||||
fileop.wFunc = FO_DELETE
|
fileop.wFunc = FO_DELETE
|
||||||
|
46
tests/test_plat_win.py
Normal file
46
tests/test_plat_win.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from os import path as op
|
||||||
|
from tempfile import gettempdir
|
||||||
|
|
||||||
|
from send2trash import send2trash as s2t
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.platform != 'win32', 'Windows only')
|
||||||
|
class TestLongPath(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
filename = 'A' * 100
|
||||||
|
self.dirname = '\\\\?\\' + os.path.join(gettempdir(), filename)
|
||||||
|
self.file = os.path.join(
|
||||||
|
self.dirname,
|
||||||
|
filename,
|
||||||
|
filename, # From there, the path is not trashable from Explorer
|
||||||
|
filename,
|
||||||
|
filename + '.txt')
|
||||||
|
self._create_tree(self.file)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
try:
|
||||||
|
os.remove(self.dirname)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _create_tree(self, path):
|
||||||
|
dirname = os.path.dirname(path)
|
||||||
|
if not os.path.isdir(dirname):
|
||||||
|
os.makedirs(dirname)
|
||||||
|
with open(path, 'w') as writer:
|
||||||
|
writer.write('Looong filename!')
|
||||||
|
|
||||||
|
def test_trash_file(self):
|
||||||
|
s2t(self.file)
|
||||||
|
self.assertFalse(op.exists(self.file))
|
||||||
|
|
||||||
|
@unittest.skipIf(
|
||||||
|
op.splitdrive(os.getcwd())[0] != op.splitdrive(gettempdir())[0],
|
||||||
|
'Cannot trash long path from other drive')
|
||||||
|
def test_trash_folder(self):
|
||||||
|
s2t(self.dirname)
|
||||||
|
self.assertFalse(op.exists(self.dirname))
|
Loading…
x
Reference in New Issue
Block a user