mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-11-23 20:36:33 +01:00
Added Events in progress to the outlook-style status labels of the main window.
- Reordered the labels to reduce flickering for short actions. - No need to fire action events in a try-catch block. Signed-off-by: Konstantina Chremmou <konstantina.chremmou@citrix.com>
This commit is contained in:
parent
8842e48b05
commit
e5bee7be6a
@ -48,8 +48,6 @@ namespace XenAdmin.Dialogs
|
||||
public Session elevatedSession;
|
||||
public string elevatedPassword;
|
||||
public string elevatedUsername;
|
||||
public string originalUsername;
|
||||
public string originalPassword;
|
||||
|
||||
private List<Role> authorizedRoles;
|
||||
|
||||
@ -72,8 +70,6 @@ namespace XenAdmin.Dialogs
|
||||
labelRequiredRoleValue.Text = Role.FriendlyCSVRoleList(authorizedRoles);
|
||||
labelServerValue.Text = Helpers.GetName(connection);
|
||||
labelServer.Text = Helpers.IsPool(connection) ? Messages.POOL_COLON : Messages.SERVER_COLON;
|
||||
originalUsername = session.Connection.Username;
|
||||
originalPassword = session.Connection.Password;
|
||||
|
||||
if (string.IsNullOrEmpty(actionTitle))
|
||||
{
|
||||
|
4
XenAdmin/MainWindow.Designer.cs
generated
4
XenAdmin/MainWindow.Designer.cs
generated
@ -1890,9 +1890,9 @@ namespace XenAdmin
|
||||
this.StatusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.statusProgressBar,
|
||||
this.statusLabel,
|
||||
this.statusLabelAlerts,
|
||||
this.statusLabelErrors,
|
||||
this.statusLabelUpdates,
|
||||
this.statusLabelErrors});
|
||||
this.statusLabelAlerts});
|
||||
this.StatusStrip.Name = "StatusStrip";
|
||||
this.StatusStrip.ShowItemToolTips = true;
|
||||
//
|
||||
|
@ -408,84 +408,60 @@ namespace XenAdmin
|
||||
TabPage.Controls.Add(contents);
|
||||
}
|
||||
|
||||
void History_CollectionChanged(object sender, CollectionChangeEventArgs e)
|
||||
private void History_CollectionChanged(object sender, CollectionChangeEventArgs e)
|
||||
{
|
||||
if (Program.Exiting)
|
||||
return;
|
||||
|
||||
Program.BeginInvoke(Program.MainWindow, () =>
|
||||
switch (e.Action)
|
||||
{
|
||||
ActionBase action = e.Element as ActionBase;
|
||||
|
||||
switch (e.Action)
|
||||
{
|
||||
case CollectionChangeAction.Add:
|
||||
case CollectionChangeAction.Add:
|
||||
if (e.Element is ActionBase action)
|
||||
{
|
||||
if (!(action is MeddlingAction))
|
||||
{
|
||||
if (action == null)
|
||||
return;
|
||||
|
||||
var meddlingAction = action as MeddlingAction;
|
||||
if (meddlingAction == null)
|
||||
Program.Invoke(this, () =>
|
||||
{
|
||||
SetStatusBar(null, null);
|
||||
if (statusBarAction != null)
|
||||
{
|
||||
statusBarAction.Changed -= actionChanged;
|
||||
statusBarAction.Completed -= actionCompleted;
|
||||
}
|
||||
statusBarAction = action;
|
||||
}
|
||||
action.Changed += actionChanged;
|
||||
action.Completed += actionCompleted;
|
||||
actionChanged(action);
|
||||
break;
|
||||
});
|
||||
}
|
||||
case CollectionChangeAction.Remove:
|
||||
|
||||
action.Changed += actionChanged;
|
||||
action.Completed += actionCompleted;
|
||||
actionChanged(action);
|
||||
}
|
||||
break;
|
||||
|
||||
case CollectionChangeAction.Remove:
|
||||
if (e.Element is ActionBase actionB)
|
||||
{
|
||||
actionB.Changed -= actionChanged;
|
||||
actionB.Completed -= actionCompleted;
|
||||
}
|
||||
else if (e.Element is List<ActionBase> range)
|
||||
{
|
||||
foreach (var a in range)
|
||||
{
|
||||
if (action != null)
|
||||
{
|
||||
action.Changed -= actionChanged;
|
||||
action.Completed -= actionCompleted;
|
||||
}
|
||||
else
|
||||
{
|
||||
var range = e.Element as List<ActionBase>;
|
||||
if (range != null)
|
||||
{
|
||||
foreach (var a in range)
|
||||
{
|
||||
a.Changed -= actionChanged;
|
||||
a.Completed -= actionCompleted;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int errorCount = ConnectionsManager.History.Count(a => a.IsCompleted && !a.Succeeded);
|
||||
navigationPane.UpdateNotificationsButton(NotificationsSubMode.Events, errorCount);
|
||||
|
||||
statusLabelErrors.Text = errorCount == 1
|
||||
? Messages.NOTIFICATIONS_SUBMODE_EVENTS_STATUS_ONE
|
||||
: string.Format(Messages.NOTIFICATIONS_SUBMODE_EVENTS_STATUS_MANY, errorCount);
|
||||
statusLabelErrors.Visible = errorCount > 0;
|
||||
|
||||
if (eventsPage.Visible)
|
||||
{
|
||||
TitleLabel.Text = NotificationsSubModeItem.GetText(NotificationsSubMode.Events, errorCount);
|
||||
TitleIcon.Image = NotificationsSubModeItem.GetImage(NotificationsSubMode.Events, errorCount);
|
||||
}
|
||||
break;
|
||||
a.Changed -= actionChanged;
|
||||
a.Completed -= actionCompleted;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
UpdateErrorStatusLabel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void actionCompleted(ActionBase action)
|
||||
{
|
||||
action.Changed -= actionChanged;
|
||||
action.Completed -= actionCompleted;
|
||||
|
||||
actionChanged(action);
|
||||
|
||||
if (action is SrAction)
|
||||
Program.Invoke(this, UpdateToolbars);
|
||||
}
|
||||
@ -495,55 +471,70 @@ namespace XenAdmin
|
||||
if (Program.Exiting)
|
||||
return;
|
||||
|
||||
Program.Invoke(this, () => actionChanged_(action));
|
||||
Program.Invoke(this, () =>
|
||||
{
|
||||
UpdateStatusProgressBar(action);
|
||||
UpdateErrorStatusLabel();
|
||||
});
|
||||
}
|
||||
|
||||
private void actionChanged_(ActionBase action)
|
||||
private void UpdateStatusProgressBar(ActionBase action)
|
||||
{
|
||||
// suppress updates when the PureAsyncAction runs the action to populate the ApiMethodsToRoleCheck
|
||||
if (action.SuppressProgressReport)
|
||||
if (statusBarAction != action)
|
||||
return;
|
||||
|
||||
statusProgressBar.Visible = action.ShowProgress && !action.IsCompleted;
|
||||
|
||||
var percentage = action.PercentComplete;
|
||||
Debug.Assert(0 <= percentage && percentage <= 100,
|
||||
"PercentComplete is out of range, the reporting action needs to be fixed."); //CA-8517
|
||||
|
||||
var meddlingAction = action as MeddlingAction;
|
||||
if (meddlingAction == null)
|
||||
if (percentage < 0)
|
||||
percentage = 0;
|
||||
else if (percentage > 100)
|
||||
percentage = 100;
|
||||
statusProgressBar.Value = percentage;
|
||||
|
||||
// Don't show cancelled exception
|
||||
if (action.Exception != null && !(action.Exception is CancelledException))
|
||||
{
|
||||
statusProgressBar.Visible = action.ShowProgress && !action.IsCompleted;
|
||||
|
||||
if (percentage < 0)
|
||||
percentage = 0;
|
||||
else if (percentage > 100)
|
||||
percentage = 100;
|
||||
statusProgressBar.Value = percentage;
|
||||
|
||||
// Don't show cancelled exception
|
||||
if (action.Exception != null && !(action.Exception is CancelledException))
|
||||
{
|
||||
SetStatusBar(Images.StaticImages._000_error_h32bit_16, action.Exception.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetStatusBar(null, action.IsCompleted
|
||||
? null
|
||||
: !string.IsNullOrEmpty(action.Description)
|
||||
? action.Description
|
||||
: !string.IsNullOrEmpty(action.Title)
|
||||
? action.Title
|
||||
: null);
|
||||
}
|
||||
SetStatusBar(Images.StaticImages._000_error_h32bit_16, action.Exception.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetStatusBar(null, action.IsCompleted
|
||||
? null
|
||||
: !string.IsNullOrEmpty(action.Description)
|
||||
? action.Description
|
||||
: !string.IsNullOrEmpty(action.Title)
|
||||
? action.Title
|
||||
: null);
|
||||
}
|
||||
}
|
||||
|
||||
int errorCount = ConnectionsManager.History.Count(a => a.IsCompleted && !a.Succeeded && !(a is CancellingAction && ((CancellingAction)a).Cancelled));
|
||||
private void UpdateErrorStatusLabel()
|
||||
{
|
||||
int errorCount = ConnectionsManager.History.Count(a =>
|
||||
a.IsCompleted && !a.Succeeded && !(a is CancellingAction ca && ca.Cancelled));
|
||||
|
||||
navigationPane.UpdateNotificationsButton(NotificationsSubMode.Events, errorCount);
|
||||
|
||||
statusLabelErrors.Text = errorCount == 1
|
||||
? Messages.NOTIFICATIONS_SUBMODE_EVENTS_STATUS_ONE
|
||||
: string.Format(Messages.NOTIFICATIONS_SUBMODE_EVENTS_STATUS_MANY, errorCount);
|
||||
statusLabelErrors.Visible = errorCount > 0;
|
||||
var errorText = errorCount == 1
|
||||
? Messages.NOTIFICATIONS_SUBMODE_EVENTS_STATUS_ERROR
|
||||
: string.Format(Messages.NOTIFICATIONS_SUBMODE_EVENTS_STATUS_ERRORS, errorCount);
|
||||
|
||||
int progressCount = ConnectionsManager.History.Count(a => !a.IsCompleted);
|
||||
var progressText = string.Format(Messages.NOTIFICATIONS_SUBMODE_EVENTS_STATUS_IN_PROGRESS, progressCount);
|
||||
|
||||
if (errorCount > 0 && progressCount > 0)
|
||||
statusLabelErrors.Text = string.Format(Messages.NOTIFICATIONS_SUBMODE_EVENTS_STATUS,
|
||||
string.Format(Messages.STRING_COMMA_SPACE_STRING, errorText, progressText));
|
||||
else if (errorCount > 0)
|
||||
statusLabelErrors.Text = string.Format(Messages.NOTIFICATIONS_SUBMODE_EVENTS_STATUS, errorText);
|
||||
else if (progressCount > 0)
|
||||
statusLabelErrors.Text = string.Format(Messages.NOTIFICATIONS_SUBMODE_EVENTS_STATUS, progressText);
|
||||
|
||||
statusLabelErrors.Visible = errorCount > 0 || progressCount > 0;
|
||||
|
||||
if (eventsPage.Visible)
|
||||
{
|
||||
|
@ -53,7 +53,7 @@ namespace XenAdmin.SettingsPanels
|
||||
{
|
||||
get
|
||||
{
|
||||
return string.Format(Messages.SIZE_LOCATION_SUB,
|
||||
return string.Format(Messages.STRING_COMMA_SPACE_STRING,
|
||||
Util.DiskSizeString(diskSpinner1.CanResize ? diskSpinner1.SelectedSize : vdi.virtual_size, 2),
|
||||
vdi.Connection.Resolve<SR>(vdi.SR));
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ namespace XenAdmin.Wizards.NewVMWizard
|
||||
|
||||
var disk = new VDI
|
||||
{
|
||||
name_label = string.Format(Messages.NEWVMWIZARD_STORAGEPAGE_VDINAME, SelectedName, device.userdevice),
|
||||
name_label = string.Format(Messages.STRING_SPACE_STRING, SelectedName, device.userdevice),
|
||||
name_description = Messages.NEWVMWIZARD_STORAGEPAGE_DISK_DESCRIPTION,
|
||||
virtual_size = diskSize,
|
||||
type = (vdi_type)Enum.Parse(typeof(vdi_type), diskNode.Attributes["type"].Value),
|
||||
|
@ -255,8 +255,9 @@ namespace XenAdmin.Actions
|
||||
_percentComplete = 100;
|
||||
_isCompleted = true;
|
||||
}
|
||||
if (NewAction != null && !suppressHistory)
|
||||
NewAction(this);
|
||||
|
||||
if (!suppressHistory)
|
||||
NewAction?.Invoke(this);
|
||||
}
|
||||
|
||||
public string Description
|
||||
@ -309,7 +310,7 @@ namespace XenAdmin.Actions
|
||||
}
|
||||
}
|
||||
|
||||
public bool SuppressProgressReport { get; set; }
|
||||
protected bool SuppressProgressReport { get; set; }
|
||||
|
||||
public void Tick(int percent, string description)
|
||||
{
|
||||
@ -380,31 +381,13 @@ namespace XenAdmin.Actions
|
||||
|
||||
protected void OnChanged()
|
||||
{
|
||||
if (Changed != null)
|
||||
try
|
||||
{
|
||||
if (!SuppressProgressReport)
|
||||
Changed(this);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.Debug($"Exception firing OnChanged for Action {Title}.", e);
|
||||
}
|
||||
if (!SuppressProgressReport)
|
||||
Changed?.Invoke(this);
|
||||
}
|
||||
|
||||
protected virtual void OnCompleted()
|
||||
{
|
||||
if (Completed != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Completed(this);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Debug($"Exception firing OnCompleted for Action {Title}.", ex);
|
||||
}
|
||||
}
|
||||
Completed?.Invoke(this);
|
||||
}
|
||||
|
||||
protected void MarkCompleted(Exception e = null)
|
||||
|
@ -75,12 +75,13 @@ namespace XenAdmin.Actions
|
||||
RbacMethodList rbacMethods = new RbacMethodList();
|
||||
var session = Session;
|
||||
Session = new Session(RbacCollectorProxy.GetProxy(rbacMethods), Connection);
|
||||
base.SuppressProgressReport = true;
|
||||
var startDescription = Description;
|
||||
SuppressProgressReport = true;
|
||||
Run();
|
||||
base.SuppressProgressReport = false;
|
||||
Session = session; // reset Session
|
||||
Description = startDescription; // reset Description;
|
||||
Description = startDescription; // reset Description
|
||||
//reset SuppressProgressReport after the Description to avoid firing unnecessarily the Changed event
|
||||
SuppressProgressReport = false;
|
||||
return rbacMethods;
|
||||
}
|
||||
}
|
||||
|
66
XenModel/Messages.Designer.cs
generated
66
XenModel/Messages.Designer.cs
generated
@ -24695,15 +24695,6 @@ namespace XenAdmin {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} {1}.
|
||||
/// </summary>
|
||||
public static string NAME_WITH_LOCATION {
|
||||
get {
|
||||
return ResourceManager.GetString("NAME_WITH_LOCATION", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Select the SR to reattach or create a new SR.
|
||||
/// </summary>
|
||||
@ -27363,15 +27354,6 @@ namespace XenAdmin {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} {1}.
|
||||
/// </summary>
|
||||
public static string NEWVMWIZARD_STORAGEPAGE_VDINAME {
|
||||
get {
|
||||
return ResourceManager.GetString("NEWVMWIZARD_STORAGEPAGE_VDINAME", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to [XenCenter] has selected a different SR for you..
|
||||
/// </summary>
|
||||
@ -28236,20 +28218,38 @@ namespace XenAdmin {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Events: {0} errors.
|
||||
/// Looks up a localized string similar to Events: {0}.
|
||||
/// </summary>
|
||||
public static string NOTIFICATIONS_SUBMODE_EVENTS_STATUS_MANY {
|
||||
public static string NOTIFICATIONS_SUBMODE_EVENTS_STATUS {
|
||||
get {
|
||||
return ResourceManager.GetString("NOTIFICATIONS_SUBMODE_EVENTS_STATUS_MANY", resourceCulture);
|
||||
return ResourceManager.GetString("NOTIFICATIONS_SUBMODE_EVENTS_STATUS", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Events: 1 error.
|
||||
/// Looks up a localized string similar to 1 error.
|
||||
/// </summary>
|
||||
public static string NOTIFICATIONS_SUBMODE_EVENTS_STATUS_ONE {
|
||||
public static string NOTIFICATIONS_SUBMODE_EVENTS_STATUS_ERROR {
|
||||
get {
|
||||
return ResourceManager.GetString("NOTIFICATIONS_SUBMODE_EVENTS_STATUS_ONE", resourceCulture);
|
||||
return ResourceManager.GetString("NOTIFICATIONS_SUBMODE_EVENTS_STATUS_ERROR", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} errors.
|
||||
/// </summary>
|
||||
public static string NOTIFICATIONS_SUBMODE_EVENTS_STATUS_ERRORS {
|
||||
get {
|
||||
return ResourceManager.GetString("NOTIFICATIONS_SUBMODE_EVENTS_STATUS_ERRORS", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} in progress.
|
||||
/// </summary>
|
||||
public static string NOTIFICATIONS_SUBMODE_EVENTS_STATUS_IN_PROGRESS {
|
||||
get {
|
||||
return ResourceManager.GetString("NOTIFICATIONS_SUBMODE_EVENTS_STATUS_IN_PROGRESS", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
@ -34017,15 +34017,6 @@ namespace XenAdmin {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0}, {1}.
|
||||
/// </summary>
|
||||
public static string SIZE_LOCATION_SUB {
|
||||
get {
|
||||
return ResourceManager.GetString("SIZE_LOCATION_SUB", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Negligible.
|
||||
/// </summary>
|
||||
@ -35357,6 +35348,15 @@ namespace XenAdmin {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0}, {1}.
|
||||
/// </summary>
|
||||
public static string STRING_COMMA_SPACE_STRING {
|
||||
get {
|
||||
return ResourceManager.GetString("STRING_COMMA_SPACE_STRING", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} {1}.
|
||||
/// </summary>
|
||||
|
@ -8578,9 +8578,6 @@ To ensure system stability, it is strongly recommended that you use multipathing
|
||||
<data name="NAME_DESCRIPTION_TAGS" xml:space="preserve">
|
||||
<value>General</value>
|
||||
</data>
|
||||
<data name="NAME_WITH_LOCATION" xml:space="preserve">
|
||||
<value>{0} {1}</value>
|
||||
</data>
|
||||
<data name="NETAPP_EQUAL_PAGE_TITLE" xml:space="preserve">
|
||||
<value>Select the SR to reattach or create a new SR</value>
|
||||
</data>
|
||||
@ -9336,9 +9333,6 @@ Review these settings, then click Previous if you need to change anything. Other
|
||||
<data name="NEWVMWIZARD_STORAGEPAGE_TITLE" xml:space="preserve">
|
||||
<value>Configure storage for the new VM</value>
|
||||
</data>
|
||||
<data name="NEWVMWIZARD_STORAGEPAGE_VDINAME" xml:space="preserve">
|
||||
<value>{0} {1}</value>
|
||||
</data>
|
||||
<data name="NEWVMWIZARD_STORAGEPAGE_XC_SELECTION" xml:space="preserve">
|
||||
<value>[XenCenter] has selected a different SR for you.</value>
|
||||
</data>
|
||||
@ -9662,11 +9656,17 @@ It is strongly recommended that you Cancel and apply the latest version of the p
|
||||
<data name="NOTIFICATIONS_SUBMODE_EVENTS_READ" xml:space="preserve">
|
||||
<value>Events</value>
|
||||
</data>
|
||||
<data name="NOTIFICATIONS_SUBMODE_EVENTS_STATUS_MANY" xml:space="preserve">
|
||||
<value>Events: {0} errors</value>
|
||||
<data name="NOTIFICATIONS_SUBMODE_EVENTS_STATUS" xml:space="preserve">
|
||||
<value>Events: {0}</value>
|
||||
</data>
|
||||
<data name="NOTIFICATIONS_SUBMODE_EVENTS_STATUS_ONE" xml:space="preserve">
|
||||
<value>Events: 1 error</value>
|
||||
<data name="NOTIFICATIONS_SUBMODE_EVENTS_STATUS_IN_PROGRESS" xml:space="preserve">
|
||||
<value>{0} in progress</value>
|
||||
</data>
|
||||
<data name="NOTIFICATIONS_SUBMODE_EVENTS_STATUS_ERRORS" xml:space="preserve">
|
||||
<value>{0} errors</value>
|
||||
</data>
|
||||
<data name="NOTIFICATIONS_SUBMODE_EVENTS_STATUS_ERROR" xml:space="preserve">
|
||||
<value>1 error</value>
|
||||
</data>
|
||||
<data name="NOTIFICATIONS_SUBMODE_EVENTS_UNREAD_MANY" xml:space="preserve">
|
||||
<value>Events ({0} errors)</value>
|
||||
@ -11782,9 +11782,6 @@ The master must be upgraded first, so if you skip the master, the rolling pool u
|
||||
<data name="SIZE_IS" xml:space="preserve">
|
||||
<value>size is</value>
|
||||
</data>
|
||||
<data name="SIZE_LOCATION_SUB" xml:space="preserve">
|
||||
<value>{0}, {1}</value>
|
||||
</data>
|
||||
<data name="SIZE_NEGLIGIBLE" xml:space="preserve">
|
||||
<value>Negligible</value>
|
||||
</data>
|
||||
@ -12240,6 +12237,9 @@ The upper limit: SR size / {2}</value>
|
||||
<data name="STRINGIFY_LIST_LASTSEP" xml:space="preserve">
|
||||
<value> and </value>
|
||||
</data>
|
||||
<data name="STRING_COMMA_SPACE_STRING" xml:space="preserve">
|
||||
<value>{0}, {1}</value>
|
||||
</data>
|
||||
<data name="STRING_SPACE_STRING" xml:space="preserve">
|
||||
<value>{0} {1}</value>
|
||||
</data>
|
||||
|
@ -308,7 +308,7 @@ namespace XenAPI
|
||||
|
||||
public virtual string NameWithLocation()
|
||||
{
|
||||
return string.Format(Messages.NAME_WITH_LOCATION, Name(), LocationString());
|
||||
return string.Format(Messages.STRING_SPACE_STRING, Name(), LocationString());
|
||||
}
|
||||
|
||||
internal virtual string LocationString()
|
||||
|
Loading…
Reference in New Issue
Block a user