mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-26 23:46:36 +00:00
refactored addRace functionality, introducing polymorphic connectivity parameter objects across connectors
This commit is contained in:
+5
-3
@@ -25,6 +25,7 @@ import com.sap.sailing.domain.tracking.DynamicTrackedEvent;
|
||||
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
|
||||
import com.sap.sailing.domain.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
|
||||
import com.sap.sailing.domain.tracking.TrackedEvent;
|
||||
import com.sap.sailing.domain.tracking.TrackedEventRegistry;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
@@ -203,8 +204,7 @@ public interface DomainFactory {
|
||||
*/
|
||||
void updateCourseWaypoints(Course courseToUpdate, List<ControlPoint> controlPoints) throws PatchFailedException;
|
||||
|
||||
TracTracConfiguration createTracTracConfiguration(String name, String jsonURL, String liveDataURI,
|
||||
String storedDataURI);
|
||||
TracTracConfiguration createTracTracConfiguration(String name, String jsonURL, String liveDataURI, String storedDataURI);
|
||||
|
||||
/**
|
||||
* Fetch the race definition for <code>race</code>. If the race definition hasn't been created yet, the call blocks
|
||||
@@ -220,7 +220,9 @@ public interface DomainFactory {
|
||||
RaceDefinition getAndWaitForRaceDefinition(Race race, long timeoutInMilliseconds);
|
||||
|
||||
Pair<List<com.sap.sailing.domain.base.Competitor>, BoatClass> getCompetitorsAndDominantBoatClass(Race race);
|
||||
|
||||
|
||||
RaceTrackingConnectivityParameters createTrackingConnectivityParameters(URL paramURL, URI liveURI, URI storedURI,
|
||||
TimePoint startOfTracking, TimePoint endOfTracking, WindStore windStore);
|
||||
/**
|
||||
* Removes all knowledge about <code>tractracRace</code> which includes removing it from the race cache, from the
|
||||
* {@link com.sap.sailing.domain.base.Event} and, if a {@link TrackedRace} for the corresponding
|
||||
|
||||
+1
@@ -1,5 +1,6 @@
|
||||
package com.sap.sailing.domain.tractracadapter;
|
||||
|
||||
|
||||
/**
|
||||
* Configuration parameters that can be used to connect to a TracTrac event / race.
|
||||
*
|
||||
|
||||
+7
@@ -49,6 +49,7 @@ import com.sap.sailing.domain.tracking.DynamicTrackedEvent;
|
||||
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
|
||||
import com.sap.sailing.domain.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
|
||||
import com.sap.sailing.domain.tracking.TrackedEvent;
|
||||
import com.sap.sailing.domain.tracking.TrackedEventRegistry;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
@@ -475,4 +476,10 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
return new TracTracConfigurationImpl(name, jsonURL, liveDataURI, storedDataURI);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RaceTrackingConnectivityParameters createTrackingConnectivityParameters(URL paramURL, URI liveURI,
|
||||
URI storedURI, TimePoint startOfTracking, TimePoint endOfTracking, WindStore windStore) {
|
||||
return new RaceTrackingConnectivityParametersImpl(paramURL, liveURI, storedURI, startOfTracking, endOfTracking, windStore, this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package com.sap.sailing.domain.tractracadapter.impl;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.tracking.RaceTracker;
|
||||
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
|
||||
import com.sap.sailing.domain.tracking.TrackedEventRegistry;
|
||||
import com.sap.sailing.domain.tracking.WindStore;
|
||||
import com.sap.sailing.domain.tractracadapter.DomainFactory;
|
||||
|
||||
public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConnectivityParameters {
|
||||
private final URL paramURL;
|
||||
private final URI liveURI;
|
||||
private final URI storedURI;
|
||||
private final TimePoint startOfTracking;
|
||||
private final TimePoint endOfTracking;
|
||||
private final WindStore windStore;
|
||||
private final DomainFactory domainFactory;
|
||||
|
||||
public RaceTrackingConnectivityParametersImpl(URL paramURL, URI liveURI, URI storedURI,
|
||||
TimePoint startOfTracking, TimePoint endOfTracking, WindStore windStore, DomainFactory domainFactory) {
|
||||
super();
|
||||
this.paramURL = paramURL;
|
||||
this.liveURI = liveURI;
|
||||
this.storedURI = storedURI;
|
||||
this.startOfTracking = startOfTracking;
|
||||
this.endOfTracking = endOfTracking;
|
||||
this.windStore = windStore;
|
||||
this.domainFactory = domainFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RaceTracker createRaceTracker(TrackedEventRegistry trackedEventRegistry) throws MalformedURLException,
|
||||
FileNotFoundException, URISyntaxException {
|
||||
RaceTracker tracker = domainFactory.createRaceTracker(paramURL, liveURI, storedURI, startOfTracking,
|
||||
endOfTracking, windStore, trackedEventRegistry);
|
||||
return tracker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getTrackerID() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -43,4 +43,5 @@ public class TracTracConfigurationImpl implements TracTracConfiguration {
|
||||
return "<Exception during TracTracConfiguration.toString(): "+e.getMessage()+">";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.sap.sailing.domain.tracking;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Different tracking providers require different sets of arguments to start tracking a race.
|
||||
* This interface represents the functionality required to start tracking a race, agnostic of the
|
||||
* particular parameters and ways of launching the connector. Use {@link #createRaceTracker()} to
|
||||
* create the tracker that starts tracking the race identified by these tracking parameters.
|
||||
*
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
*/
|
||||
public interface RaceTrackingConnectivityParameters {
|
||||
/**
|
||||
* Starts a {@link RaceTracker} using the connectivity parameters provided by this object.
|
||||
*/
|
||||
RaceTracker createRaceTracker(TrackedEventRegistry trackedEventRegistry) throws Exception;
|
||||
|
||||
/**
|
||||
* Deliver an ID object equal to that of the {@link RaceTracker#getID()} delivered by the {@link RaceTracker}
|
||||
* that will be created from these parameters by calling {@link #createRaceTracker(TrackedEventRegistry)}.
|
||||
*/
|
||||
Object getTrackerID();
|
||||
|
||||
}
|
||||
@@ -14,7 +14,6 @@ import com.sap.sailing.domain.base.impl.DouglasPeucker;
|
||||
import com.sap.sailing.domain.common.Distance;
|
||||
import com.sap.sailing.domain.common.LegType;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.Placemark;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.RaceIdentifier;
|
||||
import com.sap.sailing.domain.common.Tack;
|
||||
|
||||
+4
-4
@@ -5,7 +5,6 @@ import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
@@ -54,7 +53,7 @@ public class RaceTrackerTest {
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws MalformedURLException, FileNotFoundException, URISyntaxException, InterruptedException {
|
||||
public void setUp() throws Exception {
|
||||
service = new RacingEventServiceImpl();
|
||||
raceHandle = service.addTracTracRace(paramUrl, liveUri, storedUri, EmptyWindStore.INSTANCE, /* timeoutInMilliseconds */ 60000);
|
||||
raceHandle.getRaces(); // wait for RaceDefinition to be completely wired in Event
|
||||
@@ -96,7 +95,7 @@ public class RaceTrackerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStopTracking() throws MalformedURLException, IOException, InterruptedException, URISyntaxException {
|
||||
public void testStopTracking() throws Exception {
|
||||
TrackedEvent oldTrackedEvent = raceHandle.getTrackedEvent();
|
||||
TrackedRace oldTrackedRace = getTrackedRace(oldTrackedEvent);
|
||||
RaceDefinition oldRaceDefinition = oldTrackedRace.getRace();
|
||||
@@ -117,9 +116,10 @@ public class RaceTrackerTest {
|
||||
/**
|
||||
* This test asserts that tracking the same race twice doesn't create another tracker and in particular no
|
||||
* new tracked event / tracked race.
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testTrackingSameRaceWithoutStopping() throws MalformedURLException, IOException, InterruptedException, URISyntaxException {
|
||||
public void testTrackingSameRaceWithoutStopping() throws Exception {
|
||||
TrackedEvent oldTrackedEvent = raceHandle.getTrackedEvent();
|
||||
TrackedRace oldTrackedRace = getTrackedRace(oldTrackedEvent);
|
||||
RacesHandle myRaceHandle = service.addTracTracRace(paramUrl, liveUri, storedUri, EmptyWindStore.INSTANCE, /* timeoutInMilliseconds */ 60000);
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
<intAttribute key="default_start_level" value="4"/>
|
||||
<booleanAttribute key="includeOptional" value="true"/>
|
||||
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog -console -clean"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
|
||||
@@ -26,4 +26,5 @@
|
||||
<booleanAttribute key="useCustomFeatures" value="false"/>
|
||||
<booleanAttribute key="useDefaultConfigArea" value="false"/>
|
||||
<stringAttribute key="workspace_bundles" value="com.google.gwt.osgi@default:default,com.googlecode.java-diff-utils@default:default,com.mongodb.driver@default:default,com.sap.sailing.declination@default:default,com.sap.sailing.domain.common@default:default,com.sap.sailing.domain.persistence@default:default,com.sap.sailing.domain.swisstimingadapter.persistence@default:default,com.sap.sailing.domain.swisstimingadapter@default:default,com.sap.sailing.domain.tractracadapter.persistence@default:default,com.sap.sailing.domain.tractracadapter@default:default,com.sap.sailing.domain@default:default,com.sap.sailing.expeditionconnector@default:default,com.sap.sailing.geocoding@default:default,com.sap.sailing.gwt.ui@default:default,com.sap.sailing.mongodb@default:default,com.sap.sailing.operationaltransformation@default:default,com.sap.sailing.server@default:default,com.sap.sailing.udpconnector@default:default,com.sap.sailing.www.events@default:default,com.sap.sailing.www@default:default,com.sap.sailing.xcelsiusadapter@default:default,com.tractrac.clientmodule@default:default,com.tractrac.resultapi@default:default,org.json.simple@default:default,org.moxieapps.gwt.highcharts@default:default"/>
|
||||
<stringAttribute key="yk-options" value=" additional-options2=onexit\=snapshot "/>
|
||||
</launchConfiguration>
|
||||
|
||||
@@ -25,11 +25,13 @@ import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
|
||||
import com.sap.sailing.domain.leaderboard.RaceInLeaderboard;
|
||||
import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory;
|
||||
import com.sap.sailing.domain.tracking.RaceTracker;
|
||||
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
|
||||
import com.sap.sailing.domain.tracking.RacesHandle;
|
||||
import com.sap.sailing.domain.tracking.TrackedEvent;
|
||||
import com.sap.sailing.domain.tracking.TrackedEventRegistry;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.WindStore;
|
||||
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
|
||||
import com.sap.sailing.domain.tractracadapter.DomainFactory;
|
||||
import com.sap.sailing.domain.tractracadapter.RaceRecord;
|
||||
import com.sap.sailing.domain.tractracadapter.TracTracRaceTracker;
|
||||
@@ -92,7 +94,7 @@ public interface RacingEventService extends TrackedEventRegistry {
|
||||
*/
|
||||
Event addEvent(URL jsonURL, URI liveURI, URI storedURI, WindStore windStore, long timeoutInMilliseconds)
|
||||
throws MalformedURLException, FileNotFoundException, URISyntaxException, IOException, ParseException,
|
||||
org.json.simple.parser.ParseException;
|
||||
org.json.simple.parser.ParseException, Exception;
|
||||
|
||||
/**
|
||||
* If not already tracking the URL/URI/URI combination, adds a single race tracker and starts tracking the race,
|
||||
@@ -117,7 +119,7 @@ public interface RacingEventService extends TrackedEventRegistry {
|
||||
* that race is stopped; use -1 to wait forever
|
||||
*/
|
||||
RacesHandle addTracTracRace(URL paramURL, URI liveURI, URI storedURI, WindStore windStore, long timeoutInMilliseconds)
|
||||
throws MalformedURLException, FileNotFoundException, URISyntaxException;
|
||||
throws MalformedURLException, FileNotFoundException, URISyntaxException, Exception;
|
||||
|
||||
/**
|
||||
* Same as {@link #addTracTracRace(URL, URI, URI, WindStore, long)}, only that start and end of tracking are
|
||||
@@ -126,7 +128,7 @@ public interface RacingEventService extends TrackedEventRegistry {
|
||||
*/
|
||||
RacesHandle addTracTracRace(URL paramURL, URI liveURI, URI storedURI, TimePoint trackingStartTime,
|
||||
TimePoint trackingEndTime, WindStore windStore, long timeoutForReceivingRaceDefinitionInMilliseconds)
|
||||
throws MalformedURLException, FileNotFoundException, URISyntaxException;
|
||||
throws MalformedURLException, FileNotFoundException, URISyntaxException, Exception;
|
||||
|
||||
/**
|
||||
* Stops tracking all races of the event specified. This will also stop tracking wind for all races of this event.
|
||||
@@ -303,4 +305,10 @@ public interface RacingEventService extends TrackedEventRegistry {
|
||||
|
||||
void removeExpeditionListener(ExpeditionListener listener);
|
||||
|
||||
/**
|
||||
* @param windStore must not be <code>null</code>, but can, e.g., be an {@link EmptyWindStore}
|
||||
*/
|
||||
RacesHandle addRace(RaceTrackingConnectivityParameters params, WindStore windStore, long timeoutInMilliseconds)
|
||||
throws MalformedURLException, FileNotFoundException, URISyntaxException, Exception;
|
||||
|
||||
}
|
||||
|
||||
@@ -448,8 +448,7 @@ public class AdminApp extends Servlet {
|
||||
}
|
||||
}
|
||||
|
||||
private void addEvent(HttpServletRequest req, HttpServletResponse resp) throws URISyntaxException, IOException,
|
||||
ParseException, org.json.simple.parser.ParseException {
|
||||
private void addEvent(HttpServletRequest req, HttpServletResponse resp) throws MongoException, Exception {
|
||||
URL jsonURL = new URL(req.getParameter(PARAM_NAME_EVENT_JSON_URL));
|
||||
URI liveURI = new URI(req.getParameter(PARAM_NAME_LIVE_URI));
|
||||
URI storedURI = new URI(req.getParameter(PARAM_NAME_STORED_URI));
|
||||
@@ -471,8 +470,7 @@ public class AdminApp extends Servlet {
|
||||
return EmptyWindStore.INSTANCE;
|
||||
}
|
||||
|
||||
private void addRace(HttpServletRequest req, HttpServletResponse resp) throws URISyntaxException, IOException,
|
||||
ParseException, org.json.simple.parser.ParseException {
|
||||
private void addRace(HttpServletRequest req, HttpServletResponse resp) throws MongoException, Exception {
|
||||
URL paramURL = new URL(req.getParameter(PARAM_NAME_PARAM_URL));
|
||||
URI liveURI = new URI(req.getParameter(PARAM_NAME_LIVE_URI));
|
||||
URI storedURI = new URI(req.getParameter(PARAM_NAME_STORED_URI));
|
||||
|
||||
+18
-12
@@ -1,6 +1,5 @@
|
||||
package com.sap.sailing.server.impl;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.SocketException;
|
||||
@@ -55,6 +54,7 @@ import com.sap.sailing.domain.swisstimingadapter.persistence.SwissTimingAdapterP
|
||||
import com.sap.sailing.domain.tracking.DynamicTrackedEvent;
|
||||
import com.sap.sailing.domain.tracking.RaceListener;
|
||||
import com.sap.sailing.domain.tracking.RaceTracker;
|
||||
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
|
||||
import com.sap.sailing.domain.tracking.RacesHandle;
|
||||
import com.sap.sailing.domain.tracking.TrackedEvent;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
@@ -356,7 +356,7 @@ public class RacingEventServiceImpl implements RacingEventService, EventFetcher,
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Event addEvent(URL jsonURL, URI liveURI, URI storedURI, WindStore windStore, long timeoutInMilliseconds) throws URISyntaxException, IOException, ParseException, org.json.simple.parser.ParseException {
|
||||
public synchronized Event addEvent(URL jsonURL, URI liveURI, URI storedURI, WindStore windStore, long timeoutInMilliseconds) throws Exception {
|
||||
JSONService jsonService = getDomainFactory().parseJSONURL(jsonURL);
|
||||
Event event = null;
|
||||
for (RaceRecord rr : jsonService.getRaceRecords()) {
|
||||
@@ -433,20 +433,18 @@ public class RacingEventServiceImpl implements RacingEventService, EventFetcher,
|
||||
|
||||
@Override
|
||||
public synchronized RacesHandle addTracTracRace(URL paramURL, URI liveURI, URI storedURI, WindStore windStore,
|
||||
long timeoutInMilliseconds) throws MalformedURLException, FileNotFoundException, URISyntaxException {
|
||||
return addTracTracRace(paramURL, liveURI, storedURI, /* startOfTracking */ null,
|
||||
/* endOfTracking */ null, windStore, timeoutInMilliseconds);
|
||||
long timeoutInMilliseconds) throws Exception {
|
||||
return addRace(getDomainFactory().createTrackingConnectivityParameters(paramURL, liveURI, storedURI, /* startOfTracking */ null,
|
||||
/* endOfTracking */ null, windStore), windStore, timeoutInMilliseconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized RacesHandle addTracTracRace(URL paramURL, URI liveURI, URI storedURI,
|
||||
TimePoint startOfTracking, TimePoint endOfTracking, WindStore windStore,
|
||||
long timeoutInMilliseconds) throws MalformedURLException, FileNotFoundException, URISyntaxException {
|
||||
Triple<URL, URI, URI> key = new Triple<URL, URI, URI>(paramURL, liveURI, storedURI);
|
||||
RaceTracker tracker = raceTrackersByID.get(key);
|
||||
public synchronized RacesHandle addRace(RaceTrackingConnectivityParameters params, WindStore windStore,
|
||||
long timeoutInMilliseconds) throws Exception {
|
||||
RaceTracker tracker = raceTrackersByID.get(params.getTrackerID());
|
||||
if (tracker == null) {
|
||||
tracker = getDomainFactory().createRaceTracker(paramURL, liveURI, storedURI, startOfTracking, endOfTracking, windStore, this);
|
||||
raceTrackersByID.put(tracker.getID(), tracker);
|
||||
tracker = params.createRaceTracker(this);
|
||||
raceTrackersByID.put(params.getTrackerID(), tracker);
|
||||
Set<RaceTracker> trackers = raceTrackersByEvent.get(tracker.getEvent());
|
||||
if (trackers == null) {
|
||||
trackers = new HashSet<RaceTracker>();
|
||||
@@ -483,6 +481,14 @@ public class RacingEventServiceImpl implements RacingEventService, EventFetcher,
|
||||
}
|
||||
return tracker.getRacesHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized RacesHandle addTracTracRace(URL paramURL, URI liveURI, URI storedURI,
|
||||
TimePoint startOfTracking, TimePoint endOfTracking, WindStore windStore,
|
||||
long timeoutInMilliseconds) throws Exception {
|
||||
return addRace(getDomainFactory().createTrackingConnectivityParameters(paramURL, liveURI, storedURI, startOfTracking,
|
||||
endOfTracking, windStore), windStore, timeoutInMilliseconds);
|
||||
}
|
||||
|
||||
private void ensureEventIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking(DynamicTrackedEvent trackedEvent) {
|
||||
synchronized (eventsObservedForDefaultLeaderboard) {
|
||||
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
package com.sap.sailing.server.operationaltransformation;
|
||||
|
||||
import com.sap.sailing.server.RacingEventService;
|
||||
|
||||
public class TrackRace extends AbstractRacingEventServiceOperation {
|
||||
|
||||
@Override
|
||||
public RacingEventServiceOperation transformClientOp(RacingEventServiceOperation serverOp) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RacingEventServiceOperation transformServerOp(RacingEventServiceOperation clientOp) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RacingEventService applyTo(RacingEventService toState) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user