Outlook VSTO addin: Iterating through Exceptions of a recurring meeting during deletion (Item_Add to trashfolder) throws COMException - vsto

My Outlook Addin adds info to each AppointmentItem which should get deleted in case the AppointmentItem gets deleted (user hits delete on AppointmentItem in the Calendar).
For this I use the following event handling. On startup I attach to the trash folder:
[...]
Outlook.MAPIFolder trashFolder =
currentExplorer.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderDeletedItems);
_DeletedItems = trashFolder.Items;
_DeletedItems.ItemAdd += Item_Delete_Add;
[...]
Item_Delete_Add gets called when the user hits delete on an appointment series and the user selects "whole series":
[...]
if (myAppointment.RecurrenceState == Outlook.OlRecurrenceState.olApptMaster)
{
DialogResult dialogResult = MessageBox.Show(Resources.Resources.DeleteAllAgreeDoMeetingsInSeries, Resources.Resources.AlsoDeleteAgreeDoMeeting_DialogTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (dialogResult == DialogResult.Yes)
{
Outlook.RecurrencePattern pattern = myAppointment.GetRecurrencePattern();
foreach (Outlook.Exception exception in pattern.Exceptions)
{
if (!deleteAppointment(exception.AppointmentItem,true))
{
wasSuccessfull = false;
}
}
}
} else
[...]
When accessing exception.AppointmentItem the following COMException is thrown:
The german exception description translates to something: You have changed an element of this series. and this instance is not available anymore. Close all elements and try again.
So the question boils down to: How can I handle the deletion of an appointment series in a way so I can handle each exception within the series individually (i.e. delete data stored in that exceptions).

Found the solution here
You just need to check whether the exception has already been deleted by modifying the above code:
[...]
if (myAppointment.RecurrenceState == Outlook.OlRecurrenceState.olApptMaster)
{
DialogResult dialogResult = MessageBox.Show(Resources.Resources.DeleteAllAgreeDoMeetingsInSeries, Resources.Resources.AlsoDeleteAgreeDoMeeting_DialogTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (dialogResult == DialogResult.Yes)
{
Outlook.RecurrencePattern pattern = myAppointment.GetRecurrencePattern();
foreach (Outlook.Exception exception in pattern.Exceptions)
{
if (!exception.Deleted)
{
if (!deleteAppointment(exception.AppointmentItem, true))
{
wasSuccessfull = false;
}
}
}
}
} else
[...]
At least this avoids COMExceptions.

Related

Handling ExecuteFilesInUse with Retry and resolving used files

I'm having a custom Managed Bootstrapper Application for my installer and I am hooking into the ExecuteFilesInUse event to show a UI to the user. In this UI I list the processes provided in the event and 2 buttons: Retry and Cancel. Everything seems to work fine. When I lock some of my files and I press retry, it checks again for files in use. If I press cancel the installation aborts. When I resolve the files in use by closing all applications the installation/uninstallation continues when pressing retry.
But then the problem starts: The msiexec.exe process gets stuck. It utilizes 1 CPU core at 100%. Almost as if it is in an endless loop doing nothing. The logfiles do not contain any details that something is done and nothing happens.
My code looks like this:
// bootstrapper.ExecuteFilesInUse += Bootstrapper_ExecuteFilesInUse;
private void Bootstrapper_ExecuteFilesInUse(object sender, ExecuteFilesInUseEventArgs e)
{
if (!Application.Current.Dispatcher.CheckAccess())
{
Application.Current.Dispatcher.Invoke(new EventHandler<ExecuteFilesInUseEventArgs>(Bootstrapper_ExecuteFilesInUse), sender, e);
return;
}
IList<string> files = e.Files;
if (files == null || files.Count == 0)
{
e.Result = Microsoft.Tools.WindowsInstallerXml.Bootstrapper.Result.Ignore;
return;
}
var hasEmptyRecords = e.Files.Any(string.IsNullOrEmpty);
if (hasEmptyRecords)
{
e.Result = Microsoft.Tools.WindowsInstallerXml.Bootstrapper.Result.Retry;
return;
}
var window = new FilesInUseWindow();
window.DataContext = new FilesInUseViewModel(e.Files);
var result = window.ShowDialog();
if (result == true)
{
e.Result = Microsoft.Tools.WindowsInstallerXml.Bootstrapper.Result.Retry;
}
else
{
Cancelled = true;
e.Result = Microsoft.Tools.WindowsInstallerXml.Bootstrapper.Result.Cancel;
}
}
```
The default WiX bootstrapper by default only provides the options to close them automatically or to schedule a reboot afterwards. But I want the user to manually close everything clean and then continue with the installation. I also attached the debugger ot my MBA to check if ti might be still firing the event
Is this workflow not supported or am I simply doing something wrong here?
Update: I decided also to file an issue on the Wix project page as I was able to reproduce this hang also on a fresh project.

Handling exceptions with in velocity template

How do I handle exceptions with in velocity template when I am processing say 100 records in a loop. If I get an exception while processing one record then I should be able to continue with the next record. Is this possible with velocity template or this needs to be handled in java.
What is the best way to handle exceptions when using velocity templates?
Thanks for your clarification
There is no exception flow control handling inside the template itself. If an exception is thrown, the rendering of the current template will stop and the exception will be logged and displayed in the output. The overall philosophy is to try to contain the exceptions to the Java objects methods.
For instance, instead of exposing Object MyObject.mayThow() into the template, you can use a wrapper:
class MyWrapper
{
bool doesntThrow()
{
try
{
return mayThrow()
}
catch (MyException e)
{
// log it if necessary
return null
}
}
}
And in the template:
#foreach($i in $items)
## ...
#set ($obj = $i.doesntThrow())
#if($obj)
## ...
#end
#end
Instead of a wrapper, you can also use a MethodExceptionEventHandler:
package mypackage;
import org.apache.velocity.app.event.MethodExceptionEventHandler;
public class MyHandler implements MethodExceptionEventHandler
{
public Object methodException(Class claz, String method, Exception e) throws Exception
{
// for instance, return null as a convention
if (claz == MyObject.class && method.equals("doesThrow")) return null;
// something else happened...
else throw e;
}
}
And you can then directly call mayThrow() in the template:
#foreach($i in $items)
## ...
#set ($obj = $i.mayThrow())
#if($obj)
## ...
#end
#end
Of course you have to register your event handler in your velocity.properties file:
eventhandler.methodexception.class = mypackage.MyHandler

How to make an Attended call transfer with UCMA

I'm struggling with making a call transfer in a UMCA IVR app I've built. This is not using Lync.
Essentially, I have an established call from an outside user and as part of the IVR application, they select an option to be transferred. This transfer is to a configured outside number (ie: Our Live Operator). What I want to do is transfer the original caller to the outside number, and if a valid transfer is established, I want to terminate the original call. If the transfer isn't established, I want to send control back to the IVR application to handle this gracefully.
My problem is my EndTransferCall doesn't get hit when the transfer is established. I would have expected it to hit, set my AutoResetEvent and return a True, and then in my application I can disconnect the original call. Can somebody tell me what I'm missing here?
_call is an established AudioVideoCall. My application calls the Transfer method
private AutoResetEvent _waitForTransferComplete = new AutoResetEvent(false);
public override bool Transfer(string number, int retries = 3)
{
var success = false;
var attempt = 0;
CallTransferOptions transferOptions = new CallTransferOptions(CallTransferType.Attended);
while ((attempt < retries) && (success == false))
{
try
{
attempt++;
_call.BeginTransfer(number, transferOptions, EndTransferCall, null);
// Wait for the transfer to complete
_waitForTransferComplete.WaitOne();
success = true;
}
catch (Exception)
{
//TODO: Log that the transfer failed
//TODO: Find out what exceptions get thrown and catch the specific ones
}
}
return success;
}
private void EndTransferCall(IAsyncResult ar)
{
try
{
_call.EndTransfer(ar);
}
catch (OperationFailureException opFailEx)
{
Console.WriteLine(opFailEx.ToString());
}
catch (RealTimeException realTimeEx)
{
Console.WriteLine(realTimeEx.ToString());
}
finally
{
_waitForTransferComplete.Set();
}
}
Is the behavior the same if you don't use the _waitForTransferComplete object? You shouldn't need it - it should be fine that the method ends, the event will still be raised. If you're forcing synchronous behavoir in order to fit in with the rest of the application though, try it like this:
_call.EndTransfer(
_call.BeginTransfer (number,transferOptions,null,null)
);
I'm just wondering if the waiting like that causes a problem if running on a single thread or something...

How to test if an item exists in a telerik grid without causing an exception

I have a rad grid bound to a collection of custom objects and occasionally I grab a rows index like this:
e.Item.ItemIndexHierarchical
at some point later when I want to retrieve that item (if it still exists) I use this:
gvAgendaItems.Items(HierarchicalIndexKey)
The problem is that sometimes the item doesn't exist anymore - and I'm OK with that - but I'd like to gracefully skip over the section of code that is working on the items it can find. As it stands, searching for an item using a no longer valid key throws an exception so I can't just check if the resulting item is nothing.
How can I test if that HierarchicalIndexKey is still valid without throwing an exception?
I don't see any elegant ways to do this in the Telerik documentation (leaving only blunt/inefficient ways, unfortunately).
You could loop through the items and match the HierarchicalIndexKey on each item's ItemIndexHierarchical:
int i = 0;
bool bIndexExists = false;
GridDataItem item = null;
for (i = 0; i <= gvAgendaItems.Items.Count - 1; i++) {
bIndexExists = gvAgendaItems.Items(i).ItemIndexHierarchical == HierarchicalIndexKey;
if (bIndexExists) {
item = gvAgendaItems.Items(HierarchicalIndexKey);
break;
}
}
You could wrap it in a Try/Catch block and simply fail silently.
GridDataItem item = null;
try {
item = gvAgendaItems.Items(HierarchicalIndexKey);
} catch (Exception ex) {
//fail silently without throwing an exception.
}
Looping will be more efficient with smaller datasources, where as wrapping will be more efficient with larger datasources (the breakpoint is when it takes more time to loop through the collection than it does to throw an exception).
Wish I had a better answer, but I hope this helps.

Unsubscribe from IObservableElementEnumerable.EnumerableChanged doesn't work?

Parts of our UI uses IObservableElementEnumerable.EnumerableChanged in order to update if the user e.g. deletes a domain object from a folder.
When the UI is disposed, we unsubscribe from the event... or so we thought. It turns out that the unsubscribe doesn't have any effect, and our event handler is still called. This caused a number of odd bugs, but also leads to memory leaks.
The only time unsubscription works, is if we store the IObservableElementEnumerable reference instead of calling IObservableElementEnumerableFactory.GetEnumerable(obj) again. But this, in turn, is likely to keep a live reference to the folder object, which will break if the folder itself is deleted by the user.
This is particularly puzzling as the GetEnumerable() documentation clearly states: "It is expected that subsequent calls with the same domain object will yield the same instance of IObservableElementEnumerable." Is this not to be interpreted as a guarantee?
Should there be any reason for unsubscription not working?
The following code replicates the issue on Petrel 2011 (add to a simple plugin with a menu extension, or get the full solution here (DropBox)):
using System;
using System.Linq;
using System.Windows.Forms;
using Slb.Ocean.Core;
using Slb.Ocean.Petrel;
using Slb.Ocean.Petrel.Basics;
using Slb.Ocean.Petrel.UI;
namespace ObservableElementEnumerable
{
public class OEEForm : Form
{
private Droid _droid;
private bool _disposed;
public OEEForm()
{
IInput input = PetrelProject.Inputs;
IIdentifiable selected = input.GetSelected<object>().FirstOrDefault() as IIdentifiable;
if (selected == null)
{
PetrelLogger.InfoOutputWindow("Select a folder first");
return;
}
_droid = selected.Droid;
GetEnumerable().EnumerableChanged += enumerable_EnumerableChanged;
PetrelLogger.InfoOutputWindow("Enumerable subscribed");
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing && !_disposed)
{
GetEnumerable().EnumerableChanged -= enumerable_EnumerableChanged;
PetrelLogger.InfoOutputWindow("Enumerable unsubscribed (?)");
_droid = null;
_disposed = true;
}
}
IObservableElementEnumerable GetEnumerable()
{
if (_disposed)
throw new ObjectDisposedException("OEEForm");
object obj = DataManager.Resolve(_droid);
IObservableElementEnumerableFactory factory = CoreSystem.GetService<IObservableElementEnumerableFactory>(obj);
IObservableElementEnumerable enumerable = factory.GetEnumerable(obj);
return enumerable;
}
void enumerable_EnumerableChanged(object sender, ElementEnumerableChangeEventArgs e)
{
PetrelLogger.InfoOutputWindow("Enumerable changed");
if (_disposed)
PetrelLogger.InfoOutputWindow("... but I am disposed and unsubscribed!");
}
}
public static class Menu1
{
public static void OEEBegin1_ToolClick(object sender, System.EventArgs e)
{
OEEForm f = new OEEForm();
f.Show();
}
}
}
To replicate:
Run Petrel with the plugin
Load a project with a folder with objects
Select the folder
Activate the plugin menu item
With the popup open, delete an object in the folder
Close the Form popping up
Delete an object in the folder
The message log should clearly show that the event handler is still called after the form is disposed.
You already keep a reference to the underlying enumerable by connecting the event. Events are references as well. Just keep a reference to the enumerable and unsubscribe from the same instance as the one you subscribe to.
To deal with the issue of objects that are deleted by the user you need to listen to the delete event.