#829 Integrates JMBE Creator with new user preferences editor for auto-downloading and compiling the JMBE library from within sdrtrunk. (#844)

Co-authored-by: Denny <denny@denny-desktop>
This commit is contained in:
Denny Sheirer
2020-06-07 08:04:57 -04:00
committed by GitHub
parent a1e8c761ef
commit b22177ee17
29 changed files with 2260 additions and 77 deletions

View File

@@ -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")
}

View File

@@ -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
*/

View File

@@ -243,7 +243,7 @@ public class PlaylistEditor extends BorderPane
{
if(mChannelEditor == null)
{
mChannelEditor = new ChannelEditor(mPlaylistManager);
mChannelEditor = new ChannelEditor(mPlaylistManager, mUserPreferences);
}
return mChannelEditor;

View File

@@ -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());

View File

@@ -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<Channel>
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<Channel>
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<Channel>
});
}
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<ButtonType> 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<Channel>
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
*/

View File

@@ -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))
{

View File

@@ -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<Channel> 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)
{

View File

@@ -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());

View File

@@ -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());

View File

@@ -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());

View File

@@ -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());

View File

@@ -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());

View File

@@ -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());

View File

@@ -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());

View File

@@ -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());
}

View File

@@ -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<ActionEvent>()
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<ActionEvent>()
{
@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<ActionEvent>()
{
@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());
}
}
}

View File

@@ -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<ActionEvent>()
{
@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<ActionEvent>()
{
@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)

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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());
}
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
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<Path> 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<String> 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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<Asset> getAssets()
{
List<Asset> 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();
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<Version>
{
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;
}
}
}

View File

@@ -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.

View File

@@ -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
*/

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<Path> stream = Files.newDirectoryStream(directory);
Iterator<Path> 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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<OSType> WINDOWS_TYPES = EnumSet.of(WINDOWS_32, WINDOWS_64);
public static EnumSet<OSType> 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;
}
}

View File

@@ -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<File> 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<File> listFiles = new ArrayList<File>();
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;
}
}