Sync all folders outlook win32 Python

Viewed 369

I want to wait until all folders in outlook are synced to start downloading attachments. If I run the code without opening outlook and syncing all folders first, the code does not download the latest emails.

The code is below:

import os
import win32com


class DailyExtracts:

    def __init__(self):
        self.outlook = win32com.client.Dispatch("outlook.application").GetNamespace("MAPI")
        self.root = self.outlook.Folders.item(1)
        self.daily_reports = self.root.Folders[".data"].Folders["daily_reports"]


    def download_extracts(self):
        for message in self.daily_reports.Items:
            for attachment in message.Attachments:
                fname = attachment.Filename
                attachment.SaveAsFile(os.path.join(os.path.expanduser(r"~\Downloads"), fname))
                message.Unread = False

I have read that I can use SyncObjects.SyncEnd event to wait until the send/receive finish. However, I don't know how to implement it.

1 Answers

Here's one (possibly inelegant) solution, which shows the basic principle of handling Outlook events. The issue with handling COM events (such as produced by Outlook) is that they are asynchronous, and the code has to wait around for the event to be triggered. This code uses the thread's message loop to block until all the Sync events have come back.

The handler class does more than the OP needs, but also responds to errors on the syncronizations.

import win32com.client as wc
import pythoncom

import win32api
import win32con

nSync = 0

#Class to handle the SyncObject events from Outlook
class SyncHandler(object):
    #Save the dispatch interface to identify the SyncObject if needed
    def set(self,disp):
        self._disp = disp

    def _process(self):
        #Decrement sync counter
        global nSync
        nSync -= 1
        print(nSync,'Sync remaining to complete')

        #If nothing left to sync, then send WM_QUIT to thread message loop
        if nSync <= 0:
            print('Closing message loop')
            win32api.PostThreadMessage(win32api.GetCurrentThreadId(), win32con.WM_QUIT, 0, 0)
    
    def OnSyncStart(self):
        print('Starting sync on',self._disp.Name)

    def OnSyncEnd(self):    
        print('Sync complete on',self._disp.Name)
        self._process()

    def OnProgress(self,state,description,value,max):
        print('Sync progress: {0:} {1:} {2:}%'.format(self._disp.Name,description,100 * value/max))

    def OnError(self,code,description):
        print('Sync Error',description)
        self._process()

#Get the application Dispatch interface
ol = wc.Dispatch('Outlook.Application')
syncObjects = ol.GetNamespace('MAPI').SyncObjects

#Find out how many SyncObjects we have
nSync = syncObjects.Count
print('Number of Sync objects:',nSync)

for syncObj in syncObjects:
    #Set up an event handler and specify the event handler class
    handler = wc.WithEvents(syncObj,SyncHandler)
    handler.set(syncObj)
    syncObj.Start()
  
#This will block until a WM_QUIT message is sent to the message queue
pythoncom.PumpMessages()

print('Sync completed')

I tested this on my Outlook client which has a number of email accounts, and it seemed to work OK, though I have a feeling it only sync'd the non-Exchange accounts (as I guess Exchange accounts are continuously synchronized?).

Related