gui: add new version notification
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
package jadx.gui.ui;
|
||||
|
||||
import jadx.core.Jadx;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.gui.utils.NLS;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JButton;
|
||||
@@ -26,7 +27,7 @@ class AboutDialog extends JDialog {
|
||||
public final void initUI() {
|
||||
Font font = new Font("Serif", Font.BOLD, 13);
|
||||
|
||||
JLabel name = new JLabel("JADX");
|
||||
JLabel name = new JLabel("jadx");
|
||||
name.setFont(font);
|
||||
name.setAlignmentX(0.5f);
|
||||
|
||||
@@ -34,11 +35,12 @@ class AboutDialog extends JDialog {
|
||||
desc.setFont(font);
|
||||
desc.setAlignmentX(0.5f);
|
||||
|
||||
JLabel version = new JLabel("version: " + Jadx.getVersion());
|
||||
JLabel version = new JLabel("version: " + JadxDecompiler.getVersion());
|
||||
version.setFont(font);
|
||||
version.setAlignmentX(0.5f);
|
||||
|
||||
JPanel textPane = new JPanel();
|
||||
textPane.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
|
||||
textPane.setLayout(new BoxLayout(textPane, BoxLayout.PAGE_AXIS));
|
||||
textPane.add(Box.createRigidArea(new Dimension(0, 10)));
|
||||
textPane.add(name);
|
||||
|
||||
@@ -4,11 +4,15 @@ import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.treemodel.JRoot;
|
||||
import jadx.gui.update.JadxUpdate;
|
||||
import jadx.gui.update.data.Release;
|
||||
import jadx.gui.utils.JadxPreferences;
|
||||
import jadx.gui.utils.Link;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.Position;
|
||||
import jadx.gui.utils.Utils;
|
||||
|
||||
import javax.swing.Box;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBoxMenuItem;
|
||||
@@ -83,12 +87,25 @@ public class MainWindow extends JFrame {
|
||||
private JCheckBoxMenuItem flatPkgMenuItem;
|
||||
private JToggleButton flatPkgButton;
|
||||
private boolean isFlattenPackage;
|
||||
private Link updateLink;
|
||||
|
||||
public MainWindow(JadxWrapper wrapper) {
|
||||
this.wrapper = wrapper;
|
||||
|
||||
initUI();
|
||||
initMenuAndToolbar();
|
||||
JadxUpdate.check(new JadxUpdate.IUpdateCallback() {
|
||||
@Override
|
||||
public void onUpdate(final Release r) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateLink.setText(String.format(NLS.str("menu.update_label"), r.getName()));
|
||||
updateLink.setVisible(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void openFile() {
|
||||
@@ -96,16 +113,13 @@ public class MainWindow extends JFrame {
|
||||
fileChooser.setAcceptAllFileFilterUsed(true);
|
||||
fileChooser.setFileFilter(new FileNameExtensionFilter("supported files", "dex", "apk", "jar"));
|
||||
fileChooser.setToolTipText(NLS.str("file.open"));
|
||||
|
||||
String currentDirectory = JadxPreferences.getLastOpenFilePath();
|
||||
if (!currentDirectory.isEmpty()) {
|
||||
fileChooser.setCurrentDirectory(new File(currentDirectory));
|
||||
}
|
||||
|
||||
int ret = fileChooser.showDialog(mainPanel, NLS.str("file.open"));
|
||||
if (ret == JFileChooser.APPROVE_OPTION) {
|
||||
JadxPreferences.putLastOpenFilePath(fileChooser.getCurrentDirectory().getPath());
|
||||
|
||||
openFile(fileChooser.getSelectedFile());
|
||||
}
|
||||
}
|
||||
@@ -125,12 +139,11 @@ public class MainWindow extends JFrame {
|
||||
if (!currentDirectory.isEmpty()) {
|
||||
fileChooser.setCurrentDirectory(new File(currentDirectory));
|
||||
}
|
||||
|
||||
|
||||
int ret = fileChooser.showDialog(mainPanel, NLS.str("file.select"));
|
||||
if (ret == JFileChooser.APPROVE_OPTION) {
|
||||
JadxPreferences.putLastSaveFilePath(fileChooser.getCurrentDirectory().getPath());
|
||||
|
||||
ProgressMonitor progressMonitor = new ProgressMonitor(mainPanel, "Saving sources", "", 0, 100);
|
||||
ProgressMonitor progressMonitor = new ProgressMonitor(mainPanel, NLS.str("msg.saving_sources"), "", 0, 100);
|
||||
progressMonitor.setMillisToPopup(500);
|
||||
wrapper.saveAll(fileChooser.getSelectedFile(), progressMonitor);
|
||||
}
|
||||
@@ -378,6 +391,11 @@ public class MainWindow extends JFrame {
|
||||
forwardButton.setToolTipText(NLS.str("nav.forward"));
|
||||
toolbar.add(forwardButton);
|
||||
|
||||
toolbar.add(Box.createHorizontalGlue());
|
||||
updateLink = new Link("", JadxUpdate.JADX_RELEASES_URL);
|
||||
updateLink.setVisible(false);
|
||||
toolbar.add(updateLink);
|
||||
|
||||
mainPanel.add(toolbar, BorderLayout.NORTH);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package jadx.gui.update;
|
||||
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.gui.update.data.Release;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
public class JadxUpdate {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxUpdate.class);
|
||||
|
||||
public static final String JADX_RELEASES_URL = "https://github.com/skylot/jadx/releases";
|
||||
|
||||
private static final String GITHUB_API_URL = "https://api.github.com/";
|
||||
private static final String GITHUB_RELEASES_URL = GITHUB_API_URL + "repos/skylot/jadx/releases";
|
||||
|
||||
private static final Gson GSON = new Gson();
|
||||
|
||||
private static final Type RELEASES_LIST_TYPE = new TypeToken<List<Release>>() {}.getType();
|
||||
|
||||
private static final Comparator<Release> RELEASE_COMPARATOR = new Comparator<Release>() {
|
||||
@Override
|
||||
public int compare(Release o1, Release o2) {
|
||||
return VersionComparator.checkAndCompare(o1.getName(), o2.getName());
|
||||
}
|
||||
};
|
||||
|
||||
public static interface IUpdateCallback {
|
||||
void onUpdate(Release r);
|
||||
}
|
||||
|
||||
public static void check(final IUpdateCallback callback) {
|
||||
Runnable run = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Release release = checkForNewRelease();
|
||||
if (release != null) {
|
||||
callback.onUpdate(release);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Jadx update error", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
Thread thread = new Thread(run);
|
||||
thread.setName("Jadx update thread");
|
||||
thread.setPriority(Thread.MIN_PRIORITY);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private static Release checkForNewRelease() throws Exception {
|
||||
String version = JadxDecompiler.getVersion();
|
||||
if (version.contains("dev")) {
|
||||
LOG.debug("Ignore check for update: development version");
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Release> list = get(GITHUB_RELEASES_URL, RELEASES_LIST_TYPE);
|
||||
if (list == null) {
|
||||
return null;
|
||||
}
|
||||
for (Iterator<Release> it = list.iterator(); it.hasNext(); ) {
|
||||
Release release = it.next();
|
||||
if (release.getName().equalsIgnoreCase(version)
|
||||
|| release.isPrerelease()) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Collections.sort(list, RELEASE_COMPARATOR);
|
||||
Release latest = list.get(list.size() - 1);
|
||||
if (VersionComparator.checkAndCompare(version, latest.getName()) == 0) {
|
||||
return null;
|
||||
}
|
||||
LOG.debug("Found new version: {}", latest);
|
||||
return latest;
|
||||
}
|
||||
|
||||
private static <T> T get(String url, Type type) throws Exception {
|
||||
URL obj = new URL(url);
|
||||
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
|
||||
con.setRequestMethod("GET");
|
||||
if (con.getResponseCode() == 200) {
|
||||
Reader reader = new InputStreamReader(con.getInputStream());
|
||||
return GSON.fromJson(reader, type);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package jadx.gui.update;
|
||||
|
||||
public class VersionComparator {
|
||||
|
||||
public static int checkAndCompare(String str1, String str2) {
|
||||
try {
|
||||
return compare(clean(str1), clean(str2));
|
||||
} catch (NumberFormatException e) {
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
private static String clean(String str) {
|
||||
String result = str.trim().toLowerCase();
|
||||
if (result.charAt(0) == 'v') {
|
||||
result = result.substring(1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int compare(String str1, String str2) {
|
||||
String[] s1 = str1.split("\\.");
|
||||
int l1 = s1.length;
|
||||
String[] s2 = str2.split("\\.");
|
||||
int l2 = s2.length;
|
||||
|
||||
int i = 0;
|
||||
// skip equals parts
|
||||
while (i < l1 && i < l2) {
|
||||
if (!s1[i].equals(s2[i])) {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
// compare first non-equal ordinal number
|
||||
if (i < l1 && i < l2) {
|
||||
return Integer.valueOf(s1[i]).compareTo(Integer.valueOf(s2[i]));
|
||||
}
|
||||
boolean checkFirst = l1 > l2;
|
||||
boolean zeroTail = isZeroTail(checkFirst ? s1 : s2, i);
|
||||
if (zeroTail) {
|
||||
return 0;
|
||||
}
|
||||
return checkFirst ? 1 : -1;
|
||||
}
|
||||
|
||||
private static boolean isZeroTail(String[] arr, int pos) {
|
||||
for (int i = pos; i < arr.length; i++) {
|
||||
String s = arr[i];
|
||||
if (Integer.valueOf(s) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package jadx.gui.update.data;
|
||||
|
||||
public class Asset {
|
||||
private int id;
|
||||
private String url;
|
||||
private String name;
|
||||
private String label;
|
||||
private long size;
|
||||
private int download_count;
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(long size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public int getDownload_count() {
|
||||
return download_count;
|
||||
}
|
||||
|
||||
public void setDownload_count(int download_count) {
|
||||
this.download_count = download_count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package jadx.gui.update.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Release {
|
||||
private int id;
|
||||
private String name;
|
||||
private boolean prerelease;
|
||||
private List<Asset> assets;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public boolean isPrerelease() {
|
||||
return prerelease;
|
||||
}
|
||||
|
||||
public void setPrerelease(boolean prerelease) {
|
||||
this.prerelease = prerelease;
|
||||
}
|
||||
|
||||
public List<Asset> getAssets() {
|
||||
return assets;
|
||||
}
|
||||
|
||||
public void setAssets(List<Asset> assets) {
|
||||
this.assets = assets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(name);
|
||||
for (Asset asset : getAssets()) {
|
||||
sb.append('\n');
|
||||
sb.append(" ").append(asset.getName())
|
||||
.append(", asset id: ").append(asset.getId())
|
||||
.append(", size: ").append(asset.getSize())
|
||||
.append(", dc: ").append(asset.getDownload_count());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JTextArea;
|
||||
import java.awt.Color;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Desktop;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.awt.Desktop.Action;
|
||||
|
||||
public class Link extends JLabel implements MouseListener {
|
||||
private String url;
|
||||
|
||||
public Link(String text, String url) {
|
||||
super(text);
|
||||
this.url = url;
|
||||
this.setToolTipText("Open " + url + " in your browser");
|
||||
this.addMouseListener(this);
|
||||
this.setForeground(Color.BLUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent arg0) {
|
||||
browse();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent arg0) {
|
||||
setCursor(new Cursor(Cursor.HAND_CURSOR));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent arg0) {
|
||||
setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent arg0) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent arg0) {
|
||||
}
|
||||
|
||||
private void browse() {
|
||||
if (Desktop.isDesktopSupported()) {
|
||||
Desktop desktop = Desktop.getDesktop();
|
||||
if (desktop.isSupported(Action.BROWSE)) {
|
||||
try {
|
||||
desktop.browse(new java.net.URI(url));
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
String os = System.getProperty("os.name").toLowerCase();
|
||||
if (os.contains("win")) {
|
||||
Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
|
||||
return;
|
||||
}
|
||||
if (os.contains("mac")) {
|
||||
Runtime.getRuntime().exec("open " + url);
|
||||
return;
|
||||
}
|
||||
Map<String, String> env = System.getenv();
|
||||
if (env.get("BROWSER") != null) {
|
||||
Runtime.getRuntime().exec(env.get("BROWSER") + " " + url);
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
showUrlDialog();
|
||||
}
|
||||
|
||||
private void showUrlDialog() {
|
||||
JTextArea urlArea = new JTextArea("Can't open browser. Please browse to:\n"+url);
|
||||
JOptionPane.showMessageDialog(null, urlArea);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user