mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-08-18 19:39:17 +02:00
DBG: History values: evaluate in background, if windows are closed
git-svn-id: trunk@30752 -
This commit is contained in:
parent
156372ea58
commit
65b111ae79
@ -256,6 +256,7 @@ var
|
|||||||
First, Count: Integer;
|
First, Count: Integer;
|
||||||
Source: String;
|
Source: String;
|
||||||
Snap: TSnapshot;
|
Snap: TSnapshot;
|
||||||
|
CStack: TCallStack;
|
||||||
begin
|
begin
|
||||||
if (not ToolButtonPower.Down) or FInUpdateView then exit;
|
if (not ToolButtonPower.Down) or FInUpdateView then exit;
|
||||||
BeginUpdate;
|
BeginUpdate;
|
||||||
@ -267,22 +268,35 @@ begin
|
|||||||
else Caption:= lisMenuViewCallStack;
|
else Caption:= lisMenuViewCallStack;
|
||||||
|
|
||||||
FInUpdateView := True; // ignore change triggered by count, if there is a change event, then Count will be updated already
|
FInUpdateView := True; // ignore change triggered by count, if there is a change event, then Count will be updated already
|
||||||
if (GetSelectedCallstack = nil) or (GetSelectedCallstack.Count=0)
|
CStack := GetSelectedCallstack;
|
||||||
|
FInUpdateView := False;
|
||||||
|
|
||||||
|
if (CStack = nil) or ((Snap <> nil) and (CStack.Count = 0)) then begin
|
||||||
|
lvCallStack.Items.Clear;
|
||||||
|
Item := lvCallStack.Items.Add;
|
||||||
|
Item.SubItems.Add('');
|
||||||
|
Item.SubItems.Add(lisCallStackNotEvaluated);
|
||||||
|
Item.SubItems.Add('');
|
||||||
|
Item.SubItems.Add('');
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if (CStack.Count=0)
|
||||||
then begin
|
then begin
|
||||||
txtGoto.Text:= '0';
|
txtGoto.Text:= '0';
|
||||||
lvCallStack.Items.Clear;
|
lvCallStack.Items.Clear;
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
FInUpdateView := False;
|
|
||||||
|
|
||||||
if Snap <> nil then begin
|
if Snap <> nil then begin
|
||||||
First := 0;
|
First := 0;
|
||||||
Count := GetSelectedCallstack.Count;
|
Count := CStack.Count;
|
||||||
end else begin
|
end else begin
|
||||||
First := FViewStart;
|
First := FViewStart;
|
||||||
if First + FViewLimit <= GetSelectedCallstack.Count
|
if First + FViewLimit <= CStack.Count
|
||||||
then Count := FViewLimit
|
then Count := FViewLimit
|
||||||
else Count := GetSelectedCallstack.Count - First;
|
else Count := CStack.Count - First;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Reuse entries, so add and remove only
|
// Reuse entries, so add and remove only
|
||||||
@ -301,12 +315,12 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
FInUpdateView := True;
|
FInUpdateView := True;
|
||||||
GetSelectedCallstack.PrepareRange(First, Count);
|
CStack.PrepareRange(First, Count);
|
||||||
FInUpdateView := False;
|
FInUpdateView := False;
|
||||||
for n := 0 to Count - 1 do
|
for n := 0 to Count - 1 do
|
||||||
begin
|
begin
|
||||||
Item := lvCallStack.Items[n];
|
Item := lvCallStack.Items[n];
|
||||||
Entry := GetSelectedCallstack.Entries[First + n];
|
Entry := CStack.Entries[First + n];
|
||||||
if Entry = nil
|
if Entry = nil
|
||||||
then begin
|
then begin
|
||||||
Item.Caption := '';
|
Item.Caption := '';
|
||||||
@ -653,16 +667,18 @@ procedure TCallStackDlg.BreakPointChanged(const ASender: TIDEBreakPoints;
|
|||||||
var
|
var
|
||||||
i, idx: Integer;
|
i, idx: Integer;
|
||||||
Entry: TCallStackEntry;
|
Entry: TCallStackEntry;
|
||||||
|
Stack: TCallStack;
|
||||||
begin
|
begin
|
||||||
if BreakPoints = nil then
|
Stack := GetSelectedCallstack;
|
||||||
|
if (BreakPoints = nil) or (Stack = nil) then
|
||||||
Exit;
|
Exit;
|
||||||
|
|
||||||
for i := 0 to lvCallStack.Items.Count - 1 do
|
for i := 0 to lvCallStack.Items.Count - 1 do
|
||||||
begin
|
begin
|
||||||
idx := FViewStart + lvCallStack.Items[i].Index;
|
idx := FViewStart + lvCallStack.Items[i].Index;
|
||||||
if idx >= GetSelectedCallstack.Count then
|
if idx >= Stack.Count then
|
||||||
Continue;
|
Continue;
|
||||||
Entry := GetSelectedCallstack.Entries[idx];
|
Entry := Stack.Entries[idx];
|
||||||
if Entry <> nil then
|
if Entry <> nil then
|
||||||
lvCallStack.Items[i].ImageIndex := GetImageIndex(Entry)
|
lvCallStack.Items[i].ImageIndex := GetImageIndex(Entry)
|
||||||
else
|
else
|
||||||
|
@ -1937,6 +1937,8 @@ type
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
{ TSnapshotManager }
|
{ TSnapshotManager }
|
||||||
|
TSnapshotManagerRequestedFlags = set of
|
||||||
|
(smrThreads, smrCallStackCnt, smrCallStack, smrLocals, smrWatches);
|
||||||
|
|
||||||
TSnapshotManager = class
|
TSnapshotManager = class
|
||||||
private
|
private
|
||||||
@ -1948,6 +1950,7 @@ type
|
|||||||
FThreads: TThreadsMonitor;
|
FThreads: TThreadsMonitor;
|
||||||
FCurrentState: TDBGState;
|
FCurrentState: TDBGState;
|
||||||
FCurrentSnapshot: TSnapshot; // snapshot fo rcurrent pause. Not yet in list
|
FCurrentSnapshot: TSnapshot; // snapshot fo rcurrent pause. Not yet in list
|
||||||
|
FRequestsDone: TSnapshotManagerRequestedFlags;
|
||||||
private
|
private
|
||||||
FActive: Boolean;
|
FActive: Boolean;
|
||||||
FHistoryCapacity: Integer;
|
FHistoryCapacity: Integer;
|
||||||
@ -1969,6 +1972,7 @@ type
|
|||||||
procedure AddNotification(const ANotification: TSnapshotNotification);
|
procedure AddNotification(const ANotification: TSnapshotNotification);
|
||||||
procedure RemoveNotification(const ANotification: TSnapshotNotification);
|
procedure RemoveNotification(const ANotification: TSnapshotNotification);
|
||||||
procedure DoStateChange(const AOldState: TDBGState);
|
procedure DoStateChange(const AOldState: TDBGState);
|
||||||
|
procedure DoDebuggerIdle;
|
||||||
property Active: Boolean read FActive write SetActive;
|
property Active: Boolean read FActive write SetActive;
|
||||||
public
|
public
|
||||||
function SelectedId: Pointer;
|
function SelectedId: Pointer;
|
||||||
@ -2315,6 +2319,7 @@ type
|
|||||||
FLineInfo: TDBGLineInfo;
|
FLineInfo: TDBGLineInfo;
|
||||||
FOnConsoleOutput: TDBGOutputEvent;
|
FOnConsoleOutput: TDBGOutputEvent;
|
||||||
FOnFeedback: TDBGFeedbackEvent;
|
FOnFeedback: TDBGFeedbackEvent;
|
||||||
|
FOnIdle: TNotifyEvent;
|
||||||
FRegisters: TDBGRegisters;
|
FRegisters: TDBGRegisters;
|
||||||
FShowConsole: Boolean;
|
FShowConsole: Boolean;
|
||||||
FSignals: TDBGSignals;
|
FSignals: TDBGSignals;
|
||||||
@ -2363,6 +2368,7 @@ type
|
|||||||
function GetSupportedCommands: TDBGCommands; virtual;
|
function GetSupportedCommands: TDBGCommands; virtual;
|
||||||
function GetTargetWidth: Byte; virtual;
|
function GetTargetWidth: Byte; virtual;
|
||||||
function GetWaiting: Boolean; virtual;
|
function GetWaiting: Boolean; virtual;
|
||||||
|
function GetIsIdle: Boolean; virtual;
|
||||||
function RequestCommand(const ACommand: TDBGCommand;
|
function RequestCommand(const ACommand: TDBGCommand;
|
||||||
const AParams: array of const): Boolean;
|
const AParams: array of const): Boolean;
|
||||||
virtual; abstract; // True if succesful
|
virtual; abstract; // True if succesful
|
||||||
@ -2433,6 +2439,7 @@ type
|
|||||||
property Watches: TWatchesSupplier read FWatches; // list of all watches etc
|
property Watches: TWatchesSupplier read FWatches; // list of all watches etc
|
||||||
property Threads: TThreadsSupplier read FThreads;
|
property Threads: TThreadsSupplier read FThreads;
|
||||||
property WorkingDir: String read FWorkingDir write FWorkingDir; // The working dir of the exe being debugged
|
property WorkingDir: String read FWorkingDir write FWorkingDir; // The working dir of the exe being debugged
|
||||||
|
property IsIdle: Boolean read GetIsIdle; // Nothing queued
|
||||||
// Events
|
// Events
|
||||||
property OnCurrent: TDBGCurrentLineEvent read FOnCurrent write FOnCurrent; // Passes info about the current line being debugged
|
property OnCurrent: TDBGCurrentLineEvent read FOnCurrent write FOnCurrent; // Passes info about the current line being debugged
|
||||||
property OnDbgOutput: TDBGOutputEvent read FOnDbgOutput write FOnDbgOutput; // Passes all debuggeroutput
|
property OnDbgOutput: TDBGOutputEvent read FOnDbgOutput write FOnDbgOutput; // Passes all debuggeroutput
|
||||||
@ -2443,6 +2450,7 @@ type
|
|||||||
property OnBreakPointHit: TDebuggerBreakPointHitEvent read FOnBreakPointHit write FOnBreakPointHit; // Fires when the program is paused at a breakpoint
|
property OnBreakPointHit: TDebuggerBreakPointHitEvent read FOnBreakPointHit write FOnBreakPointHit; // Fires when the program is paused at a breakpoint
|
||||||
property OnConsoleOutput: TDBGOutputEvent read FOnConsoleOutput write FOnConsoleOutput; // Passes Application Console Output
|
property OnConsoleOutput: TDBGOutputEvent read FOnConsoleOutput write FOnConsoleOutput; // Passes Application Console Output
|
||||||
property OnFeedback: TDBGFeedbackEvent read FOnFeedback write FOnFeedback;
|
property OnFeedback: TDBGFeedbackEvent read FOnFeedback write FOnFeedback;
|
||||||
|
property OnIdle: TNotifyEvent read FOnIdle write FOnIdle; // Called if all outstanding requests are processed (queue empty)
|
||||||
end;
|
end;
|
||||||
TDebuggerClass = class of TDebugger;
|
TDebuggerClass = class of TDebugger;
|
||||||
|
|
||||||
@ -2719,6 +2727,7 @@ begin
|
|||||||
FCurrentState := Debugger.State;
|
FCurrentState := Debugger.State;
|
||||||
|
|
||||||
if FDebugger.State = dsPause then begin
|
if FDebugger.State = dsPause then begin
|
||||||
|
FRequestsDone := [];
|
||||||
if FActive then CreateHistoryEntry;
|
if FActive then CreateHistoryEntry;
|
||||||
HistorySelected := False;
|
HistorySelected := False;
|
||||||
end
|
end
|
||||||
@ -2737,6 +2746,48 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TSnapshotManager.DoDebuggerIdle;
|
||||||
|
var
|
||||||
|
i, j, k: LongInt;
|
||||||
|
w: TCurrentWatches;
|
||||||
|
begin
|
||||||
|
if FCurrentState <> dsPause then exit;
|
||||||
|
if not(smrThreads in FRequestsDone) then begin
|
||||||
|
include(FRequestsDone, smrThreads);
|
||||||
|
FThreads.CurrentThreads.Count;
|
||||||
|
if not Debugger.IsIdle then exit;
|
||||||
|
end;
|
||||||
|
if not(smrCallStackCnt in FRequestsDone) then begin
|
||||||
|
include(FRequestsDone, smrCallStackCnt);
|
||||||
|
i := FThreads.CurrentThreads.CurrentThreadId;
|
||||||
|
FCallStack.CurrentCallStackList.EntriesForThreads[i].Count;
|
||||||
|
if not Debugger.IsIdle then exit;
|
||||||
|
end;
|
||||||
|
if not(smrCallStack in FRequestsDone) then begin
|
||||||
|
include(FRequestsDone, smrCallStack);
|
||||||
|
i := FThreads.CurrentThreads.CurrentThreadId;
|
||||||
|
k := FCallStack.CurrentCallStackList.EntriesForThreads[i].Count;
|
||||||
|
if k > 0
|
||||||
|
then FCallStack.CurrentCallStackList.EntriesForThreads[i].PrepareRange(0, Min(5, k));
|
||||||
|
if not Debugger.IsIdle then exit;
|
||||||
|
end;
|
||||||
|
if not(smrLocals in FRequestsDone) then begin
|
||||||
|
include(FRequestsDone, smrLocals);
|
||||||
|
i := FThreads.CurrentThreads.CurrentThreadId;
|
||||||
|
j := FCallStack.CurrentCallStackList.EntriesForThreads[i].CurrentIndex;
|
||||||
|
FLocals.CurrentLocalsList.Entries[i, j].Count;
|
||||||
|
if not Debugger.IsIdle then exit;
|
||||||
|
end;
|
||||||
|
if not(smrWatches in FRequestsDone) then begin
|
||||||
|
include(FRequestsDone, smrWatches);
|
||||||
|
i := FThreads.CurrentThreads.CurrentThreadId;
|
||||||
|
j := FCallStack.CurrentCallStackList.EntriesForThreads[i].CurrentIndex;
|
||||||
|
w := FWatches.CurrentWatches;
|
||||||
|
for k := 0 to w.Count - 1 do w[k].Values[i, j].Value;
|
||||||
|
if not Debugger.IsIdle then exit;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
function TSnapshotManager.SelectedId: Pointer;
|
function TSnapshotManager.SelectedId: Pointer;
|
||||||
begin
|
begin
|
||||||
if (HistoryIndex < 0) or (HistoryIndex >= HistoryCount) or (not FHistorySelected)
|
if (HistoryIndex < 0) or (HistoryIndex >= HistoryCount) or (not FHistorySelected)
|
||||||
@ -4607,6 +4658,11 @@ begin
|
|||||||
FCurEnvironment.Assign(FEnvironment);
|
FCurEnvironment.Assign(FEnvironment);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TDebugger.GetIsIdle: Boolean;
|
||||||
|
begin
|
||||||
|
Result := False;
|
||||||
|
end;
|
||||||
|
|
||||||
function TDebugger.Evaluate(const AExpression: String; var AResult: String;
|
function TDebugger.Evaluate(const AExpression: String; var AResult: String;
|
||||||
var ATypeInfo: TDBGType; EvalFlags: TDBGEvaluateFlags = []): Boolean;
|
var ATypeInfo: TDBGType; EvalFlags: TDBGEvaluateFlags = []): Boolean;
|
||||||
begin
|
begin
|
||||||
|
@ -387,6 +387,7 @@ type
|
|||||||
function ParseInitialization: Boolean; virtual;
|
function ParseInitialization: Boolean; virtual;
|
||||||
function RequestCommand(const ACommand: TDBGCommand; const AParams: array of const): Boolean; override;
|
function RequestCommand(const ACommand: TDBGCommand; const AParams: array of const): Boolean; override;
|
||||||
procedure ClearCommandQueue;
|
procedure ClearCommandQueue;
|
||||||
|
function GetIsIdle: Boolean; override;
|
||||||
procedure DoState(const OldState: TDBGState); override;
|
procedure DoState(const OldState: TDBGState); override;
|
||||||
procedure DoThreadChanged;
|
procedure DoThreadChanged;
|
||||||
property TargetPID: Integer read FTargetInfo.TargetPID;
|
property TargetPID: Integer read FTargetInfo.TargetPID;
|
||||||
@ -5609,6 +5610,10 @@ begin
|
|||||||
FInExecuteCount := SavedInExecuteCount;
|
FInExecuteCount := SavedInExecuteCount;
|
||||||
FCurrentCommand := NestedCurrentCmd;
|
FCurrentCommand := NestedCurrentCmd;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
if (FCommandQueue.Count = 0) and assigned(OnIdle)
|
||||||
|
then OnIdle(Self);
|
||||||
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TGDBMIDebugger.QueueCommand(const ACommand: TGDBMIDebuggerCommand; ForceQueue: Boolean = False);
|
procedure TGDBMIDebugger.QueueCommand(const ACommand: TGDBMIDebuggerCommand; ForceQueue: Boolean = False);
|
||||||
@ -6402,6 +6407,11 @@ begin
|
|||||||
FCommandQueue.Clear;
|
FCommandQueue.Clear;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TGDBMIDebugger.GetIsIdle: Boolean;
|
||||||
|
begin
|
||||||
|
Result := FCommandQueue.Count = 0;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TGDBMIDebugger.ClearSourceInfo;
|
procedure TGDBMIDebugger.ClearSourceInfo;
|
||||||
var
|
var
|
||||||
n: Integer;
|
n: Integer;
|
||||||
|
@ -184,6 +184,9 @@ begin
|
|||||||
if Locals = nil
|
if Locals = nil
|
||||||
then begin
|
then begin
|
||||||
lvLocals.Items.Clear;
|
lvLocals.Items.Clear;
|
||||||
|
Item := lvLocals.Items.Add;
|
||||||
|
Item.Caption := '';
|
||||||
|
Item.SubItems.add(lisLocalsNotEvaluated);
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
@ -69,9 +69,15 @@ begin
|
|||||||
Caption:= lisThreads;
|
Caption:= lisThreads;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if Threads = nil then begin
|
if (Threads = nil) or ((Snap <> nil) and (Threads.Count=0)) then begin
|
||||||
lvThreads.Clear;
|
lvThreads.Clear;
|
||||||
// Todo: display "no info available"
|
Item := lvThreads.Items.Add;
|
||||||
|
Item.SubItems.add('');
|
||||||
|
Item.SubItems.add('');
|
||||||
|
Item.SubItems.add('');
|
||||||
|
Item.SubItems.add(lisThreadsNotEvaluated);
|
||||||
|
Item.SubItems.add('');
|
||||||
|
Item.SubItems.add('');
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
@ -727,7 +727,8 @@ begin
|
|||||||
include(FStateFlags, wdsfUpdating);
|
include(FStateFlags, wdsfUpdating);
|
||||||
AItem.Caption := AWatch.Expression;
|
AItem.Caption := AWatch.Expression;
|
||||||
WatchValue := AWatch.Values[GetThreadId, GetStackframe];
|
WatchValue := AWatch.Values[GetThreadId, GetStackframe];
|
||||||
if WatchValue <> nil
|
if (WatchValue <> nil) and
|
||||||
|
( (GetSelectedSnapshot = nil) or not(WatchValue.Validity in [ddsUnknown, ddsEvaluating, ddsRequested]) )
|
||||||
then AItem.SubItems[0] := ClearMultiline(WatchValue.Value)
|
then AItem.SubItems[0] := ClearMultiline(WatchValue.Value)
|
||||||
else AItem.SubItems[0] := '<not evaluated>';
|
else AItem.SubItems[0] := '<not evaluated>';
|
||||||
exclude(FStateFlags, wdsfUpdating);
|
exclude(FStateFlags, wdsfUpdating);
|
||||||
|
@ -65,6 +65,7 @@ type
|
|||||||
{ TDebugManager }
|
{ TDebugManager }
|
||||||
|
|
||||||
TDebugManager = class(TBaseDebugManager)
|
TDebugManager = class(TBaseDebugManager)
|
||||||
|
procedure DebuggerIdle(Sender: TObject);
|
||||||
private
|
private
|
||||||
procedure BreakAutoContinueTimer(Sender: TObject);
|
procedure BreakAutoContinueTimer(Sender: TObject);
|
||||||
procedure OnRunTimer(Sender: TObject);
|
procedure OnRunTimer(Sender: TObject);
|
||||||
@ -613,6 +614,11 @@ begin
|
|||||||
Result := ExecuteFeedbackDialog(AText, AInfo, AType, AButtons);
|
Result := ExecuteFeedbackDialog(AText, AInfo, AType, AButtons);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TDebugManager.DebuggerIdle(Sender: TObject);
|
||||||
|
begin
|
||||||
|
FSnapshots.DoDebuggerIdle;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TDebugManager.BreakAutoContinueTimer(Sender: TObject);
|
procedure TDebugManager.BreakAutoContinueTimer(Sender: TObject);
|
||||||
begin
|
begin
|
||||||
FAutoContinueTimer.Enabled := False;
|
FAutoContinueTimer.Enabled := False;
|
||||||
@ -1806,6 +1812,7 @@ begin
|
|||||||
FDebugger.OnException := @DebuggerException;
|
FDebugger.OnException := @DebuggerException;
|
||||||
FDebugger.OnConsoleOutput := @DebuggerConsoleOutput;
|
FDebugger.OnConsoleOutput := @DebuggerConsoleOutput;
|
||||||
FDebugger.OnFeedback := @DebuggerFeedback;
|
FDebugger.OnFeedback := @DebuggerFeedback;
|
||||||
|
FDebugger.OnIdle := @DebuggerIdle;
|
||||||
|
|
||||||
if FDebugger.State = dsNone
|
if FDebugger.State = dsNone
|
||||||
then begin
|
then begin
|
||||||
|
@ -4692,10 +4692,14 @@ resourcestring
|
|||||||
lisRecordStruct = 'Record/Structure';
|
lisRecordStruct = 'Record/Structure';
|
||||||
lisMemoryDump = 'Memory Dump';
|
lisMemoryDump = 'Memory Dump';
|
||||||
|
|
||||||
|
// Callstack
|
||||||
|
lisCallStackNotEvaluated = 'Stack not evaluated';
|
||||||
|
|
||||||
// Locals Dialog
|
// Locals Dialog
|
||||||
lisLocals = 'Locals';
|
lisLocals = 'Locals';
|
||||||
lisLocalsDlgName = 'Name';
|
lisLocalsDlgName = 'Name';
|
||||||
lisLocalsDlgValue = 'Value';
|
lisLocalsDlgValue = 'Value';
|
||||||
|
lisLocalsNotEvaluated = 'Locals not evaluated';
|
||||||
|
|
||||||
// Registers Dialog
|
// Registers Dialog
|
||||||
lisRegisters = 'Registers';
|
lisRegisters = 'Registers';
|
||||||
@ -4712,6 +4716,7 @@ resourcestring
|
|||||||
lisThreadsFunc = 'Function';
|
lisThreadsFunc = 'Function';
|
||||||
lisThreadsCurrent = 'Current';
|
lisThreadsCurrent = 'Current';
|
||||||
lisThreadsGoto = 'Goto';
|
lisThreadsGoto = 'Goto';
|
||||||
|
lisThreadsNotEvaluated = 'Threads not evaluated';
|
||||||
|
|
||||||
// HistoryDlg
|
// HistoryDlg
|
||||||
histdlgFormName = 'History';
|
histdlgFormName = 'History';
|
||||||
|
Loading…
Reference in New Issue
Block a user