diff --git a/owlplug-client/pom.xml b/owlplug-client/pom.xml index e7a07aec..994fe2c7 100644 --- a/owlplug-client/pom.xml +++ b/owlplug-client/pom.xml @@ -11,7 +11,7 @@ com.owlplug owlplug - 1.33.0 + 1.33.1 owlplug-client @@ -35,17 +35,17 @@ com.owlplug owlplug-host - 1.33.0 + 1.33.1 com.owlplug owlplug-controls - 1.33.0 + 1.33.1 com.owlplug owlplug-parsers - 1.33.0 + 1.33.1 org.springframework.boot diff --git a/owlplug-client/src/main/java/com/owlplug/core/components/ApplicationDefaults.java b/owlplug-client/src/main/java/com/owlplug/core/components/ApplicationDefaults.java index e32bcdf5..57855792 100644 --- a/owlplug-client/src/main/java/com/owlplug/core/components/ApplicationDefaults.java +++ b/owlplug-client/src/main/java/com/owlplug/core/components/ApplicationDefaults.java @@ -108,6 +108,7 @@ public class ApplicationDefaults { public static final String LV2_EXTRA_DIRECTORY_KEY = "LV2_EXTRA_DIRECTORY_KEY"; public static final String NATIVE_HOST_ENABLED_KEY = "NATIVE_HOST_ENABLED_KEY"; public static final String PREFERRED_NATIVE_LOADER = "PREFERRED_NATIVE_LOADER"; + public static final String NATIVE_LOADER_TIMEOUT_KEY = "NATIVE_LOADER_TIMEOUT_KEY"; public static final String SELECTED_ACCOUNT_KEY = "SELECTED_ACCOUNT_KEY"; public static final String SYNC_PLUGINS_STARTUP_KEY = "SYNC_PLUGINS_STARTUP_KEY"; public static final String STORE_DIRECTORY_ENABLED_KEY = "STORE_DIRECTORY_ENABLED_KEY"; diff --git a/owlplug-client/src/main/java/com/owlplug/core/controllers/OptionsController.java b/owlplug-client/src/main/java/com/owlplug/core/controllers/OptionsController.java index 9ec93569..f25a963b 100644 --- a/owlplug-client/src/main/java/com/owlplug/core/controllers/OptionsController.java +++ b/owlplug-client/src/main/java/com/owlplug/core/controllers/OptionsController.java @@ -63,6 +63,8 @@ public class OptionsController extends BaseController { @FXML private ComboBox pluginNativeComboBox; @FXML + private TextField loaderTimeoutTextField; + @FXML private CheckBox syncPluginsCheckBox; @FXML private CheckBox syncFileStatCheckbox; @@ -155,6 +157,7 @@ public void initialize() { pluginNativeCheckbox.selectedProperty().addListener((observable, oldValue, newValue) -> { this.getPreferences().putBoolean(ApplicationDefaults.NATIVE_HOST_ENABLED_KEY, newValue); this.pluginNativeComboBox.setDisable(!newValue); + updateScannerTimeoutFieldState(); }); ObservableList pluginLoaders = FXCollections.observableArrayList( @@ -163,8 +166,27 @@ public void initialize() { pluginNativeComboBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { if (newValue != null) { - this.getPreferences().put(ApplicationDefaults.PREFERRED_NATIVE_LOADER,newValue.getId()); + this.getPreferences().put(ApplicationDefaults.PREFERRED_NATIVE_LOADER, newValue.getId()); nativeHostService.setCurrentPluginLoader(newValue); + updateScannerTimeoutFieldState(); + } + }); + + loaderTimeoutTextField.textProperty().addListener((observable, oldValue, newValue) -> { + if (!newValue.matches("\\d*")) { + loaderTimeoutTextField.setText(newValue.replaceAll("[^\\d]", "")); + return; + } + try { + long timeout = Long.parseLong(newValue); + if (timeout >= 0 && timeout <= 3600) { + this.getPreferences().putLong(ApplicationDefaults.NATIVE_LOADER_TIMEOUT_KEY, timeout); + nativeHostService.setScannerTimeout(timeout); + } else { + loaderTimeoutTextField.setText(oldValue); + } + } catch (NumberFormatException ignored) { + // Ignore in case of invalid values (empty string) } }); @@ -265,6 +287,13 @@ public void initialize() { refreshView(); } + private void updateScannerTimeoutFieldState() { + NativePluginLoader selected = pluginNativeComboBox.getSelectionModel().getSelectedItem(); + boolean isOwlPlugScanner = selected != null && "owlplug-scanner".equals(selected.getId()); + boolean nativeEnabled = pluginNativeCheckbox.isSelected() && !pluginNativeCheckbox.isDisable(); + loaderTimeoutTextField.setDisable(!nativeEnabled || !isOwlPlugScanner); + } + public void refreshView() { vst2PluginPathFragment.refresh(); @@ -287,6 +316,10 @@ public void refreshView() { NativePluginLoader pluginLoader = nativeHostService.getCurrentPluginLoader(); pluginNativeComboBox.getSelectionModel().select(pluginLoader); + long timeout = this.getPreferences().getLong(ApplicationDefaults.NATIVE_LOADER_TIMEOUT_KEY, 10L); + loaderTimeoutTextField.setText(String.valueOf(timeout)); + updateScannerTimeoutFieldState(); + if (!storeDirectoryCheckBox.isSelected()) { storeDirectoryTextField.setVisible(false); } diff --git a/owlplug-client/src/main/java/com/owlplug/core/services/OptionsService.java b/owlplug-client/src/main/java/com/owlplug/core/services/OptionsService.java index 0f28b0cf..494989a4 100644 --- a/owlplug-client/src/main/java/com/owlplug/core/services/OptionsService.java +++ b/owlplug-client/src/main/java/com/owlplug/core/services/OptionsService.java @@ -89,6 +89,9 @@ private void initialize() { if (prefs.get(ApplicationDefaults.STORE_SUBDIRECTORY_ENABLED, null) == null) { prefs.putBoolean(ApplicationDefaults.STORE_SUBDIRECTORY_ENABLED, Boolean.TRUE); } + if (prefs.get(ApplicationDefaults.NATIVE_LOADER_TIMEOUT_KEY, null) == null) { + prefs.putLong(ApplicationDefaults.NATIVE_LOADER_TIMEOUT_KEY, 10L); + } } /** diff --git a/owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginInfoController.java b/owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginInfoController.java index 72a2d7f5..b6590b73 100644 --- a/owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginInfoController.java +++ b/owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginInfoController.java @@ -43,7 +43,6 @@ import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; -import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.Label; @@ -111,6 +110,8 @@ public class PluginInfoController extends BaseController { private ListView pluginComponentListView; @FXML private ToggleSwitch nativeDiscoveryToggleButton; + @FXML + private Label lastScanErrorLabel; private final ObjectProperty pluginProperty = new SimpleObjectProperty(); private final ArrayList knownPluginImages = new ArrayList<>(); @@ -157,6 +158,8 @@ public void initialize() { }); pluginComponentListView.setCellFactory(new PluginComponentCellFactory(this.getApplicationDefaults())); + lastScanErrorLabel.managedProperty().bind(lastScanErrorLabel.visibleProperty()); + lastScanErrorLabel.setVisible(false); nativeDiscoveryToggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> { Plugin plugin = pluginProperty.get(); @@ -175,8 +178,6 @@ public void refresh() { return; } - // All reads below come from already-loaded in-memory fields — safe to run - // on the FX thread without risk of blocking. pluginFormatIcon.setImage(this.getApplicationDefaults().getPluginFormatIcon(plugin.getFormat())); pluginFormatLabel.setText(plugin.getFormat().getText() + " Plugin"); pluginTitleLabel.setText(plugin.getName()); @@ -202,6 +203,11 @@ public void refresh() { if (plugin.getFootprint() != null) { nativeDiscoveryToggleButton.setSelected(plugin.getFootprint().isNativeDiscoveryEnabled()); + String scanError = plugin.getFootprint().getLastScanStatus(); + lastScanErrorLabel.setText(scanError != null ? scanError : ""); + lastScanErrorLabel.setVisible(scanError != null); + } else { + lastScanErrorLabel.setVisible(false); } setPluginImage(); diff --git a/owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginTableController.java b/owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginTableController.java index 16172886..86a71eae 100644 --- a/owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginTableController.java +++ b/owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginTableController.java @@ -23,6 +23,7 @@ import com.owlplug.core.utils.FileUtils; import com.owlplug.core.utils.PlatformUtils; import com.owlplug.plugin.controllers.dialogs.DisablePluginDialogController; +import com.owlplug.plugin.model.IPlugin; import com.owlplug.plugin.model.Plugin; import com.owlplug.plugin.model.PluginFormat; import com.owlplug.plugin.model.PluginState; @@ -63,9 +64,9 @@ public class PluginTableController extends BaseController { private PluginService pluginService; private final SimpleStringProperty search = new SimpleStringProperty(); - private final TableView tableView; + private final TableView tableView; - private final ObservableList pluginList; + private final ObservableList pluginList; public PluginTableController() { @@ -75,7 +76,7 @@ public PluginTableController() { createColumns(); tableView.setRowFactory(tv -> { - TableRow row = new TableRow<>(); + TableRow row = new TableRow<>(); row.itemProperty().addListener((obs, oldItem, newItem) -> { if (newItem != null) { row.setContextMenu(createPluginContextMenu(newItem)); @@ -88,7 +89,7 @@ public PluginTableController() { pluginList = FXCollections.observableArrayList(); // Wraps an ObservableList and filters its content using the provided Predicate. // All changes in the ObservableList are propagated immediately to the FilteredList. - FilteredList filteredPluginList = new FilteredList<>(pluginList); + FilteredList filteredPluginList = new FilteredList<>(pluginList); filteredPluginList.predicateProperty().bind(Bindings.createObjectBinding(() -> { if (search.getValue() == null || search.getValue().isEmpty()) { @@ -99,17 +100,17 @@ public PluginTableController() { search.getValue().toLowerCase())); }, search)); - SortedList sortedPluginList = new SortedList<>(filteredPluginList); + SortedList sortedPluginList = new SortedList<>(filteredPluginList); tableView.setItems(sortedPluginList); sortedPluginList.comparatorProperty().bind(tableView.comparatorProperty()); } private void createColumns() { - TableColumn nameColumn = new TableColumn<>("Name"); + TableColumn nameColumn = new TableColumn<>("Name"); nameColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getName())); - TableColumn formatColumn = new TableColumn<>("Format"); - formatColumn.setCellValueFactory(cellData -> new SimpleObjectProperty<>(cellData.getValue().getFormat())); + TableColumn formatColumn = new TableColumn<>("Format"); + formatColumn.setCellValueFactory(cellData -> new SimpleObjectProperty<>(cellData.getValue().asPlugin().getFormat())); formatColumn.setCellFactory(e -> new TableCell<>() { @Override public void updateItem(PluginFormat item, boolean empty) { @@ -123,17 +124,17 @@ public void updateItem(PluginFormat item, boolean empty) { } } }); - TableColumn manufacturerColumn = new TableColumn<>("Manufacturer"); + TableColumn manufacturerColumn = new TableColumn<>("Manufacturer"); manufacturerColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getManufacturerName())); - TableColumn versionColumn = new TableColumn<>("Version"); + TableColumn versionColumn = new TableColumn<>("Version"); versionColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getVersion())); - TableColumn categoryColumn = new TableColumn<>("Category"); + TableColumn categoryColumn = new TableColumn<>("Category"); categoryColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getCategory())); // Directory Column - TableColumn directoryColumn = new TableColumn<>("Directory"); + TableColumn directoryColumn = new TableColumn<>("Directory"); directoryColumn.setCellValueFactory(cellData -> new SimpleStringProperty( - FileUtils.getParentDirectoryName(cellData.getValue().getPath()))); + FileUtils.getParentDirectoryName(cellData.getValue().asPlugin().getPath()))); directoryColumn.setCellFactory(e -> new TableCell<>() { @Override public void updateItem(String item, boolean empty) { @@ -148,9 +149,9 @@ public void updateItem(String item, boolean empty) { } }); // Scan Directory Column - TableColumn scanDirectoryColumn = new TableColumn<>("Scan Dir."); + TableColumn scanDirectoryColumn = new TableColumn<>("Scan Dir."); scanDirectoryColumn.setCellValueFactory(cellData -> new SimpleStringProperty( - FileUtils.getFilename(cellData.getValue().getScanDirectoryPath()))); + FileUtils.getFilename(cellData.getValue().asPlugin().getScanDirectoryPath()))); scanDirectoryColumn.setCellFactory(e -> new TableCell<>() { @Override public void updateItem(String item, boolean empty) { @@ -165,9 +166,9 @@ public void updateItem(String item, boolean empty) { } }); // Plugin State Column - TableColumn stateColumn = new TableColumn<>("State"); + TableColumn stateColumn = new TableColumn<>("State"); stateColumn.setCellValueFactory(cellData -> new SimpleObjectProperty<>( - pluginService.getPluginState(cellData.getValue()))); + pluginService.getPluginState(cellData.getValue().asPlugin()))); stateColumn.setCellFactory(e -> new TableCell<>() { @Override public void updateItem(PluginState item, boolean empty) { @@ -188,10 +189,16 @@ public void updateItem(PluginState item, boolean empty) { public void setPlugins(Iterable plugins) { pluginList.clear(); - plugins.forEach(pluginList::add); + plugins.forEach(p -> { + pluginList.add(p); + if (p.getComponents().size() > 1) { + pluginList.addAll(p.getComponents()); + } + }); + } - public TableView getTableView() { + public TableView getTableView() { return tableView; } @@ -201,7 +208,8 @@ public void setNodeManaged(boolean isManaged) { } public void selectPluginById(long id) { - for (Plugin plugin : pluginList) { + for (IPlugin p : pluginList) { + Plugin plugin = p.asPlugin(); // Get plugin or component parent if (plugin.getId().equals(id)) { tableView.getSelectionModel().select(plugin); break; @@ -217,37 +225,39 @@ public void refresh() { tableView.refresh(); } - private ContextMenu createPluginContextMenu(Plugin plugin) { + private ContextMenu createPluginContextMenu(IPlugin plugin) { ContextMenu menu = new ContextMenu(); MenuItem openDirItem = new MenuItem("Reveal in File Explorer"); openDirItem.setOnAction(e -> { - File pluginFile = new File(plugin.getPath()); + File pluginFile = new File(plugin.asPlugin().getPath()); PlatformUtils.openFromDesktop(pluginFile.getParentFile()); }); menu.getItems().addAll(openDirItem, new SeparatorMenuItem()); - if (plugin.isDisabled()) { - MenuItem enableItem = new MenuItem("Enable plugin"); - enableItem.setOnAction(e -> { - Async.run(() -> pluginService.enablePlugin(plugin)); - }); - menu.getItems().add(enableItem); - } else { - MenuItem disableItem = new MenuItem("Disable plugin"); - disableItem.setOnAction(e -> { - if (this.getPreferences().getBoolean(ApplicationDefaults.SHOW_DIALOG_DISABLE_PLUGIN_KEY, true)) { - this.disableController.setPlugin(plugin); - this.disableController.show(); - } else { - this.disableController.disablePluginWithoutPrompt(plugin); - } - }); - menu.getItems().add(disableItem); - } + if (plugin instanceof Plugin p) { + if (p.isDisabled()) { + MenuItem enableItem = new MenuItem("Enable plugin"); + enableItem.setOnAction(e -> { + Async.run(() -> pluginService.enablePlugin(p)); + }); + menu.getItems().add(enableItem); + } else { + MenuItem disableItem = new MenuItem("Disable plugin"); + disableItem.setOnAction(e -> { + if (this.getPreferences().getBoolean(ApplicationDefaults.SHOW_DIALOG_DISABLE_PLUGIN_KEY, true)) { + this.disableController.setPlugin(p); + this.disableController.show(); + } else { + this.disableController.disablePluginWithoutPrompt(p); + } + }); + menu.getItems().add(disableItem); + } - menu.getItems().add(new SeparatorMenuItem()); + menu.getItems().add(new SeparatorMenuItem()); + } MenuItem infoDisplayItem = new MenuItem("Toggle info display"); menu.getItems().add(infoDisplayItem); diff --git a/owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginTreeViewController.java b/owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginTreeViewController.java index 4d091732..9890f4a1 100644 --- a/owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginTreeViewController.java +++ b/owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginTreeViewController.java @@ -21,6 +21,7 @@ import com.owlplug.core.controllers.BaseController; import com.owlplug.core.ui.FilterableTreeItem; import com.owlplug.plugin.model.IDirectory; +import com.owlplug.plugin.model.IPlugin; import com.owlplug.plugin.model.Plugin; import com.owlplug.plugin.model.PluginComponent; import com.owlplug.plugin.model.PluginDirectory; @@ -73,7 +74,7 @@ public PluginTreeViewController() { return null; } return (item) -> { - if (item instanceof Plugin plugin) { + if (item instanceof IPlugin plugin) { return plugin.getName().toLowerCase().contains(search.getValue().toLowerCase()) || (plugin.getCategory() != null && plugin.getCategory().toLowerCase().contains( search.getValue().toLowerCase())); @@ -89,7 +90,7 @@ public PluginTreeViewController() { return null; } return (item) -> { - if (item instanceof Plugin plugin) { + if (item instanceof IPlugin plugin) { return plugin.getName().toLowerCase().contains(search.getValue().toLowerCase()) || (plugin.getCategory() != null && plugin.getCategory().toLowerCase().contains(search.getValue().toLowerCase())); } else { diff --git a/owlplug-client/src/main/java/com/owlplug/plugin/model/IPlugin.java b/owlplug-client/src/main/java/com/owlplug/plugin/model/IPlugin.java new file mode 100644 index 00000000..22a58ff1 --- /dev/null +++ b/owlplug-client/src/main/java/com/owlplug/plugin/model/IPlugin.java @@ -0,0 +1,57 @@ +/* OwlPlug + * Copyright (C) 2021 Arthur + * + * This file is part of OwlPlug. + * + * OwlPlug is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 + * as published by the Free Software Foundation. + * + * OwlPlug is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OwlPlug. If not, see . + */ + + +package com.owlplug.plugin.model; + +public interface IPlugin { + + /** + * Retrieve the associated Plugin object from this Plugin object. + * In case of Component, it returns the parent plugin. + * In case of Plugin, it should return itself. + * @return plugin + */ + Plugin asPlugin(); + + /** + * Get object identifier. + * The identifier is not guaranteed to be unique across Plugin and Component. + * @return id + */ + Long getId(); + + String getName(); + + String getDescriptiveName(); + + String getVersion(); + + String getUid(); + + String getCategory(); + + String getManufacturerName(); + + String getIdentifier(); + + String getBundleId(); + + PluginType getType(); + +} diff --git a/owlplug-client/src/main/java/com/owlplug/plugin/model/Plugin.java b/owlplug-client/src/main/java/com/owlplug/plugin/model/Plugin.java index f3bba4db..c320dd02 100644 --- a/owlplug-client/src/main/java/com/owlplug/plugin/model/Plugin.java +++ b/owlplug-client/src/main/java/com/owlplug/plugin/model/Plugin.java @@ -42,7 +42,7 @@ @Inheritance @Table(indexes = { @Index(name = "IDX_PLUGIN_ID", columnList = "id"), @Index(name = "IDX_PLUGIN_NAME", columnList = "name") }) -public class Plugin { +public class Plugin implements IPlugin { @Id @GeneratedValue(strategy = GenerationType.AUTO) @@ -238,4 +238,9 @@ public Set getComponents() { public void setComponents(Set components) { this.components = components; } + + @Override + public Plugin asPlugin() { + return this; + } } diff --git a/owlplug-client/src/main/java/com/owlplug/plugin/model/PluginComponent.java b/owlplug-client/src/main/java/com/owlplug/plugin/model/PluginComponent.java index dfdaa2cb..d41ada15 100644 --- a/owlplug-client/src/main/java/com/owlplug/plugin/model/PluginComponent.java +++ b/owlplug-client/src/main/java/com/owlplug/plugin/model/PluginComponent.java @@ -19,6 +19,7 @@ package com.owlplug.plugin.model; import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; @@ -31,7 +32,7 @@ @Entity @Table(indexes = { @Index(name = "IDX_PLUGIN_COMPONENT_ID", columnList = "id") }) -public class PluginComponent { +public class PluginComponent implements IPlugin { @Id @GeneratedValue(strategy = GenerationType.AUTO) @@ -48,6 +49,7 @@ public class PluginComponent { @Enumerated(EnumType.STRING) protected PluginType type; + @ManyToOne @JsonIgnore private Plugin plugin; @@ -140,4 +142,8 @@ public void setPlugin(Plugin plugin) { this.plugin = plugin; } + @Override + public Plugin asPlugin() { + return this.plugin; + } } diff --git a/owlplug-client/src/main/java/com/owlplug/plugin/model/PluginFootprint.java b/owlplug-client/src/main/java/com/owlplug/plugin/model/PluginFootprint.java index e33124b7..bf116a09 100644 --- a/owlplug-client/src/main/java/com/owlplug/plugin/model/PluginFootprint.java +++ b/owlplug-client/src/main/java/com/owlplug/plugin/model/PluginFootprint.java @@ -39,6 +39,8 @@ public class PluginFootprint { protected boolean nativeDiscoveryEnabled = true; protected String screenshotUrl; + + private String lastScanStatus; public PluginFootprint() { } @@ -75,4 +77,12 @@ public Long getId() { return id; } + public String getLastScanStatus() { + return lastScanStatus; + } + + public void setLastScanStatus(String lastScanStatus) { + this.lastScanStatus = lastScanStatus; + } + } diff --git a/owlplug-client/src/main/java/com/owlplug/plugin/services/NativeHostService.java b/owlplug-client/src/main/java/com/owlplug/plugin/services/NativeHostService.java index e746e5f3..ebd41fb0 100644 --- a/owlplug-client/src/main/java/com/owlplug/plugin/services/NativeHostService.java +++ b/owlplug-client/src/main/java/com/owlplug/plugin/services/NativeHostService.java @@ -23,6 +23,7 @@ import com.owlplug.host.NativePlugin; import com.owlplug.host.loaders.DummyPluginLoader; import com.owlplug.host.loaders.EmbeddedScannerPluginLoader; +import com.owlplug.host.loaders.NativeLoaderException; import com.owlplug.host.loaders.NativePluginLoader; import com.owlplug.host.loaders.jni.JNINativePluginLoader; import jakarta.annotation.PostConstruct; @@ -52,6 +53,8 @@ private void init() { loader.init(); } configureCurrentPluginLoader(); + long timeoutSeconds = this.getPreferences().getLong(ApplicationDefaults.NATIVE_LOADER_TIMEOUT_KEY, 30L); + EmbeddedScannerPluginLoader.getInstance().setTimeout(timeoutSeconds * 1000); } private void configureCurrentPluginLoader() { @@ -110,7 +113,11 @@ public void setCurrentPluginLoader(NativePluginLoader pluginLoader) { this.currentPluginLoader = pluginLoader; } - public List loadPlugin(String path) { + public void setScannerTimeout(long timeoutSeconds) { + EmbeddedScannerPluginLoader.getInstance().setTimeout(timeoutSeconds * 1000); + } + + public List loadPlugin(String path) throws NativeLoaderException { if (currentPluginLoader != null) { return currentPluginLoader.loadPlugin(path); } else { diff --git a/owlplug-client/src/main/java/com/owlplug/plugin/tasks/PluginScanTask.java b/owlplug-client/src/main/java/com/owlplug/plugin/tasks/PluginScanTask.java index 1a5f96b4..d665515a 100644 --- a/owlplug-client/src/main/java/com/owlplug/plugin/tasks/PluginScanTask.java +++ b/owlplug-client/src/main/java/com/owlplug/plugin/tasks/PluginScanTask.java @@ -21,7 +21,9 @@ import com.owlplug.core.tasks.AbstractTask; import com.owlplug.core.tasks.TaskException; import com.owlplug.core.tasks.TaskResult; +import com.owlplug.core.utils.StringUtils; import com.owlplug.host.NativePlugin; +import com.owlplug.host.loaders.NativeLoaderException; import com.owlplug.plugin.model.Plugin; import com.owlplug.plugin.model.PluginComponent; import com.owlplug.plugin.model.PluginFootprint; @@ -172,27 +174,35 @@ protected void collect() throws Exception { log.debug("Load plugin using native discovery: " + plugin.getPath()); this.updateMessage("Exploring plugin " + plugin.getName()); - List nativePlugins = nativeHostService.loadPlugin(plugin.getPath()); + try { + List nativePlugins = nativeHostService.loadPlugin(plugin.getPath()); + pluginFootprint.setLastScanStatus(null); - if (nativePlugins != null && !nativePlugins.isEmpty()) { - log.debug("Found {} components (nativePlugin) for plugin {}", nativePlugins.size(), plugin.getName()); + if (!nativePlugins.isEmpty()) { + log.debug("Found {} components (nativePlugin) for plugin {}", nativePlugins.size(), plugin.getName()); - plugin.setNativeCompatible(true); + plugin.setNativeCompatible(true); - for (NativePlugin nativePlugin : nativePlugins) { - PluginComponent component = createComponentFromNative(nativePlugin); - component.setPlugin(plugin); - plugin.getComponents().add(component); - log.debug("Created component {} for plugin {}", component.getName(), plugin.getName()); - } - - // Hardcode plugin properties from the first component (nativePlugin) retrieved. - mapPluginPropertiesFromNative(plugin, nativePlugins.get(0)); + for (NativePlugin nativePlugin : nativePlugins) { + PluginComponent component = createComponentFromNative(nativePlugin); + component.setPlugin(plugin); + plugin.getComponents().add(component); + log.debug("Created component {} for plugin {}", component.getName(), plugin.getName()); + } + // Hardcode plugin properties from the first component (nativePlugin) retrieved. + mapPluginPropertiesFromNative(plugin, nativePlugins.get(0)); + } + } catch (NativeLoaderException e) { + log.error("Native scan failed for plugin {}: {}", plugin.getName(), e.getMessage()); + pluginFootprint.setLastScanStatus(StringUtils.truncate( + e.getMessage(), 240, "..." + )); } } plugin.setScanComplete(true); + pluginFootprintRepository.save(pluginFootprint); pluginRepository.save(plugin); this.commitProgress(80.0 / pluginFiles.size()); diff --git a/owlplug-client/src/main/resources/fxml/OptionsView.fxml b/owlplug-client/src/main/resources/fxml/OptionsView.fxml index ec61670c..38ebbd37 100644 --- a/owlplug-client/src/main/resources/fxml/OptionsView.fxml +++ b/owlplug-client/src/main/resources/fxml/OptionsView.fxml @@ -29,13 +29,14 @@ - - - + + diff --git a/owlplug-client/src/main/resources/fxml/plugins/PluginInfoView.fxml b/owlplug-client/src/main/resources/fxml/plugins/PluginInfoView.fxml index 174fe092..d4eb5c28 100644 --- a/owlplug-client/src/main/resources/fxml/plugins/PluginInfoView.fxml +++ b/owlplug-client/src/main/resources/fxml/plugins/PluginInfoView.fxml @@ -137,5 +137,12 @@ + + + diff --git a/owlplug-controls/pom.xml b/owlplug-controls/pom.xml index 88454622..88387828 100644 --- a/owlplug-controls/pom.xml +++ b/owlplug-controls/pom.xml @@ -5,7 +5,7 @@ owlplug com.owlplug - 1.33.0 + 1.33.1 4.0.0 diff --git a/owlplug-host/pom.xml b/owlplug-host/pom.xml index f3448be4..45394ed2 100644 --- a/owlplug-host/pom.xml +++ b/owlplug-host/pom.xml @@ -7,7 +7,7 @@ com.owlplug owlplug - 1.33.0 + 1.33.1 owlplug-host owlplug-host diff --git a/owlplug-host/src/main/java/com/owlplug/host/io/CommandRunner.java b/owlplug-host/src/main/java/com/owlplug/host/io/CommandRunner.java index 573f6bbd..59b621f0 100644 --- a/owlplug-host/src/main/java/com/owlplug/host/io/CommandRunner.java +++ b/owlplug-host/src/main/java/com/owlplug/host/io/CommandRunner.java @@ -57,12 +57,10 @@ public CommandResult run(String... command) throws IOException { Process process = pb.start(); ExecutorService executor = Executors.newFixedThreadPool(1); + Future stdoutFuture = executor.submit(new StreamReader(process.getInputStream())); - Callable stdoutReader = new StreamReader(process.getInputStream()); - Future stdoutFuture = executor.submit(stdoutReader); - - boolean finished; try { + boolean finished; if (timeoutActivated) { finished = process.waitFor(timeout, TimeUnit.MILLISECONDS); } else { @@ -70,27 +68,21 @@ public CommandResult run(String... command) throws IOException { finished = true; } - } catch (InterruptedException e) { - log.error("Interrupted while waiting for process"); - process.destroy(); - throw new IOException("Interrupted while waiting for process", e); - } - - if (!finished) { - log.error("Forcibly destroying process after timeout {}ms exceeded.", timeout); - process.destroyForcibly(); - throw new IOException("Process timeout exceeded: " + timeout + "ms"); - } + if (!finished) { + log.error("Forcibly destroying process after timeout {}ms exceeded.", timeout); + throw new IOException("Process timeout exceeded: " + timeout + "ms"); + } - try { - // Let 1 seconds for gracefully read and complete process String stdout = stdoutFuture.get(1, TimeUnit.SECONDS); return new CommandResult(process.exitValue(), stdout); } catch (InterruptedException e) { - throw new IOException("Interrupted while reading process output", e); + throw new IOException("Process execution interrupted", e); } catch (ExecutionException | TimeoutException e) { throw new IOException("Failed to read process output", e); } finally { + // destroyForcibly closes the process streams, which unblocks any readLine() + // call in the StreamReader thread — safe to call even if the process already exited + process.destroyForcibly(); executor.shutdownNow(); } } diff --git a/owlplug-host/src/main/java/com/owlplug/host/loaders/DummyPluginLoader.java b/owlplug-host/src/main/java/com/owlplug/host/loaders/DummyPluginLoader.java index e75bf0b2..e17fc23b 100644 --- a/owlplug-host/src/main/java/com/owlplug/host/loaders/DummyPluginLoader.java +++ b/owlplug-host/src/main/java/com/owlplug/host/loaders/DummyPluginLoader.java @@ -47,8 +47,8 @@ public void open() { } @Override - public List loadPlugin(String path) { - return null; + public List loadPlugin(String path) throws NativeLoaderException { + return List.of(); } @Override diff --git a/owlplug-host/src/main/java/com/owlplug/host/loaders/EmbeddedScannerPluginLoader.java b/owlplug-host/src/main/java/com/owlplug/host/loaders/EmbeddedScannerPluginLoader.java index 2d3fb555..bf31eec8 100644 --- a/owlplug-host/src/main/java/com/owlplug/host/loaders/EmbeddedScannerPluginLoader.java +++ b/owlplug-host/src/main/java/com/owlplug/host/loaders/EmbeddedScannerPluginLoader.java @@ -58,9 +58,11 @@ public class EmbeddedScannerPluginLoader implements NativePluginLoader { private static final String DEFAULT_SCANNER_ID = DEFAULT_SCANNER_NAME + "-" + DEFAULT_SCANNER_VERSION + "-" + DEFAULT_SCANNER_PLATFORM_TAG + DEFAULT_SCANNER_EXT; + private final String scannerDirectory; + private final String scannerId; private boolean available = false; - private String scannerDirectory; - private String scannerId; + // 10s default timeout for scanner execution + private long timeoutMillis = 10000; public static EmbeddedScannerPluginLoader getInstance() { if (INSTANCE == null) { @@ -112,7 +114,7 @@ public void open() { } @Override - public List loadPlugin(String path) { + public List loadPlugin(String path) throws NativeLoaderException { log.debug("Load plugin {}", path); @@ -123,27 +125,22 @@ public List loadPlugin(String path) { try { CommandRunner commandRunner = new CommandRunner(); commandRunner.setTimeoutActivated(true); - commandRunner.setTimeout(10000); // 10 seconds timeout - CommandResult result = commandRunner.run(scannerDirectory + SEPARATOR + scannerId, path); + commandRunner.setTimeout(timeoutMillis); + CommandResult result = commandRunner.run(scannerDirectory + SEPARATOR + scannerId, path); log.debug("Response received from scanner"); log.debug(result.getOutput()); - if (result.getExitValue() >= 0) { - + if (result.getExitValue() == 0) { log.debug("Extracting XML from content received by the scanner"); - String output = result.getOutput(); - - return createPluginsFromCommandOutput(output); - + return createPluginsFromCommandOutput(result.getOutput()); } else { - log.debug("Invalid return code {} received from plugin scanner", result.getExitValue()); + throw new NativeLoaderException("Scanner exited with code " + result.getExitValue()); } } catch (IOException e) { log.error("Error executing plugin scanner {}", path, e); + throw new NativeLoaderException("Scanner execution failed: " + e.getMessage(), e); } - - return null; } private List createPluginsFromCommandOutput(String output) { @@ -223,6 +220,10 @@ public String getId() { return "owlplug-scanner"; } + public void setTimeout(long timeoutMillis) { + this.timeoutMillis = timeoutMillis; + } + @Override public String toString() { return this.getName(); diff --git a/owlplug-host/src/main/java/com/owlplug/host/loaders/NativeLoaderException.java b/owlplug-host/src/main/java/com/owlplug/host/loaders/NativeLoaderException.java new file mode 100644 index 00000000..a3e96636 --- /dev/null +++ b/owlplug-host/src/main/java/com/owlplug/host/loaders/NativeLoaderException.java @@ -0,0 +1,31 @@ +/* OwlPlug + * Copyright (C) 2021 Arthur + * + * This file is part of OwlPlug. + * + * OwlPlug is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 + * as published by the Free Software Foundation. + * + * OwlPlug is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OwlPlug. If not, see . + */ + +package com.owlplug.host.loaders; + +public class NativeLoaderException extends Exception { + + public NativeLoaderException(String message) { + super(message); + } + + public NativeLoaderException(String message, Throwable cause) { + super(message, cause); + } + +} \ No newline at end of file diff --git a/owlplug-host/src/main/java/com/owlplug/host/loaders/NativePluginLoader.java b/owlplug-host/src/main/java/com/owlplug/host/loaders/NativePluginLoader.java index 3b37ff73..54bcaa0e 100644 --- a/owlplug-host/src/main/java/com/owlplug/host/loaders/NativePluginLoader.java +++ b/owlplug-host/src/main/java/com/owlplug/host/loaders/NativePluginLoader.java @@ -27,7 +27,7 @@ public interface NativePluginLoader { public void open(); - public List loadPlugin(String path); + public List loadPlugin(String path) throws NativeLoaderException; public void close(); diff --git a/owlplug-host/src/main/java/com/owlplug/host/loaders/jni/JNINativePluginLoader.java b/owlplug-host/src/main/java/com/owlplug/host/loaders/jni/JNINativePluginLoader.java index 6ab320a8..fe3934ed 100644 --- a/owlplug-host/src/main/java/com/owlplug/host/loaders/jni/JNINativePluginLoader.java +++ b/owlplug-host/src/main/java/com/owlplug/host/loaders/jni/JNINativePluginLoader.java @@ -19,6 +19,7 @@ package com.owlplug.host.loaders.jni; import com.owlplug.host.NativePlugin; +import com.owlplug.host.loaders.NativeLoaderException; import com.owlplug.host.loaders.NativePluginLoader; import java.util.List; @@ -50,8 +51,12 @@ public void open() { } @Override - public List loadPlugin(String path) { - return nativePluginMapper.mapPlugin(path); + public List loadPlugin(String path) throws NativeLoaderException { + List plugins = nativePluginMapper.mapPlugin(path); + if (plugins == null) { + throw new NativeLoaderException("Scan rejected this plugin, probably incompatible with current platform."); + } + return plugins; } @Override diff --git a/owlplug-parsers/pom.xml b/owlplug-parsers/pom.xml index 4cb0cd39..552605a8 100644 --- a/owlplug-parsers/pom.xml +++ b/owlplug-parsers/pom.xml @@ -5,7 +5,7 @@ owlplug com.owlplug - 1.33.0 + 1.33.1 4.0.0 diff --git a/pom.xml b/pom.xml index be54529e..6cfb1a93 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.owlplug owlplug - 1.33.0 + 1.33.1 pom