from win32com.server.util import wrap import pythoncom, sys, os, time, win32api, win32event, tempfile from win32com.bits import bits TIMEOUT = 200 # ms StopEvent = win32event.CreateEvent(None, 0, 0, None) job_name = 'bits-pywin32-test' states = dict([(val, (name[13:])) for name, val in vars(bits).items() if name.startswith('BG_JOB_STATE_')]) bcm = pythoncom.CoCreateInstance(bits.CLSID_BackgroundCopyManager, None, pythoncom.CLSCTX_LOCAL_SERVER, bits.IID_IBackgroundCopyManager) class BackgroundJobCallback: _com_interfaces_ = [bits.IID_IBackgroundCopyCallback] _public_methods_ = ["JobTransferred", "JobError", "JobModification"] def JobTransferred(self, job): print('Job Transferred', job) job.Complete() win32event.SetEvent(StopEvent) # exit msg pump def JobError(self, job, error): print('Job Error', job, error) f = error.GetFile() print('While downloading', f.GetRemoteName()) print('To', f.GetLocalName()) print('The following error happened:') self._print_error(error) if f.GetRemoteName().endswith('missing-favicon.ico'): print('Changing to point to correct file') f2 = f.QueryInterface(bits.IID_IBackgroundCopyFile2) favicon = 'http://www.python.org/favicon.ico' print('Changing RemoteName from', f2.GetRemoteName(), 'to', favicon) f2.SetRemoteName(favicon) job.Resume() else: job.Cancel() def _print_error(self, err): ctx, hresult = err.GetError() try: hresult_msg = win32api.FormatMessage(hresult) except win32api.error: hresult_msg = "" print("Context=0x%x, hresult=0x%x (%s)" % (ctx, hresult, hresult_msg)) print(err.GetErrorDescription()) def JobModification(self, job, reserved): state = job.GetState() print('Job Modification', job.GetDisplayName(), states.get(state)) # Need to catch TRANSIENT_ERROR here, as JobError doesn't get # called (apparently) when the error is transient. if state == bits.BG_JOB_STATE_TRANSIENT_ERROR: print("Error details:") err = job.GetError() self._print_error(err) job = bcm.CreateJob(job_name, bits.BG_JOB_TYPE_DOWNLOAD) job.SetNotifyInterface(wrap(BackgroundJobCallback())) job.SetNotifyFlags(bits.BG_NOTIFY_JOB_TRANSFERRED | bits.BG_NOTIFY_JOB_ERROR | bits.BG_NOTIFY_JOB_MODIFICATION) # The idea here is to intentionally make one of the files fail to be # downloaded. Then the JobError notification will be triggered, where # we do fix the failing file by calling SetRemoteName to a valid URL # and call Resume() on the job, making the job finish successfully. # # Note to self: A domain that cannot be resolved will cause # TRANSIENT_ERROR instead of ERROR, and the JobError notification will # not be triggered! This can bite you during testing depending on how # your DNS is configured. For example, if you use OpenDNS.org's DNS # servers, an invalid hostname will *always* be resolved (they # redirect you to a search page), so be careful when testing. job.AddFile('http://www.python.org/favicon.ico', os.path.join(tempfile.gettempdir(), 'bits-favicon.ico')) job.AddFile('http://www.python.org/missing-favicon.ico', os.path.join(tempfile.gettempdir(), 'bits-missing-favicon.ico')) for f in job.EnumFiles(): print('Downloading', f.GetRemoteName()) print('To', f.GetLocalName()) job.Resume() while True: rc = win32event.MsgWaitForMultipleObjects( (StopEvent,), 0, TIMEOUT, win32event.QS_ALLEVENTS) if rc == win32event.WAIT_OBJECT_0: break elif rc == win32event.WAIT_OBJECT_0+1: if pythoncom.PumpWaitingMessages(): break # wm_quit