2017-02-13 53 views
1

在Windows上,我想用Python將一堆文件複製到網絡上。有時,網絡沒有響應,副本停滯。我想檢查,如果發生這種情況,並跳過有問題的文件,當發生這種情況。通過詢問相關問題here,我發現了CopyFileEx函數,它允許使用回調函數,該函數可以中止文件複製。在窗口中取消python中的停頓文件副本

在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 [在,可選]如果複製操作期間這個標誌被設置爲TRUE ,則操作被取消。否則,複製 操作將繼續完成。

我想不出如何做到這一點。我試着用相同的文件句柄再次調用相同的函數,但是將取消標誌設置爲TRUE,但由於有問題的文件正在被另一個進程使用,導致出現錯誤。

另一種可能性似乎是回調函數:

lpProgressRoutine [在,可選],每當的 另一部分文件已被時間稱爲 類型LPPROGRESS_ROUTINE的回調函數的地址複製。該參數可以是NULL。有關進度回調函數的更多 信息,請參閱 CopyProgressRoutine函數。

documentation of this ProgressRoutine指出,此回調要麼在複製開始時要麼調用,或者文件的垃圾完成複製時調用。回叫功能可以取消複製過程,如果它返回12(取消,停止)。但是,當垃圾副本停滯時,此回調函數似乎不會被調用。

所以我的問題是:如何在每個文件的基礎上取消這個副本,當它停滯?

回答

1

win32file.CopyFileEx不允許將Cancel作爲布爾值或整數值。在API中,這是一個LPBOOL指針,它允許調用方在另一個線程中同時設置其值。你將不得不使用ctypes,Cython或C擴展來獲得這個級別的控制。下面我寫了一個使用ctypes的例子。

如果由於線程在同步I/O上被阻塞而取消複製不起作用,您可以嘗試在您正在進度例程中傳遞的文件句柄上撥打CancelIoEx,或者用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) 
+0

只是:哇。這比我原先想象的要困難得多。我會在我的相關鏈接中加入這個答案的鏈接。 – Dschoni