Cleaned up user property handling, to prevent leaking COM objects

This commit is contained in:
Patrick Simpson 2017-02-07 13:49:26 +01:00
parent bda1d7908a
commit b952f46ff9
19 changed files with 118 additions and 137 deletions

View File

@ -279,6 +279,8 @@
<Compile Include="Native\MAPI.cs" />
<Compile Include="Native\IOleWindow.cs" />
<Compile Include="OutlookConstants.cs" />
<Compile Include="Stubs\IComWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\OutlookItemWrapper.cs" />
<Compile Include="UI\Outlook\OutlookImageList.cs" />
<Compile Include="UI\Outlook\RibbonToggleButton.cs" />
<Compile Include="UI\Outlook\RibbonButton.cs" />
@ -310,7 +312,7 @@
<Compile Include="ZPush\Connect\Soap\SoapRequestEncoder.cs" />
<Compile Include="ZPush\Connect\Soap\SoapRequest.cs" />
<Compile Include="Stubs\ItemType.cs" />
<Compile Include="Stubs\OutlookWrappers\DisposableWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\ComWrapper.cs" />
<Compile Include="UI\FeatureSettings.cs">
<SubType>UserControl</SubType>
</Compile>
@ -396,7 +398,6 @@
<Compile Include="Stubs\INoteItem.cs" />
<Compile Include="Stubs\ISearch.cs" />
<Compile Include="Stubs\IStorageItem.cs" />
<Compile Include="Stubs\IUserProperty.cs" />
<Compile Include="Stubs\IZPushItem.cs" />
<Compile Include="Stubs\OutlookWrappers\AddressBookWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\DistributionListWrapper.cs" />
@ -411,7 +412,6 @@
<Compile Include="Stubs\OutlookWrappers\SearchWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\StorageItemWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\StoreWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\UserPropertyWrapper.cs" />
<Compile Include="Stubs\IStore.cs" />
<Compile Include="UI\ProgressDialog.cs">
<SubType>Form</SubType>

View File

