|
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:
-
Retrieve RDOFolderSynchronizer
object by calling RDOFolder2.ExchangeSynchronizer
-
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.
-
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.
-
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;
}
|
|
|