feat: basiquement tout fonctionne sauf le callback en version serveur
on dirait que le truc ets pas sérialisable et jsp comment faire
This commit is contained in:
parent
d304043c92
commit
72ebb50107
47
.vscode/launch.json
vendored
Normal file
47
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "Launch Main",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "linda.search.basic.Main",
|
||||||
|
"projectName": "Projet PDR"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "Launch Searcher",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "linda.search.basic.Searcher",
|
||||||
|
"args": [
|
||||||
|
"4000"
|
||||||
|
],
|
||||||
|
"projectName": "Projet PDR"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "Launch Manager",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "linda.search.basic.Manager",
|
||||||
|
"args": [
|
||||||
|
"4000",
|
||||||
|
"/usr/share/dict/french",
|
||||||
|
"agneau"
|
||||||
|
],
|
||||||
|
"projectName": "Projet PDR"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "Launch LindaServer",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "linda.server.LindaServer",
|
||||||
|
"args": [
|
||||||
|
"4000"
|
||||||
|
],
|
||||||
|
"projectName": "Projet PDR"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import linda.*;
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
|
|
||||||
private static final int NB_SEARCHER = 10;
|
private static final int NB_SEARCHER = 2;
|
||||||
|
|
||||||
public static void main(String args[]) throws InterruptedException {
|
public static void main(String args[]) throws InterruptedException {
|
||||||
// if (args.length != 2) {
|
// if (args.length != 2) {
|
||||||
|
@ -13,19 +13,20 @@ public class Main {
|
||||||
// }
|
// }
|
||||||
Linda linda = new linda.shm.CentralizedLinda();
|
Linda linda = new linda.shm.CentralizedLinda();
|
||||||
|
|
||||||
Manager manager1 = new Manager(linda, "/usr/share/dict/french", "abasourdir");
|
Manager manager1 = new Manager(linda, "/usr/share/dict/french", "abasourdir", 500);
|
||||||
Thread thread1 = new Thread(manager1);
|
Thread thread1 = new Thread(manager1);
|
||||||
thread1.start();
|
|
||||||
|
|
||||||
Manager manager2 = new Manager(linda, "/usr/share/dict/french", "agneau");
|
Manager manager2 = new Manager(linda, "/usr/share/dict/french", "agneau");
|
||||||
Thread thread2 = new Thread(manager2);
|
Thread thread2 = new Thread(manager2);
|
||||||
thread2.start();
|
|
||||||
|
|
||||||
for (int i = 0; i < NB_SEARCHER; i++) {
|
for (int i = 0; i < NB_SEARCHER; i++) {
|
||||||
Searcher searcher = new Searcher(linda);
|
Searcher searcher = new Searcher(linda);
|
||||||
(new Thread(searcher)).start();
|
(new Thread(searcher)).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
thread1.start();
|
||||||
|
Thread.sleep(100);
|
||||||
|
thread2.start();
|
||||||
thread1.join();
|
thread1.join();
|
||||||
thread2.join();
|
thread2.join();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,21 @@
|
||||||
package linda.search.basic;
|
package linda.search.basic;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.Timer;
|
|
||||||
import java.util.TimerTask;
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import linda.*;
|
import java.rmi.NotBoundException;
|
||||||
|
import java.rmi.RemoteException;
|
||||||
|
|
||||||
|
import linda.Callback;
|
||||||
|
import linda.Linda;
|
||||||
|
import linda.Tuple;
|
||||||
|
import linda.Linda.eventMode;
|
||||||
|
import linda.Linda.eventTiming;
|
||||||
|
import linda.server.LindaClient;
|
||||||
|
import linda.server.SCallback;
|
||||||
|
|
||||||
public class Manager implements Runnable {
|
public class Manager implements Runnable {
|
||||||
|
|
||||||
|
@ -17,6 +26,7 @@ public class Manager implements Runnable {
|
||||||
private String search;
|
private String search;
|
||||||
private int bestvalue = Integer.MAX_VALUE; // lower is better
|
private int bestvalue = Integer.MAX_VALUE; // lower is better
|
||||||
private String bestresult;
|
private String bestresult;
|
||||||
|
private Integer timeout = null;
|
||||||
|
|
||||||
public Manager(Linda linda, String pathname, String search) {
|
public Manager(Linda linda, String pathname, String search) {
|
||||||
this.linda = linda;
|
this.linda = linda;
|
||||||
|
@ -26,12 +36,34 @@ public class Manager implements Runnable {
|
||||||
this.search = search;
|
this.search = search;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Manager(Linda linda, String pathname, String search, int timeout) {
|
||||||
|
this(linda, pathname, search);
|
||||||
|
this.timeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws MalformedURLException, RemoteException, NotBoundException {
|
||||||
|
if (args.length != 3) {
|
||||||
|
System.err.println("linda.search.basic.Main search file.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int port = Integer.parseInt(args[0]);
|
||||||
|
String url = "rmi://localhost:" + port + "/linda";
|
||||||
|
LindaClient lc = new LindaClient(url);
|
||||||
|
|
||||||
|
String dict = args[1];
|
||||||
|
String word = args[2];
|
||||||
|
|
||||||
|
Manager manager = new Manager(lc, dict, word);
|
||||||
|
Thread thread = new Thread(manager);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
private void addSearch() {
|
private void addSearch() {
|
||||||
System.out.println("Search (" + this.reqUUID + ") for " + this.search);
|
System.out.println("Search (" + this.reqUUID + ") for " + this.search);
|
||||||
linda.eventRegister(
|
linda.eventRegister(
|
||||||
Linda.eventMode.TAKE, Linda.eventTiming.FUTURE,
|
Linda.eventMode.TAKE, Linda.eventTiming.FUTURE,
|
||||||
new Tuple(Code.Result, this.reqUUID, String.class, Integer.class),
|
new Tuple(Code.Result, this.reqUUID, String.class, Integer.class),
|
||||||
new CbGetResult());
|
new CbUpdate());
|
||||||
linda.write(new Tuple(Code.Request, this.reqUUID, this.search));
|
linda.write(new Tuple(Code.Request, this.reqUUID, this.search));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,18 +75,35 @@ public class Manager implements Runnable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void waitForEndSearch() {
|
class CbLoop implements Callback {
|
||||||
linda.take(new Tuple(Code.Searcher, "done", this.reqUUID)); // wait for the search to be finished
|
|
||||||
linda.take(new Tuple(Code.Request, this.reqUUID, String.class)); // remove search query
|
eventMode mode;
|
||||||
|
eventTiming timing;
|
||||||
|
Tuple template;
|
||||||
|
|
||||||
|
CbLoop(eventMode mode, eventTiming timing, Tuple template) {
|
||||||
|
this.mode = mode;
|
||||||
|
this.timing = timing;
|
||||||
|
this.template = template;
|
||||||
|
|
||||||
|
linda.eventRegister(this.mode, this.timing, this.template, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void call(Tuple t) {
|
||||||
|
linda.eventRegister(this.mode, this.timing, this.template, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CbEnd implements Callback {
|
||||||
|
@Override
|
||||||
|
public void call(Tuple t) {
|
||||||
|
new CbLoop(eventMode.TAKE, eventTiming.IMMEDIATE, t); // on supprime tous les futurs "done"
|
||||||
|
linda.take(new Tuple(Code.Request, reqUUID, String.class)); // remove search query
|
||||||
System.out.println("query done");
|
System.out.println("query done");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void waitForResult() {
|
|
||||||
linda.take(new Tuple(Code.Searcher, "end", this.reqUUID));
|
|
||||||
System.out.println("Final result: \"" + bestresult + '"');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CbGetResult implements linda.Callback {
|
private class CbUpdate implements SCallback {
|
||||||
public void call(Tuple t) { // [ Result, ?UUID, ?String, ?Integer ]
|
public void call(Tuple t) { // [ Result, ?UUID, ?String, ?Integer ]
|
||||||
UUID reqUUID = (UUID) t.get(1);
|
UUID reqUUID = (UUID) t.get(1);
|
||||||
String s = (String) t.get(2);
|
String s = (String) t.get(2);
|
||||||
|
@ -68,7 +117,8 @@ public class Manager implements Runnable {
|
||||||
|
|
||||||
// Tant qu'il reste des mots à chercher, ou des résultats à traiter
|
// Tant qu'il reste des mots à chercher, ou des résultats à traiter
|
||||||
if ((linda.tryRead(new Tuple(Code.Result, reqUUID, String.class, Integer.class)) != null)
|
if ((linda.tryRead(new Tuple(Code.Result, reqUUID, String.class, Integer.class)) != null)
|
||||||
|| (linda.tryRead(new Tuple(Code.Value, String.class, reqUUID)) != null)) {
|
|| (linda.tryRead(new Tuple(Code.Value, String.class, reqUUID)) != null)
|
||||||
|
|| (linda.tryRead(new Tuple(Code.Request, reqUUID, String.class)) != null)) {
|
||||||
|
|
||||||
linda.eventRegister(
|
linda.eventRegister(
|
||||||
Linda.eventMode.TAKE, Linda.eventTiming.IMMEDIATE,
|
Linda.eventMode.TAKE, Linda.eventTiming.IMMEDIATE,
|
||||||
|
@ -81,10 +131,51 @@ public class Manager implements Runnable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TimeoutExit implements Runnable {
|
||||||
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
this.loadData();
|
try {
|
||||||
this.addSearch();
|
Thread.sleep(timeout);
|
||||||
this.waitForEndSearch();
|
} catch (InterruptedException e) {
|
||||||
this.waitForResult();
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("Search (" + reqUUID + ") timed out ");
|
||||||
|
linda.take(new Tuple(Code.Request, reqUUID, String.class)); // remove search query
|
||||||
|
linda.takeAll(new Tuple(Code.Value, String.class, reqUUID)); // remove words
|
||||||
|
|
||||||
|
new CbLoop(Linda.eventMode.TAKE, Linda.eventTiming.IMMEDIATE,
|
||||||
|
new Tuple(Code.Result, reqUUID, String.class, Integer.class)); // remove results
|
||||||
|
|
||||||
|
new CbLoop(Linda.eventMode.TAKE, Linda.eventTiming.IMMEDIATE,
|
||||||
|
new Tuple(Code.Searcher, "done", reqUUID)); // remove "done" messages
|
||||||
|
|
||||||
|
new CbLoop(Linda.eventMode.TAKE, Linda.eventTiming.IMMEDIATE,
|
||||||
|
new Tuple(Code.Searcher, "end", reqUUID)); // remove "end" messages
|
||||||
|
|
||||||
|
// TODO: chercheur doivent écrire qui ils cherchent, pour que le manager puisse
|
||||||
|
// se stopper su personne ne le cherche
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
if (this.timeout != null) {
|
||||||
|
TimeoutExit exit = new TimeoutExit();
|
||||||
|
Thread thread = new Thread(exit);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadData(); // on charge les mots du dict
|
||||||
|
this.addSearch(); // on enregistre la query
|
||||||
|
|
||||||
|
this.linda.eventRegister( // on attend la fin de la recherche
|
||||||
|
eventMode.TAKE, eventTiming.IMMEDIATE,
|
||||||
|
new Tuple(Code.Searcher, "done", this.reqUUID),
|
||||||
|
new CbEnd());
|
||||||
|
|
||||||
|
// on affiche le résultat (ou on le récupère...)
|
||||||
|
linda.take(new Tuple(Code.Searcher, "end", this.reqUUID));
|
||||||
|
System.out.println("Final result: \"" + bestresult + '"');
|
||||||
|
linda.debug("Tuple Space : ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package linda.search.basic;
|
package linda.search.basic;
|
||||||
|
|
||||||
import linda.*;
|
import linda.*;
|
||||||
|
import linda.server.LindaClient;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class Searcher implements Runnable {
|
public class Searcher implements Runnable {
|
||||||
|
@ -13,6 +14,20 @@ public class Searcher implements Runnable {
|
||||||
this.linda = linda;
|
this.linda = linda;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
if (args.length != 1) {
|
||||||
|
System.err.println("linda.search.basic.Main search file.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int port = Integer.parseInt(args[0]);
|
||||||
|
String url = "rmi://localhost:" + port + "/linda";
|
||||||
|
LindaClient lc = new LindaClient(url);
|
||||||
|
|
||||||
|
Searcher searcher = new Searcher(lc);
|
||||||
|
Thread thread = new Thread(searcher);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
private void search(Tuple treq) {
|
private void search(Tuple treq) {
|
||||||
UUID reqUUID = (UUID) treq.get(1);
|
UUID reqUUID = (UUID) treq.get(1);
|
||||||
String req = (String) treq.get(2);
|
String req = (String) treq.get(2);
|
||||||
|
@ -37,9 +52,9 @@ public class Searcher implements Runnable {
|
||||||
public void run() {
|
public void run() {
|
||||||
System.out.println("Ready to search");
|
System.out.println("Ready to search");
|
||||||
|
|
||||||
linda.read(new Tuple(Code.Request, UUID.class, String.class));
|
Tuple treq;
|
||||||
Collection<Tuple> treqs = linda.readAll(new Tuple(Code.Request, UUID.class, String.class));
|
while (true) {
|
||||||
for (Tuple treq : treqs) {
|
treq = linda.read(new Tuple(Code.Request, UUID.class, String.class));
|
||||||
search(treq);
|
search(treq);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package linda.server;
|
package linda.server;
|
||||||
|
|
||||||
|
import linda.Callback;
|
||||||
import linda.Linda;
|
import linda.Linda;
|
||||||
import linda.Tuple;
|
import linda.Tuple;
|
||||||
import linda.Callback;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
|
@ -24,7 +24,8 @@ public interface LindaRemote extends Remote {
|
||||||
|
|
||||||
public Collection<Tuple> readAll(Tuple template) throws RemoteException;
|
public Collection<Tuple> readAll(Tuple template) throws RemoteException;
|
||||||
|
|
||||||
public void eventRegister(Linda.eventMode mode, Linda.eventTiming timing, Tuple template, Callback callback) throws RemoteException;
|
public void eventRegister(Linda.eventMode mode, Linda.eventTiming timing, Tuple template, Callback callback)
|
||||||
|
throws RemoteException;
|
||||||
|
|
||||||
public void debug(String prefix) throws RemoteException;
|
public void debug(String prefix) throws RemoteException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import java.util.Collection;
|
||||||
|
|
||||||
import java.rmi.server.UnicastRemoteObject;
|
import java.rmi.server.UnicastRemoteObject;
|
||||||
import java.rmi.*;
|
import java.rmi.*;
|
||||||
|
import java.rmi.registry.LocateRegistry;
|
||||||
|
|
||||||
import linda.Callback;
|
import linda.Callback;
|
||||||
import linda.Linda;
|
import linda.Linda;
|
||||||
|
@ -21,16 +22,40 @@ public class LindaServer extends UnicastRemoteObject implements LindaRemote {
|
||||||
this.lindaInstance = new CentralizedLinda();
|
this.lindaInstance = new CentralizedLinda();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Integer port = 4000;
|
||||||
|
|
||||||
|
if (args.length == 2) {
|
||||||
|
port = Integer.parseInt(args[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = "rmi://localhost:" + port + "/linda";
|
||||||
|
|
||||||
|
try {
|
||||||
|
LindaServer ls = new LindaServer();
|
||||||
|
LocateRegistry.createRegistry(port);
|
||||||
|
Naming.rebind(url, ls);
|
||||||
|
System.out.println("L'instance Linda a été publiée sur le registre (" + url + ") !");
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void write(Tuple t) {
|
public void write(Tuple t) {
|
||||||
|
System.out.println("received write request;" + t);
|
||||||
lindaInstance.write(t);
|
lindaInstance.write(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Tuple take(Tuple template) throws RemoteException {
|
public Tuple take(Tuple template) throws RemoteException {
|
||||||
return lindaInstance.take(template);
|
Tuple res = lindaInstance.take(template);
|
||||||
|
System.out.println("received take request, with template: " + template + "\nreturning:" + res);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Tuple read(Tuple template) throws RemoteException {
|
public Tuple read(Tuple template) throws RemoteException {
|
||||||
return lindaInstance.read(template);
|
Tuple res = lindaInstance.read(template);
|
||||||
|
System.out.println("received read request, with template: " + template + "\nreturning:" + res);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Tuple tryTake(Tuple template) throws RemoteException {
|
public Tuple tryTake(Tuple template) throws RemoteException {
|
||||||
|
@ -49,7 +74,8 @@ public class LindaServer extends UnicastRemoteObject implements LindaRemote {
|
||||||
return lindaInstance.readAll(template);
|
return lindaInstance.readAll(template);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void eventRegister(Linda.eventMode mode, Linda.eventTiming timing, Tuple template, Callback callback) throws RemoteException {
|
public void eventRegister(Linda.eventMode mode, Linda.eventTiming timing, Tuple template, Callback callback)
|
||||||
|
throws RemoteException {
|
||||||
lindaInstance.eventRegister(mode, timing, template, callback);
|
lindaInstance.eventRegister(mode, timing, template, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
16
src/main/java/linda/server/SCallback.java
Normal file
16
src/main/java/linda/server/SCallback.java
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package linda.server;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import linda.Callback;
|
||||||
|
import linda.Tuple;
|
||||||
|
|
||||||
|
public interface SCallback extends Serializable, Callback {
|
||||||
|
/**
|
||||||
|
* Callback when a tuple appears.
|
||||||
|
* See Linda.eventRegister for details.
|
||||||
|
*
|
||||||
|
* @param t the new tuple
|
||||||
|
*/
|
||||||
|
void call(Tuple t);
|
||||||
|
}
|
|
@ -11,15 +11,13 @@ import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import java.util.concurrent.locks.Condition;
|
import java.util.concurrent.locks.Condition;
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
|
||||||
class Reveil {
|
class Reveil {
|
||||||
|
|
||||||
Lock lock = new ReentrantLock();
|
Lock lock = new ReentrantLock();
|
||||||
|
|
||||||
Tuple template;
|
|
||||||
Condition condition;
|
Condition condition;
|
||||||
|
Tuple template;
|
||||||
|
|
||||||
Reveil(Tuple template) {
|
Reveil(Tuple template) {
|
||||||
this.template = template;
|
this.template = template;
|
||||||
|
@ -271,7 +269,7 @@ public class CentralizedLinda implements Linda {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void debug(String prefix) {
|
public void debug(String prefix) {
|
||||||
System.out.println(prefix + tuples);
|
System.out.println(prefix + tuples.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
package linda.test;
|
|
||||||
|
|
||||||
import linda.server.LindaServer;
|
|
||||||
|
|
||||||
import java.rmi.*;
|
|
||||||
import java.rmi.registry.*;
|
|
||||||
|
|
||||||
public class ServerTest {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
try {
|
|
||||||
LindaServer ls = new LindaServer();
|
|
||||||
LocateRegistry.createRegistry(Integer.parseInt(args[1]));
|
|
||||||
Naming.rebind(args[0], ls);
|
|
||||||
System.out.println("L'instance Linda a été publié sur le registre (" + args[0] + ") !\n");
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in a new issue