remote tab misc progress

This commit is contained in:
rusefi 2020-08-30 00:21:34 -04:00
parent c80a03ab84
commit 24f6ead04b
19 changed files with 189 additions and 64 deletions

View File

@ -8,7 +8,7 @@ import java.util.concurrent.atomic.AtomicInteger;
public class NamedThreadFactory implements ThreadFactory {
private final AtomicInteger counter = new AtomicInteger();
private String name;
private final String name;
private final boolean isDaemon;
public NamedThreadFactory(String name) {
@ -20,7 +20,6 @@ public class NamedThreadFactory implements ThreadFactory {
this.isDaemon = isDaemon;
}
@Override
public Thread newThread(@NotNull Runnable r) {
Thread t = Executors.defaultThreadFactory().newThread(r);

View File

@ -15,6 +15,7 @@ public class SessionDetails {
public static final String AUTH_TOKEN = "authToken";
public static final String CONNECTOR_VERSION = "connectorVersion";
public static final String IMPLEMENTATION = "implementation";
public static final String AGE = "age";
private static final String CONTROLLER = "controller";
private static final String HARDCODED_ONE_TIME_CODE = System.getProperty("ONE_TIME_CODE");
@ -74,11 +75,12 @@ public class SessionDetails {
JSONObject jsonObject = HttpUtil.parse(jsonString);
String authToken = (String) jsonObject.get(AUTH_TOKEN);
long oneTimeCode = (Long)jsonObject.get(VEHICLE_TOKEN);
long oneTimeCode = (Long) jsonObject.get(VEHICLE_TOKEN);
long connectorVersion = (long) jsonObject.get(CONNECTOR_VERSION);
String age = (String) jsonObject.get(AGE);
NetworkConnector.Implementation implementation = NetworkConnector.Implementation.find((String) jsonObject.get(IMPLEMENTATION));
ControllerInfo controllerInfo = ControllerInfo.valueOf((String) jsonObject.get(CONTROLLER));
ControllerInfo controllerInfo = ControllerInfo.valueOf((String) jsonObject.get(CONTROLLER));
return new SessionDetails(implementation, controllerInfo, authToken, (int) oneTimeCode, (int) connectorVersion);
}

View File

@ -2,6 +2,7 @@ package com.rusefi.tools.online;
import com.rusefi.proxy.client.LocalApplicationProxy;
import com.rusefi.server.ControllerInfo;
import com.rusefi.server.SessionDetails;
import com.rusefi.server.UserDetails;
import org.jetbrains.annotations.NotNull;
import org.json.simple.JSONArray;
@ -54,7 +55,8 @@ public class ProxyClient {
UserDetails vehicleOwner = UserDetails.valueOf(element);
boolean isUsed = (Boolean) element.get(IS_USED);
String ownerName = (String) element.get(OWNER);
userLists.add(new PublicSession(vehicleOwner, ci, isUsed, ownerName));
String age = (String) element.get(SessionDetails.AGE);
userLists.add(new PublicSession(vehicleOwner, ci, isUsed, ownerName, age));
}
System.out.println("object=" + array);

View File

@ -14,12 +14,18 @@ public class PublicSession {
* Person currently in control of tuning session
*/
private final String tunerName;
private final String age;
public PublicSession(UserDetails vehicleOwner, ControllerInfo controllerInfo, boolean isUsed, String tunerName) {
public PublicSession(UserDetails vehicleOwner, ControllerInfo controllerInfo, boolean isUsed, String tunerName, String age) {
this.vehicleOwner = vehicleOwner;
this.controllerInfo = controllerInfo;
this.isUsed = isUsed;
this.tunerName = tunerName;
this.age = age;
}
public String getAge() {
return age;
}
public UserDetails getVehicleOwner() {

View File

@ -1,5 +1,6 @@
package com.rusefi.ui;
import com.devexperts.logging.Logging;
import com.rusefi.auth.AuthTokenUtil;
import com.rusefi.ui.storage.PersistentConfiguration;
import com.rusefi.ui.util.URLLabel;
@ -16,6 +17,7 @@ import java.io.IOException;
import static com.rusefi.ui.storage.PersistentConfiguration.getConfig;
public class AuthTokenPanel {
private final static Logging log = Logging.getLogging(AuthTokenPanel.class);
private final JPanel content = new JPanel(new BorderLayout());
private final JTextField authTokenTestField = new JTextField();
@ -105,18 +107,18 @@ public class AuthTokenPanel {
String data = (String) clipboard.getData(DataFlavor.stringFlavor);
paste.setEnabled(AuthTokenUtil.isToken(data));
} catch (IOException | IllegalStateException | UnsupportedFlavorException ex) {
// ignoring this exception
log.info("Ignoring " + ex);
}
}
private void grabText() {
private void persistToken() {
setAuthToken(AuthTokenPanel.this.authTokenTestField.getText());
PersistentConfiguration.getConfig().save();
}
private void onTextChange() {
if (AuthTokenUtil.isToken(authTokenTestField.getText())) {
grabText();
persistToken();
}
}

View File

@ -116,7 +116,7 @@ public class FullServerTest {
TestHelper.assertLatch("controllerRegistered", controllerRegistered);
SessionDetails authenticatorSessionDetails = new SessionDetails(NetworkConnector.Implementation.Unknown, controllerInfo, TEST_TOKEN_3, networkConnectorResult.getOneTimeToken(), rusEFIVersion.CONSOLE_VERSION);
SessionDetails authenticatorSessionDetails = new SessionDetails(NetworkConnector.Implementation.Unknown, controllerInfo, TEST_TOKEN_3, networkConnectorResult.getOneTimeToken(), rusEFIVersion.CONSOLE_VERSION, "");
ApplicationRequest applicationRequest = new ApplicationRequest(authenticatorSessionDetails, userDetailsResolver.apply(TestHelper.TEST_TOKEN_1));
// start authenticator

View File

@ -59,7 +59,6 @@ public class Backend implements Closeable {
* @see BinaryProtocolProxy#USER_IO_TIMEOUT
*/
private static final int APPLICATION_INACTIVITY_TIMEOUT = 3 * Timeouts.MINUTE;
static final String AGE = "age";
private static final ThreadFactory APPLICATION_CONNECTION_CLEANUP = new NamedThreadFactory("rusEFI Application connections Cleanup");
private static final ThreadFactory GAUGE_POKER = new NamedThreadFactory("rusEFI gauge poker");
@ -277,7 +276,7 @@ public class Backend implements Closeable {
JsonObjectBuilder b = Json.createObjectBuilder()
.add(UserDetails.USER_ID, application.getUserDetails().getUserId())
.add(UserDetails.USERNAME, application.getUserDetails().getUserName())
.add(AGE, application.getBirthday().getDuration())
.add(SessionDetails.AGE, application.getBirthday().getDuration())
;
JsonObject applicationObject = addStreamStats(b, application.getClientStream())
.build();
@ -308,7 +307,7 @@ public class Backend implements Closeable {
JsonObjectBuilder objectBuilder = Json.createObjectBuilder()
.add(UserDetails.USER_ID, client.getUserDetails().getUserId())
.add(UserDetails.USERNAME, client.getUserDetails().getUserName())
.add(AGE, client.getBirthday().getDuration())
.add(SessionDetails.AGE, client.getBirthday().getDuration())
.add("OUTPUT_ROUND_TRIP", client.getOutputRoundAroundDuration())
.add(ProxyClient.IS_USED, client.getTwoKindSemaphore().isUsed())
.add(ControllerStateDetails.RPM, rpm)

View File

@ -74,7 +74,7 @@ public class Monitoring {
builder.add("framework version", rusEFIVersion.CONSOLE_VERSION);
builder.add("compiled", new Date(rusEFIVersion.classBuildTimeMillis()).toString());
builder.add("now", System.currentTimeMillis());
builder.add(Backend.AGE, birthday.getDuration());
builder.add(SessionDetails.AGE, birthday.getDuration());
return new RsJson(builder.build());
}

View File

@ -11,7 +11,7 @@ public class SessionDetailsTest {
@Test
public void testSerialization() {
SessionDetails sd = new SessionDetails(NetworkConnector.Implementation.Unknown, TestHelper.CONTROLLER_INFO, "auth", 123, rusEFIVersion.CONSOLE_VERSION);
SessionDetails sd = new SessionDetails(NetworkConnector.Implementation.Unknown, TestHelper.CONTROLLER_INFO, "auth", 123, rusEFIVersion.CONSOLE_VERSION, "");
String json = sd.toJson();
SessionDetails fromJson = SessionDetails.valueOf(json);
@ -20,7 +20,7 @@ public class SessionDetailsTest {
@Test
public void testApplicationRequest() {
SessionDetails sd = new SessionDetails(NetworkConnector.Implementation.Unknown, TestHelper.CONTROLLER_INFO, "auth", 123, rusEFIVersion.CONSOLE_VERSION);
SessionDetails sd = new SessionDetails(NetworkConnector.Implementation.Unknown, TestHelper.CONTROLLER_INFO, "auth", 123, rusEFIVersion.CONSOLE_VERSION, "");
ApplicationRequest ar = new ApplicationRequest(sd, new UserDetails("", 321));
String json = ar.toJson();

View File

@ -1,10 +1,13 @@
package com.rusefi;
import com.rusefi.core.Pair;
public class SignatureHelper {
public static final String PREFIX = "rusEFI ";
public static final char SLASH = '/';
public static String getUrl(String signature) {
public static Pair<String, String> getUrl(String signature) {
if (!signature.startsWith(PREFIX))
return null;
signature = signature.substring(PREFIX.length()).trim();
@ -18,6 +21,7 @@ public class SignatureHelper {
String bundle = elements[3];
String hash = elements[4];
return "https://rusefi.com/online/ini/rusefi/" + year + "/" + month + "/" + day + "/" + bundle + "/" + hash + ".ini";
String fileName = hash + ".ini";
return new Pair("https://rusefi.com/online/ini/rusefi/" + year + SLASH + month + SLASH + day + SLASH + bundle + SLASH + fileName, fileName);
}
}

View File

@ -2,6 +2,7 @@ package com.rusefi.ts_plugin;
import com.efiAnalytics.plugin.ecu.ControllerAccess;
import com.rusefi.autoupdate.AutoupdateUtil;
import com.rusefi.ts_plugin.auth.InstanceAuthContext;
import com.rusefi.ts_plugin.util.ManifestHelper;
import com.rusefi.tune.xml.Constant;
@ -12,6 +13,7 @@ import java.util.function.Supplier;
/**
* {@link TsPluginLauncher} creates an instance of this class via reflection.
*
* @see TuneUploadTab upload tune & TODO upload logs
* @see RemoteTab remote ECU access & control
* @see BroadcastTab offer your ECU for remove access & control
@ -52,6 +54,18 @@ public class PluginEntry implements TsPluginBody {
tabbedPane.addTab("Remote ECU", remoteTab.getContent());
tabbedPane.addTab("Read SD Card", new SdCardReader(controllerAccessSupplier).getContent());
content.add(tabbedPane);
InstanceAuthContext.startup();
}
public static String getNonDaemonThreads() {
StringBuilder sb = new StringBuilder();
for (Thread thread : Thread.getAllStackTraces().keySet()) {
// Daemon thread will not prevent the JVM from exiting
if (!thread.isDaemon())
sb.append(thread.getName() + "\n");
}
return sb.toString();
}
private boolean isLauncherTooOld() {

View File

@ -4,6 +4,7 @@ import com.rusefi.NamedThreadFactory;
import com.rusefi.SignatureHelper;
import com.rusefi.Timeouts;
import com.rusefi.autoupdate.AutoupdateUtil;
import com.rusefi.core.Pair;
import com.rusefi.io.serial.StreamStatistics;
import com.rusefi.io.tcp.ServerSocketReference;
import com.rusefi.io.tcp.TcpIoStream;
@ -18,6 +19,8 @@ import com.rusefi.server.UserDetails;
import com.rusefi.tools.online.HttpUtil;
import com.rusefi.tools.online.ProxyClient;
import com.rusefi.tools.online.PublicSession;
import com.rusefi.ts_plugin.auth.InstanceAuthContext;
import com.rusefi.ts_plugin.auth.SelfInfo;
import com.rusefi.ui.AuthTokenPanel;
import com.rusefi.ui.util.URLLabel;
import org.jetbrains.annotations.NotNull;
@ -45,6 +48,8 @@ public class RemoteTab {
private static final String APPLICATION_PORT = "application_port";
public static final String HOWTO_REMOTE_TUNING = "https://github.com/rusefi/rusefi/wiki/HOWTO-Remote-Tuning";
private final JComponent content = new JPanel(new BorderLayout());
private final JScrollPane scroll = new JScrollPane(content, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
private final JPanel list = new JPanel(new VerticalFlowLayout());
private final JTextField oneTimePasswordControl = new JTextField("0") {
@ -61,18 +66,18 @@ public class RemoteTab {
private final JButton disconnect = new JButton("Disconnect");
private final Executor listDownloadExecutor = Executors.newSingleThreadExecutor(new NamedThreadFactory("online list downloader"));
private final Executor listDownloadExecutor = Executors.newSingleThreadExecutor(new NamedThreadFactory("online list downloader", true));
public RemoteTab() {
JButton refresh = new JButton("Refresh List");
refresh.addActionListener(e -> requestListDownload());
JButton refresh = new JButton("Refresh Remote Controllers List");
refresh.addActionListener(e -> requestControllersList());
disconnect.addActionListener(e -> {
LocalApplicationProxy localApplicationProxy = RemoteTabController.INSTANCE.getLocalApplicationProxy();
if (localApplicationProxy != null)
localApplicationProxy.close();
RemoteTabController.INSTANCE.setState(RemoteTabController.State.NOT_CONNECTED);
requestListDownload();
requestControllersList();
});
@ -100,24 +105,23 @@ public class RemoteTab {
JPanel topLines = new JPanel(new VerticalFlowLayout());
JPanel topPanel = new JPanel(new FlowLayout());
topPanel.add(refresh);
topPanel.add(new JLabel(" Local Port: "));
topPanel.add(applicationPort);
topPanel.add(new JLabel(" One time password:"));
topPanel.add(oneTimePasswordControl);
topLines.add(topPanel);
topLines.add(new URLLabel(HOWTO_REMOTE_TUNING));
topLines.add(new SelfInfo().getContent());
topLines.add(refresh);
topLines.add(new JLabel("Local Port for tuning software"));
topLines.add(applicationPort);
topLines.add(new JLabel("One time password:"));
topLines.add(oneTimePasswordControl);
content.add(topLines, BorderLayout.NORTH);
content.add(list, BorderLayout.CENTER);
list.add(new JLabel("Requesting list of ECUs"));
InstanceAuthContext.listeners.add(userDetails -> requestControllersList());
LocalApplicationProxy currentState = RemoteTabController.INSTANCE.getLocalApplicationProxy();
if (currentState == null) {
requestListDownload();
requestControllersList();
} else {
setConnectedStatus(currentState.getApplicationRequest().getVehicleOwner(), null,
currentState.getApplicationRequest().getSessionDetails().getControllerInfo());
@ -128,15 +132,13 @@ public class RemoteTab {
return getConfig().getRoot().getProperty(APPLICATION_PORT, "29001");
}
private void requestListDownload() {
private void requestControllersList() {
listDownloadExecutor.execute(() -> {
List<PublicSession> userDetails;
try {
userDetails = ProxyClient.getOnlineApplications(HttpUtil.PROXY_JSON_API_HTTP_PORT);
List<PublicSession> userDetails = ProxyClient.getOnlineApplications(HttpUtil.PROXY_JSON_API_HTTP_PORT);
SwingUtilities.invokeLater(() -> showList(userDetails));
} catch (IOException e) {
e.printStackTrace();
return;
}
});
}
@ -150,20 +152,20 @@ public class RemoteTab {
} else {
JPanel verticalPanel = new JPanel(new VerticalFlowLayout());
JScrollPane scroll = new JScrollPane(verticalPanel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
list.add(scroll);
list.add(verticalPanel);
for (PublicSession user : userDetails) {
verticalPanel.add(createSessionControl(user));
verticalPanel.add(createControllerRow(user));
}
}
AutoupdateUtil.trueLayout(list);
}
private JComponent createSessionControl(PublicSession publicSession) {
private JComponent createControllerRow(PublicSession publicSession) {
ControllerInfo controllerInfo = publicSession.getControllerInfo();
JComponent topLine = new JPanel(new FlowLayout());
topLine.add(new JLabel(publicSession.getVehicleOwner().getUserName()));
ControllerInfo controllerInfo = publicSession.getControllerInfo();
topLine.add(new JLabel(controllerInfo.getVehicleName() + " " + controllerInfo.getEngineMake() + " " + controllerInfo.getEngineCode()));
JPanel bottomPanel = new JPanel(new FlowLayout());
@ -175,26 +177,34 @@ public class RemoteTab {
connect.addActionListener(event -> connectToProxy(publicSession));
bottomPanel.add(connect);
JButton updateSoftware = new JButton("Update Connector");
updateSoftware.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
try {
LocalApplicationProxy.requestSoftwareUpdate(HttpUtil.PROXY_JSON_API_HTTP_PORT,
getApplicationRequest(publicSession));
} catch (IOException ioException) {
ioException.printStackTrace();
if (InstanceAuthContext.isOurController(publicSession.getVehicleOwner().getUserId())) {
JButton updateSoftware = new JButton("Update Connector");
updateSoftware.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
try {
LocalApplicationProxy.requestSoftwareUpdate(HttpUtil.PROXY_JSON_API_HTTP_PORT,
getApplicationRequest(publicSession));
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
});
bottomPanel.add(updateSoftware);
});
bottomPanel.add(updateSoftware);
}
}
JPanel userPanel = new JPanel(new BorderLayout());
JPanel infoLine = new JPanel(new FlowLayout());
infoLine.add(new JLabel("Age " + publicSession.getAge()));
infoLine.add(getSignatureDownload(controllerInfo));
userPanel.add(topLine, BorderLayout.NORTH);
userPanel.add(new URLLabel(SignatureHelper.getUrl(controllerInfo.getSignature())), BorderLayout.CENTER);
userPanel.add(infoLine, BorderLayout.CENTER);
userPanel.add(bottomPanel, BorderLayout.SOUTH);
userPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
@ -202,6 +212,13 @@ public class RemoteTab {
return userPanel;
}
@NotNull
private URLLabel getSignatureDownload(ControllerInfo controllerInfo) {
Pair<String, String> url = SignatureHelper.getUrl(controllerInfo.getSignature());
return new URLLabel(url.second, url.first);
}
private void connectToProxy(PublicSession publicSession) {
RemoteTabController.INSTANCE.setState(RemoteTabController.State.CONNECTING);
setStatus("Connecting to " + publicSession.getVehicleOwner().getUserName());
@ -228,7 +245,7 @@ public class RemoteTab {
setStatus("Connected to " + userDetails.getUserName(),
new JLabel("You can now connect your TunerStudio to IP address localhost and port " + getLocalPort()),
new URLLabel(SignatureHelper.getUrl(controllerInfo.getSignature())),
new URLLabel(SignatureHelper.getUrl(controllerInfo.getSignature()).first),
disconnect, streamStatusControl == null ? null : streamStatusControl.getContent());
}
@ -280,11 +297,10 @@ public class RemoteTab {
publicSession.getControllerInfo(), AuthTokenPanel.getAuthToken(),
Integer.parseInt(oneTimePasswordControl.getText()), rusEFIVersion.CONSOLE_VERSION);
ApplicationRequest applicationRequest = new ApplicationRequest(sessionDetails, publicSession.getVehicleOwner());
return applicationRequest;
return new ApplicationRequest(sessionDetails, publicSession.getVehicleOwner());
}
public JComponent getContent() {
return content;
return scroll;
}
}

View File

@ -24,7 +24,7 @@ import java.util.Map;
import java.util.function.Supplier;
/**
*
* @see PluginBodySandbox
*/
public class TuneUploadTab {
private final JComponent content = new JPanel(new VerticalFlowLayout());
@ -69,7 +69,7 @@ public class TuneUploadTab {
};
upload.setBackground(new Color(0x90EE90));
new Thread(new Runnable() {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
@ -97,7 +97,9 @@ public class TuneUploadTab {
}
}
}
}).start();
});
t.setDaemon(true);
t.start();
upload.addActionListener(new AbstractAction() {
@Override

View File

@ -26,13 +26,15 @@ public class UploadQueue {
return;
isStarted = true;
readOutbox();
new Thread(() -> {
Thread t = new Thread(() -> {
try {
uploadLoop();
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}, "Positing Thread").start();
}, "Posting Thread");
t.setDaemon(true);
t.start();
}
private static void readOutbox() {

View File

@ -0,0 +1,44 @@
package com.rusefi.ts_plugin.auth;
import com.devexperts.logging.Logging;
import com.rusefi.server.JsonUserDetailsResolver;
import com.rusefi.server.UserDetails;
import com.rusefi.ui.AuthTokenPanel;
import java.util.concurrent.CopyOnWriteArrayList;
public class InstanceAuthContext {
private final static Logging log = Logging.getLogging(InstanceAuthContext.class);
private static boolean isInitialized;
public static CopyOnWriteArrayList<Listener> listeners = new CopyOnWriteArrayList<>();
public static UserDetails self;
public synchronized static void startup() {
if (isInitialized)
return;
if (!AuthTokenPanel.hasToken()) {
// exiting without marking initialized - UI has a chance to re-start the process once token is provided
return;
}
log.info("Startup");
isInitialized = true;
new Thread(() -> {
self = new JsonUserDetailsResolver().apply(AuthTokenPanel.getAuthToken());
for (Listener listener : listeners)
listener.onUserDetails(self);
}).start();
}
public static boolean isOurController(int userId) {
if (self == null)
return false;
return self.getUserId() == userId;
}
public interface Listener {
void onUserDetails(UserDetails userDetails);
}
}

View File

@ -0,0 +1,33 @@
package com.rusefi.ts_plugin.auth;
import com.rusefi.server.UserDetails;
import javax.swing.*;
import java.awt.*;
public class SelfInfo {
private final JLabel jLabel = new JLabel();
public SelfInfo() {
InstanceAuthContext.listeners.add(new InstanceAuthContext.Listener() {
@Override
public void onUserDetails(UserDetails userDetails) {
setInfo();
}
});
setInfo();
}
private void setInfo() {
UserDetails userDetails = InstanceAuthContext.self;
if (userDetails == null) {
jLabel.setText("Authorizing...");
} else {
jLabel.setText("Logged in as " + userDetails.getUserName());
}
}
public Component getContent() {
return jLabel;
}
}

View File

@ -7,7 +7,7 @@ import static org.junit.Assert.assertEquals;
public class SignatureHelperTest {
@Test
public void test() {
String url = SignatureHelper.getUrl("rusEFI 2020.07.06.frankenso_na6.2468827536");
String url = SignatureHelper.getUrl("rusEFI 2020.07.06.frankenso_na6.2468827536").first;
assertEquals("https://rusefi.com/online/ini/rusefi/2020/07/06/frankenso_na6/2468827536.ini", url);
}
}