From e2e45550091982339b7399b364f4b0d4bff531bd Mon Sep 17 00:00:00 2001 From: catbref Date: Wed, 24 Jun 2020 11:54:06 +0100 Subject: [PATCH] Added /websockets/admin/status and improved GET version. NodeStatus contructor now fills in fields, which themselves are now 'final'. NodeStatus also includes numberOfConnections and height as per systray. AdminResource.status() unified with websocket version. --- src/main/java/org/qortal/api/ApiService.java | 2 + .../java/org/qortal/api/model/NodeStatus.java | 24 ++++++- .../qortal/api/resource/AdminResource.java | 6 -- .../api/websocket/AdminStatusWebSocket.java | 68 +++++++++++++++++++ .../org/qortal/controller/Controller.java | 4 ++ .../org/qortal/controller/StatusNotifier.java | 42 ++++++++++++ 6 files changed, 137 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/qortal/api/websocket/AdminStatusWebSocket.java create mode 100644 src/main/java/org/qortal/controller/StatusNotifier.java diff --git a/src/main/java/org/qortal/api/ApiService.java b/src/main/java/org/qortal/api/ApiService.java index 1bddef5f..52b03cac 100644 --- a/src/main/java/org/qortal/api/ApiService.java +++ b/src/main/java/org/qortal/api/ApiService.java @@ -40,6 +40,7 @@ import org.glassfish.jersey.servlet.ServletContainer; import org.qortal.api.resource.AnnotationPostProcessor; import org.qortal.api.resource.ApiDefinition; import org.qortal.api.websocket.ActiveChatsWebSocket; +import org.qortal.api.websocket.AdminStatusWebSocket; import org.qortal.api.websocket.BlocksWebSocket; import org.qortal.api.websocket.ChatMessagesWebSocket; import org.qortal.settings.Settings; @@ -192,6 +193,7 @@ public class ApiService { rewriteHandler.addRule(new RedirectPatternRule("/api-documentation", "/api-documentation/")); // redirect to add trailing slash if missing } + context.addServlet(AdminStatusWebSocket.class, "/websockets/admin/status"); context.addServlet(BlocksWebSocket.class, "/websockets/blocks"); context.addServlet(ActiveChatsWebSocket.class, "/websockets/chat/active/*"); context.addServlet(ChatMessagesWebSocket.class, "/websockets/chat/messages"); diff --git a/src/main/java/org/qortal/api/model/NodeStatus.java b/src/main/java/org/qortal/api/model/NodeStatus.java index a9f0e167..9e50df24 100644 --- a/src/main/java/org/qortal/api/model/NodeStatus.java +++ b/src/main/java/org/qortal/api/model/NodeStatus.java @@ -3,16 +3,34 @@ package org.qortal.api.model; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; +import org.qortal.controller.Controller; +import org.qortal.network.Network; + @XmlAccessorType(XmlAccessType.FIELD) public class NodeStatus { - public boolean isMintingPossible; - public boolean isSynchronizing; + public final boolean isMintingPossible; + public final boolean isSynchronizing; // Not always present - public Integer syncPercent; + public final Integer syncPercent; + + public final int numberOfConnections; + + public final int height; public NodeStatus() { + isMintingPossible = Controller.getInstance().isMintingPossible(); + isSynchronizing = Controller.getInstance().isSynchronizing(); + + if (isSynchronizing) + syncPercent = Controller.getInstance().getSyncPercent(); + else + syncPercent = null; + + numberOfConnections = Network.getInstance().getHandshakedPeers().size(); + + height = Controller.getInstance().getChainHeight(); } } diff --git a/src/main/java/org/qortal/api/resource/AdminResource.java b/src/main/java/org/qortal/api/resource/AdminResource.java index db4d3026..9b765f40 100644 --- a/src/main/java/org/qortal/api/resource/AdminResource.java +++ b/src/main/java/org/qortal/api/resource/AdminResource.java @@ -137,12 +137,6 @@ public class AdminResource { NodeStatus nodeStatus = new NodeStatus(); - nodeStatus.isMintingPossible = Controller.getInstance().isMintingPossible(); - nodeStatus.isSynchronizing = Controller.getInstance().isSynchronizing(); - - if (nodeStatus.isSynchronizing) - nodeStatus.syncPercent = Controller.getInstance().getSyncPercent(); - return nodeStatus; } diff --git a/src/main/java/org/qortal/api/websocket/AdminStatusWebSocket.java b/src/main/java/org/qortal/api/websocket/AdminStatusWebSocket.java new file mode 100644 index 00000000..2a957921 --- /dev/null +++ b/src/main/java/org/qortal/api/websocket/AdminStatusWebSocket.java @@ -0,0 +1,68 @@ +package org.qortal.api.websocket; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.servlet.WebSocketServlet; +import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; +import org.qortal.api.model.NodeStatus; +import org.qortal.controller.StatusNotifier; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; + +@WebSocket +@SuppressWarnings("serial") +public class AdminStatusWebSocket extends WebSocketServlet implements ApiWebSocket { + + @Override + public void configure(WebSocketServletFactory factory) { + factory.register(AdminStatusWebSocket.class); + } + + @OnWebSocketConnect + public void onWebSocketConnect(Session session) { + AtomicReference previousOutput = new AtomicReference<>(null); + + StatusNotifier.Listener listener = timestamp -> onNotify(session, previousOutput); + StatusNotifier.getInstance().register(session, listener); + + this.onNotify(session, previousOutput); + } + + @OnWebSocketClose + public void onWebSocketClose(Session session, int statusCode, String reason) { + StatusNotifier.getInstance().deregister(session); + } + + @OnWebSocketMessage + public void onWebSocketMessage(Session session, String message) { + } + + private void onNotify(Session session,AtomicReference previousOutput) { + try (final Repository repository = RepositoryManager.getRepository()) { + NodeStatus nodeStatus = new NodeStatus(); + + StringWriter stringWriter = new StringWriter(); + + this.marshall(stringWriter, nodeStatus); + + // Only output if something has changed + String output = stringWriter.toString(); + if (output.equals(previousOutput.get())) + return; + + previousOutput.set(output); + session.getRemote().sendString(output); + } catch (DataException | IOException e) { + // No output this time? + } + } + +} diff --git a/src/main/java/org/qortal/controller/Controller.java b/src/main/java/org/qortal/controller/Controller.java index 9f9b80de..9a93505a 100644 --- a/src/main/java/org/qortal/controller/Controller.java +++ b/src/main/java/org/qortal/controller/Controller.java @@ -647,6 +647,10 @@ public class Controller extends Thread { String tooltip = String.format("%s - %d %s - %s %d", actionText, numberOfPeers, connectionsText, heightText, height); SysTray.getInstance().setToolTipText(tooltip); + + this.callbackExecutor.execute(() -> { + StatusNotifier.getInstance().onStatusChange(NTP.getTime()); + }); } public void deleteExpiredTransactions() { diff --git a/src/main/java/org/qortal/controller/StatusNotifier.java b/src/main/java/org/qortal/controller/StatusNotifier.java new file mode 100644 index 00000000..02090db3 --- /dev/null +++ b/src/main/java/org/qortal/controller/StatusNotifier.java @@ -0,0 +1,42 @@ +package org.qortal.controller; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jetty.websocket.api.Session; + +public class StatusNotifier { + + private static StatusNotifier instance; + + @FunctionalInterface + public interface Listener { + void notify(long timestamp); + } + + private Map listenersBySession = new HashMap<>(); + + private StatusNotifier() { + } + + public static synchronized StatusNotifier getInstance() { + if (instance == null) + instance = new StatusNotifier(); + + return instance; + } + + public synchronized void register(Session session, Listener listener) { + this.listenersBySession.put(session, listener); + } + + public synchronized void deregister(Session session) { + this.listenersBySession.remove(session); + } + + public synchronized void onStatusChange(long now) { + for (Listener listener : this.listenersBySession.values()) + listener.notify(now); + } + +}