mirror of
https://github.com/Kopano-dev/kopano-ol-extension.git
synced 2023-10-10 13:37:40 +02:00
Cleaned up user property handling, to prevent leaking COM objects
This commit is contained in:
parent
bda1d7908a
commit
b952f46ff9
@ -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>
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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; }
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
||||
|
@ -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(); }
|
||||
|
@ -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)
|
@ -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(); }
|
||||
|
@ -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(); }
|
||||
|
@ -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(); }
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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(); }
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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(); }
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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}";
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user