#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

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