RDOFolderSynchronizer object

 

RDOFolderSynchronizer allows to synchronize Exchange folder contents (regular or hidden messages) and subfolders. This object is not available for the stores other than Exchange (PST, IMAP, etc).

RDOFolderSynchronizer object wraps the ICS (Incremental Change Synchronization) API in MAPI (IExchangeImportContentsChanges, IExchangeImportHierarchyChanges, IExchangeExportChanges, etc). This is the same API used by the Exchange cached store provider to synchronize its folders with an Exchange Server.

 

Returned by:

RDOFolder2.ExchangeSynchronizer

 

ICS API is the only supported method of synchronizing Exchange folders. You can use folder events (RDOItems.ItemChange / ItemAdd / ItemRemove), but that requires the code to be running at all times; plus events can be dropped under heavy loads. Also, if you are monitoring multiple folders, you can easily run out of the 255 RPC channels/process limit imposed by the Exchange Server.

ICS API does not require persistent connection to the folder, you can synchronize a folder at any moment, be that every minute or once a month.

 

To synchronize messages in a given folder (RDOFolder2 object) follow the steps below:

  1. Retrieve RDOFolderSynchronizer object by calling RDOFolder2.ExchangeSynchronizer

  2. Call RDOFolderSynchronizer.SyncItems method (or SyncHiddenItems in case of hidden messages or SyncFolders in case of subfolders) passing the value of the RDOSyncMessagesCollection.SyncData property from the previous sync cycle. When running for the very first time, pass an empty string; in this case Exchange will return all existing items in the folder in the RDOSyncMessagesCollection collection. The value of the SyncData property must be persisted on the per-folder basis, i.e. you cannot use the value returned by synching a different folder. This value can thought of as a cookie that Exchange will use next time to synchronize the folder.

  3. Loop through the items in the returned RDOSyncMessagesCollection (or RDOSyncFoldersCollection if calling SyncFolders). If you need to identify messages in the folder, it is better to use the RDOSyncMessageItem.SourceKey property (corresponds to the PR_SOURCE_KEY property in MAPI): the value of the entry id is not guaranteed to remain the same between sessions, while source key stays the same for any given message or folder. If you know the value of the source key, you can retrieve the corresponding message using RDOExchangeStore.GetMessageFromSourceKey method.

  4. Save the value of the RDOSyncMessagesCollection.SyncData property for use next time (at step 2).

If your code modifies Outlook items, and you need to filter out and disregard changes made by your code, compare the value of the PR_CHANGE_KEY MAPI property (you can access it using RDOMail.Fields() and convert to a hex string using MAPIUtils.HrArrayToString) retrieved immediately after saving the changed item (RDOMail.Save) with the value of the corresponding (match by the PR_SOURCE_KEY property) RDOSyncMessageItem.ChangeKey property retrieved later. If the values are the same, your code was the last to modify the item, if it is different, the item was modified at a later time after you called RDOMail.Save.

 

The example below synchronizes the contents of the default Inbox folder. On the first run it will report all items in the folder as changed (since the sync never ran before), displays a message boxes to wait to the user to modify something in the folder (new message, modify an existing message, delete a message), then performs the sync again and reports the items that were added/modified/deleted while the prompt was displayed.

 

MAPI_NO_CACHE = &H200
MAPI_BEST_ACCESS = &H10

 

' Optional SQL restriction:
' the restriction must be static, it cannot change, otherwise you will get 'all items in the folder back

' In most cases no restriction is used (pass an empty string for a restriction)
strSQLRestriction = " MessageClass = 'IPM.Post' "

set Session = CreateObject("Redemption.RDOSession")
Session.Logon

' retrieve the folder to be synchronized
' the folder must either come from an online store (no cached mode for the profile)
' or it can be reopened in the online mode even if the store is cached (MAPI_NO_CACHE flag below)

' otherwise Redemption will attempt to reopen the online version of the folder
' set Folder = Session.GetFolderfromID(Application.ActiveExplorer.CurrentFolder.EntryID, , MAPI_BEST_ACCESS or MAPI_NO_CACHE)
set Folder = Session.GetDefaultFolder(olFolderInbox)

