Skip to content

Commit 476e9ac

Browse files
committed
Implement image clients, default to storing screencaps in base-64 mongo collection
1 parent 0b62d06 commit 476e9ac

File tree

11 files changed

+195
-28
lines changed

11 files changed

+195
-28
lines changed

app/ux-analytics/monitor/src/main/java/org/phoebus/applications/uxanalytics/monitor/ActiveTab.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import javafx.scene.control.Tab;
55
import org.csstudio.display.builder.model.Widget;
66
import org.csstudio.display.builder.representation.ToolkitListener;
7+
import org.csstudio.display.builder.representation.javafx.JFXRepresentation;
78
import org.csstudio.display.builder.runtime.app.DisplayInfo;
89
import org.csstudio.display.builder.runtime.app.DisplayRuntimeInstance;
910
import org.phoebus.ui.docking.DockItemWithInput;
@@ -123,6 +124,20 @@ public DockItemWithInput getParentTab() {
123124
return parentTab;
124125
}
125126

127+
public JFXRepresentation getJFXRepresentation() {return ((DisplayRuntimeInstance)parentTab.getApplication()).getRepresentation();}
128+
129+
public double getZoom(){
130+
return getJFXRepresentation().getZoom();
131+
}
132+
133+
public double getHeight(){
134+
return getJFXRepresentation().getModelRoot().getHeight();
135+
}
136+
137+
public double getWidth(){
138+
return getJFXRepresentation().getModelRoot().getWidth();
139+
}
140+
126141
//public String getHashFileName() {
127142
// return hashFilename;
128143
//}

