Merge remote-tracking branch 'origin/main'

This commit is contained in:
Mark Nellemann 2023-08-09 09:09:30 +02:00
commit d99f6962df
11 changed files with 206 additions and 250 deletions

View file

@ -69,7 +69,7 @@ Add the platform-tools to you PATH to use 'adb' command:
export PATH="$PATH:/home/mark/Android/Sdk/platform-tools/"
```
Run the 'adb devices' and 'adb usb' to verify your Android device is in development mode and allows connections.
Run the 'adb devices' and 'adb usb' to verify your Android networkService is in development mode and allows connections.
### MacOS

View file

@ -1,44 +0,0 @@
package biz.nellemann.mdexpl;
import biz.nellemann.mdexpl.model.Device;
import biz.nellemann.mdexpl.model.Devices;
import com.gluonhq.charm.glisten.control.CharmListCell;
import com.gluonhq.charm.glisten.control.ListTile;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
public class DeviceCell extends CharmListCell<Device> {
private final ListTile tile;
private final ImageView imageView;
public DeviceCell() {
this.tile = new ListTile();
imageView = new ImageView();
imageView.setFitHeight(15);
imageView.setFitWidth(25);
tile.setPrimaryGraphic(imageView);
setText(null);
}
@Override
public void updateItem(Device item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
tile.textProperty().setAll(item.getName() + " (" + item.getAbbr() + ")",
"Capital: " + item.getCapital() +
", Population (M): " + String.format("%.2f", item.getPopulation() / 1_000_000d),
"Area (km" + "\u00B2" + "): " + item.getArea() +
", Density (pop/km" + "\u00B2" + "): " + String.format("%.1f", item.getDensity())
);
final Image image = Devices.getImage(item.getFlag());
if (image != null) {
imageView.setImage(image);
}
setGraphic(tile);
} else {
setGraphic(null);
}
}
}

View file

@ -0,0 +1,45 @@
package biz.nellemann.mdexpl;
import biz.nellemann.mdexpl.model.NetworkService;
import com.gluonhq.charm.glisten.control.CharmListCell;
import com.gluonhq.charm.glisten.control.ListTile;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
public class NetworkServiceCell extends CharmListCell<NetworkService> {
private final ListTile tile;
//private final ImageView imageView;
private final Rectangle icon;
public NetworkServiceCell() {
this.tile = new ListTile();
//imageView = new ImageView();
//imageView.setFitHeight(15);
//imageView.setFitWidth(25);
//tile.setPrimaryGraphic(imageView);
icon = new Rectangle();
icon.setHeight(25);
icon.setWidth(25);
tile.setPrimaryGraphic(icon);
tile.setOnMouseClicked(e -> { System.out.println("Selected -> " + itemProperty().get().getName() ); });
setText(null);
}
@Override
public void updateItem(NetworkService item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
tile.textProperty().setAll(item.getName(),
"App: " + item.getApp(), "URL: " + item.getUrl()
);
icon.setFill(item.getColor());
setGraphic(tile);
} else {
setGraphic(null);
}
}
}

View file

@ -1,24 +1,30 @@
package biz.nellemann.mdexpl;
import biz.nellemann.mdexpl.model.NetworkService;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.scene.paint.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceInfo;
import javax.jmdns.ServiceListener;
import java.util.HashMap;
public class NetworkServiceListener implements ServiceListener {
private static final Logger log = LoggerFactory.getLogger(NetworkServiceListener.class);
private final HashMap<String, String> onlineList = new HashMap<>();
private final String service;
private final ObservableList<NetworkService> observableList;
private final String serviceType;
private final Color color;
public NetworkServiceListener(String type) {
log.info("NetworkServiceListener() - type: {}", type);
this.serviceType = type;
public NetworkServiceListener(String service, ObservableList<NetworkService> observableList, Color color) {
log.info("NetworkServiceListener() - type: {}", service);
this.service = service;
this.observableList = observableList;
this.color = color;
}
@Override
@ -30,8 +36,11 @@ public class NetworkServiceListener implements ServiceListener {
ServiceInfo serviceInfo = event.getInfo();
if (serviceInfo != null) {
String name = serviceInfo.getName();
onlineList.remove(name);
log.info("serviceRemoved() - Removed service: " + name);
log.info("serviceRemoved() - Service: " + name);
NetworkService oldNetworkService = new NetworkService(name, service, serviceInfo.getApplication(), serviceInfo.getURLs()[0], color);
Platform.runLater(() -> {
observableList.remove(oldNetworkService);
});
}
}
@ -42,9 +51,13 @@ public class NetworkServiceListener implements ServiceListener {
String url = serviceInfo.getURLs()[0];
String name = serviceInfo.getName();
String app = serviceInfo.getApplication();
log.debug(serviceInfo.toString());
onlineList.put(name, url);
log.info("serviceResolved() - Found {}: {} with url {}", app, name, url);
log.info("serviceResolved() - Service: {} - {} with url {}", app, name, url);
NetworkService newNetworkService = new NetworkService(name, service, app, url, color);
Platform.runLater(() -> {
if(!observableList.contains(newNetworkService)) {
observableList.add(newNetworkService);
}
});
}
}

View file

@ -1,87 +0,0 @@
package biz.nellemann.mdexpl.model;
public class Device {
private String name;
private String abbr;
private String capital;
private int population;
private int area; /* km^2 */
private String flag;
public Device(String name, String abbr, String capital, int population, int area, String flag) {
this.name = name;
this.abbr = abbr;
this.capital = capital;
this.population = population;
this.area = area;
this.flag = flag;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAbbr() {
return abbr;
}
public void setAbbr(String abbr) {
this.abbr = abbr;
}
public String getCapital() {
return capital;
}
public void setCapital(String capital) {
this.capital = capital;
}
public int getPopulation() {
return population;
}
public void setPopulation(int population) {
this.population = population;
}
public int getArea() {
return area;
}
public void setArea(int area) {
this.area = area;
}
public String getFlag() {
return flag;
}
public void setFlag(String flag) {
this.flag = flag;
}
/**
* Population density
* @return population density (pop. per km^2)
*/
public double getDensity() {
if (area > 0) {
return (double) population / (double) area;
}
return 0;
}
@Override
public String toString() {
return name + " (" + abbr + "), capital=" + capital + ", population=" + population + ", area=" + area;
}
}

View file

@ -1,65 +0,0 @@
package biz.nellemann.mdexpl.model;
import com.gluonhq.attach.cache.Cache;
import com.gluonhq.attach.cache.CacheService;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.image.Image;
public class Devices {
private static final Cache<String, Image> CACHE;
static {
CACHE = CacheService.create()
.map(cache -> cache.<String, Image>getCache("images"))
.orElseThrow(() -> new RuntimeException("No CacheService available"));
}
private static final String URL_PATH = "https://upload.wikimedia.org/wikipedia/commons/thumb/";
public static ObservableList<Device> statesList = FXCollections.observableArrayList(
new Device("Alabama", "AL", "Montgomery", 4903185, 135767, URL_PATH + "5/5c/Flag_of_Alabama.svg/23px-Flag_of_Alabama.svg.png"),
new Device("Alaska", "AK", "Juneau", 731545, 1723337, URL_PATH + "e/e6/Flag_of_Alaska.svg/21px-Flag_of_Alaska.svg.png"),
new Device("Arizona", "AZ", "Phoenix", 7278717, 295233, URL_PATH + "9/9d/Flag_of_Arizona.svg/23px-Flag_of_Arizona.svg.png"),
new Device("Arkansas", "AR", "Little Rock", 3017804, 137733, URL_PATH + "9/9d/Flag_of_Arkansas.svg/23px-Flag_of_Arkansas.svg.png"),
new Device("California", "CA", "Sacramento", 39512223, 423968, URL_PATH + "0/01/Flag_of_California.svg/23px-Flag_of_California.svg.png"),
new Device("Colorado", "CO", "Denver", 5758736, 269602, URL_PATH + "4/46/Flag_of_Colorado.svg/23px-Flag_of_Colorado.svg.png"),
new Device("Connecticut", "CT", "Hartford", 3565287, 14356, URL_PATH + "9/96/Flag_of_Connecticut.svg/20px-Flag_of_Connecticut.svg.png"),
new Device("Delaware", "DE", "Dover", 973764, 6446, URL_PATH + "c/c6/Flag_of_Delaware.svg/23px-Flag_of_Delaware.svg.png"),
new Device("Florida", "FL", "Tallahassee", 21477737, 170312, URL_PATH + "f/f7/Flag_of_Florida.svg/23px-Flag_of_Florida.svg.png"),
new Device("Georgia", "GA", "Atlanta", 10617423, 153910, URL_PATH + "5/54/Flag_of_Georgia_%28U.S._state%29.svg/23px-Flag_of_Georgia_%28U.S._state%29.svg.png"),
new Device("Hawaii", "HI", "Honolulu", 1415872, 28314, URL_PATH + "e/ef/Flag_of_Hawaii.svg/23px-Flag_of_Hawaii.svg.png"),
new Device("Idaho", "ID", "Boise", 1787065, 216443, URL_PATH + "a/a4/Flag_of_Idaho.svg/19px-Flag_of_Idaho.svg.png"),
new Device("Illinois", "IL", "Springfield", 12671821, 149997, URL_PATH + "0/01/Flag_of_Illinois.svg/23px-Flag_of_Illinois.svg.png")
);
public static Image getUSFlag() {
return getImage(URL_PATH + "a/a4/Flag_of_the_United_States.svg/320px-Flag_of_the_United_States.svg.png");
}
/**
* This method will always return the required image.
* It will cache the image and return from cache if still there.
* @param image: A valid url to retrieve the image
* @return an Image
*/
public static Image getImage(String image) {
if (image == null || image.isEmpty()) {
return null;
}
Image cachedImage = CACHE.get(image);
if (cachedImage == null) {
cachedImage = new Image(image, true);
cachedImage.errorProperty().addListener((obs, ov, nv) -> {
if (nv) {
CACHE.remove(image);
}
});
CACHE.put(image, cachedImage);
}
return cachedImage;
}
}

View file

@ -0,0 +1,79 @@
package biz.nellemann.mdexpl.model;
import javafx.scene.paint.Color;
public class NetworkService {
private String name;
private String type;
private String app;
private String url;
private Color color;
public NetworkService(String name, String type, String app, String url, Color color) {
this.name = name;
this.type = type;
this.app = app;
this.url = url;
this.color = color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getApp() {
return app;
}
public void setApp(String app) {
this.app = app;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
@Override
public String toString() {
return name + " (" + type + "), app=" + app + ", url=" + url;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof NetworkService networkService)) {
return false;
}
return networkService.name.equals(name) && networkService.type.equals(type) && networkService.url.equals(url);
}
}

View file

@ -1,18 +1,20 @@
package biz.nellemann.mdexpl.service;
import biz.nellemann.mdexpl.NetworkServiceListener;
import biz.nellemann.mdexpl.model.NetworkService;
import javafx.collections.ObservableList;
import javafx.scene.paint.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import javax.inject.Singleton;
import javax.jmdns.JmDNS;
import javax.jmdns.ServiceInfo;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Singleton
@ -20,29 +22,57 @@ public class DiscoveryService {
private final static Logger log = LoggerFactory.getLogger(DiscoveryService.class);
// http://www.dns-sd.org/serviceTypes.html
private final List<String> services = Arrays.asList(
private ObservableList<NetworkService> observableList;
private JmDNS jmdns;
/*private final HashMap<String, Color> services = HashMap<>({
"http", "https", "upnp", "ssh", "sip", "rdp", "ntp", "rdp", "rtsp",
"ntp", "smb", "nfs", "llrp", "ftp", "ep", "daap", "ipp", "ipps",
"googlecast", "appletv", "appletv-itunes", "smartenergy", "skype", "bittorrent",
"sonos", "airplay"
);
"googlecast", "sonos", "airplay", "smartenergy", "skype", "bittorrent"
);*/
// From: http://www.dns-sd.org/serviceTypes.html + some guesses
private final Map<String, Color> services = new HashMap<>() {{
put("http", Color.BLUE);
put("https", Color.DARKBLUE);
put("googlecast", Color.RED);
put("airplay", Color.SLATEGRAY);
put("sonos", Color.SANDYBROWN);
put("ipp", Color.LIGHTGRAY);
put("ipps", Color.LIGHTGRAY);
put("nfs", Color.CORAL);
put("smb", Color.CORAL);
put("cifs", Color.CORAL);
put("smartenergy", Color.LIGHTGREEN);
put("sip", Color.YELLOW);
put("skype", Color.YELLOW);
}};
@PostConstruct
public void initialize() {
log.info("initialize()");
try {
JmDNS jmdns = JmDNS.create(null, "mdnsExplorer");
services.forEach(service -> {
String serviceType = String.format("_%s._%s.local.", service, "tcp");
NetworkServiceListener networkServiceListener = new NetworkServiceListener(serviceType);
jmdns.addServiceListener(serviceType, networkServiceListener);
});
jmdns = JmDNS.create(null, "mdnsExplorer");
} catch (IOException e) {
log.error("initialize() - {}", e.getMessage());
}
}
public void setObservableList(ObservableList<NetworkService> list) {
this.observableList = list;
services.forEach((item, color) -> {
String service = String.format("_%s._%s.local.", item, "tcp");
NetworkServiceListener networkServiceListener = new NetworkServiceListener(service, observableList, color);
jmdns.addServiceListener(service, networkServiceListener);
});
}
}

View file

@ -1,20 +1,20 @@
package biz.nellemann.mdexpl.view;
import biz.nellemann.mdexpl.NetworkServiceCell;
import biz.nellemann.mdexpl.model.NetworkService;
import biz.nellemann.mdexpl.model.MainModel;
import biz.nellemann.mdexpl.service.DiscoveryService;
import com.gluonhq.charm.glisten.application.AppManager;
import com.gluonhq.charm.glisten.control.AppBar;
import com.gluonhq.charm.glisten.control.CharmListView;
import com.gluonhq.charm.glisten.control.Icon;
import com.gluonhq.charm.glisten.control.LifecycleEvent;
import com.gluonhq.charm.glisten.mvc.View;
import com.gluonhq.charm.glisten.visual.MaterialDesignIcon;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.geometry.Orientation;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TouchEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -41,6 +41,8 @@ public class MainPresenter {
@FXML
private CharmListView charmListView;
private ObservableList<NetworkService> devicesList = FXCollections.observableArrayList();
@FXML
public void initialize() {
@ -56,19 +58,13 @@ public class MainPresenter {
appManager.getDrawer().open()));
appBar.setTitleText("mDNS Explorer");
//appBar.getActionItems().add(progressIndicator);
}
});
discoveryService.setObservableList(devicesList);
}
@FXML
protected void onButtonRefresh() {
log.info("onButtonRefresh()");
charmListView.setItems(devicesList);
charmListView.setCellFactory(p -> new NetworkServiceCell());
}

View file

@ -8,7 +8,7 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<View fx:id="about" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" xmlns="http://javafx.com/javafx/20.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="biz.nellemann.mdexpl.view.AboutPresenter">
<View fx:id="about" xmlns="http://javafx.com/javafx/20.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="biz.nellemann.mdexpl.view.AboutPresenter">
<VBox alignment="CENTER">
<Label alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" text="mDNS Explorer">

View file

@ -7,19 +7,8 @@
<?import com.gluonhq.charm.glisten.mvc.View?>
<?import javafx.scene.layout.BorderPane?>
<View fx:id="main" onHiding="#onEventHiding" onShowing="#onEventShowing" prefHeight="800.0" prefWidth="1280.0" xmlns="http://javafx.com/javafx/20.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="biz.nellemann.mdexpl.view.MainPresenter">
<View fx:id="main" onHiding="#onEventHiding" onShowing="#onEventShowing" xmlns="http://javafx.com/javafx/20.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="biz.nellemann.mdexpl.view.MainPresenter">
<bottom>
<BottomNavigation>
<BottomNavigationButton onAction="#onButtonRefresh" selected="true" text="Refresh">
<graphic>
<Icon content="REFRESH" />
</graphic>
</BottomNavigationButton>
</BottomNavigation>
</bottom>
<center>
<CharmListView fx:id="charmListView" BorderPane.alignment="CENTER" />
</center>