' first synchronization
set Synchonizer = Folder.ExchangeSynchonizer
strPreviousSyncData = "" 'no data at first run, this really needs to come from some persistent storage saved after the previous sync
set SyncItems = Synchonizer.SyncItems(strPreviousSyncData, strSQLRestriction)
'first run will report all items in the folder as modified since strPreviousSyncData is empty
for each Item in SyncItems
    Debug.Print Item.Item.Subject
next
' remember/store the sync cookie
strPreviousSyncData = SyncItems.SyncData

' Now modify something and report what was modified

' this is just an example...
MsgBox "Waiting for modifications. Modify an item in the folder and click Ok when done"

' collect new modifications

' since we are using strPreviousSyncData from the sync above, only messages added/modified/deleted while the message box

' above was displayed will be returned
set SyncItems = Synchonizer.SyncItems(strPreviousSyncData, strSQLRestriction)

if SyncItems.Count = 0 Then

    Debug.Print "There were no changes in the folder"

Else

    Debug.Print "There were " & SyncItems.Count & " changes in the folder. The list of changes follows:"
    for each Item in SyncItems
        if Item.Kind = 0 Then 'sikChanged
            'modification

            if Item.IsNewMessage Then

              Debug.Print "New: " & Item.Item.Subject

            Else
              Debug.Print "Modified: " & Item.Item.Subject

            End  If
        Elseif Item.Kind = 1 Then 'sikDeleted
            'deletion. Since the item is gone, RDOSyncMessageItem.Item will be NULL

            Debug.Print "Deletion. Source key = " & Item.SourceKey
        Elseif Item.Kind = 2 Then 'sikReadStatusChanged
            'read/unread state changed

            Debug.Print "Read/unread state changed: " & Item.Item.Subject
        End If
   next

EndIf

 

' remember the value of the strPreviousSyncData property, we will need it next time

strPreviousSyncData = SyncItems.SyncData

' strPreviousSyncData now needs to be persisted to be used in the next sync instead of using an empty string

 

Methods

Events

 


Derived from: IDispatch


Properties


IncludeSyncProperties  PropList Collection that allows to specify which properties must be included for sync, which can significantly reduce the network traffic during synchronization 
set Synchonizer = Folder.ExchangeSynchonizer

set syncProps = Synchonizer.IncludeSyncProperties
syncProps.Clear
syncProps.Add(0x0FFF0102) 'PR_ENTRYID
syncProps.Add(0x0037001F) 'PR_SUBJECT_W
syncProps.Add(0x65E00102) 'PR_SOURCE_KEY
syncProps.Add(0x65E20102) 'PR_CHANGE_KEY


Methods


SyncItems(PreviousSyncData, SQLRestriction)

Synchronizes the folder contents (messages); returns an instance of the RDOSyncMessagesCollection object.

 

PreviousSyncData - the value returned by RDOSyncMessagesCollection.SyncData property during the previous sync cycle. For the very first sync, pass an empty string.

 

SQLRestriction - optional, string. An optional SQL restriction (e.g. " MessageClass = 'IPM.Post' "). The restriction must never change on subsequent syncs, or all items in the folder will be returned

 

see example above

SyncHiddenItems(PreviousSyncData, SQLRestriction)

Synchronizes the folder associated contents (hidden messages); returns an instance of the RDOSyncMessagesCollection object.

 

PreviousSyncData - the value returned by RDOSyncMessagesCollection.SyncData property during the previous sync cycle. For the very first sync, pass an empty string.

 

SQLRestriction - optional, string. An optional SQL restriction. The restriction must never change on subsequent syncs, or all items in the folder will be returned

 

 

SyncFolders(PreviousSyncData, SQLRestriction)

Synchronizes the subfolders; returns an instance of the RDOSyncFoldersCollection object.

 

PreviousSyncData - the value returned by RDOSyncFoldersCollection.SyncData property during the previous sync cycle. For the very first sync, pass an empty string.

 

SQLRestriction - optional, string. An optional SQL restriction. The restriction must never change on subsequent syncs, or all subfolders in the folder will be returned

 

 

Events