app/ux-analytics/monitor/src/main/java/org/phoebus/applications/uxanalytics/monitor/BackendConnection.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public interface BackendConnection {
1313
public default String getDefaultPort(){return "";}
1414
public default String getDefaultUsername(){return "";}
1515
public default Integer tearDown(){return -1;}
16+
public default void setImageClient(ImageClient imageClient){}
1617
public default void handleClick(ActiveTab who, Widget widget, Integer x, Integer y){}
1718
public default void handleClick(ActiveTab who, Integer x, Integer y){this.handleClick(who, null, x, y);}
1819
public default void handleAction(ActiveTab who, Widget widget, ActionInfo info){}

app/ux-analytics/monitor/src/main/java/org/phoebus/applications/uxanalytics/monitor/FileUtils.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.security.MessageDigest;
88
import java.security.NoSuchAlgorithmException;
99

10+
import net.dongliu.commons.Sys;
1011
import org.csstudio.display.builder.model.util.ModelResourceUtil;
1112
import org.phoebus.framework.preferences.PhoebusPreferenceService;
1213

@@ -140,6 +141,7 @@ public static String getSHA256Suffix(String path){
140141

141142
public static String getAnalyticsPathFor(String path){
142143
String pathWithoutRoot = ModelResourceUtil.normalize(getPathWithoutSourceRoot(path));
144+
pathWithoutRoot = pathWithoutRoot.substring(0, pathWithoutRoot.lastIndexOf("."));
143145
String first8OfSHA256 = getSHA256Suffix(path);
144146
if(first8OfSHA256 == null){
145147
return null;

app/ux-analytics/monitor/src/main/java/org/phoebus/applications/uxanalytics/monitor/FilesystemImageClient.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.phoebus.applications.uxanalytics.monitor;
22

3+
import java.awt.image.BufferedImage;
34
import java.io.File;
45
import java.net.URI;
56

@@ -9,13 +10,23 @@ public class FilesystemImageClient implements ImageClient{
910
private String imageLocation;
1011

1112
@Override
12-
public Integer uploadImage(URI image, File file) {
13-
return 0;
13+
public Integer uploadImage(URI image, BufferedImage screenshot) {
14+
try {
15+
File outputfile = new File(imageLocation +"/"+ image.getPath()+".png");
16+
//make directories if they don't exist
17+
outputfile.getParentFile().mkdirs();
18+
System.out.println("Saving image to: " + outputfile.getAbsolutePath());
19+
javax.imageio.ImageIO.write(screenshot, "png", outputfile);
20+
return 0;
21+
} catch (Exception e) {
22+
e.printStackTrace();
23+
return -1;
24+
}
1425
}
1526

1627
@Override
1728
public boolean imageExists(URI image) {
18-
return false;
29+
return new File(imageLocation +"/"+ image.getPath()+".png").exists();
1930
}
2031

2132
public boolean setImageLocation(String location) {
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package org.phoebus.applications.uxanalytics.monitor;
22

3+
import java.awt.image.BufferedImage;
34
import java.io.File;
45
import java.net.URI;
56

67
public interface ImageClient {
7-
public Integer uploadImage(URI image, File file);
8+
public Integer uploadImage(URI image, BufferedImage screenshot);
89
public boolean imageExists(URI image);
910
}

app/ux-analytics/monitor/src/main/java/org/phoebus/applications/uxanalytics/monitor/MongoDBConnection.java

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,56 @@
11
package org.phoebus.applications.uxanalytics.monitor;
22

3+
import com.mongodb.ConnectionString;
4+
import com.mongodb.MongoClientSettings;
5+
import com.mongodb.client.ClientSession;
36
import com.mongodb.client.MongoClient;
47
import com.mongodb.client.MongoClients;
5-
8+
import com.mongodb.client.MongoDatabase;
9+
import javafx.application.Platform;
10+
import javafx.embed.swing.SwingFXUtils;
11+
import javafx.scene.Node;
12+
import javafx.scene.SnapshotParameters;
13+
import javafx.scene.image.WritableImage;
14+
15+
import java.awt.image.BufferedImage;
616
import java.net.URI;
17+
import java.nio.Buffer;
18+
import java.time.Instant;
19+
import java.util.UUID;
20+
import java.util.concurrent.TimeUnit;
721
import java.util.logging.Level;
822
import java.util.logging.Logger;
923

24+
import org.bson.Document;
25+
import org.bson.UuidRepresentation;
26+
import org.csstudio.display.builder.runtime.app.DisplayRuntimeInstance;
27+
1028
public class MongoDBConnection implements BackendConnection{
1129

1230
Logger logger = Logger.getLogger(MongoDBConnection.class.getName());
1331

1432
public static final String PROTOCOL = "mongodb://";
1533

1634
private MongoClient mongoClient = null;
35+
private MongoDatabase database = null;
1736
private ImageClient imageClient = null;
1837

1938
@Override
2039
public Boolean connect(String hostname, Integer port, String username, String password) {
21-
String uri = PROTOCOL + username + ":" + password + "@" + hostname + ":" + port.toString();
40+
String uri;
41+
if(username == null || password == null || username.isEmpty() || password.isEmpty())
42+
uri = PROTOCOL + hostname + ":" + port.toString();
43+
else
44+
uri = PROTOCOL + username + ":" + password + "@" + hostname + ":" + port.toString();
2245
try {
23-
mongoClient = MongoClients.create(uri);
46+
MongoClientSettings settings = MongoClientSettings.builder()
47+
.applyConnectionString(new ConnectionString(uri))
48+
.uuidRepresentation(UuidRepresentation.STANDARD)
49+
.build();
50+
mongoClient = MongoClients.create(settings);
51+
database = mongoClient.getDatabase("phoebus-analytics");
52+
database.createCollection("clicks");
53+
UXAMonitor.getInstance().notifyConnectionChange(this);
2454
return true;
2555
} catch (Exception ex) {
2656
logger.log(Level.WARNING, "Failed to connect to " + hostname, ex);
@@ -60,10 +90,49 @@ public Integer tearDown() {
6090
return 0;
6191
}
6292

93+
static BufferedImage getSnapshot(ActiveTab who) {
94+
Node jfxNode = who.getParentTab().getContent();
95+
SnapshotParameters params = new SnapshotParameters();
96+
WritableImage snapshot = jfxNode.snapshot(params, null);
97+
return SwingFXUtils.fromFXImage(snapshot, null);
98+
}
99+
63100
@Override
64101
public void handleClick(ActiveTab who, Integer x, Integer y) {
65-
logger.log(Level.INFO, "MongoDB Connection would've handled click at " + x + ", " + y + " from " + who);
66-
102+
if(database != null && imageClient == null){
103+
imageClient = MongoDBImageClient.getInstance();
104+
((MongoDBImageClient) imageClient).connect(mongoClient);
105+
}
106+
Platform.runLater(() -> {
107+
String path = FileUtils.analyticsPathForTab(who);
108+
logger.log(Level.FINE, "MongoDB Connection handled click at " + x + ", " + y + " from " + path);
109+
double zoom = who.getZoom();
110+
Integer clickX = (int) (x / zoom);
111+
Integer clickY = (int) (y / zoom);
112+
if (imageClient!=null && !imageClient.imageExists(URI.create(path))) {
113+
logger.log(Level.INFO, "Uploading image for " + who + " to " + path);
114+
try {
115+
((DisplayRuntimeInstance) who.getParentTab().getApplication()).getRepresentation_init().get(1, TimeUnit.SECONDS);
116+
BufferedImage snapshot = getSnapshot(who);
117+
imageClient.uploadImage(URI.create(path), snapshot);
118+
}
119+
catch (Exception ex) {
120+
logger.log(Level.WARNING, "Failed to upload image for " + who + " to " + path, ex);
121+
}
122+
}
123+
//write a click event to mongodb
124+
Document clickEvent = new Document()
125+
.append("id", UUID.randomUUID())
126+
.append("x", clickX)
127+
.append("y", clickY)
128+
.append("path", path)
129+
.append("time", Instant.now().getEpochSecond());
130+
try {
131+
database.getCollection("clicks").insertOne(clickEvent);
132+
} catch (Exception ex) {
133+
logger.log(Level.WARNING, "Failed to write click event to MongoDB", ex);
134+
}
135+
});
67136
}
68137

69138
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package org.phoebus.applications.uxanalytics.monitor;
2+
3+
import com.mongodb.client.MongoClient;
4+
import com.mongodb.client.MongoDatabase;
5+
import org.bson.Document;
6+
7+
import javax.imageio.ImageIO;
8+
import java.awt.image.BufferedImage;
9+
import java.io.ByteArrayOutputStream;
10+
import java.net.URI;
11+
import java.util.Base64;
12+
13+
public class MongoDBImageClient implements ImageClient{
14+
15+
static MongoDBImageClient instance;
16+
MongoDatabase database = null;
17+
18+
private MongoDBImageClient(){
19+
}
20+
21+
public static MongoDBImageClient getInstance(){
22+
if(instance == null){
23+
instance = new MongoDBImageClient();
24+
}
25+
return instance;
26+
}
27+
28+
public void connect(MongoClient client){
29+
database = client.getDatabase("phoebus-analytics");
30+
database.createCollection("images");
31+
}
32+
33+
public boolean imageExists(URI image){
34+
return database.getCollection("images")
35+
.find(new Document("name", image.toString()))
36+
.first() != null;
37+
}
38+
39+
@Override
40+
public Integer uploadImage(URI name, BufferedImage screenshot) {
41+
try {
42+
//write png-encoded BufferedImage to output stream
43+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
44+
ImageIO.write(screenshot, "png", baos);
45+
//encode the blob in base64
46+
byte[] imageBytes = baos.toByteArray();
47+
String base64Blob = Base64.getEncoder().encodeToString(imageBytes);
48+
Document imageDoc = new Document()
49+
.append("name", name.toString())
50+
.append("type", "image/png")
51+
.append("width", screenshot.getWidth())
52+
.append("height", screenshot.getHeight())
53+
.append("image", base64Blob);
54+
database.getCollection("images").insertOne(imageDoc);
55+
return 0;
56+
}
57+
catch (Exception e) {
58+
e.printStackTrace();
59+
return -1;
60+
}
61+
}
62+
63+
}

app/ux-analytics/monitor/src/main/java/org/phoebus/applications/uxanalytics/monitor/S3ImageClient.java

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
package org.phoebus.applications.uxanalytics.monitor;
22

33

4+
import java.awt.image.BufferedImage;
45
import java.io.*;
56
import java.util.logging.Level;
67
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
78
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
89
import software.amazon.awssdk.core.ResponseInputStream;
10+
import software.amazon.awssdk.core.sync.RequestBody;
911
import software.amazon.awssdk.regions.Region;
1012
import software.amazon.awssdk.services.s3.S3Client;
1113
import software.amazon.awssdk.services.s3.model.*;
1214

15+
import javax.imageio.ImageIO;
1316
import java.net.URI;
1417
import java.util.logging.Logger;
1518

@@ -60,17 +63,27 @@ public boolean imageExists(URI key) {
6063
}
6164

6265
@Override
63-
public Integer uploadImage(URI imagePath, File file) {
64-
if(!imageExists(imagePath)) {
65-
PutObjectResponse response = s3.putObject(builder -> builder.bucket(BUCKET_NAME)
66-
.key(imagePath.toString())
67-
.build(),
68-
file.toPath());
69-
return response.sdkHttpResponse().statusCode();
66+
public Integer uploadImage(URI imagePath, BufferedImage screenshot) {
67+
try {
68+
ByteArrayOutputStream os = new ByteArrayOutputStream();
69+
ImageIO.write(screenshot, "png", os);
70+
byte[] buffer = os.toByteArray();
71+
ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer);
72+
73+
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
74+
.bucket(BUCKET_NAME)
75+
.key(imagePath.toString())
76+
.contentType("image/png")
77+
.build();
78+
PutObjectResponse resp = s3.putObject(putObjectRequest, RequestBody.fromInputStream(inputStream, buffer.length));
79+
logger.log(Level.INFO, "Uploaded image to S3: " + imagePath.toString());
80+
return resp.sdkHttpResponse().statusCode();
7081
}
71-
else{
72-
return 409;
82+
catch (IOException e) {
83+
logger.log(Level.WARNING, "Failed to upload image to S3", e);
84+
return 500;
7385
}
86+
7487
}
7588

7689
public static ImageClient getInstance(){

app/ux-analytics/monitor/src/main/java/org/phoebus/applications/uxanalytics/monitor/UXAMouseMonitor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public UXAMouseMonitor(ActiveTab tab){
2020
@Override
2121
public void handle(MouseEvent event) {
2222
if(event.getEventType().equals(MouseEvent.MOUSE_CLICKED)){
23-
monitor.getJfxConnection().handleClick(tab, (int) event.getSceneX(), (int) event.getSceneY());
23+
monitor.getJfxConnection().handleClick(tab, (int) event.getX(), (int) event.getY());
2424
}
2525
}
2626
}

app/ux-analytics/ui/src/main/java/org/phoebus/applications/uxanalytics/ui/UXAController.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,6 @@ public int tryConnect(Event event) {
7070
}
7171

7272
String user = txtUser.getText();
73-
if (user.isEmpty()) {
74-
lblSuccessFailure.setText("Set a user name.");
75-
lblSuccessFailure.setVisible(true);
76-
return 1;
77-
}
7873
String pass = passPassword.getText();
7974
try {
8075
if (!connectionLogic.connect(host, Integer.parseInt(port), user, pass)) {

0 commit comments

Comments
 (0)