mirror of
https://github.com/Kopano-dev/kopano-ol-extension.git
synced 2023-10-10 13:37:40 +02:00
[KOE-103] Improvements to tracing: added property logging and explicit gc to item events log.
This commit is contained in:
parent
d8b09c1d9d
commit
e05768a399
@ -36,6 +36,7 @@
|
||||
System.Windows.Forms.ColumnHeader columnCount;
|
||||
System.Windows.Forms.ColumnHeader columnId;
|
||||
System.Windows.Forms.ColumnHeader columnEvents;
|
||||
System.Windows.Forms.ColumnHeader columnSubject;
|
||||
this.tableMain = new System.Windows.Forms.TableLayoutPanel();
|
||||
this.flowButtons = new System.Windows.Forms.FlowLayoutPanel();
|
||||
this.buttonGC = new System.Windows.Forms.Button();
|
||||
@ -59,6 +60,9 @@
|
||||
this._layoutItemEvents = new System.Windows.Forms.TableLayoutPanel();
|
||||
this.listItemEventDetails = new System.Windows.Forms.ListView();
|
||||
this.listItemEvents = new System.Windows.Forms.ListView();
|
||||
this._layoutEventsButtons = new System.Windows.Forms.FlowLayoutPanel();
|
||||
this.buttonCleanGC = new System.Windows.Forms.Button();
|
||||
this.columnProperties = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
columnMethod = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
columnFile = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
columnLine = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
@ -66,6 +70,7 @@
|
||||
columnCount = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
columnId = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
columnEvents = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
columnSubject = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.tableMain.SuspendLayout();
|
||||
this.flowButtons.SuspendLayout();
|
||||
this._tabs.SuspendLayout();
|
||||
@ -75,6 +80,7 @@
|
||||
this._layoutLocations.SuspendLayout();
|
||||
this._tabItemEvents.SuspendLayout();
|
||||
this._layoutItemEvents.SuspendLayout();
|
||||
this._layoutEventsButtons.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// columnMethod
|
||||
@ -101,6 +107,14 @@
|
||||
//
|
||||
resources.ApplyResources(columnId, "columnId");
|
||||
//
|
||||
// columnEvents
|
||||
//
|
||||
resources.ApplyResources(columnEvents, "columnEvents");
|
||||
//
|
||||
// columnSubject
|
||||
//
|
||||
resources.ApplyResources(columnSubject, "columnSubject");
|
||||
//
|
||||
// tableMain
|
||||
//
|
||||
resources.ApplyResources(this.tableMain, "tableMain");
|
||||
@ -262,13 +276,15 @@
|
||||
resources.ApplyResources(this._layoutItemEvents, "_layoutItemEvents");
|
||||
this._layoutItemEvents.Controls.Add(this.listItemEventDetails, 0, 1);
|
||||
this._layoutItemEvents.Controls.Add(this.listItemEvents, 0, 0);
|
||||
this._layoutItemEvents.Controls.Add(this._layoutEventsButtons, 0, 2);
|
||||
this._layoutItemEvents.Name = "_layoutItemEvents";
|
||||
//
|
||||
// listItemEventDetails
|
||||
//
|
||||
this.listItemEventDetails.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
|
||||
columnEvent,
|
||||
columnCount});
|
||||
columnCount,
|
||||
this.columnProperties});
|
||||
resources.ApplyResources(this.listItemEventDetails, "listItemEventDetails");
|
||||
this.listItemEventDetails.FullRowSelect = true;
|
||||
this.listItemEventDetails.MultiSelect = false;
|
||||
@ -281,7 +297,8 @@
|
||||
//
|
||||
this.listItemEvents.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
|
||||
columnId,
|
||||
columnEvents});
|
||||
columnEvents,
|
||||
columnSubject});
|
||||
resources.ApplyResources(this.listItemEvents, "listItemEvents");
|
||||
this.listItemEvents.FullRowSelect = true;
|
||||
this.listItemEvents.HideSelection = false;
|
||||
@ -293,9 +310,22 @@
|
||||
this.listItemEvents.View = System.Windows.Forms.View.Details;
|
||||
this.listItemEvents.SelectedIndexChanged += new System.EventHandler(this.listItemEvents_SelectedIndexChanged);
|
||||
//
|
||||
// columnEvents
|
||||
// _layoutEventsButtons
|
||||
//
|
||||
resources.ApplyResources(columnEvents, "columnEvents");
|
||||
resources.ApplyResources(this._layoutEventsButtons, "_layoutEventsButtons");
|
||||
this._layoutEventsButtons.Controls.Add(this.buttonCleanGC);
|
||||
this._layoutEventsButtons.Name = "_layoutEventsButtons";
|
||||
//
|
||||
// buttonCleanGC
|
||||
//
|
||||
resources.ApplyResources(this.buttonCleanGC, "buttonCleanGC");
|
||||
this.buttonCleanGC.Name = "buttonCleanGC";
|
||||
this.buttonCleanGC.UseVisualStyleBackColor = true;
|
||||
this.buttonCleanGC.Click += new System.EventHandler(this.buttonCleanGC_Click);
|
||||
//
|
||||
// columnProperties
|
||||
//
|
||||
resources.ApplyResources(this.columnProperties, "columnProperties");
|
||||
//
|
||||
// DebugDialog
|
||||
//
|
||||
@ -317,6 +347,9 @@
|
||||
this._layoutLocations.ResumeLayout(false);
|
||||
this._tabItemEvents.ResumeLayout(false);
|
||||
this._layoutItemEvents.ResumeLayout(false);
|
||||
this._layoutItemEvents.PerformLayout();
|
||||
this._layoutEventsButtons.ResumeLayout(false);
|
||||
this._layoutEventsButtons.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
@ -347,5 +380,8 @@
|
||||
private System.Windows.Forms.TableLayoutPanel _layoutItemEvents;
|
||||
private System.Windows.Forms.ListView listItemEventDetails;
|
||||
private System.Windows.Forms.ListView listItemEvents;
|
||||
private System.Windows.Forms.FlowLayoutPanel _layoutEventsButtons;
|
||||
private System.Windows.Forms.Button buttonCleanGC;
|
||||
private System.Windows.Forms.ColumnHeader columnProperties;
|
||||
}
|
||||
}
|
@ -158,6 +158,7 @@ namespace Acacia.Features.DebugSupport
|
||||
ListViewItem item = new ListViewItem(events.Id);
|
||||
item.Tag = events;
|
||||
item.SubItems.Add(string.Join(", ", events.GetEvents()));
|
||||
item.SubItems.Add(events.Subject);
|
||||
listItemEvents.Items.Add(item);
|
||||
}
|
||||
|
||||
@ -175,6 +176,10 @@ namespace Acacia.Features.DebugSupport
|
||||
{
|
||||
ListViewItem item = new ListViewItem(evt.ToString());
|
||||
item.SubItems.Add(debug.GetEventCount(evt).ToString());
|
||||
|
||||
if (evt == MailEvents.DebugEvent.PropertyChange)
|
||||
item.SubItems.Add(string.Join(", ", debug.Properties));
|
||||
|
||||
listItemEventDetails.Items.Add(item);
|
||||
}
|
||||
}
|
||||
@ -182,6 +187,12 @@ namespace Acacia.Features.DebugSupport
|
||||
header.Width = -2;
|
||||
}
|
||||
|
||||
private void buttonCleanGC_Click(object sender, EventArgs e)
|
||||
{
|
||||
MailEvents.MailEventsDebugClean();
|
||||
RefreshItemEvents();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cycling
|
||||
|
@ -166,6 +166,18 @@
|
||||
<data name="columnId.Width" type="System.Int32, mscorlib">
|
||||
<value>44</value>
|
||||
</data>
|
||||
<metadata name="columnEvents.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>False</value>
|
||||
</metadata>
|
||||
<data name="columnEvents.Text" xml:space="preserve">
|
||||
<value>Events</value>
|
||||
</data>
|
||||
<metadata name="columnSubject.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>False</value>
|
||||
</metadata>
|
||||
<data name="columnSubject.Text" xml:space="preserve">
|
||||
<value>Subject</value>
|
||||
</data>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="tableMain.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
|
||||
<value>Top, Bottom, Left, Right</value>
|
||||
@ -576,14 +588,17 @@
|
||||
<data name="_layoutItemEvents.ColumnCount" type="System.Int32, mscorlib">
|
||||
<value>1</value>
|
||||
</data>
|
||||
<data name="columnProperties.Text" xml:space="preserve">
|
||||
<value>Properties</value>
|
||||
</data>
|
||||
<data name="listItemEventDetails.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
|
||||
<value>Fill</value>
|
||||
</data>
|
||||
<data name="listItemEventDetails.Location" type="System.Drawing.Point, System.Drawing">
|
||||
<value>3, 328</value>
|
||||
<value>3, 305</value>
|
||||
</data>
|
||||
<data name="listItemEventDetails.Size" type="System.Drawing.Size, System.Drawing">
|
||||
<value>597, 320</value>
|
||||
<value>597, 296</value>
|
||||
</data>
|
||||
<data name="listItemEventDetails.TabIndex" type="System.Int32, mscorlib">
|
||||
<value>3</value>
|
||||
@ -600,12 +615,6 @@
|
||||
<data name=">>listItemEventDetails.ZOrder" xml:space="preserve">
|
||||
<value>0</value>
|
||||
</data>
|
||||
<metadata name="columnEvents.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>False</value>
|
||||
</metadata>
|
||||
<data name="columnEvents.Text" xml:space="preserve">
|
||||
<value>Events</value>
|
||||
</data>
|
||||
<data name="listItemEvents.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
|
||||
<value>Fill</value>
|
||||
</data>
|
||||
@ -613,7 +622,7 @@
|
||||
<value>3, 3</value>
|
||||
</data>
|
||||
<data name="listItemEvents.Size" type="System.Drawing.Size, System.Drawing">
|
||||
<value>597, 319</value>
|
||||
<value>597, 296</value>
|
||||
</data>
|
||||
<data name="listItemEvents.TabIndex" type="System.Int32, mscorlib">
|
||||
<value>2</value>
|
||||
@ -630,6 +639,66 @@
|
||||
<data name=">>listItemEvents.ZOrder" xml:space="preserve">
|
||||
<value>1</value>
|
||||
</data>
|
||||
<data name="_layoutEventsButtons.AutoSize" type="System.Boolean, mscorlib">
|
||||
<value>True</value>
|
||||
</data>
|
||||
<data name="_layoutEventsButtons.AutoSizeMode" type="System.Windows.Forms.AutoSizeMode, System.Windows.Forms">
|
||||
<value>GrowAndShrink</value>
|
||||
</data>
|
||||
<data name="buttonCleanGC.AutoSize" type="System.Boolean, mscorlib">
|
||||
<value>True</value>
|
||||
</data>
|
||||
<data name="buttonCleanGC.Location" type="System.Drawing.Point, System.Drawing">
|
||||
<value>465, 3</value>
|
||||
</data>
|
||||
<data name="buttonCleanGC.Size" type="System.Drawing.Size, System.Drawing">
|
||||
<value>129, 35</value>
|
||||
</data>
|
||||
<data name="buttonCleanGC.TabIndex" type="System.Int32, mscorlib">
|
||||
<value>0</value>
|
||||
</data>
|
||||
<data name="buttonCleanGC.Text" xml:space="preserve">
|
||||
<value>Remove GC</value>
|
||||
</data>
|
||||
<data name=">>buttonCleanGC.Name" xml:space="preserve">
|
||||
<value>buttonCleanGC</value>
|
||||
</data>
|
||||
<data name=">>buttonCleanGC.Type" xml:space="preserve">
|
||||
<value>System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name=">>buttonCleanGC.Parent" xml:space="preserve">
|
||||
<value>_layoutEventsButtons</value>
|
||||
</data>
|
||||
<data name=">>buttonCleanGC.ZOrder" xml:space="preserve">
|
||||
<value>0</value>
|
||||
</data>
|
||||
<data name="_layoutEventsButtons.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
|
||||
<value>Fill</value>
|
||||
</data>
|
||||
<data name="_layoutEventsButtons.FlowDirection" type="System.Windows.Forms.FlowDirection, System.Windows.Forms">
|
||||
<value>RightToLeft</value>
|
||||
</data>
|
||||
<data name="_layoutEventsButtons.Location" type="System.Drawing.Point, System.Drawing">
|
||||
<value>3, 607</value>
|
||||
</data>
|
||||
<data name="_layoutEventsButtons.Size" type="System.Drawing.Size, System.Drawing">
|
||||
<value>597, 41</value>
|
||||
</data>
|
||||
<data name="_layoutEventsButtons.TabIndex" type="System.Int32, mscorlib">
|
||||
<value>4</value>
|
||||
</data>
|
||||
<data name=">>_layoutEventsButtons.Name" xml:space="preserve">
|
||||
<value>_layoutEventsButtons</value>
|
||||
</data>
|
||||
<data name=">>_layoutEventsButtons.Type" xml:space="preserve">
|
||||
<value>System.Windows.Forms.FlowLayoutPanel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name=">>_layoutEventsButtons.Parent" xml:space="preserve">
|
||||
<value>_layoutItemEvents</value>
|
||||
</data>
|
||||
<data name=">>_layoutEventsButtons.ZOrder" xml:space="preserve">
|
||||
<value>2</value>
|
||||
</data>
|
||||
<data name="_layoutItemEvents.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
|
||||
<value>Fill</value>
|
||||
</data>
|
||||
@ -637,7 +706,7 @@
|
||||
<value>3, 3</value>
|
||||
</data>
|
||||
<data name="_layoutItemEvents.RowCount" type="System.Int32, mscorlib">
|
||||
<value>2</value>
|
||||
<value>3</value>
|
||||
</data>
|
||||
<data name="_layoutItemEvents.Size" type="System.Drawing.Size, System.Drawing">
|
||||
<value>603, 651</value>
|
||||
@ -658,7 +727,7 @@
|
||||
<value>0</value>
|
||||
</data>
|
||||
<data name="_layoutItemEvents.LayoutSettings" type="System.Windows.Forms.TableLayoutSettings, System.Windows.Forms">
|
||||
<value><?xml version="1.0" encoding="utf-16"?><TableLayoutSettings><Controls><Control Name="listItemEventDetails" Row="1" RowSpan="1" Column="0" ColumnSpan="1" /><Control Name="listItemEvents" Row="0" RowSpan="1" Column="0" ColumnSpan="1" /></Controls><Columns Styles="Percent,100,Absolute,20" /><Rows Styles="Percent,50,Percent,50" /></TableLayoutSettings></value>
|
||||
<value><?xml version="1.0" encoding="utf-16"?><TableLayoutSettings><Controls><Control Name="listItemEventDetails" Row="1" RowSpan="1" Column="0" ColumnSpan="1" /><Control Name="listItemEvents" Row="0" RowSpan="1" Column="0" ColumnSpan="1" /><Control Name="_layoutEventsButtons" Row="2" RowSpan="1" Column="0" ColumnSpan="1" /></Controls><Columns Styles="Percent,100" /><Rows Styles="Percent,50,Percent,50,AutoSize,0" /></TableLayoutSettings></value>
|
||||
</data>
|
||||
<data name="_tabItemEvents.Location" type="System.Drawing.Point, System.Drawing">
|
||||
<value>4, 33</value>
|
||||
@ -798,6 +867,18 @@
|
||||
<data name=">>columnId.Type" xml:space="preserve">
|
||||
<value>System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name=">>columnEvents.Name" xml:space="preserve">
|
||||
<value>columnEvents</value>
|
||||
</data>
|
||||
<data name=">>columnEvents.Type" xml:space="preserve">
|
||||
<value>System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name=">>columnSubject.Name" xml:space="preserve">
|
||||
<value>columnSubject</value>
|
||||
</data>
|
||||
<data name=">>columnSubject.Type" xml:space="preserve">
|
||||
<value>System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name=">>columnHeader1.Name" xml:space="preserve">
|
||||
<value>columnHeader1</value>
|
||||
</data>
|
||||
@ -822,10 +903,10 @@
|
||||
<data name=">>columnHeader4.Type" xml:space="preserve">
|
||||
<value>System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name=">>columnEvents.Name" xml:space="preserve">
|
||||
<value>columnEvents</value>
|
||||
<data name=">>columnProperties.Name" xml:space="preserve">
|
||||
<value>columnProperties</value>
|
||||
</data>
|
||||
<data name=">>columnEvents.Type" xml:space="preserve">
|
||||
<data name=">>columnProperties.Type" xml:space="preserve">
|
||||
<value>System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name=">>$this.Name" xml:space="preserve">
|
||||
|
@ -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,
|
||||
@ -380,8 +380,10 @@ namespace Acacia.Utils
|
||||
public interface MailEventDebug
|
||||
{
|
||||
string Id { get; }
|
||||
string Subject { get; }
|
||||
int GetEventCount(DebugEvent which);
|
||||
IEnumerable<DebugEvent> GetEvents();
|
||||
IEnumerable<string> Properties { get; }
|
||||
}
|
||||
|
||||
public static IEnumerable<MailEventDebug> MailEventsDebug
|
||||
@ -389,6 +391,18 @@ namespace Acacia.Utils
|
||||
get { return _hookers?.Values; }
|
||||
}
|
||||
|
||||
public static void MailEventsDebugClean()
|
||||
{
|
||||
foreach (MailEventDebugImpl impl in _hookers.Values)
|
||||
{
|
||||
if (impl.GetEventCount(DebugEvent.GC) > 0)
|
||||
{
|
||||
MailEventDebugImpl dummy;
|
||||
_hookers.TryRemove(impl._id, out dummy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ConcurrentDictionary<int, MailEventDebugImpl> _hookers =
|
||||
GlobalOptions.INSTANCE.WrapperTrace ? new ConcurrentDictionary<int, MailEventDebugImpl>() : null;
|
||||
private static int _nextHookerId;
|
||||
@ -423,14 +437,13 @@ namespace Acacia.Utils
|
||||
if (_debug != null)
|
||||
{
|
||||
_debug.RecordEvent(DebugEvent.Dispose);
|
||||
MailEventDebugImpl dummy;
|
||||
_hookers.TryRemove(_debug._id, out dummy);
|
||||
}
|
||||
}
|
||||
|
||||
~MailEventHooker()
|
||||
{
|
||||
_debug?.RecordEvent(DebugEvent.GC);
|
||||
_debug?.Finished();
|
||||
}
|
||||
|
||||
private void HookEvents(bool add)
|
||||
@ -478,13 +491,17 @@ namespace Acacia.Utils
|
||||
|
||||
private void HandlePropertyChange(string name)
|
||||
{
|
||||
_debug?.RecordEvent(DebugEvent.PropertyChange);
|
||||
_debug?.RecordEvent(DebugEvent.PropertyChange, name);
|
||||
_events.OnPropertyChange(_item, name);
|
||||
}
|
||||
|
||||
private void HandleRead()
|
||||
{
|
||||
_debug?.RecordEvent(DebugEvent.Read);
|
||||
if (_debug != null)
|
||||
{
|
||||
_debug.RecordEvent(DebugEvent.Read);
|
||||
_debug.Subject = _item.Subject;
|
||||
}
|
||||
// TODO: should this not be simply an IItem?
|
||||
_events.OnRead(_item as IMailItem);
|
||||
}
|
||||
@ -521,8 +538,14 @@ namespace Acacia.Utils
|
||||
private class MailEventDebugImpl : MailEventDebug
|
||||
{
|
||||
private readonly ConcurrentDictionary<DebugEvent, int> _eventCounts = new ConcurrentDictionary<DebugEvent, int>();
|
||||
private readonly List<string> _properties = new List<string>();
|
||||
|
||||
public readonly int _id;
|
||||
public DateTime? GCTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public MailEventDebugImpl(int id)
|
||||
{
|
||||
@ -536,9 +559,11 @@ namespace Acacia.Utils
|
||||
return count;
|
||||
}
|
||||
|
||||
public void RecordEvent(DebugEvent which)
|
||||
public void RecordEvent(DebugEvent which, string property = null)
|
||||
{
|
||||
_eventCounts.AddOrUpdate(which, 1, (i, value) => value + 1);
|
||||
if (property != null)
|
||||
_properties.Add(property);
|
||||
}
|
||||
|
||||
public IEnumerable<DebugEvent> GetEvents()
|
||||
@ -546,7 +571,18 @@ namespace Acacia.Utils
|
||||
return _eventCounts.Keys;
|
||||
}
|
||||
|
||||
public IEnumerable<string> Properties
|
||||
{
|
||||
get { return _properties; }
|
||||
}
|
||||
|
||||
public void Finished()
|
||||
{
|
||||
GCTime = DateTime.Now;
|
||||
}
|
||||
|
||||
public string Id { get { return _id.ToString(); } }
|
||||
public string Subject { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
Loading…
Reference in New Issue
Block a user