OnProgress(Step, Progress, ByRef Cancel)

Occurs every time Redemption calls IExchangeExportChanges::Synchronize() MAPI method.

 

Step - integer

Progress - integer

Cancel (by reference) -  boolean, set this parameter to TRUE to cancel the sync process.

 

OnSyncFolder(Folder, ByRef Cancel)

 

Occurs each time ICS sends a subfolder change/delete notification.

 

Folder - RDOSyncFolderItem object.

 

Cancel (by reference) - boolean, set this parameter to TRUE to cancel the sync process.

 

 

OnSyncHiddenItem(Item, RyRef TargetMessage, ByRef Cancel)

 

Occurs each time ICS sends a hidden message add/change/delete notification.

 

Item - RDOSyncMessageItem object representing added/changed/deleted message in the folder.

 

TargetMessage (by reference) - RDOMail object. Redemption passes NULL to the event handler. You optionally can return a message from your callback; in this case ICS will populate it with the changed properties from the source message (see an example below)

 

Cancel (by reference) - boolean, set this parameter to TRUE to cancel the sync process.

 

 

OnSyncItem(Item, RyRef TargetMessage, ByRef Cancel)

 

Occurs each time ICS sends a message add/change/delete notification.

 

Item - RDOSyncMessageItem object representing added/changed/deleted message in the folder.

 

TargetMessage (by reference) - RDOMail object. Redemption passes NULL to the event handler. You optionally can return a message from your callback; in this case ICS will populate it with the changed properties from the source message (see an example below)

 

Cancel (by reference) - boolean, set this parameter to TRUE to cancel the sync process.

 

private RDOSession session = null;

private RDOFolderSynchronizer synchronizer = null;

private string strSyncData = "";

 

private void button1_Click(object sender, EventArgs e)

{

  if (session == null)

  {

    session = new RDOSession();

    session.Logon("Test", "", false, true, 0, false);

  }

  RDOFolderSynchronizer synchronizer = ((RDOFolder2)session.GetDefaultFolder(rdoDefaultFolders.olFolderDrafts)).ExchangeSynchonizer;

  synchronizer.OnSyncItem += new IRDOFolderSynchronizerEvents_OnSyncItemEventHandler(synchronizer_OnSyncItem);

  RDOSyncMessagesCollection items = synchronizer.SyncItems(strSyncData, "");

  strSyncData = items.SyncData;

}

void synchronizer_OnSyncItem(RDOSyncMessageItem Item, out RDOMail TargetMessage, ref bool Cancel)

{

  //Item parameter

  if (Item.Kind != rdoSyncItemKind.sikDeleted)

  {

    //just a simple example of a user notification.

    //We check above for RDOSyncMessageItem.Kind != rdoSyncItemKind.sikDeleted

    //since for the deleted messages we cannot access the RDOSyncMessageItem.Item

    //property and retrieve any message properties

    MessageBox.Show(Item.Item.Subject);

  }

  //TargetMessage parameter - if this parameter is set to an RDOMail object by the event handler,

  //ICS will populate the message properties.

  //This is optional, you can simply do nothing.

  //This parameter is only valid for the sikChanged notifications, other notification

  //kinds (sikDeleted and sikReadStatusChanged) are easy to handle explicitly

  TargetMessage = null; //we may set it below, it is an optional step

  if (Item.Kind == rdoSyncItemKind.sikChanged)

  {

    //this check is not really necessary, you can return a new/existing message

    //We do this just for the simplicity sake so that we won't have to search for an existing message

    if (Item.IsNewMessage)

    {

      //lets create a new message in the Deleted Items folder and let Exchange populate its properties

      //this is optional and not very real-life like, but why not?

      TargetMessage = session.GetDefaultFolder(rdoDefaultFolders.olFolderDeletedItems).Items.Add("IPM.Note");

    }

  }

  //Cancel parameter

  //this is a good place to display a progress UI and allow the user to cancel

  //the sync by setting the Cancel parameter to true

  //Note that RDOFolderSynchronizer.SyncItems/SyncHiddenItems will return a

  //MAPI_E_USER_CANCEL error if the sync is canceled.

  //Cancel = true;

}