mirror of
https://github.com/hneemann/Digital.git
synced 2025-09-27 15:03:21 -04:00
If an error occurs, the name of the affected file is shown.
This commit is contained in:
parent
44534b84a6
commit
81eac8aedd
@ -2,6 +2,7 @@ Release Notes
|
|||||||
|
|
||||||
planned as v0.13
|
planned as v0.13
|
||||||
- In case of oscillations almost all effected components are shown.
|
- In case of oscillations almost all effected components are shown.
|
||||||
|
- If an error occurs, the name of the affected file is shown.
|
||||||
|
|
||||||
v0.12.1, released on 05. Jun 2016
|
v0.12.1, released on 05. Jun 2016
|
||||||
- added a fuse to simulate a PROM or PAL.
|
- added a fuse to simulate a PROM or PAL.
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
package de.neemann.digital.core;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A exception which has a file as an origin
|
||||||
|
* Created by hneemann on 16.06.17.
|
||||||
|
*/
|
||||||
|
public class ExceptionWithOrigin extends Exception {
|
||||||
|
private File origin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new exception
|
||||||
|
*
|
||||||
|
* @param message message
|
||||||
|
*/
|
||||||
|
public ExceptionWithOrigin(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new exception
|
||||||
|
*
|
||||||
|
* @param message message
|
||||||
|
* @param cause the cause
|
||||||
|
*/
|
||||||
|
public ExceptionWithOrigin(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new exception
|
||||||
|
*
|
||||||
|
* @param cause the cause
|
||||||
|
*/
|
||||||
|
public ExceptionWithOrigin(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the origin of the error
|
||||||
|
*/
|
||||||
|
public File getOrigin() {
|
||||||
|
return origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the origin of an error
|
||||||
|
*
|
||||||
|
* @param origin the file which causes the exception
|
||||||
|
*/
|
||||||
|
public void setOrigin(File origin) {
|
||||||
|
if (getOrigin() == null)
|
||||||
|
this.origin = origin;
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,6 @@ import java.io.File;
|
|||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This exception is thrown if there was a problem creating or running the model.
|
* This exception is thrown if there was a problem creating or running the model.
|
||||||
@ -17,7 +16,7 @@ import java.util.HashSet;
|
|||||||
*
|
*
|
||||||
* @author hneemann
|
* @author hneemann
|
||||||
*/
|
*/
|
||||||
public class NodeException extends Exception {
|
public class NodeException extends ExceptionWithOrigin {
|
||||||
private final ArrayList<Node> nodes;
|
private final ArrayList<Node> nodes;
|
||||||
private final ImmutableList<ObservableValue> values;
|
private final ImmutableList<ObservableValue> values;
|
||||||
private final int input;
|
private final int input;
|
||||||
@ -88,11 +87,7 @@ public class NodeException extends Exception {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (nodes != null && nodes.size() > 0) {
|
if (nodes != null && nodes.size() > 0) {
|
||||||
HashSet<File> origins = new HashSet<>();
|
|
||||||
for (Node node : nodes) {
|
for (Node node : nodes) {
|
||||||
if (node != null && node.getOrigin() != null && node.getOrigin().length() > 0)
|
|
||||||
origins.add(node.getOrigin());
|
|
||||||
|
|
||||||
if (node != null)
|
if (node != null)
|
||||||
try { // pick the nodes description if available
|
try { // pick the nodes description if available
|
||||||
final Field field = node.getClass().getField("DESCRIPTION");
|
final Field field = node.getClass().getField("DESCRIPTION");
|
||||||
@ -111,8 +106,6 @@ public class NodeException extends Exception {
|
|||||||
// ignore an error accessing the ElementTypeDescription
|
// ignore an error accessing the ElementTypeDescription
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (File o : origins)
|
|
||||||
items.addItem(o.getName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return items.toString();
|
return items.toString();
|
||||||
@ -152,6 +145,17 @@ public class NodeException extends Exception {
|
|||||||
sb.append(")");
|
sb.append(")");
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getOrigin() {
|
||||||
|
File o = super.getOrigin();
|
||||||
|
if (o != null)
|
||||||
|
return o;
|
||||||
|
|
||||||
|
for (Node n : nodes)
|
||||||
|
if (n.getOrigin() != null)
|
||||||
|
return n.getOrigin();
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package de.neemann.digital.draw.elements;
|
package de.neemann.digital.draw.elements;
|
||||||
|
|
||||||
|
import de.neemann.digital.core.ExceptionWithOrigin;
|
||||||
import de.neemann.digital.draw.model.Net;
|
import de.neemann.digital.draw.model.Net;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -7,7 +8,7 @@ import de.neemann.digital.draw.model.Net;
|
|||||||
*
|
*
|
||||||
* @author hneemann
|
* @author hneemann
|
||||||
*/
|
*/
|
||||||
public class PinException extends Exception {
|
public class PinException extends ExceptionWithOrigin {
|
||||||
private VisualElement element;
|
private VisualElement element;
|
||||||
private Net net;
|
private Net net;
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ public class ModelCreator implements Iterable<ModelEntry> {
|
|||||||
private final NetList netList;
|
private final NetList netList;
|
||||||
private final ArrayList<ModelEntry> entries;
|
private final ArrayList<ModelEntry> entries;
|
||||||
private final HashMap<String, Pin> ioMap;
|
private final HashMap<String, Pin> ioMap;
|
||||||
|
private final File origin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the ModelDescription.
|
* Creates the ModelDescription.
|
||||||
@ -69,7 +70,7 @@ public class ModelCreator implements Iterable<ModelEntry> {
|
|||||||
* @param circuit the circuit to use
|
* @param circuit the circuit to use
|
||||||
* @param library the library to use
|
* @param library the library to use
|
||||||
* @param isNestedCircuit if true the model is created for use as nested element
|
* @param isNestedCircuit if true the model is created for use as nested element
|
||||||
* @param fileName only used for better messages in exceptions
|
* @param origin only used for better messages in exceptions
|
||||||
* @param netList the NetList of the model. If known it is not necessary to create it.
|
* @param netList the NetList of the model. If known it is not necessary to create it.
|
||||||
* @param subName name of the circuit, used to name unique elements
|
* @param subName name of the circuit, used to name unique elements
|
||||||
* @param depth recursion depth, used to detect a circuit which contains itself
|
* @param depth recursion depth, used to detect a circuit which contains itself
|
||||||
@ -77,7 +78,8 @@ public class ModelCreator implements Iterable<ModelEntry> {
|
|||||||
* @throws NodeException NodeException
|
* @throws NodeException NodeException
|
||||||
* @throws ElementNotFoundException ElementNotFoundException
|
* @throws ElementNotFoundException ElementNotFoundException
|
||||||
*/
|
*/
|
||||||
public ModelCreator(Circuit circuit, ElementLibrary library, boolean isNestedCircuit, File fileName, NetList netList, String subName, int depth) throws PinException, NodeException, ElementNotFoundException {
|
public ModelCreator(Circuit circuit, ElementLibrary library, boolean isNestedCircuit, File origin, NetList netList, String subName, int depth) throws PinException, NodeException, ElementNotFoundException {
|
||||||
|
this.origin = origin;
|
||||||
this.circuit = circuit;
|
this.circuit = circuit;
|
||||||
this.netList = netList;
|
this.netList = netList;
|
||||||
entries = new ArrayList<>();
|
entries = new ArrayList<>();
|
||||||
@ -86,107 +88,112 @@ public class ModelCreator implements Iterable<ModelEntry> {
|
|||||||
else
|
else
|
||||||
ioMap = null;
|
ioMap = null;
|
||||||
|
|
||||||
for (VisualElement ve : circuit.getElements()) {
|
try {
|
||||||
Pins pins = ve.getPins();
|
for (VisualElement ve : circuit.getElements()) {
|
||||||
ElementTypeDescription elementType = library.getElementType(ve.getElementName());
|
Pins pins = ve.getPins();
|
||||||
ElementAttributes attr = ve.getElementAttributes();
|
ElementTypeDescription elementType = library.getElementType(ve.getElementName());
|
||||||
if (attr.getCleanLabel().contains("*")) {
|
ElementAttributes attr = ve.getElementAttributes();
|
||||||
attr = new ElementAttributes(attr);
|
if (attr.getCleanLabel().contains("*")) {
|
||||||
attr.set(Keys.LABEL, attr.getCleanLabel().replace("*", subName));
|
attr = new ElementAttributes(attr);
|
||||||
}
|
attr.set(Keys.LABEL, attr.getCleanLabel().replace("*", subName));
|
||||||
Element element = elementType.createElement(attr);
|
|
||||||
ve.setElement(element);
|
|
||||||
pins.bindOutputsToOutputPins(element.getOutputs());
|
|
||||||
|
|
||||||
// sets the nodes origin to create better error messages
|
|
||||||
if (element instanceof Node)
|
|
||||||
((Node) element).setOrigin(fileName);
|
|
||||||
|
|
||||||
// if handled as nested element, don't put pins in EntryList, but put the pins in a
|
|
||||||
// separate map to connect it with the parent!
|
|
||||||
boolean isNotAIO = true;
|
|
||||||
if (isNestedCircuit) {
|
|
||||||
if (elementType == In.DESCRIPTION || elementType == Out.DESCRIPTION || elementType == Clock.DESCRIPTION) {
|
|
||||||
String label = ve.getElementAttributes().getLabel();
|
|
||||||
if (label == null || label.length() == 0)
|
|
||||||
throw new PinException(Lang.get("err_pinWithoutName", fileName));
|
|
||||||
if (pins.size() != 1)
|
|
||||||
throw new PinException(Lang.get("err_N_isNotInputOrOutput", label, fileName));
|
|
||||||
if (ioMap.containsKey(label))
|
|
||||||
throw new PinException(Lang.get("err_duplicatePinLabel", label, fileName));
|
|
||||||
|
|
||||||
ioMap.put(label, pins.get(0));
|
|
||||||
isNotAIO = false;
|
|
||||||
}
|
}
|
||||||
}
|
Element element = elementType.createElement(attr);
|
||||||
|
ve.setElement(element);
|
||||||
|
pins.bindOutputsToOutputPins(element.getOutputs());
|
||||||
|
|
||||||
if (isNotAIO)
|
// sets the nodes origin to create better error messages
|
||||||
entries.add(new ModelEntry(element, pins, ve, elementType.getInputDescription(ve.getElementAttributes()), isNestedCircuit));
|
if (element instanceof Node)
|
||||||
|
((Node) element).setOrigin(origin);
|
||||||
|
|
||||||
for (Pin p : pins)
|
// if handled as nested element, don't put pins in EntryList, but put the pins in a
|
||||||
netList.add(p);
|
// separate map to connect it with the parent!
|
||||||
}
|
boolean isNotAIO = true;
|
||||||
|
if (isNestedCircuit) {
|
||||||
|
if (elementType == In.DESCRIPTION || elementType == Out.DESCRIPTION || elementType == Clock.DESCRIPTION) {
|
||||||
|
String label = ve.getElementAttributes().getLabel();
|
||||||
|
if (label == null || label.length() == 0)
|
||||||
|
throw new PinException(Lang.get("err_pinWithoutName", origin));
|
||||||
|
if (pins.size() != 1)
|
||||||
|
throw new PinException(Lang.get("err_N_isNotInputOrOutput", label, origin));
|
||||||
|
if (ioMap.containsKey(label))
|
||||||
|
throw new PinException(Lang.get("err_duplicatePinLabel", label, origin));
|
||||||
|
|
||||||
// connect all custom elements to the parents net
|
ioMap.put(label, pins.get(0));
|
||||||
ArrayList<ModelCreator> cmdl = new ArrayList<>();
|
isNotAIO = false;
|
||||||
Iterator<ModelEntry> it = entries.iterator();
|
|
||||||
while (it.hasNext()) {
|
|
||||||
ModelEntry me = it.next();
|
|
||||||
if (me.getElement() instanceof CustomElement) { // at first look for custom elements
|
|
||||||
CustomElement ce = (CustomElement) me.getElement();
|
|
||||||
ModelCreator child = ce.getModelDescription(combineNames(subName, me.getVisualElement().getElementAttributes().getCleanLabel()), depth + 1);
|
|
||||||
cmdl.add(child);
|
|
||||||
|
|
||||||
HashMap<Net, Net> netMatch = new HashMap<>();
|
|
||||||
|
|
||||||
for (Pin p : me.getPins()) { // connect the custom elements to the parents net
|
|
||||||
Net childNet = child.getNetOfIOandRemove(p.getName());
|
|
||||||
|
|
||||||
Net otherParentNet = netMatch.get(childNet);
|
|
||||||
if (otherParentNet != null) {
|
|
||||||
// direct connection!
|
|
||||||
// two nets in the parent are connected directly by the nested circuit
|
|
||||||
// merge the nets in the parent!
|
|
||||||
|
|
||||||
// remove the childs inner pin which is already added to the other net
|
|
||||||
Pin insertedPin = child.getPinOfIO(p.getName());
|
|
||||||
otherParentNet.removePin(insertedPin);
|
|
||||||
|
|
||||||
Net parentNet = netList.getNetOfPin(p);
|
|
||||||
if (parentNet != null) {
|
|
||||||
// Disconnect the parents net from the pin
|
|
||||||
parentNet.removePin(p);
|
|
||||||
|
|
||||||
// connect the two parent nets if they are not already the same
|
|
||||||
if (otherParentNet != parentNet) {
|
|
||||||
otherParentNet.addNet(parentNet);
|
|
||||||
netList.remove(parentNet);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Net parentNet = netList.getNetOfPin(p);
|
|
||||||
if (parentNet != null) {
|
|
||||||
// Disconnect the parents net from the pin
|
|
||||||
parentNet.removePin(p);
|
|
||||||
// and connect it to the nested inner net!
|
|
||||||
parentNet.addAll(childNet.getPins());
|
|
||||||
|
|
||||||
// store net connection
|
|
||||||
netMatch.put(childNet, parentNet);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove connected nets form child
|
if (isNotAIO)
|
||||||
for (Net childNet : netMatch.keySet())
|
entries.add(new ModelEntry(element, pins, ve, elementType.getInputDescription(ve.getElementAttributes()), isNestedCircuit));
|
||||||
child.remove(childNet);
|
|
||||||
|
|
||||||
it.remove();
|
for (Pin p : pins)
|
||||||
|
netList.add(p);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
for (ModelCreator md : cmdl) { // put the elements of the custom element to the parent
|
// connect all custom elements to the parents net
|
||||||
entries.addAll(md.entries);
|
ArrayList<ModelCreator> cmdl = new ArrayList<>();
|
||||||
netList.add(md.netList);
|
Iterator<ModelEntry> it = entries.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
ModelEntry me = it.next();
|
||||||
|
if (me.getElement() instanceof CustomElement) { // at first look for custom elements
|
||||||
|
CustomElement ce = (CustomElement) me.getElement();
|
||||||
|
ModelCreator child = ce.getModelDescription(combineNames(subName, me.getVisualElement().getElementAttributes().getCleanLabel()), depth + 1);
|
||||||
|
cmdl.add(child);
|
||||||
|
|
||||||
|
HashMap<Net, Net> netMatch = new HashMap<>();
|
||||||
|
|
||||||
|
for (Pin p : me.getPins()) { // connect the custom elements to the parents net
|
||||||
|
Net childNet = child.getNetOfIOandRemove(p.getName());
|
||||||
|
|
||||||
|
Net otherParentNet = netMatch.get(childNet);
|
||||||
|
if (otherParentNet != null) {
|
||||||
|
// direct connection!
|
||||||
|
// two nets in the parent are connected directly by the nested circuit
|
||||||
|
// merge the nets in the parent!
|
||||||
|
|
||||||
|
// remove the childs inner pin which is already added to the other net
|
||||||
|
Pin insertedPin = child.getPinOfIO(p.getName());
|
||||||
|
otherParentNet.removePin(insertedPin);
|
||||||
|
|
||||||
|
Net parentNet = netList.getNetOfPin(p);
|
||||||
|
if (parentNet != null) {
|
||||||
|
// Disconnect the parents net from the pin
|
||||||
|
parentNet.removePin(p);
|
||||||
|
|
||||||
|
// connect the two parent nets if they are not already the same
|
||||||
|
if (otherParentNet != parentNet) {
|
||||||
|
otherParentNet.addNet(parentNet);
|
||||||
|
netList.remove(parentNet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Net parentNet = netList.getNetOfPin(p);
|
||||||
|
if (parentNet != null) {
|
||||||
|
// Disconnect the parents net from the pin
|
||||||
|
parentNet.removePin(p);
|
||||||
|
// and connect it to the nested inner net!
|
||||||
|
parentNet.addAll(childNet.getPins());
|
||||||
|
|
||||||
|
// store net connection
|
||||||
|
netMatch.put(childNet, parentNet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove connected nets form child
|
||||||
|
for (Net childNet : netMatch.keySet())
|
||||||
|
child.remove(childNet);
|
||||||
|
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (ModelCreator md : cmdl) { // put the elements of the custom element to the parent
|
||||||
|
entries.addAll(md.entries);
|
||||||
|
netList.add(md.netList);
|
||||||
|
}
|
||||||
|
} catch (PinException | NodeException e) {
|
||||||
|
e.setOrigin(origin);
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,24 +239,28 @@ public class ModelCreator implements Iterable<ModelEntry> {
|
|||||||
* @throws NodeException NodeException
|
* @throws NodeException NodeException
|
||||||
*/
|
*/
|
||||||
public Model createModel(boolean attachWires) throws PinException, NodeException {
|
public Model createModel(boolean attachWires) throws PinException, NodeException {
|
||||||
|
try {
|
||||||
|
Model m = new Model();
|
||||||
|
|
||||||
Model m = new Model();
|
for (Net n : netList)
|
||||||
|
n.interconnect(m, attachWires);
|
||||||
|
|
||||||
for (Net n : netList)
|
for (ModelEntry e : entries)
|
||||||
n.interconnect(m, attachWires);
|
e.applyInputs();
|
||||||
|
|
||||||
for (ModelEntry e : entries)
|
for (ModelEntry e : entries)
|
||||||
e.applyInputs();
|
e.getElement().registerNodes(m);
|
||||||
|
|
||||||
for (ModelEntry e : entries)
|
for (ModelEntry e : entries) {
|
||||||
e.getElement().registerNodes(m);
|
e.getElement().init(m);
|
||||||
|
e.getVisualElement().getShape().registerModel(this, m, e);
|
||||||
|
}
|
||||||
|
|
||||||
for (ModelEntry e : entries) {
|
return m;
|
||||||
e.getElement().init(m);
|
} catch (PinException | NodeException e) {
|
||||||
e.getVisualElement().getShape().registerModel(this, m, e);
|
e.setOrigin(origin);
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
return m;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package de.neemann.gui;
|
package de.neemann.gui;
|
||||||
|
|
||||||
|
import de.neemann.digital.core.ExceptionWithOrigin;
|
||||||
import de.neemann.digital.lang.Lang;
|
import de.neemann.digital.lang.Lang;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to show error messages.
|
* Used to show error messages.
|
||||||
@ -44,6 +46,16 @@ public class ErrorMessage implements Runnable {
|
|||||||
if (message.length() > 0)
|
if (message.length() > 0)
|
||||||
message.append('\n');
|
message.append('\n');
|
||||||
addExceptionMessage(e);
|
addExceptionMessage(e);
|
||||||
|
|
||||||
|
if (e instanceof ExceptionWithOrigin) {
|
||||||
|
File o = ((ExceptionWithOrigin) e).getOrigin();
|
||||||
|
if (o!=null) {
|
||||||
|
if (message.length() > 0)
|
||||||
|
message.append('\n');
|
||||||
|
message.append(Lang.get("msg_errInFile_N", o.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -914,6 +914,7 @@ Die Icons stammen aus dem Tango Desktop Project.</string>
|
|||||||
<string name="msg_inputsToInvert">Zu invertierende Eingänge</string>
|
<string name="msg_inputsToInvert">Zu invertierende Eingänge</string>
|
||||||
<string name="msg_none">keine</string>
|
<string name="msg_none">keine</string>
|
||||||
<string name="msg_errGettingPinNames">Die Namen der Pins konnten nicht ermittelt werden.</string>
|
<string name="msg_errGettingPinNames">Die Namen der Pins konnten nicht ermittelt werden.</string>
|
||||||
|
<string name="msg_errInFile_N">Aufgetreten in Datei {0}!</string>
|
||||||
|
|
||||||
<string name="ok">Ok</string>
|
<string name="ok">Ok</string>
|
||||||
<string name="rot_0">0°</string>
|
<string name="rot_0">0°</string>
|
||||||
|
@ -901,6 +901,7 @@ The icons are taken from the Tango Desktop Project.</string>
|
|||||||
<string name="msg_inputsToInvert">Inputs to invert</string>
|
<string name="msg_inputsToInvert">Inputs to invert</string>
|
||||||
<string name="msg_none">none</string>
|
<string name="msg_none">none</string>
|
||||||
<string name="msg_errGettingPinNames">Could not determine the names of the pins.</string>
|
<string name="msg_errGettingPinNames">Could not determine the names of the pins.</string>
|
||||||
|
<string name="msg_errInFile_N">Occurred in file {0}!</string>
|
||||||
|
|
||||||
<string name="ok">Ok</string>
|
<string name="ok">Ok</string>
|
||||||
<string name="rot_0">0°</string>
|
<string name="rot_0">0°</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user