104 lines
3.9 KiB
Python
104 lines
3.9 KiB
Python
|
|
|
|
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
|