From 201bfc5b7d990b2dd7250c43c2f966e4897d2116 Mon Sep 17 00:00:00 2001 From: Mark Nellemann Date: Mon, 14 Aug 2023 14:33:48 +0200 Subject: [PATCH] Remove gluon stuff and run as plain desktop app. --- build.gradle | 106 +++++++++++++++--- settings.gradle | 3 - src/main/java/biz/nellemann/mdexpl/App.java | 50 ++++++--- .../biz/nellemann/mdexpl/MainPresenter.java | 55 +++++++++ .../nellemann/mdexpl/NetworkServiceCell.java | 41 +++---- .../biz/nellemann/mdexpl/model/MainModel.java | 20 ---- .../mdexpl/model/NetworkService.java | 2 +- .../mdexpl/service/DiscoveryService.java | 4 - .../nellemann/mdexpl/view/AboutPresenter.java | 64 ----------- .../nellemann/mdexpl/view/AppViewManager.java | 52 --------- .../nellemann/mdexpl/view/MainPresenter.java | 77 ------------- src/main/java/module-info.java | 12 ++ .../{view => }/configuration.properties | 0 .../resources/biz/nellemann/mdexpl/main.fxml | 12 ++ .../biz/nellemann/mdexpl/view/about.fxml | 45 -------- .../biz/nellemann/mdexpl/view/main.fxml | 16 --- 16 files changed, 227 insertions(+), 332 deletions(-) create mode 100644 src/main/java/biz/nellemann/mdexpl/MainPresenter.java delete mode 100644 src/main/java/biz/nellemann/mdexpl/model/MainModel.java delete mode 100644 src/main/java/biz/nellemann/mdexpl/view/AboutPresenter.java delete mode 100644 src/main/java/biz/nellemann/mdexpl/view/AppViewManager.java delete mode 100644 src/main/java/biz/nellemann/mdexpl/view/MainPresenter.java create mode 100644 src/main/java/module-info.java rename src/main/resources/biz/nellemann/mdexpl/{view => }/configuration.properties (100%) create mode 100644 src/main/resources/biz/nellemann/mdexpl/main.fxml delete mode 100644 src/main/resources/biz/nellemann/mdexpl/view/about.fxml delete mode 100644 src/main/resources/biz/nellemann/mdexpl/view/main.fxml diff --git a/build.gradle b/build.gradle index 088bbbb..c5adeb8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,13 +1,16 @@ -import org.apache.tools.ant.filters.ReplaceTokens - plugins { id 'application' id 'org.openjfx.javafxplugin' version '0.0.14' - id "com.github.johnrengelman.shadow" version "8.1.1" - id 'com.gluonhq.gluonfx-gradle-plugin' version '1.0.19' + id 'com.google.osdetector' version '1.7.3' + id 'org.beryx.jlink' version '2.26.0' + //id "com.github.johnrengelman.shadow" version "8.1.1" + //id 'com.gluonhq.gluonfx-gradle-plugin' version '1.0.19' + } +import org.apache.tools.ant.filters.ReplaceTokens + repositories { mavenCentral() mavenLocal() @@ -27,23 +30,22 @@ tasks.withType(JavaCompile).configureEach { java { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 - modularity.inferModulePath = false + //modularity.inferModulePath = false } /* This is to be able to build with a JDK not bundled with JavaFX */ javafx { - version = '21+' + version = '17.0.8' modules = [ 'javafx.controls', 'javafx.fxml' ] // platform("linux-aarch64") } dependencies { - implementation 'com.gluonhq:charm-glisten:6.2.3' - implementation 'com.gluonhq:glisten-afterburner:2.1.0' - implementation 'org.slf4j:slf4j-api:2.0.7' // Logging API runtimeOnly 'org.slf4j:slf4j-simple:2.0.7' // Logging API + implementation 'javax.inject:javax.inject:1' + implementation 'javax.annotation:javax.annotation-api:1.3.2' implementation 'org.jmdns:jmdns:3.5.8' testImplementation 'org.spockframework:spock-core:2.3-groovy-3.0' @@ -54,7 +56,7 @@ test { useJUnitPlatform() } - +/* gluonfx { verbose = true target = project.hasProperty("target") ? project.getProperty("target") : 'host' @@ -102,18 +104,96 @@ gluonfx { //skipSigning = true // Will not run or deploy if not signed } -} +}*/ +/* shadowJar { //archiveBaseName.set("vtd-poc-app") //archiveClassifier.set('all') archiveVersion.set("${System.env.BITBUCKET_BRANCH ?: 'dev' }") -} +}*/ -tasks.build.dependsOn tasks.shadowJar +//tasks.build.dependsOn tasks.shadowJar tasks.processResources { filesMatching('**/configuration.properties') { filter(ReplaceTokens, tokens: [copyright: '2023', version: System.env.BITBUCKET_BUILD_NUMBER ?: 'development' ]) } } + +jlink { + + forceMerge 'slf4j' + + options = [ + '--strip-debug', + '--compress', '2', + '--no-header-files', + '--no-man-pages' + ] + + launcher { + name = 'mDNS-Explorer' + noConsole = true + } + + // Only works with Java 14 (and later) + jpackage { + imageName = "mDNS-Explorer" + skipInstaller = true + installerName = "mDNS-Explorer-${osdetector.arch}" + installerOptions += [ + '--vendor', 'Nellemann Data', + '--description', 'mDNS Explorer', + '--copyright', 'Mark Nellemann ', + '--app-version', version + ] + + // Requires: https://wixtoolset.org/ to create installer on Windows + if(osdetector.os == 'windows') { + installerType = 'msi' + skipInstaller = false + installerOptions += [ + '--win-per-user-install', + '--win-dir-chooser', + '--win-menu', + // '--icon', 'src/main/resources/icon.ico' + ] + } + + // Requires: xcode-select --install + if(osdetector.os == 'osx') { + installerType = 'dmg' + skipInstaller = false + installerOptions += [ + //'--icon', 'src/main/resources/icon.icns' + ] + } + + if(osdetector.os == 'linux') { + + installerOptions += [ + '--linux-menu-group', 'Internet', + '--linux-shortcut', + //'--icon', 'src/main/resources/icon_256x256.png' + ] + + if (osdetector.release.isLike('debian')) { + installerType = 'deb' + skipInstaller = false + installerOptions += [ + '--linux-deb-maintainer', 'mark.nellemann@gmail.com' + ] + } else if (osdetector.release.isLike('centos')) { + installerType = 'rpm' + skipInstaller = false + installerOptions += [ + '--linux-rpm-license-type', 'APACHE-20' + ] + } + + } + + } + +} diff --git a/settings.gradle b/settings.gradle index 4aeb3ad..616f1de 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,9 +1,6 @@ pluginManagement { repositories { mavenLocal() - maven { - url "https://nexus.gluonhq.com/nexus/content/repositories/releases" - } gradlePluginPortal() } } diff --git a/src/main/java/biz/nellemann/mdexpl/App.java b/src/main/java/biz/nellemann/mdexpl/App.java index 5742834..83c2d9a 100644 --- a/src/main/java/biz/nellemann/mdexpl/App.java +++ b/src/main/java/biz/nellemann/mdexpl/App.java @@ -1,36 +1,58 @@ package biz.nellemann.mdexpl; -import biz.nellemann.mdexpl.view.AppViewManager; -import com.gluonhq.charm.glisten.application.AppManager; -import com.gluonhq.charm.glisten.visual.Swatch; import javafx.application.Application; +import javafx.application.Platform; +import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.stage.Stage; +import javafx.stage.WindowEvent; -import java.util.Objects; +import java.awt.Taskbar; +import java.awt.Toolkit; +import java.awt.Taskbar.Feature; +import java.io.IOException; public class App extends Application { - private final AppManager appManager = AppManager.initialize(this::postInit); - @Override public void init() { - AppViewManager.registerViewsAndDrawer(); + Platform.setImplicitExit(true); } @Override - public void start(Stage primaryStage) { - //System.setProperty(com.gluonhq.attach.util.Constants.ATTACH_DEBUG,"true"); - appManager.start(primaryStage); + public void start(Stage primaryStage) throws IOException { + + // Make all stages close and the app exit when the primary stage is closed + primaryStage.setOnCloseRequest(e -> Platform.exit()); + + // Set icon on the application bar + var appIcon = new Image("/icon.png"); + primaryStage.getIcons().add(appIcon); + + // Set icon on the taskbar/dock + if (Taskbar.isTaskbarSupported()) { + var taskbar = Taskbar.getTaskbar(); + + if (taskbar.isSupported(Feature.ICON_IMAGE)) { + final Toolkit defaultToolkit = Toolkit.getDefaultToolkit(); + var dockIcon = defaultToolkit.getImage(getClass().getResource("/icon.png")); + taskbar.setIconImage(dockIcon); + } + + } + + FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource("main.fxml")); + Scene scene = new Scene(fxmlLoader.load()); + primaryStage.setTitle("mDNS Explorer"); + primaryStage.setScene(scene); + primaryStage.show(); } - private void postInit(Scene scene) { - Swatch.GREEN.assignTo(scene); - //scene.getStylesheets().add(App.class.getResource("style.css").toExternalForm()); - ((Stage) scene.getWindow()).getIcons().add(new Image(Objects.requireNonNull(App.class.getResourceAsStream("/icon.png")))); + @Override + public void stop() { } diff --git a/src/main/java/biz/nellemann/mdexpl/MainPresenter.java b/src/main/java/biz/nellemann/mdexpl/MainPresenter.java new file mode 100644 index 0000000..9bc698a --- /dev/null +++ b/src/main/java/biz/nellemann/mdexpl/MainPresenter.java @@ -0,0 +1,55 @@ +package biz.nellemann.mdexpl; + +import biz.nellemann.mdexpl.model.NetworkService; +import biz.nellemann.mdexpl.service.DiscoveryService; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.scene.control.ListView; +import javafx.scene.layout.BorderPane; +import javafx.stage.WindowEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import java.util.ResourceBundle; + +public class MainPresenter { + + private static final Logger log = LoggerFactory.getLogger(MainPresenter.class); + + + @FXML + private BorderPane view; + + @FXML + private ResourceBundle resources; + + @FXML + private ListView listView; + + @Inject + DiscoveryService discoveryService; + + private final ObservableList devicesList = FXCollections.observableArrayList(); + + + @FXML + public void initialize() { + log.info("initialize()"); + + view.addEventFilter(WindowEvent.WINDOW_CLOSE_REQUEST, event -> { + log.info("Window closing"); + discoveryService.destroy(); + }); + + discoveryService = new DiscoveryService(); + discoveryService.initialize(); + discoveryService.setObservableList(devicesList); + listView.setItems(devicesList); + listView.setCellFactory(p -> new NetworkServiceCell()); + } + + +} diff --git a/src/main/java/biz/nellemann/mdexpl/NetworkServiceCell.java b/src/main/java/biz/nellemann/mdexpl/NetworkServiceCell.java index c01398f..bbd658d 100644 --- a/src/main/java/biz/nellemann/mdexpl/NetworkServiceCell.java +++ b/src/main/java/biz/nellemann/mdexpl/NetworkServiceCell.java @@ -1,40 +1,37 @@ package biz.nellemann.mdexpl; import biz.nellemann.mdexpl.model.NetworkService; -import com.gluonhq.attach.util.impl.ClipboardUtils; -import com.gluonhq.charm.glisten.control.CharmListCell; -import com.gluonhq.charm.glisten.control.ListTile; +import javafx.application.Platform; +import javafx.scene.control.ListCell; import javafx.scene.input.ClipboardContent; +import javafx.scene.layout.HBox; import javafx.scene.shape.Rectangle; import javafx.scene.input.Clipboard; -import java.util.Optional; -public class NetworkServiceCell extends CharmListCell { +public class NetworkServiceCell extends ListCell { - private final ListTile tile; private final Rectangle icon; private final Clipboard clipboard; private final ClipboardContent clipboardContent; + HBox hBox; public NetworkServiceCell() { clipboard = Clipboard.getSystemClipboard(); clipboardContent = new ClipboardContent(); - 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 -> { - clipboardContent.putString(itemProperty().get().getUrl()); - clipboard.setContent(clipboardContent); + this.setGraphic(icon); + + this.setOnMouseClicked(e -> { + if(itemProperty().get() != null) { + clipboardContent.putString(itemProperty().get().getUrl()); + clipboard.setContent(clipboardContent); + }; }); setText(null); } @@ -43,16 +40,14 @@ public class NetworkServiceCell extends CharmListCell { @Override public void updateItem(NetworkService item, boolean empty) { super.updateItem(item, empty); - if (item != null && !empty) { - tile.textProperty().setAll(item.getName(), - item.getApp() + " (" + item.getSubType() + ") - " + item.getUrl() - ); - icon.setFill(item.getColor()); - setGraphic(tile); - } else { + if (empty) { + setText(null); setGraphic(null); + } else { + setText(item.toString()); + icon.setFill(item.getColor()); + setGraphic(icon); } } - } diff --git a/src/main/java/biz/nellemann/mdexpl/model/MainModel.java b/src/main/java/biz/nellemann/mdexpl/model/MainModel.java deleted file mode 100644 index 4c96aaa..0000000 --- a/src/main/java/biz/nellemann/mdexpl/model/MainModel.java +++ /dev/null @@ -1,20 +0,0 @@ -package biz.nellemann.mdexpl.model; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.PostConstruct; -import javax.inject.Singleton; - -@Singleton -public class MainModel { - - private static final Logger log = LoggerFactory.getLogger(MainModel.class); - - - @PostConstruct - public void initialize() { - log.info("initialize()"); - } - -} diff --git a/src/main/java/biz/nellemann/mdexpl/model/NetworkService.java b/src/main/java/biz/nellemann/mdexpl/model/NetworkService.java index b148c0a..d6c5caa 100644 --- a/src/main/java/biz/nellemann/mdexpl/model/NetworkService.java +++ b/src/main/java/biz/nellemann/mdexpl/model/NetworkService.java @@ -74,7 +74,7 @@ public class NetworkService { @Override public String toString() { - return name + " (" + type + "), app=" + app + ", url=" + url; + return name + " (" + app + ") " + url; } diff --git a/src/main/java/biz/nellemann/mdexpl/service/DiscoveryService.java b/src/main/java/biz/nellemann/mdexpl/service/DiscoveryService.java index d72761d..071a619 100644 --- a/src/main/java/biz/nellemann/mdexpl/service/DiscoveryService.java +++ b/src/main/java/biz/nellemann/mdexpl/service/DiscoveryService.java @@ -7,8 +7,6 @@ import javafx.scene.paint.Color; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; import javax.inject.Singleton; import javax.jmdns.JmDNS; import java.io.IOException; @@ -71,7 +69,6 @@ public class DiscoveryService { }}; - @PostConstruct public void initialize() { log.info("initialize()"); try { @@ -82,7 +79,6 @@ public class DiscoveryService { } - @PreDestroy public void destroy() { if(jmdns != null) { try { diff --git a/src/main/java/biz/nellemann/mdexpl/view/AboutPresenter.java b/src/main/java/biz/nellemann/mdexpl/view/AboutPresenter.java deleted file mode 100644 index 2062ca9..0000000 --- a/src/main/java/biz/nellemann/mdexpl/view/AboutPresenter.java +++ /dev/null @@ -1,64 +0,0 @@ -package biz.nellemann.mdexpl.view; - -import com.gluonhq.charm.glisten.application.AppManager; -import com.gluonhq.charm.glisten.control.AppBar; -import com.gluonhq.charm.glisten.mvc.View; -import com.gluonhq.charm.glisten.visual.MaterialDesignIcon; -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import java.util.ResourceBundle; - -public class AboutPresenter { - - private static final Logger log = LoggerFactory.getLogger(AboutPresenter.class); - - - @FXML - private ResourceBundle resources; - - @FXML - View about; - - @FXML - Label labelInfoWebsite; - - @FXML - Label labelVersion; - - @Inject String appVersion; - @Inject String aboutWebsite; - - - @FXML - public void initialize() { - log.info("initialize()"); - - about.showingProperty().addListener((obs, oldValue, newValue) -> { - if (newValue) { - AppManager appManager = AppManager.getInstance(); - AppBar appBar = appManager.getAppBar(); - - appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> - appManager.getDrawer().open())); - - appBar.setTitleText("About"); - - appBar.getActionItems().add(MaterialDesignIcon.CLOSE.button(e -> { - appManager.goHome(); - })); - - } - }); - - labelVersion.setText(appVersion); - labelInfoWebsite.setText(aboutWebsite); - - } - - - -} diff --git a/src/main/java/biz/nellemann/mdexpl/view/AppViewManager.java b/src/main/java/biz/nellemann/mdexpl/view/AppViewManager.java deleted file mode 100644 index f656d26..0000000 --- a/src/main/java/biz/nellemann/mdexpl/view/AppViewManager.java +++ /dev/null @@ -1,52 +0,0 @@ -package biz.nellemann.mdexpl.view; - -import biz.nellemann.mdexpl.App; -import com.gluonhq.charm.glisten.afterburner.AppView; -import com.gluonhq.charm.glisten.afterburner.AppViewRegistry; -import com.gluonhq.charm.glisten.afterburner.Utils; -import com.gluonhq.charm.glisten.application.AppManager; -import com.gluonhq.charm.glisten.control.Avatar; -import com.gluonhq.charm.glisten.control.NavigationDrawer; -import com.gluonhq.charm.glisten.visual.MaterialDesignIcon; -import javafx.scene.image.Image; - -import java.util.Locale; -import java.util.Objects; - -import static com.gluonhq.charm.glisten.afterburner.AppView.Flag.*; - -public class AppViewManager { - - public static final AppViewRegistry REGISTRY = new AppViewRegistry(); - - // Shown in drawer - public static final AppView PRIMARY_VIEW = view("Explorer", MainPresenter.class, MaterialDesignIcon.HOME, SHOW_IN_DRAWER, HOME_VIEW, SKIP_VIEW_STACK); - public static final AppView ABOUT_VIEW = view("About", AboutPresenter.class, MaterialDesignIcon.HELP, SHOW_IN_DRAWER); - - - - private static AppView view(String title, Class presenterClass, MaterialDesignIcon menuIcon, AppView.Flag... flags ) { - return REGISTRY.createView(name(presenterClass), title, presenterClass, menuIcon, flags); - } - - private static String name(Class presenterClass) { - return presenterClass.getSimpleName().toUpperCase(Locale.ROOT).replace("PRESENTER", ""); - } - - public static void registerViewsAndDrawer() { - for (AppView view : REGISTRY.getViews()) { - view.registerView(); - } - - NavigationDrawer.Header header = new NavigationDrawer.Header("mDNS Explorer", - "Multicast DNS Explorer", - new Avatar(48, new Image(Objects.requireNonNull(App.class.getResourceAsStream("/icon.png")))) - ); - - Utils.buildDrawer(AppManager.getInstance().getDrawer(), header, REGISTRY.getViews()); - - //NavigationDrawer.Footer footer = new NavigationDrawer.Footer("Bla"); - //AppManager.getInstance().getDrawer().setFooter(footer); - } - -} diff --git a/src/main/java/biz/nellemann/mdexpl/view/MainPresenter.java b/src/main/java/biz/nellemann/mdexpl/view/MainPresenter.java deleted file mode 100644 index 7c8d6a8..0000000 --- a/src/main/java/biz/nellemann/mdexpl/view/MainPresenter.java +++ /dev/null @@ -1,77 +0,0 @@ -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.LifecycleEvent; -import com.gluonhq.charm.glisten.mvc.View; -import com.gluonhq.charm.glisten.visual.MaterialDesignIcon; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.input.MouseEvent; -import javafx.scene.input.TouchEvent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import java.util.ResourceBundle; - -public class MainPresenter { - - private static final Logger log = LoggerFactory.getLogger(MainPresenter.class); - - - @FXML - private View main; - - @Inject - private MainModel model; - - @Inject - private DiscoveryService discoveryService; - - @FXML - private ResourceBundle resources; - - @FXML - private CharmListView charmListView; - - private final ObservableList devicesList = FXCollections.observableArrayList(); - - - @FXML - public void initialize() { - - log.info("initialize()"); - - main.showingProperty().addListener((obs, oldValue, newValue) -> { - if (newValue) { - AppManager appManager = AppManager.getInstance(); - AppBar appBar = appManager.getAppBar(); - - appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> - appManager.getDrawer().open())); - - appBar.setTitleText("mDNS Explorer"); - } - }); - - discoveryService.setObservableList(devicesList); - charmListView.setItems(devicesList); - charmListView.setCellFactory(p -> new NetworkServiceCell()); - } - - - public void onEventShowing(LifecycleEvent lifecycleEvent) { - - } - - public void onEventHiding(LifecycleEvent lifecycleEvent) { - } - -} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java new file mode 100644 index 0000000..387106d --- /dev/null +++ b/src/main/java/module-info.java @@ -0,0 +1,12 @@ +module biz.nellemann.mdexpl { + requires javafx.controls; + requires javafx.fxml; + requires javax.jmdns; + requires org.slf4j; + requires javax.inject; + requires java.annotation; + requires java.desktop; + + opens biz.nellemann.mdexpl to javafx.fxml; + exports biz.nellemann.mdexpl; +} diff --git a/src/main/resources/biz/nellemann/mdexpl/view/configuration.properties b/src/main/resources/biz/nellemann/mdexpl/configuration.properties similarity index 100% rename from src/main/resources/biz/nellemann/mdexpl/view/configuration.properties rename to src/main/resources/biz/nellemann/mdexpl/configuration.properties diff --git a/src/main/resources/biz/nellemann/mdexpl/main.fxml b/src/main/resources/biz/nellemann/mdexpl/main.fxml new file mode 100644 index 0000000..bdef55f --- /dev/null +++ b/src/main/resources/biz/nellemann/mdexpl/main.fxml @@ -0,0 +1,12 @@ + + + + + + + +
+ +
+ +
diff --git a/src/main/resources/biz/nellemann/mdexpl/view/about.fxml b/src/main/resources/biz/nellemann/mdexpl/view/about.fxml deleted file mode 100644 index df8b47f..0000000 --- a/src/main/resources/biz/nellemann/mdexpl/view/about.fxml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/biz/nellemann/mdexpl/view/main.fxml b/src/main/resources/biz/nellemann/mdexpl/view/main.fxml deleted file mode 100644 index 31171e8..0000000 --- a/src/main/resources/biz/nellemann/mdexpl/view/main.fxml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - -
- -
- -