Commit e6b91900 authored by Skylot's avatar Skylot

gui: add new version notification

parent ac5a6096
...@@ -6,6 +6,7 @@ dependencies { ...@@ -6,6 +6,7 @@ dependencies {
compile(project(":jadx-core")) compile(project(":jadx-core"))
compile(project(":jadx-cli")) compile(project(":jadx-cli"))
compile 'com.fifesoft:rsyntaxtextarea:2.5.0' compile 'com.fifesoft:rsyntaxtextarea:2.5.0'
compile 'com.google.code.gson:gson:2.2.4'
} }
applicationDistribution.with { applicationDistribution.with {
......
package jadx.gui.ui; package jadx.gui.ui;
import jadx.core.Jadx; import jadx.api.JadxDecompiler;
import jadx.gui.utils.NLS; import jadx.gui.utils.NLS;
import javax.swing.BorderFactory;
import javax.swing.Box; import javax.swing.Box;
import javax.swing.BoxLayout; import javax.swing.BoxLayout;
import javax.swing.JButton; import javax.swing.JButton;
...@@ -26,7 +27,7 @@ class AboutDialog extends JDialog { ...@@ -26,7 +27,7 @@ class AboutDialog extends JDialog {
public final void initUI() { public final void initUI() {
Font font = new Font("Serif", Font.BOLD, 13); Font font = new Font("Serif", Font.BOLD, 13);
JLabel name = new JLabel("JADX"); JLabel name = new JLabel("jadx");
name.setFont(font); name.setFont(font);
name.setAlignmentX(0.5f); name.setAlignmentX(0.5f);
...@@ -34,11 +35,12 @@ class AboutDialog extends JDialog { ...@@ -34,11 +35,12 @@ class AboutDialog extends JDialog {
desc.setFont(font); desc.setFont(font);
desc.setAlignmentX(0.5f); desc.setAlignmentX(0.5f);
JLabel version = new JLabel("version: " + Jadx.getVersion()); JLabel version = new JLabel("version: " + JadxDecompiler.getVersion());
version.setFont(font); version.setFont(font);
version.setAlignmentX(0.5f); version.setAlignmentX(0.5f);
JPanel textPane = new JPanel(); JPanel textPane = new JPanel();
textPane.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
textPane.setLayout(new BoxLayout(textPane, BoxLayout.PAGE_AXIS)); textPane.setLayout(new BoxLayout(textPane, BoxLayout.PAGE_AXIS));
textPane.add(Box.createRigidArea(new Dimension(0, 10))); textPane.add(Box.createRigidArea(new Dimension(0, 10)));
textPane.add(name); textPane.add(name);
......
...@@ -4,11 +4,15 @@ import jadx.gui.JadxWrapper; ...@@ -4,11 +4,15 @@ import jadx.gui.JadxWrapper;
import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JRoot; 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.JadxPreferences;
import jadx.gui.utils.Link;
import jadx.gui.utils.NLS; import jadx.gui.utils.NLS;
import jadx.gui.utils.Position; import jadx.gui.utils.Position;
import jadx.gui.utils.Utils; import jadx.gui.utils.Utils;
import javax.swing.Box;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem; import javax.swing.JCheckBoxMenuItem;
...@@ -83,12 +87,25 @@ public class MainWindow extends JFrame { ...@@ -83,12 +87,25 @@ public class MainWindow extends JFrame {
private JCheckBoxMenuItem flatPkgMenuItem; private JCheckBoxMenuItem flatPkgMenuItem;
private JToggleButton flatPkgButton; private JToggleButton flatPkgButton;
private boolean isFlattenPackage; private boolean isFlattenPackage;
private Link updateLink;
public MainWindow(JadxWrapper wrapper) { public MainWindow(JadxWrapper wrapper) {
this.wrapper = wrapper; this.wrapper = wrapper;
initUI(); initUI();
initMenuAndToolbar(); 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() { public void openFile() {
...@@ -96,16 +113,13 @@ public class MainWindow extends JFrame { ...@@ -96,16 +113,13 @@ public class MainWindow extends JFrame {
fileChooser.setAcceptAllFileFilterUsed(true); fileChooser.setAcceptAllFileFilterUsed(true);
fileChooser.setFileFilter(new FileNameExtensionFilter("supported files", "dex", "apk", "jar")); fileChooser.setFileFilter(new FileNameExtensionFilter("supported files", "dex", "apk", "jar"));
fileChooser.setToolTipText(NLS.str("file.open")); fileChooser.setToolTipText(NLS.str("file.open"));
String currentDirectory = JadxPreferences.getLastOpenFilePath(); String currentDirectory = JadxPreferences.getLastOpenFilePath();
if (!currentDirectory.isEmpty()) { if (!currentDirectory.isEmpty()) {
fileChooser.setCurrentDirectory(new File(currentDirectory)); fileChooser.setCurrentDirectory(new File(currentDirectory));
} }
int ret = fileChooser.showDialog(mainPanel, NLS.str("file.open")); int ret = fileChooser.showDialog(mainPanel, NLS.str("file.open"));
if (ret == JFileChooser.APPROVE_OPTION) { if (ret == JFileChooser.APPROVE_OPTION) {
JadxPreferences.putLastOpenFilePath(fileChooser.getCurrentDirectory().getPath()); JadxPreferences.putLastOpenFilePath(fileChooser.getCurrentDirectory().getPath());
openFile(fileChooser.getSelectedFile()); openFile(fileChooser.getSelectedFile());
} }
} }
...@@ -129,8 +143,7 @@ public class MainWindow extends JFrame { ...@@ -129,8 +143,7 @@ public class MainWindow extends JFrame {
int ret = fileChooser.showDialog(mainPanel, NLS.str("file.select")); int ret = fileChooser.showDialog(mainPanel, NLS.str("file.select"));
if (ret == JFileChooser.APPROVE_OPTION) { if (ret == JFileChooser.APPROVE_OPTION) {
JadxPreferences.putLastSaveFilePath(fileChooser.getCurrentDirectory().getPath()); JadxPreferences.putLastSaveFilePath(fileChooser.getCurrentDirectory().getPath());
ProgressMonitor progressMonitor = new ProgressMonitor(mainPanel, NLS.str("msg.saving_sources"), "", 0, 100);
ProgressMonitor progressMonitor = new ProgressMonitor(mainPanel, "Saving sources", "", 0, 100);
progressMonitor.setMillisToPopup(500); progressMonitor.setMillisToPopup(500);
wrapper.saveAll(fileChooser.getSelectedFile(), progressMonitor); wrapper.saveAll(fileChooser.getSelectedFile(), progressMonitor);
} }
...@@ -378,6 +391,11 @@ public class MainWindow extends JFrame { ...@@ -378,6 +391,11 @@ public class MainWindow extends JFrame {
forwardButton.setToolTipText(NLS.str("nav.forward")); forwardButton.setToolTipText(NLS.str("nav.forward"));
toolbar.add(forwardButton); 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); mainPanel.add(toolbar, BorderLayout.NORTH);
} }
......
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;
}
}
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;
}
}
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;
}
}
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();
}
}
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);
}
}
...@@ -7,6 +7,7 @@ menu.search=Search ... ...@@ -7,6 +7,7 @@ menu.search=Search ...
menu.find_in_file=Find in ... menu.find_in_file=Find in ...
menu.help=Help menu.help=Help
menu.about=About menu.about=About
menu.update_label=New version %s available!
file.open=Open file file.open=Open file
file.save=Save file file.save=Save file
...@@ -43,3 +44,4 @@ search_dialog.field=Field ...@@ -43,3 +44,4 @@ search_dialog.field=Field
search_dialog.code=Code search_dialog.code=Code
msg.open_file=Please open file msg.open_file=Please open file
msg.saving_sources=Saving sources
package jadx.gui.tests
import jadx.gui.update.VersionComparator
import spock.lang.Specification
class TestVersionsComparator extends Specification {
def "test"() {
expect:
VersionComparator.compare(s1, s2) == expected
VersionComparator.compare(s2, s1) == -expected
where:
s1 | s2 | expected
"" | "" | 0
"1" | "1" | 0
"1" | "2" | -1
"1.1" | "1.1" | 0
"0.5" | "0.5" | 0
"0.5" | "0.5.0" | 0
"0.5" | "0.5.00" | 0
"0.5" | "0.5.0.0" | 0
"0.5" | "0.5.0.1" | -1
"0.5.0" | "0.5.0" | 0
"0.5.0" | "0.5.1" | -1
"0.5" | "0.5.1" | -1
"0.4.8" | "0.5" | -1
"0.4.8" | "0.5.0" | -1
"0.4.8" | "0.6" | -1
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment