Spaces:
Running
Running
import os | |
import tempfile | |
import pythoncom | |
import win32api | |
import win32event | |
from win32com.bits import bits | |
from win32com.server.util import wrap | |
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 | |