@ -297,7 +297,7 @@ namespace Acacia.Features.GAB
{
using (IStorageItem index = GetIndexItem())
{
return index?.GetUserProperty<int>(PROP_CURRENT_SEQUENCE)?.Value;
return index?.GetUserProperty<int?>(PROP_CURRENT_SEQUENCE);
}
}
set
@ -306,7 +306,7 @@ namespace Acacia.Features.GAB
{
if (value != null)
{
index.GetUserProperty<int>(PROP_CURRENT_SEQUENCE, true).Value = value.Value;
index.SetUserProperty<int>(PROP_CURRENT_SEQUENCE, value.Value);
}
else
{
@ -353,7 +353,8 @@ namespace Acacia.Features.GAB
{
Logger.Instance.Trace(this, "Newest chunk: {0}", newestChunkIndex.Value);
if (!CurrentSequence.HasValue || CurrentSequence.Value != newestChunkIndex?.numberOfChunks)
int? currentSequence = CurrentSequence;
if (!currentSequence.HasValue || currentSequence.Value != newestChunkIndex?.numberOfChunks)
{
// Sequence has changed. Delete contacts
Logger.Instance.Trace(this, "Rechunked, deleting contacts");
@ -373,8 +374,8 @@ namespace Acacia.Features.GAB
int numberOfChunks = newestChunkIndex.Value.numberOfChunks;
using (IStorageItem index = GetIndexItem())
{
index.GetUserProperty<int>(PROP_CURRENT_SEQUENCE, true).Value = numberOfChunks;
index.GetUserProperty<string>(PROP_LAST_PROCESSED, true).Value = CreateChunkStateString(numberOfChunks);
index.SetUserProperty(PROP_CURRENT_SEQUENCE, numberOfChunks);
index.SetUserProperty(PROP_LAST_PROCESSED, CreateChunkStateString(numberOfChunks));
index.Save();
}
}
@ -404,7 +405,7 @@ namespace Acacia.Features.GAB
{
if (item == null)
return null;
string state = item.GetUserProperty<string>(PROP_LAST_PROCESSED)?.Value;
string state = item.GetUserProperty<string>(PROP_LAST_PROCESSED);
if (string.IsNullOrEmpty(state))
return null;
@ -422,7 +423,7 @@ namespace Acacia.Features.GAB
{
using (IStorageItem item = GetIndexItem())
{
string state = item.GetUserProperty<string>(PROP_LAST_PROCESSED)?.Value;
string state = item.GetUserProperty<string>(PROP_LAST_PROCESSED);
string[] parts;
if (string.IsNullOrEmpty(state))
parts = new string[index.numberOfChunks];
@ -436,7 +437,7 @@ namespace Acacia.Features.GAB
parts[index.chunk] = partState;
string combined = string.Join(";", parts);
item.GetUserProperty<string>(PROP_LAST_PROCESSED, true).Value = combined;
item.SetUserProperty(PROP_LAST_PROCESSED, combined);
item.Save();
}
}
@ -623,9 +624,9 @@ namespace Acacia.Features.GAB
private void SetItemStandard(IItem item, string id, Dictionary<string, object> value, ChunkIndex index)
{
// Set the chunk data
item.GetUserProperty<int>(PROP_SEQUENCE, true).Value = index.numberOfChunks;
item.GetUserProperty<int>(PROP_CHUNK, true).Value = index.chunk;
item.GetUserProperty<string>(PROP_GAB_ID, true).Value = id;
item.SetUserProperty(PROP_SEQUENCE, index.numberOfChunks);
item.SetUserProperty(PROP_CHUNK, index.chunk);
item.SetUserProperty(PROP_GAB_ID, id);
}
private void AddGroupMember(IDistributionList group, IItem item)

View File

@ -22,7 +22,7 @@ using System.Threading.Tasks;
namespace Acacia.Stubs
{
public interface IBase : IDisposable
public interface IBase : IComWrapper
{
#region MAPI properties
@ -49,8 +49,6 @@ namespace Acacia.Stubs
string StoreDisplayName { get; }
void Delete();
bool MustRelease { get; set; }
string ToString();
}
}

View File

@ -1,4 +1,4 @@
/// Copyright 2016 Kopano b.v.
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
@ -13,7 +13,6 @@
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Linq;
@ -22,17 +21,8 @@ using System.Threading.Tasks;
namespace Acacia.Stubs
{
public interface IUserProperty<Type>
public interface IComWrapper : IDisposable
{
#region Properties
Type Value
{
get;
set;
}
#endregion
bool MustRelease { get; set; }
}
}

View File

@ -39,11 +39,21 @@ namespace Acacia.Stubs
#region User properties
/// <summary>
/// Retrieves the user property with the specified name.
/// Retrieves the item's user property with the specified name.
/// </summary>
/// <param name="create">If true, the property is created if it does not exist.
/// If false, null is returned in this case</param>
IUserProperty<Type> GetUserProperty<Type>(string name, bool create = false);
/// <typeparam name="Type">The property type.</typeparam>
/// <param name="name">The name of the property.</param>
/// <returns>The property's value, if it exists. If it does not exist, the type's default value is returned.
/// A nullable type can be specified to return null if the property does not exist.</returns>
Type GetUserProperty<Type>(string name);
/// <summary>
/// Sets the property.
/// </summary>
/// <typeparam name="Type">The property type</typeparam>
/// <param name="name"></param>
/// <param name="value"></param>
void SetUserProperty<Type>(string name, Type value);
#endregion

View File

@ -24,7 +24,7 @@ using Acacia.Utils;
namespace Acacia.Stubs.OutlookWrappers
{
class AppointmentItemWrapper : OutlookWrapper<AppointmentItem>, IAppointmentItem, IZPushItem
class AppointmentItemWrapper : OutlookItemWrapper<AppointmentItem>, IAppointmentItem, IZPushItem
{
internal AppointmentItemWrapper(AppointmentItem item)
@ -79,9 +79,9 @@ namespace Acacia.Stubs.OutlookWrappers
#region Methods
public IUserProperty<Type> GetUserProperty<Type>(string name, bool create = false)
protected override UserProperties GetUserProperties()
{
return UserPropertyWrapper<Type>.Get(_item.UserProperties, name, create);
return _item.UserProperties;
}
public void Delete() { _item.Delete(); }

View File

@ -25,19 +25,19 @@ using System.Threading.Tasks;
namespace Acacia.Stubs.OutlookWrappers
{
public abstract class DisposableWrapper : IDisposable
public abstract class ComWrapper : IComWrapper
{
/// <summary>
/// Creates a wrapper.
/// </summary>
internal DisposableWrapper()
internal ComWrapper()
{
Interlocked.Increment(ref Statistics.CreatedWrappers);
this._createdTrace = new System.Diagnostics.StackTrace();
}
~DisposableWrapper()
~ComWrapper()
{
Interlocked.Increment(ref Statistics.DeletedWrappers);
if (!_isDisposed)

View File

@ -24,7 +24,7 @@ using System.Threading.Tasks;
namespace Acacia.Stubs.OutlookWrappers
{
class ContactItemWrapper : OutlookWrapper<ContactItem>, IContactItem
class ContactItemWrapper : OutlookItemWrapper<ContactItem>, IContactItem
{
internal ContactItemWrapper(ContactItem item)
:
@ -200,9 +200,9 @@ namespace Acacia.Stubs.OutlookWrappers
public string StoreId { get { return _item.Parent?.Store?.StoreID; } }
public string StoreDisplayName { get { return _item.Parent?.Store?.DisplayName; } }
public IUserProperty<Type> GetUserProperty<Type>(string name, bool create = false)
protected override UserProperties GetUserProperties()
{
return UserPropertyWrapper<Type>.Get(_item.UserProperties, name, create);
return _item.UserProperties;
}
public void Delete() { _item.Delete(); }

View File

@ -24,7 +24,7 @@ using Acacia.Utils;
namespace Acacia.Stubs.OutlookWrappers
{
class DistributionListWrapper : OutlookWrapper<DistListItem>, IDistributionList
class DistributionListWrapper : OutlookItemWrapper<DistListItem>, IDistributionList
{
internal DistributionListWrapper(DistListItem item)
:
@ -90,9 +90,9 @@ namespace Acacia.Stubs.OutlookWrappers
#region Methods
public IUserProperty<Type> GetUserProperty<Type>(string name, bool create = false)
protected override UserProperties GetUserProperties()
{
return UserPropertyWrapper<Type>.Get(_item.UserProperties, name, create);
return _item.UserProperties;
}
public void Delete() { _item.Delete(); }

View File

@ -24,7 +24,7 @@ using Acacia.Utils;
namespace Acacia.Stubs.OutlookWrappers
{
class MailItemWrapper : OutlookWrapper<MailItem>, IMailItem
class MailItemWrapper : OutlookItemWrapper<MailItem>, IMailItem
{
internal MailItemWrapper(MailItem item)
:
@ -132,9 +132,9 @@ namespace Acacia.Stubs.OutlookWrappers
#region Methods
public IUserProperty<Type> GetUserProperty<Type>(string name, bool create = false)
protected override UserProperties GetUserProperties()
{
return UserPropertyWrapper<Type>.Get(_item.UserProperties, name, create);
return _item.UserProperties;
}
public void Delete() { _item.Delete(); }

View File

@ -24,7 +24,7 @@ using System.Threading.Tasks;
namespace Acacia.Stubs.OutlookWrappers
{
public class NoteItemWrapper : OutlookWrapper<NoteItem>, INoteItem
public class NoteItemWrapper : OutlookItemWrapper<NoteItem>, INoteItem
{
internal NoteItemWrapper(NoteItem item)
:
@ -62,8 +62,9 @@ namespace Acacia.Stubs.OutlookWrappers
#region Methods
public IUserProperty<Type> GetUserProperty<Type>(string name, bool create = false)
protected override UserProperties GetUserProperties()
{
// Note item doesn't have user properties
throw new NotSupportedException();
}

View File

@ -0,0 +1,57 @@
using Acacia.Utils;
using Microsoft.Office.Interop.Outlook;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Stubs.OutlookWrappers
{
abstract public class OutlookItemWrapper<ItemType> : OutlookWrapper<ItemType>
{
public OutlookItemWrapper(ItemType item)
:
base(item)
{
}
public Type GetUserProperty<Type>(string name)
{
using (ComRelease com = new ComRelease())
{
UserProperties userProperties = com.Add(GetUserProperties());
UserProperty prop = com.Add(userProperties.Find(name, true));
if (prop == null)
return default(Type);
if (typeof(Type).IsEnum)
return typeof(Type).GetEnumValues().GetValue(prop.Value);
return prop.Value;
}
}
public void SetUserProperty<Type>(string name, Type value)
{
using (ComRelease com = new ComRelease())
{
UserProperties userProperties = com.Add(GetUserProperties());
UserProperty prop = com.Add(userProperties.Find(name, true));
if (prop == null)
prop = userProperties.Add(name, Mapping.OutlookPropertyType<Type>());
if (typeof(Type).IsEnum)
{
int i = Array.FindIndex(typeof(Type).GetEnumNames(), n => n.Equals(value.ToString()));
prop.Value = typeof(Type).GetEnumValues().GetValue(i);
}
else
{
prop.Value = value;
}
}
}
abstract protected UserProperties GetUserProperties();
}
}

View File

@ -29,7 +29,7 @@ namespace Acacia.Stubs.OutlookWrappers
/// <summary>
/// Helper for Outlook wrapper implementations
/// </summary>
abstract public class OutlookWrapper<ItemType> : DisposableWrapper
abstract public class OutlookWrapper<ItemType> : ComWrapper
{
#region Construction / Destruction

View File

@ -24,7 +24,7 @@ using System.Threading.Tasks;
namespace Acacia.Stubs.OutlookWrappers
{
public class StorageItemWrapper : OutlookWrapper<StorageItem>, IStorageItem
public class StorageItemWrapper : OutlookItemWrapper<StorageItem>, IStorageItem
{
public StorageItemWrapper(StorageItem item)
:
@ -62,9 +62,9 @@ namespace Acacia.Stubs.OutlookWrappers
#region Methods
public IUserProperty<Type> GetUserProperty<Type>(string name, bool create = false)
protected override UserProperties GetUserProperties()
{
return UserPropertyWrapper<Type>.Get(_item.UserProperties, name, create);
return _item.UserProperties;
}
public void Delete() { _item.Delete(); }

View File

@ -24,7 +24,7 @@ using Acacia.Utils;
namespace Acacia.Stubs.OutlookWrappers
{
public class StoreWrapper : DisposableWrapper, IStore
public class StoreWrapper : ComWrapper, IStore
{
public static IStore Wrap(Store store)
{

View File

@ -24,7 +24,7 @@ using System.Threading.Tasks;
namespace Acacia.Stubs.OutlookWrappers
{
public class TaskItemWrapper : OutlookWrapper<TaskItem>, ITaskItem
public class TaskItemWrapper : OutlookItemWrapper<TaskItem>, ITaskItem
{
internal TaskItemWrapper(TaskItem item)
:
@ -62,9 +62,9 @@ namespace Acacia.Stubs.OutlookWrappers
#region Methods
public IUserProperty<Type> GetUserProperty<Type>(string name, bool create = false)
protected override UserProperties GetUserProperties()
{
throw new NotSupportedException();
return _item.UserProperties;
}
public void Delete() { _item.Delete(); }

View File

@ -1,76 +0,0 @@
/// Copyright 2016 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program is distributed in the hope that it will be useful,
/// but WITHOUT ANY WARRANTY; without even the implied warranty of
/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
/// GNU Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers
{
class UserPropertyWrapper<PropType> : IUserProperty<PropType>
{
private readonly UserProperty _prop;
private UserPropertyWrapper(UserProperty prop)
{
this._prop = prop;
}
#region IUserProperty implementation
public PropType Value
{
get
{
if (typeof(PropType).IsEnum)
return typeof(PropType).GetEnumValues().GetValue(_prop.Value);
return _prop.Value;
}
set
{
if (typeof(PropType).IsEnum)
{
int i = Array.FindIndex(typeof(PropType).GetEnumNames(), n => n.Equals(value.ToString()));
_prop.Value = typeof(PropType).GetEnumValues().GetValue(i);
}
else
_prop.Value = value;
}
}
#endregion
#region Helpers
internal static IUserProperty<PropType> Get(UserProperties userProperties, string name, bool create)
{
UserProperty prop = userProperties.Find(name, true);
if (prop == null)
{
if (!create)
return null;
prop = userProperties.Add(name, Mapping.OutlookPropertyType<PropType>());
}
return new UserPropertyWrapper<PropType>(prop);
}
#endregion
}
}

View File

@ -252,7 +252,7 @@ namespace Acacia.ZPush.Connect
}
}
private class Request : DisposableWrapper
private class Request : ComWrapper
{
private const string ACTIVESYNC_URL = "https://{0}/Microsoft-Server-ActiveSync?DeviceId={1}&Cmd={2}&User={3}&DeviceType={4}";

View File

@ -31,7 +31,7 @@ namespace Acacia.ZPush
/// Manages a local store in which Z-Push data is stored.
/// </summary>
/// TODO: merge with Store where possible
public class ZPushLocalStore : DisposableWrapper
public class ZPushLocalStore : ComWrapper
{
private Store _store;