diff --git a/build.gradle b/build.gradle index 74800ded..e9c1be33 100644 --- a/build.gradle +++ b/build.gradle @@ -51,7 +51,6 @@ dependencies { implementation 'com.github.DSheirer.jmbe:jmbe-api:1.0.0' //mavenCentral/jcenter imports - implementation 'org.apache.commons:commons-math3:3.6.1' implementation 'ch.qos.logback:logback-core:1.2.3' implementation 'ch.qos.logback:logback-classic:1.2.3' implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.8' @@ -60,6 +59,7 @@ dependencies { implementation 'com.github.jiconfont:jiconfont-javafx:1.0.0' implementation 'com.github.jiconfont:jiconfont-swing:1.0.1' implementation 'com.github.wendykierp:JTransforms:3.1' + implementation 'com.google.code.gson:gson:2.8.6' implementation 'com.google.guava:guava:27.0.1-jre' implementation 'com.jidesoft:jide-oss:3.6.18' implementation 'com.miglayout:miglayout-swing:5.2' @@ -68,18 +68,22 @@ dependencies { implementation 'io.github.dsheirer:radio-reference-api:15.1.4' implementation 'javax.usb:usb-api:1.0.2' implementation 'net.coderazzi:tablefilter-swing:5.4.0' + implementation 'org.apache.commons:commons-compress:1.20' + implementation 'commons-io:commons-io:2.7' implementation 'org.apache.commons:commons-lang3:3.8.1' - implementation 'org.apache.commons:commons-io:1.3.2' + implementation 'org.apache.commons:commons-math3:3.6.1' implementation 'org.apache.mina:mina-core:2.1.3' implementation 'org.apache.mina:mina-http:2.1.3' implementation 'org.controlsfx:controlsfx:11.0.1' + implementation 'org.rauschig:jarchivelib:1.0.0' + implementation 'org.slf4j:slf4j-api:1.7.25' implementation 'org.usb4java:libusb4java:1.3.0' implementation 'org.usb4java:usb4java:1.3.0' implementation 'org.usb4java:usb4java-javax:1.3.0' implementation 'pl.edu.icm:JLargeArrays:1.6' - //The must remain as compile options to ensure they are included in the build to produce multi-OS images + //These must remain as compile options to ensure they are included in the build to produce multi-OS images compile "org.openjfx:javafx-base:13:win" compile "org.openjfx:javafx-base:13:linux" compile "org.openjfx:javafx-base:13:mac" @@ -174,7 +178,8 @@ runtime { } options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages'] - modules = ['java.desktop', 'java.naming', 'jdk.unsupported', 'jdk.unsupported.desktop', 'java.net.http', 'java.sql'] + modules = ['java.desktop', 'java.naming', 'jdk.unsupported', 'jdk.unsupported.desktop', 'java.net.http', + 'java.sql', 'jdk.crypto.ec'] imageZip = hasTargetJdk ? file("$buildDir/image/sdr-trunk.zip") : file("$buildDir/image/sdr-trunk-" + version + ".zip") } diff --git a/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java b/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java index ad0f9e4b..a2b16902 100644 --- a/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java +++ b/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java @@ -38,6 +38,8 @@ import io.github.dsheirer.gui.preference.PreferenceEditorType; import io.github.dsheirer.gui.preference.UserPreferencesEditor; import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest; import io.github.dsheirer.icon.IconModel; +import io.github.dsheirer.jmbe.JmbeEditor; +import io.github.dsheirer.jmbe.JmbeEditorRequest; import io.github.dsheirer.module.log.EventLogManager; import io.github.dsheirer.playlist.PlaylistManager; import io.github.dsheirer.preference.UserPreferences; @@ -68,12 +70,14 @@ public class JavaFxWindowManager extends Application public static final String USER_PREFERENCES_EDITOR = "preferences"; public static final String STAGE_MONITOR_KEY_CHANNEL_MAP_EDITOR = "channel.map"; public static final String STAGE_MONITOR_KEY_ICON_MANAGER_EDITOR = "icon.manager"; + public static final String STAGE_MONITOR_KEY_JMBE_EDITOR = "jmbe.editor"; public static final String STAGE_MONITOR_KEY_PLAYLIST_EDITOR = "playlist"; public static final String STAGE_MONITOR_KEY_USER_PREFERENCES_EDITOR = "user.preferences"; private JFXPanel mJFXPanel; private ChannelMapEditor mChannelMapEditor; private IconManager mIconManager; + private JmbeEditor mJmbeEditor; private PlaylistEditor mPlaylistEditor; private PlaylistManager mPlaylistManager; private UserPreferences mUserPreferences; @@ -81,6 +85,7 @@ public class JavaFxWindowManager extends Application private Stage mChannelMapStage; private Stage mIconManagerStage; + private Stage mJmbeEditorStage; private Stage mPlaylistStage; private Stage mUserPreferencesStage; @@ -205,6 +210,56 @@ public class JavaFxWindowManager extends Application return mIconManager; } + /** + * Processes a JMBE s editor request + */ + @Subscribe + public void process(final JmbeEditorRequest request) + { + if(request.isCloseEditorRequest()) + { + execute(() -> { + getJmbeEditorStage().hide(); + mJmbeEditorStage = null; + mJmbeEditor = null; + }); + } + else + { + execute(() -> { + getJmbeEditorStage().show(); + getJmbeEditorStage().requestFocus(); + getJmbeEditorStage().toFront(); + getJmbeEditor().process(request); + }); + } + } + + public Stage getJmbeEditorStage() + { + if(mJmbeEditorStage == null) + { + createJFXPanel(); + Scene scene = new Scene(getJmbeEditor(), 650, 650); + mJmbeEditorStage = new Stage(); + mJmbeEditorStage.setTitle("sdrtrunk - JMBE Library Updater"); + mJmbeEditorStage.setScene(scene); + mUserPreferences.getJavaFxPreferences().monitor(mJmbeEditorStage, STAGE_MONITOR_KEY_JMBE_EDITOR); + } + + return mJmbeEditorStage; + } + + public JmbeEditor getJmbeEditor() + { + if(mJmbeEditor == null) + { + mJmbeEditor = new JmbeEditor(mUserPreferences); + } + + return mJmbeEditor; + } + /** * Lazy construct and access the playlist editor */ diff --git a/src/main/java/io/github/dsheirer/gui/playlist/PlaylistEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/PlaylistEditor.java index 0c84705c..67b501c6 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/PlaylistEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/PlaylistEditor.java @@ -243,7 +243,7 @@ public class PlaylistEditor extends BorderPane { if(mChannelEditor == null) { - mChannelEditor = new ChannelEditor(mPlaylistManager); + mChannelEditor = new ChannelEditor(mPlaylistManager, mUserPreferences); } return mChannelEditor; diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/AMConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/AMConfigurationEditor.java index 7a596ce0..c224590b 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/AMConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/AMConfigurationEditor.java @@ -30,6 +30,7 @@ import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration; import io.github.dsheirer.module.decode.config.DecodeConfiguration; import io.github.dsheirer.module.log.config.EventLogConfiguration; import io.github.dsheirer.playlist.PlaylistManager; +import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.record.RecorderType; import io.github.dsheirer.record.config.RecordConfiguration; import io.github.dsheirer.source.config.SourceConfiguration; @@ -60,9 +61,9 @@ public class AMConfigurationEditor extends ChannelConfigurationEditor * Constructs an instance * @param playlistManager */ - public AMConfigurationEditor(PlaylistManager playlistManager) + public AMConfigurationEditor(PlaylistManager playlistManager, UserPreferences userPreferences) { - super(playlistManager); + super(playlistManager, userPreferences); getTitledPanesBox().getChildren().add(getSourcePane()); getTitledPanesBox().getChildren().add(getDecoderPane()); getTitledPanesBox().getChildren().add(getRecordPane()); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelConfigurationEditor.java index 0ef6a417..74290349 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelConfigurationEditor.java @@ -25,13 +25,17 @@ package io.github.dsheirer.gui.playlist.channel; import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.controller.channel.Channel; import io.github.dsheirer.controller.channel.ChannelException; +import io.github.dsheirer.eventbus.MyEventBus; import io.github.dsheirer.gui.control.MaxLengthUnaryOperator; import io.github.dsheirer.gui.playlist.Editor; +import io.github.dsheirer.gui.preference.PreferenceEditorType; +import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration; import io.github.dsheirer.module.decode.config.DecodeConfiguration; import io.github.dsheirer.module.log.config.EventLogConfiguration; import io.github.dsheirer.playlist.PlaylistManager; +import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.record.config.RecordConfiguration; import io.github.dsheirer.source.config.SourceConfigTuner; import io.github.dsheirer.source.config.SourceConfiguration; @@ -48,6 +52,7 @@ import javafx.geometry.Insets; import javafx.scene.control.Alert; import javafx.scene.control.Button; import javafx.scene.control.ButtonType; +import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; @@ -78,6 +83,7 @@ public abstract class ChannelConfigurationEditor extends Editor private final static Logger mLog = LoggerFactory.getLogger(ChannelConfigurationEditor.class); private PlaylistManager mPlaylistManager; + private UserPreferences mUserPreferences; protected EditorModificationListener mEditorModificationListener = new EditorModificationListener(); private Button mPlayButton; private TextField mSystemField; @@ -97,9 +103,10 @@ public abstract class ChannelConfigurationEditor extends Editor private IconNode mStopGraphicNode; private ChannelProcessingMonitor mChannelProcessingMonitor = new ChannelProcessingMonitor(); - public ChannelConfigurationEditor(PlaylistManager playlistManager) + public ChannelConfigurationEditor(PlaylistManager playlistManager, UserPreferences userPreferences) { mPlaylistManager = playlistManager; + mUserPreferences = userPreferences; setMaxWidth(Double.MAX_VALUE); @@ -317,6 +324,45 @@ public abstract class ChannelConfigurationEditor extends Editor }); } + if(requiresJmbeLibrarySetup() && + mUserPreferences.getJmbeLibraryPreference().getAlertIfMissingLibraryRequired() && + !getItem().processingProperty().get()) + { + String content = "The decoder for this channel configuration requires the (optional) JMBE " + + "library to produce audio and the JMBE library is not currently setup. Do you want to " + + "setup the JMBE library?"; + + Alert alert = new Alert(Alert.AlertType.CONFIRMATION, content, ButtonType.YES, ButtonType.NO); + alert.setTitle("JMBE Library"); + alert.setHeaderText("Setup JMBE Library?"); + + Label label = new Label(content); + label.setMaxWidth(Double.MAX_VALUE); + label.setMaxHeight(Double.MAX_VALUE); + label.getStyleClass().add("content"); + label.setWrapText(true); + + CheckBox checkBox = new CheckBox("Don't Show This Again"); + checkBox.setOnAction(event1 -> { + boolean dontShowAgain = checkBox.isSelected(); + mUserPreferences.getJmbeLibraryPreference().setAlertIfMissingLibraryRequired(!dontShowAgain); + }); + + VBox contentBox = new VBox(); + contentBox.setPrefWidth(360); + contentBox.setSpacing(10); + contentBox.getChildren().addAll(label, checkBox); + alert.getDialogPane().setContent(contentBox); + alert.initOwner(getPlayButton().getScene().getWindow()); + Optional optionalButtonType = alert.showAndWait(); + + if(optionalButtonType.isPresent() && optionalButtonType.get() == ButtonType.YES) + { + MyEventBus.getEventBus().post(new ViewUserPreferenceEditorRequest(PreferenceEditorType.JMBE_LIBRARY)); + return; + } + } + if(!getItem().processingProperty().get()) { ThreadPool.SCHEDULED.execute(() -> { @@ -366,6 +412,17 @@ public abstract class ChannelConfigurationEditor extends Editor return mPlayButton; } + /** + * Indicates if the decoder type for the channel configuration requires the JMBE library and if the + * application is not currently setup for the JMBE library. + */ + private boolean requiresJmbeLibrarySetup() + { + return getItem() != null && + getItem().getDecodeConfiguration().getDecoderType().providesMBEAudioFrames() && + !mUserPreferences.getJmbeLibraryPreference().hasJmbeLibraryPath(); + } + /** * Toggles the text and graphic of the channel play button to reflect the playing state of the channel */ diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelConfigurationEditorFactory.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelConfigurationEditorFactory.java index ac8f38f7..8f7a733d 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelConfigurationEditorFactory.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelConfigurationEditorFactory.java @@ -24,6 +24,7 @@ package io.github.dsheirer.gui.playlist.channel; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.playlist.PlaylistManager; +import io.github.dsheirer.preference.UserPreferences; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,26 +45,27 @@ public class ChannelConfigurationEditorFactory * @param playlistManager for the editor * @return constructed editor */ - public static ChannelConfigurationEditor getEditor(DecoderType decoderType, PlaylistManager playlistManager) + public static ChannelConfigurationEditor getEditor(DecoderType decoderType, PlaylistManager playlistManager, + UserPreferences userPreferences) { switch(decoderType) { case AM: - return new AMConfigurationEditor(playlistManager); + return new AMConfigurationEditor(playlistManager, userPreferences); case NBFM: - return new NBFMConfigurationEditor(playlistManager); + return new NBFMConfigurationEditor(playlistManager, userPreferences); case LTR_NET: - return new LTRNetConfigurationEditor(playlistManager); + return new LTRNetConfigurationEditor(playlistManager, userPreferences); case LTR: - return new LTRConfigurationEditor(playlistManager); + return new LTRConfigurationEditor(playlistManager, userPreferences); case MPT1327: - return new MPT1327ConfigurationEditor(playlistManager); + return new MPT1327ConfigurationEditor(playlistManager, userPreferences); case PASSPORT: - return new PassportConfigurationEditor(playlistManager); + return new PassportConfigurationEditor(playlistManager, userPreferences); case P25_PHASE1: - return new P25P1ConfigurationEditor(playlistManager); + return new P25P1ConfigurationEditor(playlistManager, userPreferences); case P25_PHASE2: - return new P25P2ConfigurationEditor(playlistManager); + return new P25P2ConfigurationEditor(playlistManager, userPreferences); default: if(decoderType != null && !mLoggedUnrecognizedTypes.contains(decoderType)) { diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelEditor.java index 56a79650..0d084fe2 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelEditor.java @@ -27,6 +27,7 @@ import io.github.dsheirer.controller.channel.Channel; import io.github.dsheirer.module.decode.DecoderFactory; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.playlist.PlaylistManager; +import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.source.config.SourceConfigTuner; import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency; import javafx.application.Platform; @@ -79,6 +80,7 @@ public class ChannelEditor extends SplitPane { private final static Logger mLog = LoggerFactory.getLogger(ChannelEditor.class); private PlaylistManager mPlaylistManager; + private UserPreferences mUserPreferences; private TableView mChannelTableView; private Label mPlaceholderLabel; private MenuButton mNewButton; @@ -101,10 +103,11 @@ public class ChannelEditor extends SplitPane * Constructs an instance * @param playlistManager containing playlists and channel configurations */ - public ChannelEditor(PlaylistManager playlistManager) + public ChannelEditor(PlaylistManager playlistManager, UserPreferences userPreferences) { mPlaylistManager = playlistManager; - mUnknownConfigurationEditor = new UnknownConfigurationEditor(mPlaylistManager); + mUserPreferences = userPreferences; + mUnknownConfigurationEditor = new UnknownConfigurationEditor(mPlaylistManager, userPreferences); HBox channelsBox = new HBox(); channelsBox.setSpacing(10.0); @@ -200,7 +203,8 @@ public class ChannelEditor extends SplitPane if(editor == null) { - editor = ChannelConfigurationEditorFactory.getEditor(channelDecoderType, mPlaylistManager); + editor = ChannelConfigurationEditorFactory.getEditor(channelDecoderType, mPlaylistManager, + mUserPreferences); if(editor != null) { diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/LTRConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/LTRConfigurationEditor.java index 57054ff8..83096779 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/LTRConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/LTRConfigurationEditor.java @@ -35,6 +35,7 @@ import io.github.dsheirer.module.decode.ltrstandard.DecodeConfigLTRStandard; import io.github.dsheirer.module.log.EventLogType; import io.github.dsheirer.module.log.config.EventLogConfiguration; import io.github.dsheirer.playlist.PlaylistManager; +import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.record.RecorderType; import io.github.dsheirer.record.config.RecordConfiguration; import io.github.dsheirer.source.config.SourceConfiguration; @@ -74,10 +75,11 @@ public class LTRConfigurationEditor extends ChannelConfigurationEditor /** * Constructs an instance * @param playlistManager + * @param userPreferences */ - public LTRConfigurationEditor(PlaylistManager playlistManager) + public LTRConfigurationEditor(PlaylistManager playlistManager, UserPreferences userPreferences) { - super(playlistManager); + super(playlistManager, userPreferences); getTitledPanesBox().getChildren().add(getSourcePane()); getTitledPanesBox().getChildren().add(getDecoderPane()); getTitledPanesBox().getChildren().add(getAuxDecoderPane()); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/LTRNetConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/LTRNetConfigurationEditor.java index 72ef6936..24ed1119 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/LTRNetConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/LTRNetConfigurationEditor.java @@ -35,6 +35,7 @@ import io.github.dsheirer.module.decode.ltrnet.DecodeConfigLTRNet; import io.github.dsheirer.module.log.EventLogType; import io.github.dsheirer.module.log.config.EventLogConfiguration; import io.github.dsheirer.playlist.PlaylistManager; +import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.record.RecorderType; import io.github.dsheirer.record.config.RecordConfiguration; import io.github.dsheirer.source.config.SourceConfiguration; @@ -74,10 +75,11 @@ public class LTRNetConfigurationEditor extends ChannelConfigurationEditor /** * Constructs an instance * @param playlistManager + * @param userPreferences */ - public LTRNetConfigurationEditor(PlaylistManager playlistManager) + public LTRNetConfigurationEditor(PlaylistManager playlistManager, UserPreferences userPreferences) { - super(playlistManager); + super(playlistManager, userPreferences); getTitledPanesBox().getChildren().add(getSourcePane()); getTitledPanesBox().getChildren().add(getDecoderPane()); getTitledPanesBox().getChildren().add(getAuxDecoderPane()); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/MPT1327ConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/MPT1327ConfigurationEditor.java index 275921be..2fbd8a33 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/MPT1327ConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/MPT1327ConfigurationEditor.java @@ -36,6 +36,7 @@ import io.github.dsheirer.module.decode.mpt1327.DecodeConfigMPT1327; import io.github.dsheirer.module.log.EventLogType; import io.github.dsheirer.module.log.config.EventLogConfiguration; import io.github.dsheirer.playlist.PlaylistManager; +import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.record.RecorderType; import io.github.dsheirer.record.config.RecordConfiguration; import io.github.dsheirer.source.config.SourceConfiguration; @@ -84,10 +85,11 @@ public class MPT1327ConfigurationEditor extends ChannelConfigurationEditor /** * Constructs an instance * @param playlistManager + * @param userPreferences */ - public MPT1327ConfigurationEditor(PlaylistManager playlistManager) + public MPT1327ConfigurationEditor(PlaylistManager playlistManager, UserPreferences userPreferences) { - super(playlistManager); + super(playlistManager, userPreferences); getTitledPanesBox().getChildren().add(getSourcePane()); getTitledPanesBox().getChildren().add(getDecoderPane()); getTitledPanesBox().getChildren().add(getEventLogPane()); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/NBFMConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/NBFMConfigurationEditor.java index 564f885a..a8a73d40 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/NBFMConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/NBFMConfigurationEditor.java @@ -33,6 +33,7 @@ import io.github.dsheirer.module.decode.nbfm.DecodeConfigNBFM; import io.github.dsheirer.module.log.EventLogType; import io.github.dsheirer.module.log.config.EventLogConfiguration; import io.github.dsheirer.playlist.PlaylistManager; +import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.record.RecorderType; import io.github.dsheirer.record.config.RecordConfiguration; import io.github.dsheirer.source.config.SourceConfiguration; @@ -73,10 +74,11 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor /** * Constructs an instance * @param playlistManager + * @param userPreferences */ - public NBFMConfigurationEditor(PlaylistManager playlistManager) + public NBFMConfigurationEditor(PlaylistManager playlistManager, UserPreferences userPreferences) { - super(playlistManager); + super(playlistManager, userPreferences); getTitledPanesBox().getChildren().add(getSourcePane()); getTitledPanesBox().getChildren().add(getDecoderPane()); getTitledPanesBox().getChildren().add(getAuxDecoderPane()); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P1ConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P1ConfigurationEditor.java index 8182ebe9..a4c5ecee 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P1ConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P1ConfigurationEditor.java @@ -34,6 +34,7 @@ import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder; import io.github.dsheirer.module.log.EventLogType; import io.github.dsheirer.module.log.config.EventLogConfiguration; import io.github.dsheirer.playlist.PlaylistManager; +import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.record.RecorderType; import io.github.dsheirer.record.config.RecordConfiguration; import io.github.dsheirer.source.config.SourceConfiguration; @@ -80,10 +81,11 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor /** * Constructs an instance * @param playlistManager + * @param userPreferences */ - public P25P1ConfigurationEditor(PlaylistManager playlistManager) + public P25P1ConfigurationEditor(PlaylistManager playlistManager, UserPreferences userPreferences) { - super(playlistManager); + super(playlistManager, userPreferences); getTitledPanesBox().getChildren().add(getSourcePane()); getTitledPanesBox().getChildren().add(getDecoderPane()); getTitledPanesBox().getChildren().add(getEventLogPane()); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P2ConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P2ConfigurationEditor.java index b7cb2a0d..722649e3 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P2ConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P2ConfigurationEditor.java @@ -35,6 +35,7 @@ import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameter import io.github.dsheirer.module.log.EventLogType; import io.github.dsheirer.module.log.config.EventLogConfiguration; import io.github.dsheirer.playlist.PlaylistManager; +import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.record.RecorderType; import io.github.dsheirer.record.config.RecordConfiguration; import io.github.dsheirer.source.config.SourceConfiguration; @@ -70,10 +71,11 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor /** * Constructs an instance * @param playlistManager + * @param userPreferences */ - public P25P2ConfigurationEditor(PlaylistManager playlistManager) + public P25P2ConfigurationEditor(PlaylistManager playlistManager, UserPreferences userPreferences) { - super(playlistManager); + super(playlistManager, userPreferences); getTitledPanesBox().getChildren().add(getSourcePane()); getTitledPanesBox().getChildren().add(getDecoderPane()); getTitledPanesBox().getChildren().add(getEventLogPane()); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/PassportConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/PassportConfigurationEditor.java index 93ac3720..c0766fce 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/PassportConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/PassportConfigurationEditor.java @@ -34,6 +34,7 @@ import io.github.dsheirer.module.decode.passport.DecodeConfigPassport; import io.github.dsheirer.module.log.EventLogType; import io.github.dsheirer.module.log.config.EventLogConfiguration; import io.github.dsheirer.playlist.PlaylistManager; +import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.record.RecorderType; import io.github.dsheirer.record.config.RecordConfiguration; import io.github.dsheirer.source.config.SourceConfiguration; @@ -66,10 +67,11 @@ public class PassportConfigurationEditor extends ChannelConfigurationEditor /** * Constructs an instance * @param playlistManager + * @param userPreferences */ - public PassportConfigurationEditor(PlaylistManager playlistManager) + public PassportConfigurationEditor(PlaylistManager playlistManager, UserPreferences userPreferences) { - super(playlistManager); + super(playlistManager, userPreferences); getTitledPanesBox().getChildren().add(getSourcePane()); getTitledPanesBox().getChildren().add(getDecoderPane()); getTitledPanesBox().getChildren().add(getAuxDecoderPane()); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/UnknownConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/UnknownConfigurationEditor.java index ba046293..0ae1d0d8 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/UnknownConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/UnknownConfigurationEditor.java @@ -27,6 +27,7 @@ import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration; import io.github.dsheirer.module.decode.config.DecodeConfiguration; import io.github.dsheirer.module.log.config.EventLogConfiguration; import io.github.dsheirer.playlist.PlaylistManager; +import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.record.config.RecordConfiguration; import io.github.dsheirer.source.config.SourceConfiguration; import javafx.scene.control.TitledPane; @@ -45,9 +46,9 @@ public class UnknownConfigurationEditor extends ChannelConfigurationEditor * Constructs an instance * @param aliasModel */ - public UnknownConfigurationEditor(PlaylistManager playlistManager) + public UnknownConfigurationEditor(PlaylistManager playlistManager, UserPreferences userPreferences) { - super(playlistManager); + super(playlistManager, userPreferences); getTitledPanesBox().getChildren().add(getDecoderPane()); } diff --git a/src/main/java/io/github/dsheirer/gui/preference/decoder/JmbeLibraryPreferenceEditor.java b/src/main/java/io/github/dsheirer/gui/preference/decoder/JmbeLibraryPreferenceEditor.java index 69d98a98..e3b41ed9 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/decoder/JmbeLibraryPreferenceEditor.java +++ b/src/main/java/io/github/dsheirer/gui/preference/decoder/JmbeLibraryPreferenceEditor.java @@ -22,17 +22,28 @@ package io.github.dsheirer.gui.preference.decoder; import com.google.common.eventbus.Subscribe; import io.github.dsheirer.eventbus.MyEventBus; +import io.github.dsheirer.jmbe.JmbeCreator; +import io.github.dsheirer.jmbe.JmbeEditorRequest; +import io.github.dsheirer.jmbe.github.GitHub; +import io.github.dsheirer.jmbe.github.Release; +import io.github.dsheirer.jmbe.github.Version; import io.github.dsheirer.preference.PreferenceType; import io.github.dsheirer.preference.UserPreferences; -import io.github.dsheirer.preference.decoder.JmbeLibraryPreference; +import io.github.dsheirer.util.ThreadPool; +import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.event.EventHandler; +import javafx.geometry.HPos; import javafx.geometry.Insets; +import javafx.scene.control.Alert; import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.CheckBox; import javafx.scene.control.Label; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; import javafx.stage.FileChooser; import javafx.stage.Stage; import org.slf4j.Logger; @@ -45,27 +56,64 @@ import java.nio.file.Path; /** * Preference settings for decoders */ -public class JmbeLibraryPreferenceEditor extends HBox +public class JmbeLibraryPreferenceEditor extends VBox { private final static Logger mLog = LoggerFactory.getLogger(JmbeLibraryPreferenceEditor.class); private static final String PATH_NOT_SET = "(not set)"; - private JmbeLibraryPreference mJmbeLibraryPreference; + private static final String CREATE_LIBRARY = "Create Library"; + private static final String CHECK_FOR_UPDATE = "Check For Library Update"; + private static final String LIBRARY_NOT_SETUP = "None. Click Select or Create Library button to setup."; + + private UserPreferences mUserPreferences; private GridPane mEditorPane; private Label mJmbeLibraryLabel; - private Button mPathToJmbeLibraryButton; - private Button mPathToJmbeLibraryResetButton; + private Label mJmbeVersionLabel; private Label mPathToJmbeLibraryLabel; + private Button mSelectButton; + private Button mResetButton; + private Button mCreateButton; + private HBox mButtonsBox; + private CheckBox mAlertUserWhenMissingCheckBox; public JmbeLibraryPreferenceEditor(UserPreferences userPreferences) { - mJmbeLibraryPreference = userPreferences.getJmbeLibraryPreference(); + mUserPreferences = userPreferences; //Register to receive directory preference update notifications so we can update the path labels MyEventBus.getEventBus().register(this); - HBox.setHgrow(getEditorPane(), Priority.ALWAYS); - getChildren().add(getEditorPane()); + setPadding(new Insets(10,10,10,10)); + setSpacing(10); + getChildren().addAll(getEditorPane(), getButtonsBox(), getAlertUserWhenMissingCheckBox()); + } + + private CheckBox getAlertUserWhenMissingCheckBox() + { + if(mAlertUserWhenMissingCheckBox == null) + { + mAlertUserWhenMissingCheckBox = new CheckBox("Alert when decoder requires missing JMBE library"); + mAlertUserWhenMissingCheckBox.setSelected(mUserPreferences.getJmbeLibraryPreference() + .getAlertIfMissingLibraryRequired()); + mAlertUserWhenMissingCheckBox.setOnAction(event -> { + boolean alert = mAlertUserWhenMissingCheckBox.isSelected(); + mUserPreferences.getJmbeLibraryPreference().setAlertIfMissingLibraryRequired(alert); + }); + } + + return mAlertUserWhenMissingCheckBox; + } + + private HBox getButtonsBox() + { + if(mButtonsBox == null) + { + mButtonsBox = new HBox(); + mButtonsBox.setSpacing(10); + mButtonsBox.getChildren().addAll(getCreateButton(), getSelectButton(), getResetButton()); + } + + return mButtonsBox; } private GridPane getEditorPane() @@ -73,85 +121,206 @@ public class JmbeLibraryPreferenceEditor extends HBox if(mEditorPane == null) { mEditorPane = new GridPane(); - mEditorPane.setPadding(new Insets(10, 10, 10, 10)); + mEditorPane.setVgap(10); + mEditorPane.setHgap(10); int row = 0; - GridPane.setMargin(getJmbeLibraryLabel(), new Insets(0, 10, 0, 0)); - mEditorPane.add(getJmbeLibraryLabel(), 0, row); + mEditorPane.add(getJmbeLibraryLabel(), 0, row, 2, 1); + + Label versionLabel = new Label("Current Version:"); + GridPane.setHalignment(versionLabel, HPos.RIGHT); + mEditorPane.add(versionLabel, 0, ++row); + + mEditorPane.add(getJmbeVersionLabel(), 1, row); + + Label fileLabel = new Label("File:"); + GridPane.setHalignment(fileLabel, HPos.RIGHT); + mEditorPane.add(fileLabel, 0, ++row); - GridPane.setMargin(getPathToJmbeLibraryLabel(), new Insets(0, 10, 0, 0)); mEditorPane.add(getPathToJmbeLibraryLabel(), 1, row); - - GridPane.setMargin(getPathToJmbeLibraryButton(), new Insets(2, 10, 2, 0)); - mEditorPane.add(getPathToJmbeLibraryButton(), 2, row); - - GridPane.setMargin(getPathToJmbeLibraryResetButton(), new Insets(2, 0, 2, 0)); - mEditorPane.add(getPathToJmbeLibraryResetButton(), 3, row++); } return mEditorPane; } + private Button getCreateButton() + { + if(mCreateButton == null) + { + mCreateButton = new Button(); + mCreateButton.setText(mUserPreferences.getJmbeLibraryPreference() + .getPathJmbeLibrary() != null ? CHECK_FOR_UPDATE : CREATE_LIBRARY); + mCreateButton.setOnAction(event -> checkForUpdatedLibrary()); + } + + return mCreateButton; + } + + /** + * Checks for availability of an updated JMBE library + */ + private void checkForUpdatedLibrary() + { + ThreadPool.SCHEDULED.execute(() -> { + try + { + Version current = mUserPreferences.getJmbeLibraryPreference().getCurrentVersion(); + final Release release = GitHub.getLatestRelease(JmbeCreator.GITHUB_JMBE_RELEASES_URL); + + mLog.info("Checking for JMBE Library Updates ..."); + mLog.info("Current: " + (current != null ? current.toString() : "empty")); + + final boolean canUpdate = (release != null) && ((current == null) || + (release.getVersion().compareTo(current) > 0)); + + if(release != null) + { + mLog.info("Available: " + release.getVersion().toString()); + } + + if(canUpdate) + { + mLog.info("JMBE Library update is available"); + } + else + { + mLog.info("No JMBE library update is available at this time"); + } + + Platform.runLater(new Runnable() + { + @Override + public void run() + { + try + { + if(canUpdate) + { + String content = "JMBE library version " + release.getVersion().toString() + + " is available. Would you like to download the latest source code and " + + "create an updated JMBE library?"; + Alert alert = new Alert(Alert.AlertType.INFORMATION, content, ButtonType.YES, ButtonType.NO); + alert.setResizable(true); + + //Workaroud for dialog sizing issue on Windows + alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); + + alert.setTitle("JMBE Library Update Check"); + alert.setHeaderText("Update is available"); + alert.initOwner(getCreateButton().getScene().getWindow()); + alert.showAndWait().ifPresent(buttonType -> { + if(buttonType == ButtonType.YES) + { + mLog.info("Posting JMBE editor request to event bus"); + MyEventBus.getEventBus().post(new JmbeEditorRequest(release)); + } + }); + } + else + { + Alert alert = new Alert(Alert.AlertType.INFORMATION, + "No JMBE library update is available.", ButtonType.OK); + alert.setTitle("JMBE Library Update Check"); + if(release != null) + { + alert.setHeaderText("No update available"); + } + else + { + alert.setHeaderText("Unable to determine latest release version. Check network connection"); + } + alert.initOwner(getCreateButton().getScene().getWindow()); + alert.showAndWait(); + } + } + catch(Throwable t) + { + mLog.error("Error during JavaFX portion of create library"); + } + } + }); + } + catch(Throwable t) + { + mLog.error("Error during create library", t); + } + }); + } + private Label getJmbeLibraryLabel() { if(mJmbeLibraryLabel == null) { - mJmbeLibraryLabel = new Label("JMBE Audio Library:"); + mJmbeLibraryLabel = new Label("JMBE Audio Library"); } return mJmbeLibraryLabel; } - private Button getPathToJmbeLibraryButton() + private Label getJmbeVersionLabel() { - if(mPathToJmbeLibraryButton == null) + if(mJmbeVersionLabel == null) { - mPathToJmbeLibraryButton = new Button("Change..."); - mPathToJmbeLibraryButton.setOnAction(new EventHandler() + mJmbeVersionLabel = new Label(); + Version version = mUserPreferences.getJmbeLibraryPreference().getCurrentVersion(); + + if(version != null) + { + mJmbeVersionLabel.setText(version.toString()); + } + else + { + mJmbeVersionLabel.setText(LIBRARY_NOT_SETUP); + } + } + + return mJmbeVersionLabel; + } + + private Button getSelectButton() + { + if(mSelectButton == null) + { + mSelectButton = new Button("Select..."); + mSelectButton.setOnAction(new EventHandler() { @Override public void handle(ActionEvent event) { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Select JMBE Audio Library Location"); - Stage stage = (Stage)getPathToJmbeLibraryButton().getScene().getWindow(); + fileChooser.setInitialDirectory(mUserPreferences.getDirectoryPreference().getDefaultJmbeDirectory().toFile()); + Stage stage = (Stage)getSelectButton().getScene().getWindow(); File selected = fileChooser.showOpenDialog(stage); if(selected != null) { - mJmbeLibraryPreference.setPathJmbeLibrary(selected.toPath()); + mUserPreferences.getJmbeLibraryPreference().setPathJmbeLibrary(selected.toPath()); } } }); } - return mPathToJmbeLibraryButton; + return mSelectButton; } - private Button getPathToJmbeLibraryResetButton() + private Button getResetButton() { - if(mPathToJmbeLibraryResetButton == null) + if(mResetButton == null) { - mPathToJmbeLibraryResetButton = new Button("Reset"); - mPathToJmbeLibraryResetButton.setOnAction(new EventHandler() - { - @Override - public void handle(ActionEvent event) - { - mJmbeLibraryPreference.resetPathJmbeLibrary(); - } - }); + mResetButton = new Button("Reset"); + mResetButton.setOnAction(event -> mUserPreferences.getJmbeLibraryPreference().resetPathJmbeLibrary()); } - return mPathToJmbeLibraryResetButton; + return mResetButton; } private Label getPathToJmbeLibraryLabel() { if(mPathToJmbeLibraryLabel == null) { - Path path = mJmbeLibraryPreference.getPathJmbeLibrary(); + Path path = mUserPreferences.getJmbeLibraryPreference().getPathJmbeLibrary(); mPathToJmbeLibraryLabel = new Label(path != null ? path.toString() : PATH_NOT_SET); } @@ -163,8 +332,14 @@ public class JmbeLibraryPreferenceEditor extends HBox { if(preferenceType != null && preferenceType == PreferenceType.JMBE_LIBRARY) { - Path path = mJmbeLibraryPreference.getPathJmbeLibrary(); + Path path = mUserPreferences.getJmbeLibraryPreference().getPathJmbeLibrary(); getPathToJmbeLibraryLabel().setText(path != null ? path.toString() : PATH_NOT_SET); + Version version = mUserPreferences.getJmbeLibraryPreference().getCurrentVersion(); + getJmbeVersionLabel().setText(version != null ? version.toString() : LIBRARY_NOT_SETUP); + getCreateButton().setText(mUserPreferences.getJmbeLibraryPreference() + .getPathJmbeLibrary() != null ? CHECK_FOR_UPDATE : CREATE_LIBRARY); + getAlertUserWhenMissingCheckBox().setSelected(mUserPreferences.getJmbeLibraryPreference() + .getAlertIfMissingLibraryRequired()); } } } diff --git a/src/main/java/io/github/dsheirer/gui/preference/directory/DirectoryPreferenceEditor.java b/src/main/java/io/github/dsheirer/gui/preference/directory/DirectoryPreferenceEditor.java index 8c9794ec..60973547 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/directory/DirectoryPreferenceEditor.java +++ b/src/main/java/io/github/dsheirer/gui/preference/directory/DirectoryPreferenceEditor.java @@ -68,6 +68,11 @@ public class DirectoryPreferenceEditor extends HBox private Button mResetEventLogsButton; private Label mEventLogsPathLabel; + private Label mJmbeLabel; + private Button mChangeJmbeButton; + private Button mResetJmbeButton; + private Label mJmbePathLabel; + private Label mPlaylistLabel; private Button mChangePlaylistButton; private Button mResetPlaylistButton; @@ -157,6 +162,19 @@ public class DirectoryPreferenceEditor extends HBox mEditorPane.add(getResetEventLogsButton(), 3, row++); + GridPane.setMargin(getJmbeLabel(), new Insets(0, 10, 0, 0)); + mEditorPane.add(getJmbeLabel(), 0, row); + + GridPane.setMargin(getJmbePathLabel(), new Insets(0, 10, 0, 0)); + mEditorPane.add(getJmbePathLabel(), 1, row); + + GridPane.setMargin(getChangeJmbeButton(), new Insets(2, 10, 2, 0)); + mEditorPane.add(getChangeJmbeButton(), 2, row); + + GridPane.setMargin(getResetJmbeButton(), new Insets(2, 0, 2, 0)); + mEditorPane.add(getResetJmbeButton(), 3, row++); + + GridPane.setMargin(getPlaylistLabel(), new Insets(0, 10, 0, 0)); mEditorPane.add(getPlaylistLabel(), 0, row); @@ -407,6 +425,71 @@ public class DirectoryPreferenceEditor extends HBox return mEventLogsPathLabel; } + private Label getJmbeLabel() + { + if(mJmbeLabel == null) + { + mJmbeLabel = new Label("JMBE Libraries"); + } + + return mJmbeLabel; + } + + private Button getChangeJmbeButton() + { + if(mChangeJmbeButton == null) + { + mChangeJmbeButton = new Button("Change..."); + mChangeJmbeButton.setOnAction(new EventHandler() + { + @Override + public void handle(ActionEvent event) + { + DirectoryChooser directoryChooser = new DirectoryChooser(); + directoryChooser.setTitle("Select JMBE Folder"); + directoryChooser.setInitialDirectory(mDirectoryPreference.getDirectoryJmbe().toFile()); + Stage stage = (Stage)getChangeJmbeButton().getScene().getWindow(); + File selected = directoryChooser.showDialog(stage); + + if(selected != null) + { + mDirectoryPreference.setDirectoryJmbe(selected.toPath()); + } + } + }); + } + + return mChangeJmbeButton; + } + + private Button getResetJmbeButton() + { + if(mResetJmbeButton == null) + { + mResetJmbeButton = new Button("Reset"); + mResetJmbeButton.setOnAction(new EventHandler() + { + @Override + public void handle(ActionEvent event) + { + mDirectoryPreference.resetDirectoryJmbe(); + } + }); + } + + return mResetJmbeButton; + } + + private Label getJmbePathLabel() + { + if(mJmbePathLabel == null) + { + mJmbePathLabel = new Label(mDirectoryPreference.getDirectoryJmbe().toString()); + } + + return mJmbePathLabel; + } + private Label getPlaylistLabel() { if(mPlaylistLabel == null) diff --git a/src/main/java/io/github/dsheirer/jmbe/JmbeCreator.java b/src/main/java/io/github/dsheirer/jmbe/JmbeCreator.java new file mode 100644 index 00000000..994731c4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/jmbe/JmbeCreator.java @@ -0,0 +1,323 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.jmbe; + +import io.github.dsheirer.jmbe.github.Asset; +import io.github.dsheirer.jmbe.github.GitHub; +import io.github.dsheirer.jmbe.github.Release; +import io.github.dsheirer.util.FileUtil; +import io.github.dsheirer.util.OSType; +import io.github.dsheirer.util.ThreadPool; +import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import org.apache.commons.io.FileUtils; +import org.rauschig.jarchivelib.ArchiveFormat; +import org.rauschig.jarchivelib.Archiver; +import org.rauschig.jarchivelib.ArchiverFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Creates the JMBE library by downloading the JMBE Creator CLI application from GitHub and executing it. + */ +public class JmbeCreator +{ + private final static Logger mLog = LoggerFactory.getLogger(JmbeCreator.class); + public static final String GITHUB_JMBE_RELEASES_URL = "https://api.github.com/repos/dsheirer/jmbe/releases"; + public static final String CREATOR_SCRIPT_LINUX = "creator"; + public static final String CREATOR_SCRIPT_WINDOWS = "creator.bat"; + + private StringProperty mConsoleOutput = new SimpleStringProperty(); + private BooleanProperty mCompleteProperty = new SimpleBooleanProperty(); + private boolean mHasErrors = false; + private Release mRelease; + private Path mLibraryPath; + private StringBuilder mConsoleStringBuilder = new StringBuilder(); + + /** + * Creates an instance. + * @param release for the version to create + * @param library path where the library should be created + */ + public JmbeCreator(Release release, Path library) + { + mRelease = release; + mLibraryPath = library; + } + + /** + * Path to where the library should be created + */ + public Path getLibraryPath() + { + return mLibraryPath; + } + + /** + * Property that indicates if the creation process has finished. + */ + public BooleanProperty completeProperty() + { + return mCompleteProperty; + } + + /** + * Console output from the build process. + */ + public StringProperty consoleOutputProperty() + { + return mConsoleOutput; + } + + /** + * Prints (adds) the message text to the console property + * @param message to add + */ + private void printToConsole(String message) + { + mConsoleStringBuilder.append(message).append("\n"); + final String console = mConsoleStringBuilder.toString(); + Platform.runLater(() -> consoleOutputProperty().setValue(console)); + } + + /** + * Indicates if the build process completed successfully (false) or there were errors (true). + */ + public boolean hasErrors() + { + return mHasErrors; + } + + /** + * Executes the build process that downloads the JMBE creator utility, unzips it, and commands the utility + * to create the library at the specified library path. + */ + public void execute() + { + final Asset asset = getJMBECreatorAsset(mRelease); + + if(asset != null) + { + ThreadPool.SCHEDULED.execute(() -> { + Path tempDirectory = null; + try + { + tempDirectory = Files.createTempDirectory("sdrtrunk-jmbe-creator"); + printToConsole("Created: Temp Directory [" + tempDirectory.toString() + "]"); + printToConsole("Downloading: JMBE Creator [" + asset.toString() + "]"); + printToConsole("Please wait ..."); + Path creator = GitHub.downloadArtifact(asset.getDownloadUrl(), tempDirectory); + + if(creator != null) + { + printToConsole("Downloaded: JMBE Creator [" + creator.toString() + "]"); + Path unzipped = creator.getParent(); + Archiver archiver = ArchiverFactory.createArchiver(ArchiveFormat.ZIP); + archiver.extract(creator.toFile(), unzipped.toFile()); + printToConsole("Unzipped: [" + unzipped.toString() + "]"); + + Path script = null; + + OSType osType = OSType.getCurrentOSType(); + + if(osType.isLinux()) + { + script = FileUtil.findFile(unzipped, CREATOR_SCRIPT_LINUX); + } + else if(osType.isWindows()) + { + script = FileUtil.findFile(unzipped, CREATOR_SCRIPT_WINDOWS); + } + + if(script != null) + { + ProcessBuilder processBuilder = new ProcessBuilder(); + processBuilder.command(script.toString(), mLibraryPath.toString()); + + try + { + Process process = processBuilder.start(); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while((line = reader.readLine()) != null) + { + printToConsole(line); + } + + int exitCode = process.waitFor(); + + if(exitCode == 0) + { + printToConsole("Library Created Successfully!"); + Platform.runLater(() -> completeProperty().set(true)); + } + else + { + terminateWithErrors("Failed: Exit Code [" + exitCode + "]"); + mLog.error("Script failed with exit code: " + exitCode); + } + } + catch(InterruptedException ie) + { + terminateWithErrors("Failed: Script Process was interrupted"); + mLog.error("Interrupted", ie); + } + } + else + { + terminateWithErrors("Failed: Unable to find JMBE creator launch script for this OS"); + mLog.error("Script was null. Unable to find JMBE creator launch script"); + } + } + } + catch(Throwable t) + { + terminateWithErrors("Failed: Unknown Error - " + t.getLocalizedMessage()); + mLog.error("Failed to create the JMBE library", t); + } + finally + { + if(tempDirectory != null) + { + try + { + FileUtils.deleteDirectory(tempDirectory.toFile()); + printToConsole("Deleted: Temporary Directory [" + tempDirectory.toString() + "]"); + } + catch(IOException ioe) + { + printToConsole("Failed: Deleting Temporary Directory [" + tempDirectory.toString() + "]"); + mLog.error("Error deleting temporary directory [" + tempDirectory.toString() + "]"); + } + } + } + }); + } + else + { + terminateWithErrors("Failed: Unable to identify correct JMBE creator utility from GitHub " + + "repository for this computer's operating system and architecture"); + mLog.error("Unable to create JMBE library. Can't find JMBE Creator for this OS and architecture"); + } + + + } + + + /** + * Terminates the execution and updates flags to indicate error state + * @param message to display as console output + */ + private void terminateWithErrors(String message) + { + printToConsole(message); + printToConsole("Please follow the instructions for manually creating and installing the JMBE library"); + Platform.runLater(() -> { + mHasErrors = true; + mCompleteProperty.set(true); + }); + } + + /** + * Attempts to find the correct JMBE creator for this operating system and architecture for the specified release + * @param release to find + * @return JMBE creator asset or null. + */ + public static Asset getJMBECreatorAsset(Release release) + { + OSType osType = OSType.getCurrentOSType(); + + if(release != null) + { + for(Asset asset: release.getAssets()) + { + if(isCorrectAsset(asset, osType)) + { + return asset; + } + } + } + + return null; + } + + /** + * Indicates if the asset is correct for the host operating system and architecture + * @param asset to check + * @param osType for the current host (OS & architecture) + * @return true if the asset is correct for this host. + */ + public static boolean isCorrectAsset(Asset asset, OSType osType) + { + if(isJMBECreator(asset)) + { + String name = asset.getName(); + + switch(osType) + { + case LINUX_32: + return name.contains("linux") && name.contains("_32"); + case LINUX_64: + return name.contains("linux") && name.contains("_64"); + case OSX_64: + return name.contains("osx") && name.contains("_64"); + case WINDOWS_32: + return name.contains("windows") && name.contains("_32"); + case WINDOWS_64: + return name.contains("windows") && name.contains("_64"); + case UNKNOWN: + case OSX_32: + default: + return false; + } + } + + return false; + } + + /** + * Indicates if the GitHub asset has a non-null asset name and is a JMBE Creator asset + */ + private static boolean isJMBECreator(Asset asset) + { + return asset.getName() != null && asset.getName().startsWith("jmbe-creator"); + } + + public static void main(String[] args) + { + Release release = GitHub.getLatestRelease(GITHUB_JMBE_RELEASES_URL); + Path library = Paths.get("/home/denny/temp/jmbe.jar"); + JmbeCreator jmbeCreator = new JmbeCreator(release, library); + + jmbeCreator.execute(); + + while(true); + } +} diff --git a/src/main/java/io/github/dsheirer/jmbe/JmbeEditor.java b/src/main/java/io/github/dsheirer/jmbe/JmbeEditor.java new file mode 100644 index 00000000..419dc77f --- /dev/null +++ b/src/main/java/io/github/dsheirer/jmbe/JmbeEditor.java @@ -0,0 +1,327 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.jmbe; + +import io.github.dsheirer.eventbus.MyEventBus; +import io.github.dsheirer.jmbe.github.Release; +import io.github.dsheirer.jmbe.github.Version; +import io.github.dsheirer.preference.UserPreferences; +import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Label; +import javafx.scene.control.Separator; +import javafx.scene.control.TextArea; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import javafx.stage.DirectoryChooser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.nio.file.Path; + +/** + * Editor pane for JMBE Library creator + */ +public class JmbeEditor extends VBox +{ + private final static Logger mLog = LoggerFactory.getLogger(JmbeEditor.class); + private UserPreferences mUserPreferences; + private Release mCurrentRelease; + private Label mCurrentVersionLabel; + private Label mUpdatedVersionLabel; + private Label mLibraryPathLabel; + private Button mChangeDirectoryButton; + private GridPane mLabelGridPane; + private Path mLibraryDirectoryPath; + private Button mCreateButton; + private TextArea mConsoleTextArea; + + /** + * Constructs an instance + * @param userPreferences + */ + public JmbeEditor(UserPreferences userPreferences) + { + mUserPreferences = userPreferences; + setPadding(new Insets(10,10,10,10)); + setSpacing(10); + Separator separator = new Separator(); + separator.setMaxWidth(Double.MAX_VALUE); + VBox.setVgrow(getConsoleTextArea(), Priority.ALWAYS); + getChildren().addAll(getLabelGridPane(), separator, getCreateButton(), getConsoleTextArea()); + } + + /** + * Processes a request to configure the editor to build the release version specified in the request. + * @param request to build a specific release version + */ + public void process(JmbeEditorRequest request) + { + if(request != null) + { + setCurrentRelease(request.getCurrentRelease()); + } + } + + private TextArea getConsoleTextArea() + { + if(mConsoleTextArea == null) + { + mConsoleTextArea = new TextArea(); + mConsoleTextArea.setMaxHeight(Double.MAX_VALUE); + mConsoleTextArea.setMaxWidth(Double.MAX_VALUE); + } + + return mConsoleTextArea; + } + + private Button getCreateButton() + { + if(mCreateButton == null) + { + mCreateButton = new Button("Create Library"); + mCreateButton.setOnAction(event -> { + String noticeText = "The JMBE library is available as source code for educational purposes " + + "only. It is a written description of how certain voice encoding/decoding algorithms " + + "could be implemented. Executable objects compiled or derived from this package may " + + "be covered by one or more patents. Users are strongly advised to check for any " + + "patent restrictions or licensing requirements before compiling or using this " + + "source code.\n\nClicking the YES button indicates that you are requesting to download " + + "the JMBE source code and compile the JMBE library."; + + Alert alert = new Alert(Alert.AlertType.WARNING, noticeText, ButtonType.YES, ButtonType.NO); + alert.setResizable(true); + + //Workaroud for dialog sizing issue on Windows + alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); + + alert.setTitle("Download and Compile JMBE Library?"); + alert.setHeaderText("Do you want to continue?"); + alert.initOwner(getCreateButton().getScene().getWindow()); + alert.showAndWait().ifPresent(buttonType -> { + if(buttonType == ButtonType.YES) + { + Path library = getLibraryDirectoryPath().resolve(getJarName(mCurrentRelease.getVersion())); + getCreateButton().setDisable(true); + createJmbeLibrary(library); + } + }); + }); + } + + return mCreateButton; + } + + /** + * Downloads the JMBE creator and creates the library using the specified path argument. + * @param library path + */ + private void createJmbeLibrary(Path library) + { + JmbeCreator jmbeCreator = new JmbeCreator(mCurrentRelease, library); + getConsoleTextArea().textProperty().bind(jmbeCreator.consoleOutputProperty()); + jmbeCreator.completeProperty().addListener((observable, oldValue, newValue) -> { + getCreateButton().setDisable(false); + boolean failed = jmbeCreator.hasErrors(); + String content = null; + if(failed) + { + content = "JMBE library creation attempt failed. Please update the library manually by " + + "downloading the JMBE creator application."; + } + else + { + content = "JMBE library successfully created/updated"; + mUserPreferences.getJmbeLibraryPreference().setPathJmbeLibrary(jmbeCreator.getLibraryPath()); + getCurrentVersionLabel().setText(mCurrentRelease.getVersion().toString()); + } + Alert alert = new Alert(Alert.AlertType.INFORMATION, content, ButtonType.OK); + alert.setTitle("JMBE Library Creator"); + alert.setHeaderText((failed ? "Attempt failed." : "Success!") + " Please click OK to close this window"); + alert.initOwner(getCreateButton().getScene().getWindow()); + alert.showAndWait().ifPresent(buttonType -> { + jmbeCreator.completeProperty().unbind(); + MyEventBus.getEventBus().post(new JmbeEditorRequest(true)); + }); + }); + jmbeCreator.execute(); + } + + /** + * Creates a jar name for the specified release version + * @param version of the GitHub JMBE release + * @return JMBE library jar name + */ + public static String getJarName(Version version) + { + String name = version.toString(); + name = name.replace("v", ""); + return "jmbe-" + name + ".jar"; + } + + private GridPane getLabelGridPane() + { + if(mLabelGridPane == null) + { + mLabelGridPane = new GridPane(); + mLabelGridPane.setHgap(10); + mLabelGridPane.setVgap(10); + + int row = 0; + + Label topLabel = new Label("Create or Update JMBE Library"); + mLabelGridPane.add(topLabel, 0, row, 2, 1); + + Label currentLabel = new Label("Current Version:"); + GridPane.setHalignment(currentLabel, HPos.RIGHT); + mLabelGridPane.add(currentLabel, 0, ++row); + mLabelGridPane.add(getCurrentVersionLabel(), 1, row); + + Label updateLabel = new Label("Updating To Version:"); + GridPane.setHalignment(updateLabel, HPos.RIGHT); + mLabelGridPane.add(updateLabel, 0, ++row); + mLabelGridPane.add(getUpdatedVersionLabel(), 1, row); + + Label pathLabel = new Label("Store Library At:"); + GridPane.setHalignment(pathLabel, HPos.RIGHT); + mLabelGridPane.add(pathLabel, 0, ++row); + mLabelGridPane.add(getLibraryPathLabel(), 1, row); + + mLabelGridPane.add(getChangeDirectoryButton(), 1, ++row); + } + + return mLabelGridPane; + } + + private Path getLibraryDirectoryPath() + { + if(mLibraryDirectoryPath == null) + { + mLibraryDirectoryPath = mUserPreferences.getDirectoryPreference().getDefaultJmbeDirectory(); + } + + return mLibraryDirectoryPath; + } + + private Button getChangeDirectoryButton() + { + if(mChangeDirectoryButton == null) + { + mChangeDirectoryButton = new Button("Change Directory ..."); + mChangeDirectoryButton.setOnAction(event -> { + DirectoryChooser chooser = new DirectoryChooser(); + chooser.setInitialDirectory(getLibraryDirectoryPath().toFile()); + chooser.setTitle("JMBE Library Directory"); + File newDirectory = chooser.showDialog(getChangeDirectoryButton().getScene().getWindow()); + setLibraryDirectoryPath(newDirectory.toPath()); + }); + } + + return mChangeDirectoryButton; + } + + /** + * Sets the library path and updates the display label + */ + private void setLibraryDirectoryPath(Path path) + { + if(path != null) + { + mLibraryDirectoryPath = path; + getLibraryPathLabel().setText(mLibraryDirectoryPath.toString()); + } + } + + private Label getLibraryPathLabel() + { + if(mLibraryPathLabel == null) + { + mLibraryPathLabel = new Label(); + + if(getLibraryDirectoryPath() != null) + { + mLibraryPathLabel.setText(getLibraryDirectoryPath().toString()); + } + } + + return mLibraryPathLabel; + } + + private Label getCurrentVersionLabel() + { + if(mCurrentVersionLabel == null) + { + mCurrentVersionLabel = new Label("(empty)"); + + Version currentVersion = mUserPreferences.getJmbeLibraryPreference().getCurrentVersion(); + + if(currentVersion != null) + { + mCurrentVersionLabel.setText(currentVersion.toString()); + } + } + + return mCurrentVersionLabel; + } + + private Label getUpdatedVersionLabel() + { + if(mUpdatedVersionLabel == null) + { + mUpdatedVersionLabel = new Label("(empty)"); + } + + return mUpdatedVersionLabel; + } + + /** + * Current release version available from GitHub. + * @return current release or null if the release hasn't been set. + */ + private Release getCurrentRelease() + { + return mCurrentRelease; + } + + /** + * Sets the current release version available from GitHub. + * @param release version + */ + public void setCurrentRelease(Release release) + { + mCurrentRelease = release; + + if(release != null) + { + Version updateVersion = release.getVersion(); + + if(updateVersion != null) + { + getUpdatedVersionLabel().setText(updateVersion.toString()); + } + } + } +} diff --git a/src/main/java/io/github/dsheirer/jmbe/JmbeEditorRequest.java b/src/main/java/io/github/dsheirer/jmbe/JmbeEditorRequest.java new file mode 100644 index 00000000..479f8c8d --- /dev/null +++ b/src/main/java/io/github/dsheirer/jmbe/JmbeEditorRequest.java @@ -0,0 +1,65 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.jmbe; + +import io.github.dsheirer.jmbe.github.Release; + +/** + * Request to create a JMBE library from the specified GitHub release version + */ +public class JmbeEditorRequest +{ + private Release mCurrentRelease; + private boolean mCloseEditor = false; + + /** + * Constructs an instance to request an edit of the specified release + * @param release available on GitHub + */ + public JmbeEditorRequest(Release release) + { + mCurrentRelease = release; + } + + /** + * Constructs a request to close and dispose of the editor + * @param close + */ + public JmbeEditorRequest(boolean close) + { + mCloseEditor = close; + } + + /** + * Indicates if this is a close (and dispose) editor request + */ + public boolean isCloseEditorRequest() + { + return mCloseEditor; + } + + /** + * Release version requested + */ + public Release getCurrentRelease() + { + return mCurrentRelease; + } +} diff --git a/src/main/java/io/github/dsheirer/jmbe/github/Asset.java b/src/main/java/io/github/dsheirer/jmbe/github/Asset.java new file mode 100644 index 00000000..e663f619 --- /dev/null +++ b/src/main/java/io/github/dsheirer/jmbe/github/Asset.java @@ -0,0 +1,75 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.jmbe.github; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +/** + * GitHub Asset + */ +public class Asset +{ + private JsonObject mJsonObject; + + public Asset(JsonObject jsonObject) + { + mJsonObject = jsonObject; + } + + public String getName() + { + return getTag("name"); + } + + /** + * URL to download this asset + */ + public String getDownloadUrl() + { + return getTag("browser_download_url"); + } + + @Override + public String toString() + { + return getName(); + } + + /** + * Access the string contents of a json tag + * @param tagName to access + * @return string value or null + */ + private String getTag(String tagName) + { + if(mJsonObject != null) + { + JsonElement element = mJsonObject.get(tagName); + + if(!element.isJsonNull()) + { + return element.toString().replace("\"", ""); + } + } + + return null; + } +} diff --git a/src/main/java/io/github/dsheirer/jmbe/github/GitHub.java b/src/main/java/io/github/dsheirer/jmbe/github/GitHub.java new file mode 100644 index 00000000..d4726356 --- /dev/null +++ b/src/main/java/io/github/dsheirer/jmbe/github/GitHub.java @@ -0,0 +1,175 @@ +/* + * ****************************************************************************** + * Copyright (C) 2015-2020 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see + * ***************************************************************************** + */ + +package io.github.dsheirer.jmbe.github; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +/** + * Utilities for accessing GitHub repositories and releases + */ +public class GitHub +{ + private final static Logger mLog = LoggerFactory.getLogger(GitHub.class); + + private GitHub() + { + //There's nothing to construct in this class + } + + /** + * Download an artifact to the specified directory + * @param url of the artifact to download + * @param directory to store the downloaded file + * @return path to the downloaded artifact or null + */ + public static Path downloadArtifact(String url, Path directory) + { + HttpClient client = HttpClient.newBuilder() + .followRedirects(HttpClient.Redirect.ALWAYS) + .build(); + + HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).build(); + + try + { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofFileDownload(directory, + StandardOpenOption.CREATE, StandardOpenOption.WRITE)); + + if(response.statusCode() != 200) + { + mLog.error("HTTP Download Status Code: " + response.statusCode()); + } + + return response.body(); + + } + catch(Exception e) + { + mLog.error("Error downloading source code from GitHub", e); + } + + return null; + } + + /** + * Obtain the latest release object from the repository. + * @param repositoryURL for the GitHub api + * @return the latest release or null + */ + public static Release getLatestRelease(String repositoryURL) + { + HttpClient httpClient = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder().uri(URI.create(repositoryURL)).build(); + + try + { + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + if(response.statusCode() == 200) + { + return parseResponse(response.body()); + } + else + { + mLog.error("Error while fetching latest releases - HTTP:" + response.statusCode()); + } + } + catch(IOException | InterruptedException e) + { + mLog.error("Error while detecting the current release version of JMBE library", e); + } + + return null; + } + + /** + * Parses a json string containing an array or GitHub release objects and returns the latest release. + * @param json string returned from URL call + * @return latest release version + */ + private static Release parseResponse(String json) + { + Release release = null; + + if(json != null) + { + JsonElement element = JsonParser.parseString(json); + + if(element.isJsonArray()) + { + JsonArray array = element.getAsJsonArray(); + + for(JsonElement child: array) + { + if(child.isJsonObject()) + { + JsonObject releaseObject = child.getAsJsonObject(); + Version version = getVersion(releaseObject); + + if(version != null) + { + if(release == null || release.getVersion().compareTo(version) < 0) + { + release = new Release(version, releaseObject); + } + } + } + } + } + } + + return release; + } + + /** + * Extracts a version object from the tag_name. + * @param jsonObject containing a tag_name formatted as: vAAAAA.BBBBB.CCCCx where: + * A = major, 0-99999 + * B = minor, 0-99999 + * C = release, 0-99999 + * x = (optional)patch, a-z + * + * @return version instance or null + */ + private static Version getVersion(JsonObject jsonObject) + { + JsonElement tagName = jsonObject.get("tag_name"); + + if(tagName != null) + { + return Version.fromString(tagName.toString()); + } + + return null; + } +} diff --git a/src/main/java/io/github/dsheirer/jmbe/github/Release.java b/src/main/java/io/github/dsheirer/jmbe/github/Release.java new file mode 100644 index 00000000..b419ab96 --- /dev/null +++ b/src/main/java/io/github/dsheirer/jmbe/github/Release.java @@ -0,0 +1,159 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.jmbe.github; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * GitHub release + */ +public class Release +{ + private final static Logger mLog = LoggerFactory.getLogger(Release.class); + + private Version mVersion; + private JsonObject mJsonObject; + + /** + * Constructs an instance + * @param version of the release + * @param jsonObject that represents the release + */ + public Release(Version version, JsonObject jsonObject) + { + mVersion = version; + mJsonObject = jsonObject; + } + + /** + * Version stamp for the release + */ + public Version getVersion() + { + return mVersion; + } + + /** + * JsonObject for the release + */ + public JsonObject getJsonObject() + { + return mJsonObject; + } + + /** + * Name of the release + */ + public String getName() + { + return getTag("name"); + } + + /** + * Assets URL + */ + public String getAssetsUrl() + { + return getTag("assets_url"); + } + + public List getAssets() + { + List assets = new ArrayList<>(); + JsonArray array = mJsonObject.getAsJsonArray("assets"); + + if(!array.isJsonNull()) + { + for(JsonElement element: array) + { + assets.add(new Asset(element.getAsJsonObject())); + } + } + + return assets; + } + + /** + * HTML URL + */ + public String getHtmlUrl() + { + return getTag("html_url"); + } + + /** + * Zip File Download URL + */ + public String getDownloadUrl() + { + return getTag("zipball_url"); + } + + /** + * Access the string contents of a json tag + * @param tagName to access + * @return string value or null + */ + private String getTag(String tagName) + { + if(mJsonObject != null) + { + JsonElement element = mJsonObject.get(tagName); + + if(!element.isJsonNull()) + { + return element.toString().replace("\"", ""); + } + } + + return null; + } + + + /** + * Indicates if the release has a download URL + */ + public boolean hasDownloadUrl() + { + return getDownloadUrl() != null; + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("\nName:").append(getName()); + sb.append("\nVersion: ").append(getVersion().toString()); + sb.append("\nHTML URL:").append(getHtmlUrl()); + sb.append("\nDownload URL:").append(getDownloadUrl()); + + for(Asset asset: getAssets()) + { + sb.append("\nAsset: " + asset.toString()); + } + return sb.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/jmbe/github/Version.java b/src/main/java/io/github/dsheirer/jmbe/github/Version.java new file mode 100644 index 00000000..1ae257e3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/jmbe/github/Version.java @@ -0,0 +1,226 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.jmbe.github; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Version representation and parsing class + */ +public class Version implements Comparable +{ + public final static Pattern VERSION_PATTERN = Pattern.compile("v?(\\d{1,5}).(\\d{1,5}).(\\d{1,5})(\\w*)"); + + private Integer mMajor; + private Integer mMinor; + private Integer mRelease; + private Character mPatch; + + /** + * Constructs an instance + * @param major version + * @param minor version + * @param release version + * @param patch version (optional) + */ + public Version(int major, int minor, int release, Character patch) + { + mMajor = major; + mMinor = minor; + mRelease = release; + mPatch = patch; + } + + /** + * Parses the argument into a version instance. + * @param version string (e.g. v1.0.6a) + * @return + */ + public static Version fromString(String version) + { + if(version != null) + { + Matcher m = VERSION_PATTERN.matcher(version.replace("\"", "")); + + if(m.matches()) + { + int major = 0; + int minor = 0; + int release = 0; + Character patch = null; + + try + { + major = Integer.parseInt(m.group(1)); + } + catch(Exception e) + { + //Do nothing, we couldn't parse the value + } + + try + { + minor = Integer.parseInt(m.group(2)); + } + catch(Exception e) + { + //Do nothing, we couldn't parse the value + } + + try + { + release = Integer.parseInt(m.group(3)); + } + catch(Exception e) + { + //Do nothing, we couldn't parse the value + } + + String rawPatch = m.group(4); + + if(rawPatch != null && rawPatch.length() >= 1) + { + patch = rawPatch.charAt(0); + } + + return new Version(major, minor, release, patch); + } + } + + return null; + } + + public Integer getMajor() + { + return mMajor; + } + + public boolean hasMajor() + { + return mMajor != null; + } + + public Integer getMinor() + { + return mMinor; + } + + public boolean hasMinor() + { + return mMinor != null; + } + + public Integer getRelease() + { + return mRelease; + } + + public boolean hasRelease() + { + return mRelease != null; + } + + public char getPatch() + { + return mPatch; + } + + public boolean hasPatch() + { + return mPatch != null; + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(hasMajor() ? getMajor() : "x"); + sb.append(".").append(hasMinor() ? getMinor() : "x"); + sb.append(".").append(hasRelease() ? getRelease() : "x"); + if(hasPatch()) + { + sb.append(mPatch); + } + + return sb.toString(); + } + + @Override + public int compareTo(Version other) + { + if(hasMajor() && other.hasMajor()) + { + if(getMajor() != other.getMajor()) + { + return Integer.compare(getMajor(), other.getMajor()); + } + else + { + if(hasMinor() && other.hasMinor()) + { + if(getMinor() != other.getMinor()) + { + return Integer.compare(getMinor(), other.getMinor()); + } + else + { + if(hasRelease() && other.hasRelease()) + { + if(getRelease() != other.getRelease()) + { + return Integer.compare(getRelease(), other.getRelease()); + } + else + { + if(hasPatch() && other.hasPatch()) + { + if(getPatch() != other.getPatch()) + { + return Character.compare(getPatch(), other.getPatch()); + } + else + { + return 0; + } + } + else + { + return hasPatch() ? 1 : -1; + } + } + } + else + { + return hasRelease() ? -1 : 1; + } + } + } + else + { + return hasMinor() ? -1 : 1; + } + } + } + else + { + return hasMajor() ? -1 : 1; + } + } +} diff --git a/src/main/java/io/github/dsheirer/preference/decoder/JmbeLibraryPreference.java b/src/main/java/io/github/dsheirer/preference/decoder/JmbeLibraryPreference.java index 49108f76..02cf10c3 100644 --- a/src/main/java/io/github/dsheirer/preference/decoder/JmbeLibraryPreference.java +++ b/src/main/java/io/github/dsheirer/preference/decoder/JmbeLibraryPreference.java @@ -22,6 +22,7 @@ package io.github.dsheirer.preference.decoder; +import io.github.dsheirer.jmbe.github.Version; import io.github.dsheirer.preference.Preference; import io.github.dsheirer.preference.PreferenceType; import io.github.dsheirer.sample.Listener; @@ -32,6 +33,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.prefs.Preferences; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Decoder preferences @@ -42,7 +45,10 @@ public class JmbeLibraryPreference extends Preference private Preferences mPreferences = Preferences.userNodeForPackage(JmbeLibraryPreference.class); private static final String PREFERENCE_KEY_PATH_JMBE_LIBRARY = "path.jmbe.library.1.0.0"; + private static final String PREFERENCE_KEY_PATH_ALERT_LIBRARY_REQUIRED = "alert.jmbe.required"; + private final Pattern VERSION_PATTERN = Pattern.compile(".*jmbe-(\\d{1,5}.\\d{1,5}.\\d{1,5}\\w*)\\.jar"); private Path mPathJmbeLibrary; + private Boolean mAlertIfMissingLibraryRequired; /** * Constructs this preference with an update listener @@ -60,6 +66,23 @@ public class JmbeLibraryPreference extends Preference return PreferenceType.JMBE_LIBRARY; } + public Version getCurrentVersion() + { + Path path = getPathJmbeLibrary(); + + if(path != null) + { + Matcher m = VERSION_PATTERN.matcher(path.toString()); + + if(m.matches()) + { + return Version.fromString(m.group(1)); + } + } + + return null; + } + /* * Path to the JMBE audio codec library */ @@ -73,6 +96,14 @@ public class JmbeLibraryPreference extends Preference return mPathJmbeLibrary; } + /** + * Indicates if a path to a JMBE library is setup (ie non-null). + */ + public boolean hasJmbeLibraryPath() + { + return getPathJmbeLibrary() != null; + } + /** * Sets the path to the JMBE library */ @@ -93,6 +124,30 @@ public class JmbeLibraryPreference extends Preference notifyPreferenceUpdated(); } + /** + * Indicates if the user should be alerted when the JMBE library is not setup, but required for decoding. + */ + public boolean getAlertIfMissingLibraryRequired() + { + if(mAlertIfMissingLibraryRequired == null) + { + mAlertIfMissingLibraryRequired = mPreferences.getBoolean(PREFERENCE_KEY_PATH_ALERT_LIBRARY_REQUIRED, true); + } + + return mAlertIfMissingLibraryRequired; + } + + /** + * Sets the value for alerting when the library is required for decoding but the library is not currently setup. + * @param alert true if the user should be alerted when the library is not setup, but required for decoding. + */ + public void setAlertIfMissingLibraryRequired(boolean alert) + { + mAlertIfMissingLibraryRequired = alert; + mPreferences.putBoolean(PREFERENCE_KEY_PATH_ALERT_LIBRARY_REQUIRED, mAlertIfMissingLibraryRequired); + notifyPreferenceUpdated(); + } + /** * Returns the path stored in preferences and referenced by the key argument if it exists, otherwise returns the * default path. diff --git a/src/main/java/io/github/dsheirer/preference/directory/DirectoryPreference.java b/src/main/java/io/github/dsheirer/preference/directory/DirectoryPreference.java index 77d829fa..9f4e2fac 100644 --- a/src/main/java/io/github/dsheirer/preference/directory/DirectoryPreference.java +++ b/src/main/java/io/github/dsheirer/preference/directory/DirectoryPreference.java @@ -42,6 +42,7 @@ public class DirectoryPreference extends Preference private static final String DIRECTORY_APPLICATION_ROOT = "SDRTrunk"; private static final String DIRECTORY_APPLICATION_LOG = "logs"; private static final String DIRECTORY_EVENT_LOG = "event_logs"; + private static final String DIRECTORY_JMBE = "jmbe"; private static final String DIRECTORY_PLAYLIST = "playlist"; private static final String DIRECTORY_RECORDING = "recordings"; private static final String DIRECTORY_SCREEN_CAPTURE = "screen_captures"; @@ -50,6 +51,7 @@ public class DirectoryPreference extends Preference private static final String PREFERENCE_KEY_DIRECTORY_APPLICATION_ROOT = "directory.application.root"; private static final String PREFERENCE_KEY_DIRECTORY_APPLICATION_LOGS = "directory.application.logs"; private static final String PREFERENCE_KEY_DIRECTORY_EVENT_LOGS = "directory.event.logs"; + private static final String PREFERENCE_KEY_DIRECTORY_JMBE = "directory.jmbe"; private static final String PREFERENCE_KEY_DIRECTORY_PLAYLIST = "directory.playlist"; private static final String PREFERENCE_KEY_DIRECTORY_RECORDING = "directory.recording"; private static final String PREFERENCE_KEY_DIRECTORY_SCREEN_CAPTURE = "directory.screen.capture"; @@ -58,6 +60,7 @@ public class DirectoryPreference extends Preference private Path mDirectoryApplicationRoot; private Path mDirectoryApplicationLogs; private Path mDirectoryEventLogs; + private Path mDirectoryJmbe; private Path mDirectoryPlaylist; private Path mDirectoryRecording; private Path mDirectoryScreenCapture; @@ -115,6 +118,7 @@ public class DirectoryPreference extends Preference { mDirectoryApplicationLogs = null; mDirectoryEventLogs = null; + mDirectoryJmbe = null; mDirectoryPlaylist = null; mDirectoryRecording = null; mDirectoryScreenCapture = null; @@ -204,6 +208,40 @@ public class DirectoryPreference extends Preference notifyPreferenceUpdated(); } + /** + * Path to the folder for storing the JMBE audio library + */ + public Path getDirectoryJmbe() + { + if(mDirectoryJmbe == null) + { + mDirectoryJmbe = getPath(PREFERENCE_KEY_DIRECTORY_JMBE, getDefaultJmbeDirectory()); + createDirectory(mDirectoryJmbe); + } + + return mDirectoryJmbe; + } + + /** + * Sets the path to the JMBE folder + */ + public void setDirectoryJmbe(Path path) + { + mDirectoryJmbe = path; + mPreferences.put(PREFERENCE_KEY_DIRECTORY_JMBE, path.toString()); + notifyPreferenceUpdated(); + } + + /** + * Removes a stored playlist directory preference so that the default path can be used again + */ + public void resetDirectoryJmbe() + { + mPreferences.remove(PREFERENCE_KEY_DIRECTORY_JMBE); + mDirectoryJmbe = null; + notifyPreferenceUpdated(); + } + /** * Path to the folder for storing playlist */ @@ -364,6 +402,14 @@ public class DirectoryPreference extends Preference return getDirectoryApplicationRoot().resolve(DIRECTORY_EVENT_LOG); } + /** + * Default JMBE directory + */ + public Path getDefaultJmbeDirectory() + { + return getDirectoryApplicationRoot().resolve(DIRECTORY_JMBE); + } + /** * Default playlist directory */ diff --git a/src/main/java/io/github/dsheirer/util/FileUtil.java b/src/main/java/io/github/dsheirer/util/FileUtil.java new file mode 100644 index 00000000..4fac7594 --- /dev/null +++ b/src/main/java/io/github/dsheirer/util/FileUtil.java @@ -0,0 +1,76 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.util; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Iterator; + +/** + * File Utilities + */ +public class FileUtil +{ + /** + * Recursively searches for the named file starting in the specified directory. + * @param directory to search for the file + * @return path to the file or null if the file can't be found + * @throws IOException if there is a file system error or if the file is not found + */ + public static Path findFile(Path directory, String name) throws IOException + { + if(Files.isDirectory(directory)) + { + DirectoryStream stream = Files.newDirectoryStream(directory); + Iterator it = stream.iterator(); + Path path = null; + while(it.hasNext()) + { + path = it.next(); + + if(Files.isDirectory(path)) + { + Path candidate = findFile(path, name); + + if(candidate != null) + { + return candidate; + } + } + else + { + if(path.endsWith(name)) + { + return path; + } + } + } + } + else + { + throw new IOException("A directory required for this method [" + + (directory != null ? directory.toString() : "null") + "]"); + } + + return null; + } +} diff --git a/src/main/java/io/github/dsheirer/util/OSType.java b/src/main/java/io/github/dsheirer/util/OSType.java new file mode 100644 index 00000000..bb04ba3a --- /dev/null +++ b/src/main/java/io/github/dsheirer/util/OSType.java @@ -0,0 +1,101 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.util; + +import java.util.EnumSet; +import java.util.Locale; + +public enum OSType +{ + LINUX_32, + LINUX_64, + OSX_32, + OSX_64, + WINDOWS_32, + WINDOWS_64, + UNKNOWN; + + public static EnumSet WINDOWS_TYPES = EnumSet.of(WINDOWS_32, WINDOWS_64); + public static EnumSet LINUX_TYPES = EnumSet.of(LINUX_32, LINUX_64, OSX_32, OSX_64); + + /** + * Indicates if this enumeration entry is a Windows type + */ + public boolean isWindows() + { + return WINDOWS_TYPES.contains(this); + } + + /** + * Indicates if this enumeration entry is a Linux type + */ + public boolean isLinux() + { + return LINUX_TYPES.contains(this); + } + + /** + * Detects the current host operating system and architecture + */ + public static OSType getCurrentOSType() + { + String os = System.getProperty("os.name", "unknown").toLowerCase(Locale.ENGLISH); + String arch = System.getProperty("os.arch"); + + if(os.contains("win")) + { + if(arch.contains("64")) + { + return OSType.WINDOWS_64; + } + else + { + return OSType.WINDOWS_32; + } + } + + if(os.contains("mac") || os.contains("darwin")) + { + if(arch.contains("64")) + { + return OSType.OSX_64; + } + else + { + return OSType.OSX_32; + } + } + + if(os.contains("nux") || os.contains("nix") || os.contains("aix")) + { + if(arch.contains("64")) + { + return OSType.LINUX_64; + } + else + { + return OSType.LINUX_32; + } + } + + return OSType.UNKNOWN; + } + +} diff --git a/src/main/java/io/github/dsheirer/util/ZipUtility.java b/src/main/java/io/github/dsheirer/util/ZipUtility.java new file mode 100644 index 00000000..e9fafab8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/util/ZipUtility.java @@ -0,0 +1,158 @@ +package io.github.dsheirer.util; + +import org.rauschig.jarchivelib.ArchiveFormat; +import org.rauschig.jarchivelib.Archiver; +import org.rauschig.jarchivelib.ArchiverFactory; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * This utility compresses a list of files to standard ZIP format file. + * It is able to compress all sub files and sub directories, recursively. + * + * @author www.codejava.net + */ +public class ZipUtility +{ + /** + * A constants for buffer size used to read/write data + */ + private static final int BUFFER_SIZE = 4096; + + /** + * Compresses a list of files to a destination zip file + * + * @param listFiles A collection of files and directories + * @param destZipFile The path of the destination zip file + * @throws FileNotFoundException + * @throws IOException + */ + public void zip(List listFiles, String destZipFile) throws FileNotFoundException, + IOException + { + ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(destZipFile)); + for(File file : listFiles) + { + if(file.isDirectory()) + { + zipDirectory(file, file.getName(), zos); + } + else + { + zipFile(file, zos); + } + } + zos.flush(); + zos.close(); + } + + /** + * Compresses files represented in an array of paths + * + * @param files a String array containing file paths + * @param destZipFile The path of the destination zip file + * @throws FileNotFoundException + * @throws IOException + */ + public void zip(String[] files, String destZipFile) throws FileNotFoundException, IOException + { + List listFiles = new ArrayList(); + + for(int i = 0; i < files.length; i++) + { + listFiles.add(new File(files[i])); + } + + zip(listFiles, destZipFile); + } + + /** + * Adds a directory to the current zip output stream + * + * @param folder the directory to be added + * @param parentFolder the path of parent directory + * @param zos the current zip output stream + * @throws FileNotFoundException + * @throws IOException + */ + private void zipDirectory(File folder, String parentFolder, ZipOutputStream zos) throws + FileNotFoundException, IOException + { + if(folder == null || folder.listFiles() == null) + { + return; + } + + for(File file : folder.listFiles()) + { + if(file.exists()) + { + if(file.isDirectory()) + { + zipDirectory(file, parentFolder + "/" + file.getName(), zos); + continue; + } + + zos.putNextEntry(new ZipEntry(parentFolder + "/" + file.getName())); + BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); + long bytesRead = 0; + byte[] bytesIn = new byte[BUFFER_SIZE]; + int read = 0; + while((read = bis.read(bytesIn)) != -1) + { + zos.write(bytesIn, 0, read); + bytesRead += read; + } + zos.closeEntry(); + } + } + } + + /** + * Adds a file to the current zip output stream + * + * @param file the file to be added + * @param zos the current zip output stream + * @throws FileNotFoundException + * @throws IOException + */ + private void zipFile(File file, ZipOutputStream zos) throws FileNotFoundException, IOException + { + zos.putNextEntry(new ZipEntry(file.getName())); + BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); + long bytesRead = 0; + byte[] bytesIn = new byte[BUFFER_SIZE]; + int read = 0; + while((read = bis.read(bytesIn)) != -1) + { + zos.write(bytesIn, 0, read); + bytesRead += read; + } + zos.closeEntry(); + } + + /** + * Unzips the specified file into the directory where it's located + * + * @param zipFile to unzip + * @return path to where the file was unzipped. + * @throws IOException if there are errors unzipping + */ + public static Path unzip(Path zipFile) throws IOException + { + Path parent = zipFile.getParent(); + Archiver archiver = ArchiverFactory.createArchiver(ArchiveFormat.ZIP); + archiver.extract(zipFile.toFile(), parent.toFile()); + return parent; + } +} \ No newline at end of file