在 windows 取消 python 中停滞的文件复制
Cancel a stalled file copy in python on windows
在 windows,我想用 Python 通过网络复制一堆文件。有时,网络没有响应,副本停止。我想检查一下,如果发生这种情况,并在发生这种情况时跳过有问题的文件。通过询问这个相关问题 here, I found out about the CopyFileEx 函数,允许使用回调函数,可以中止文件复制。
Python 中的实现如下所示:
import win32file
def Win32_CopyFileEx( ExistingFileName, NewFileName, Canc = False):
win32file.CopyFileEx(
ExistingFileName, # PyUNICODE | File to be copied
NewFileName, # PyUNICODE | Place to which it will be copied
Win32_CopyFileEx_ProgressRoutine, # CopyProgressRoutine | A python function that receives progress updates, can be None
Data = None, # object | An arbitrary object to be passed to the callback function
Cancel = Canc, # boolean | Pass True to cancel a restartable copy that was previously interrupted
CopyFlags = win32file.COPY_FILE_RESTARTABLE, # int | Combination of COPY_FILE_* flags
Transaction = None # PyHANDLE | Handle to a transaction as returned by win32transaction::CreateTransaction
)
从 CopyFileEx 函数的文档中,我可以看到取消 运行 副本的两种可能性。
pbCancel [in, optional] If this flag is set to TRUE during the copy operation, the operation is canceled. Otherwise, the copy
operation will continue to completion.
我不知道如何做到这一点。我尝试再次使用相同的文件句柄调用相同的函数,但将取消标志设置为 TRUE
,但这会导致错误,因为有问题的文件正在被另一个进程使用。
另一种可能是回调函数:
lpProgressRoutine [in, optional] The address of a callback function of
type LPPROGRESS_ROUTINE that is called each time another portion of
the file has been copied. This parameter can be NULL. For more
information on the progress callback function, see the
CopyProgressRoutine function.
documentation of this ProgressRoutine 声明,此回调在复制开始时或文件垃圾完成复制时调用。如果回调函数returns 1
或2
(取消,停止),则可以取消复制过程。但是,这个回调函数好像没有被调用,当一个垃圾副本停滞时。
所以我的问题是:当它停止时,我如何才能取消每个文件的副本?
win32file.CopyFileEx
不允许将 Cancel
作为布尔值或整数值以外的任何值传递。在 API 中它是一个 LPBOOL
指针,它允许调用者在另一个线程中同时设置它的值。您必须使用 ctypes、Cython 或 C 扩展来获得这种级别的控制。下面我写了一个使用 ctypes 的例子。
如果由于线程在同步 I/O 上被阻塞而无法取消复制,您可以尝试调用 CancelIoEx
on the file handles that you're passed in the progress routine, or CancelSynchronousIo
来取消该线程的所有同步 I/O。这些 I/O 取消功能是在 Windows Vista 中添加的。它们在 Windows XP 中不可用,以防您仍然支持它。
import ctypes
from ctypes import wintypes
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
COPY_FILE_FAIL_IF_EXISTS = 0x0001
COPY_FILE_RESTARTABLE = 0x0002
COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x0004
COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x0008
COPY_FILE_COPY_SYMLINK = 0x0800
COPY_FILE_NO_BUFFERING = 0x1000
CALLBACK_CHUNK_FINISHED = 0
CALLBACK_STREAM_SWITCH = 1
PROGRESS_CONTINUE = 0
PROGRESS_CANCEL = 1
PROGRESS_STOP = 2
PROGRESS_QUIET = 3
ERROR_REQUEST_ABORTED = 0x04D3
if not hasattr(wintypes, 'LPBOOL'):
wintypes.LPBOOL = ctypes.POINTER(wintypes.BOOL)
def _check_bool(result, func, args):
if not result:
raise ctypes.WinError(ctypes.get_last_error())
return args
LPPROGRESS_ROUTINE = ctypes.WINFUNCTYPE(
wintypes.DWORD, # _Retval_
wintypes.LARGE_INTEGER, # _In_ TotalFileSize
wintypes.LARGE_INTEGER, # _In_ TotalBytesTransferred
wintypes.LARGE_INTEGER, # _In_ StreamSize
wintypes.LARGE_INTEGER, # _In_ StreamBytesTransferred
wintypes.DWORD, # _In_ dwStreamNumber
wintypes.DWORD, # _In_ dwCallbackReason
wintypes.HANDLE, # _In_ hSourceFile
wintypes.HANDLE, # _In_ hDestinationFile
wintypes.LPVOID) # _In_opt_ lpData
kernel32.CopyFileExW.errcheck = _check_bool
kernel32.CopyFileExW.argtypes = (
wintypes.LPCWSTR, # _In_ lpExistingFileName
wintypes.LPCWSTR, # _In_ lpNewFileName
LPPROGRESS_ROUTINE, # _In_opt_ lpProgressRoutine
wintypes.LPVOID, # _In_opt_ lpData
wintypes.LPBOOL, # _In_opt_ pbCancel
wintypes.DWORD) # _In_ dwCopyFlags
@LPPROGRESS_ROUTINE
def debug_progress(tsize, ttrnsfr, stsize, sttrnsfr, stnum, reason,
hsrc, hdst, data):
print('ttrnsfr: %d, stnum: %d, stsize: %d, sttrnsfr: %d, reason: %d' %
(ttrnsfr, stnum, stsize, sttrnsfr, reason))
return PROGRESS_CONTINUE
def copy_file(src, dst, cancel=None, flags=0,
cbprogress=None, data=None):
if isinstance(cancel, int):
cancel = ctypes.byref(wintypes.BOOL(cancel))
elif cancel is not None:
cancel = ctypes.byref(cancel)
if cbprogress is None:
cbprogress = LPPROGRESS_ROUTINE()
kernel32.CopyFileExW(src, dst, cbprogress, data, cancel, flags)
例子
if __name__ == '__main__':
import os
import tempfile
import threading
src_fd, src = tempfile.mkstemp()
os.write(src_fd, os.urandom(16 * 2 ** 20))
os.close(src_fd)
dst = tempfile.mktemp()
cancel = wintypes.BOOL(False)
t = threading.Timer(0.001, type(cancel).value.__set__, (cancel, True))
t.start()
try:
copy_file(src, dst, cancel, cbprogress=debug_progress)
except OSError as e:
print(e)
assert e.winerror == ERROR_REQUEST_ABORTED
finally:
if os.path.exists(src):
os.remove(src)
if os.path.exists(dst):
os.remove(dst)
在 windows,我想用 Python 通过网络复制一堆文件。有时,网络没有响应,副本停止。我想检查一下,如果发生这种情况,并在发生这种情况时跳过有问题的文件。通过询问这个相关问题 here, I found out about the CopyFileEx 函数,允许使用回调函数,可以中止文件复制。
Python 中的实现如下所示:
import win32file
def Win32_CopyFileEx( ExistingFileName, NewFileName, Canc = False):
win32file.CopyFileEx(
ExistingFileName, # PyUNICODE | File to be copied
NewFileName, # PyUNICODE | Place to which it will be copied
Win32_CopyFileEx_ProgressRoutine, # CopyProgressRoutine | A python function that receives progress updates, can be None
Data = None, # object | An arbitrary object to be passed to the callback function
Cancel = Canc, # boolean | Pass True to cancel a restartable copy that was previously interrupted
CopyFlags = win32file.COPY_FILE_RESTARTABLE, # int | Combination of COPY_FILE_* flags
Transaction = None # PyHANDLE | Handle to a transaction as returned by win32transaction::CreateTransaction
)
从 CopyFileEx 函数的文档中,我可以看到取消 运行 副本的两种可能性。
pbCancel [in, optional] If this flag is set to TRUE during the copy operation, the operation is canceled. Otherwise, the copy operation will continue to completion.
我不知道如何做到这一点。我尝试再次使用相同的文件句柄调用相同的函数,但将取消标志设置为 TRUE
,但这会导致错误,因为有问题的文件正在被另一个进程使用。
另一种可能是回调函数:
lpProgressRoutine [in, optional] The address of a callback function of type LPPROGRESS_ROUTINE that is called each time another portion of the file has been copied. This parameter can be NULL. For more information on the progress callback function, see the CopyProgressRoutine function.
documentation of this ProgressRoutine 声明,此回调在复制开始时或文件垃圾完成复制时调用。如果回调函数returns 1
或2
(取消,停止),则可以取消复制过程。但是,这个回调函数好像没有被调用,当一个垃圾副本停滞时。
所以我的问题是:当它停止时,我如何才能取消每个文件的副本?
win32file.CopyFileEx
不允许将 Cancel
作为布尔值或整数值以外的任何值传递。在 API 中它是一个 LPBOOL
指针,它允许调用者在另一个线程中同时设置它的值。您必须使用 ctypes、Cython 或 C 扩展来获得这种级别的控制。下面我写了一个使用 ctypes 的例子。
如果由于线程在同步 I/O 上被阻塞而无法取消复制,您可以尝试调用 CancelIoEx
on the file handles that you're passed in the progress routine, or CancelSynchronousIo
来取消该线程的所有同步 I/O。这些 I/O 取消功能是在 Windows Vista 中添加的。它们在 Windows XP 中不可用,以防您仍然支持它。
import ctypes
from ctypes import wintypes
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
COPY_FILE_FAIL_IF_EXISTS = 0x0001
COPY_FILE_RESTARTABLE = 0x0002
COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x0004
COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x0008
COPY_FILE_COPY_SYMLINK = 0x0800
COPY_FILE_NO_BUFFERING = 0x1000
CALLBACK_CHUNK_FINISHED = 0
CALLBACK_STREAM_SWITCH = 1
PROGRESS_CONTINUE = 0
PROGRESS_CANCEL = 1
PROGRESS_STOP = 2
PROGRESS_QUIET = 3
ERROR_REQUEST_ABORTED = 0x04D3
if not hasattr(wintypes, 'LPBOOL'):
wintypes.LPBOOL = ctypes.POINTER(wintypes.BOOL)
def _check_bool(result, func, args):
if not result:
raise ctypes.WinError(ctypes.get_last_error())
return args
LPPROGRESS_ROUTINE = ctypes.WINFUNCTYPE(
wintypes.DWORD, # _Retval_
wintypes.LARGE_INTEGER, # _In_ TotalFileSize
wintypes.LARGE_INTEGER, # _In_ TotalBytesTransferred
wintypes.LARGE_INTEGER, # _In_ StreamSize
wintypes.LARGE_INTEGER, # _In_ StreamBytesTransferred
wintypes.DWORD, # _In_ dwStreamNumber
wintypes.DWORD, # _In_ dwCallbackReason
wintypes.HANDLE, # _In_ hSourceFile
wintypes.HANDLE, # _In_ hDestinationFile
wintypes.LPVOID) # _In_opt_ lpData
kernel32.CopyFileExW.errcheck = _check_bool
kernel32.CopyFileExW.argtypes = (
wintypes.LPCWSTR, # _In_ lpExistingFileName
wintypes.LPCWSTR, # _In_ lpNewFileName
LPPROGRESS_ROUTINE, # _In_opt_ lpProgressRoutine
wintypes.LPVOID, # _In_opt_ lpData
wintypes.LPBOOL, # _In_opt_ pbCancel
wintypes.DWORD) # _In_ dwCopyFlags
@LPPROGRESS_ROUTINE
def debug_progress(tsize, ttrnsfr, stsize, sttrnsfr, stnum, reason,
hsrc, hdst, data):
print('ttrnsfr: %d, stnum: %d, stsize: %d, sttrnsfr: %d, reason: %d' %
(ttrnsfr, stnum, stsize, sttrnsfr, reason))
return PROGRESS_CONTINUE
def copy_file(src, dst, cancel=None, flags=0,
cbprogress=None, data=None):
if isinstance(cancel, int):
cancel = ctypes.byref(wintypes.BOOL(cancel))
elif cancel is not None:
cancel = ctypes.byref(cancel)
if cbprogress is None:
cbprogress = LPPROGRESS_ROUTINE()
kernel32.CopyFileExW(src, dst, cbprogress, data, cancel, flags)
例子
if __name__ == '__main__':
import os
import tempfile
import threading
src_fd, src = tempfile.mkstemp()
os.write(src_fd, os.urandom(16 * 2 ** 20))
os.close(src_fd)
dst = tempfile.mktemp()
cancel = wintypes.BOOL(False)
t = threading.Timer(0.001, type(cancel).value.__set__, (cancel, True))
t.start()
try:
copy_file(src, dst, cancel, cbprogress=debug_progress)
except OSError as e:
print(e)
assert e.winerror == ERROR_REQUEST_ABORTED
finally:
if os.path.exists(src):
os.remove(src)
if os.path.exists(dst):
os.remove(dst)