diff --git a/send2trash/plat_win_legacy.py b/send2trash/plat_win_legacy.py index e3c404b..e8955b8 100644 --- a/send2trash/plat_win_legacy.py +++ b/send2trash/plat_win_legacy.py @@ -77,8 +77,6 @@ def send2trash(paths): paths = [op.abspath(path) if not op.isabs(path) else path for path in paths] # get short path to handle path length issues paths = [get_short_path_name(path) for path in paths] - # convert to a single string of null terminated paths - paths = "\0".join(paths) fileop = SHFILEOPSTRUCTW() fileop.hwnd = 0 fileop.wFunc = FO_DELETE @@ -93,7 +91,15 @@ def send2trash(paths): # NOTE: based on how python allocates memory for these types they should # always be zero, if this is ever not true we can go back to explicitly # setting the last two characters to null using buffer[index] = '\0'. - buffer = create_unicode_buffer(paths, len(paths) + 2) + # Additional note on another issue here, unicode_buffer expects length in + # bytes essentially, so having multi-byte characters causes issues if just + # passing pythons string length. Instead of dealing with this difference we + # just create a buffer then a new one with an extra null. Since the non-length + # specified version apparently stops after the first null, join with a space first. + buffer = create_unicode_buffer(" ".join(paths)) + # convert to a single string of null terminated paths + path_string = "\0".join(paths) + buffer = create_unicode_buffer(path_string, len(buffer) + 1) fileop.pFrom = LPCWSTR(addressof(buffer)) fileop.pTo = None fileop.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT diff --git a/tests/test_plat_win.py b/tests/test_plat_win.py index 52676fd..4ff7782 100644 --- a/tests/test_plat_win.py +++ b/tests/test_plat_win.py @@ -66,6 +66,17 @@ def _file_not_found(dir, fcn): pytest.raises(OSError, fcn, file) +def _multi_byte_unicode(dir, fcn): + file = op.join(dir, "😇.txt") + _create_tree(file) + fcn(file) + assert op.exists(file) is False + files = [op.join(dir, "😇{}.txt".format(index)) for index in range(10)] + [_create_tree(file) for file in files] + fcn(files) + assert any([op.exists(file) for file in files]) is False + + def test_trash_folder(testdir): _trash_folder(testdir, s2t) @@ -98,6 +109,10 @@ def test_file_not_found_modern(testdir): _file_not_found(testdir, s2t_modern) +def test_multi_byte_unicode_modern(testdir): + _multi_byte_unicode(testdir, s2t_modern) + + def test_trash_folder_legacy(testdir): _trash_folder(testdir, s2t_legacy) @@ -114,6 +129,10 @@ def test_file_not_found_legacy(testdir): _file_not_found(testdir, s2t_legacy) +def test_multi_byte_unicode_legacy(testdir): + _multi_byte_unicode(testdir, s2t_legacy) + + # Long path tests @pytest.fixture def longdir(tmp_path):