Skip to content

Commit

Permalink
feat(gui): add reload and live reload actions (#1537)
Browse files Browse the repository at this point in the history
  • Loading branch information
skylot committed Jun 18, 2022
1 parent d2e6bb2 commit 65f7c80
Show file tree
Hide file tree
Showing 17 changed files with 363 additions and 26 deletions.
1 change: 1 addition & 0 deletions jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ private void initInnerClasses() {
public void runPreDecompileStage() {
boolean debugEnabled = LOG.isDebugEnabled();
for (IDexTreeVisitor pass : preDecompilePasses) {
Utils.checkThreadInterrupt();
long start = debugEnabled ? System.currentTimeMillis() : 0;
try {
pass.init(this);
Expand Down
2 changes: 1 addition & 1 deletion jadx-core/src/main/java/jadx/core/utils/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ public static <T> boolean notEmpty(T[] arr) {
}

public static void checkThreadInterrupt() {
if (Thread.interrupted()) {
if (Thread.currentThread().isInterrupted()) {
throw new JadxRuntimeException("Thread interrupted");
}
}
Expand Down
44 changes: 28 additions & 16 deletions jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
Expand All @@ -11,6 +12,7 @@
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
Expand All @@ -19,6 +21,7 @@
import org.slf4j.LoggerFactory;

import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.settings.JadxSettings;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.panel.ProgressPanel;
import jadx.gui.utils.NLS;
Expand All @@ -33,16 +36,16 @@
public class BackgroundExecutor {
private static final Logger LOG = LoggerFactory.getLogger(BackgroundExecutor.class);

private final MainWindow mainWindow;
private final JadxSettings settings;
private final ProgressPanel progressPane;

private ThreadPoolExecutor taskQueueExecutor;
private final Map<Long, IBackgroundTask> taskRunning = new ConcurrentHashMap<>();
private final AtomicLong idSupplier = new AtomicLong(0);

public BackgroundExecutor(MainWindow mainWindow) {
this.mainWindow = mainWindow;
this.progressPane = mainWindow.getProgressPane();
public BackgroundExecutor(JadxSettings settings, ProgressPanel progressPane) {
this.settings = Objects.requireNonNull(settings);
this.progressPane = Objects.requireNonNull(progressPane);
reset();
}

Expand All @@ -68,9 +71,16 @@ public TaskStatus executeAndWait(IBackgroundTask task) {
public synchronized void cancelAll() {
try {
taskRunning.values().forEach(Cancelable::cancel);
taskQueueExecutor.shutdown();
boolean complete = taskQueueExecutor.awaitTermination(30, TimeUnit.SECONDS);
LOG.debug("Background task executor terminated with status: {}", complete ? "complete" : "interrupted");
taskQueueExecutor.shutdownNow();
boolean complete = taskQueueExecutor.awaitTermination(3, TimeUnit.SECONDS);
if (complete) {
LOG.debug("Background task executor canceled successfully");
} else {
String taskNames = taskRunning.values().stream()
.map(IBackgroundTask::getTitle)
.collect(Collectors.joining(", "));
LOG.debug("Background task executor cancel failed. Running tasks: {}", taskNames);
}
} catch (Exception e) {
LOG.error("Error terminating task executor", e);
} finally {
Expand Down Expand Up @@ -131,8 +141,16 @@ protected TaskStatus doInBackground() throws Exception {
try {
runJobs();
} finally {
taskComplete(id);
task.onDone(this);
try {
task.onDone(this);
// treat UI task operations as part of the task to not mix with others
UiUtils.uiRunAndWait(() -> {
progressPane.setVisible(false);
task.onFinish(this);
});
} finally {
taskComplete(id);
}
}
return status;
}
Expand All @@ -146,7 +164,7 @@ private void runJobs() throws InterruptedException {
progressPane.changeVisibility(this, true);
}
status = TaskStatus.STARTED;
int threadsCount = mainWindow.getSettings().getThreadsCount();
int threadsCount = settings.getThreadsCount();
executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(threadsCount);
for (Runnable job : jobs) {
executor.execute(job);
Expand Down Expand Up @@ -258,12 +276,6 @@ private Supplier<TaskStatus> buildCancelCheck(long startTime) {
};
}

@Override
protected void done() {
progressPane.setVisible(false);
task.onFinish(this);
}

@Override
public TaskStatus getStatus() {
return status;
Expand Down
11 changes: 11 additions & 0 deletions jadx-gui/src/main/java/jadx/gui/settings/JadxProject.java
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,17 @@ private Path buildCacheDir() {
throw new JadxRuntimeException("Can't get working dir");
}

public boolean isEnableLiveReload() {
return data.isEnableLiveReload();
}

public void setEnableLiveReload(boolean newValue) {
if (newValue != data.isEnableLiveReload()) {
data.setEnableLiveReload(newValue);
changed();
}
}

private void changed() {
JadxSettings settings = mainWindow.getSettings();
if (settings != null && settings.isAutoSaveProject()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class ProjectData {
private List<TabViewState> openTabs = Collections.emptyList();
private int activeTab = -1;
private @Nullable Path cacheDir;
private boolean enableLiveReload = false;

public List<Path> getFiles() {
return files;
Expand Down Expand Up @@ -94,4 +95,12 @@ public Path getCacheDir() {
public void setCacheDir(Path cacheDir) {
this.cacheDir = cacheDir;
}

public boolean isEnableLiveReload() {
return enableLiveReload;
}

public void setEnableLiveReload(boolean enableLiveReload) {
this.enableLiveReload = enableLiveReload;
}
}
57 changes: 49 additions & 8 deletions jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
Expand Down Expand Up @@ -135,7 +136,9 @@
import jadx.gui.utils.NLS;
import jadx.gui.utils.SystemInfo;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.fileswatcher.LiveReloadWorker;
import jadx.gui.utils.logs.LogCollector;
import jadx.gui.utils.ui.ActionHandler;

import static io.reactivex.internal.functions.Functions.EMPTY_RUNNABLE;
import static javax.swing.KeyStroke.getKeyStroke;
Expand All @@ -152,6 +155,7 @@ public class MainWindow extends JFrame {
private static final ImageIcon ICON_OPEN = UiUtils.openSvgIcon("ui/openDisk");
private static final ImageIcon ICON_ADD_FILES = UiUtils.openSvgIcon("ui/addFile");
private static final ImageIcon ICON_SAVE_ALL = UiUtils.openSvgIcon("ui/menu-saveall");
private static final ImageIcon ICON_RELOAD = UiUtils.openSvgIcon("ui/refresh");
private static final ImageIcon ICON_EXPORT = UiUtils.openSvgIcon("ui/export");
private static final ImageIcon ICON_EXIT = UiUtils.openSvgIcon("ui/exit");
private static final ImageIcon ICON_SYNC = UiUtils.openSvgIcon("ui/pagination");
Expand Down Expand Up @@ -196,6 +200,9 @@ public class MainWindow extends JFrame {
private JToggleButton deobfToggleBtn;
private JCheckBoxMenuItem deobfMenuItem;

private JCheckBoxMenuItem liveReloadMenuItem;
private final LiveReloadWorker liveReloadWorker;

private transient Link updateLink;
private transient ProgressPanel progressPane;
private transient Theme editorTheme;
Expand All @@ -208,18 +215,18 @@ public MainWindow(JadxSettings settings) {
this.cacheObject = new CacheObject();
this.project = new JadxProject(this);
this.wrapper = new JadxWrapper(this);
this.liveReloadWorker = new LiveReloadWorker(this);

resetCache();
FontUtils.registerBundledFonts();
initUI();
this.backgroundExecutor = new BackgroundExecutor(settings, progressPane);
initMenuAndToolbar();
registerMouseNavigationButtons();
UiUtils.setWindowIcons(this);
loadSettings();
update();

this.backgroundExecutor = new BackgroundExecutor(this);

update();
checkForUpdate();
}

Expand Down Expand Up @@ -405,7 +412,7 @@ private static Path getProjectPathForFile(Path loadedFile) {
return loadedFile.resolveSibling(fileName);
}

public void reopen() {
public synchronized void reopen() {
saveAll();
closeAll();
loadFiles(EMPTY_RUNNABLE);
Expand Down Expand Up @@ -439,6 +446,10 @@ private void loadFiles(Runnable onFinish) {
UiUtils.errorMessage(this, NLS.str("message.memoryLow"));
return;
}
if (status != TaskStatus.COMPLETE) {
LOG.warn("Loading task incomplete, status: {}", status);
return;
}
checkLoadedStatus();
onOpen();
exportMappingsMenu.setEnabled(true);
Expand Down Expand Up @@ -485,13 +496,29 @@ private void onOpen() {
deobfToggleBtn.setSelected(settings.isDeobfuscationOn());
initTree();
update();
updateLiveReload(project.isEnableLiveReload());
BreakpointManager.init(project.getFilePaths().get(0).toAbsolutePath().getParent());

backgroundExecutor.execute(NLS.str("progress.load"),
this::restoreOpenTabs,
status -> runInitialBackgroundJobs());
}

public void updateLiveReload(boolean state) {
if (liveReloadWorker.isStarted() == state) {
return;
}
project.setEnableLiveReload(state);
liveReloadMenuItem.setEnabled(false);
backgroundExecutor.execute(
(state ? "Starting" : "Stopping") + " live reload",
() -> liveReloadWorker.updateState(state),
s -> {
liveReloadMenuItem.setState(state);
liveReloadMenuItem.setEnabled(true);
});
}

private void addTreeCustomNodes() {
treeRoot.replaceCustomNode(ApkSignature.getApkSignature(wrapper));
treeRoot.replaceCustomNode(new SummaryNode(this));
Expand Down Expand Up @@ -829,6 +856,19 @@ public void actionPerformed(ActionEvent e) {
};
saveProjectAsAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.save_project_as"));

ActionHandler reload = new ActionHandler(ev -> UiUtils.uiRun(this::reopen));
reload.setNameAndDesc(NLS.str("file.reload"));
reload.setIcon(ICON_RELOAD);
reload.setKeyBinding(getKeyStroke(KeyEvent.VK_F5, 0));

ActionHandler liveReload = new ActionHandler(ev -> updateLiveReload(!project.isEnableLiveReload()));
liveReload.setName(NLS.str("file.live_reload"));
liveReload.setShortDescription(NLS.str("file.live_reload_desc"));
liveReload.setKeyBinding(getKeyStroke(KeyEvent.VK_F5, InputEvent.SHIFT_DOWN_MASK));

liveReloadMenuItem = new JCheckBoxMenuItem(liveReload);
liveReloadMenuItem.setState(project.isEnableLiveReload());

Action exportMappingsAsTiny2 = new AbstractAction("Tiny v2 file") {
@Override
public void actionPerformed(ActionEvent e) {
Expand Down Expand Up @@ -1045,6 +1085,9 @@ public void actionPerformed(ActionEvent e) {
file.add(saveProjectAction);
file.add(saveProjectAsAction);
file.addSeparator();
file.add(reload);
file.add(liveReloadMenuItem);
file.addSeparator();
file.add(exportMappingsMenu);
file.addSeparator();
file.add(saveAllAction);
Expand Down Expand Up @@ -1114,6 +1157,8 @@ public void actionPerformed(ActionEvent e) {
toolbar.add(openAction);
toolbar.add(addFilesAction);
toolbar.addSeparator();
toolbar.add(reload);
toolbar.addSeparator();
toolbar.add(saveAllAction);
toolbar.add(exportAction);
toolbar.addSeparator();
Expand Down Expand Up @@ -1452,10 +1497,6 @@ public BackgroundExecutor getBackgroundExecutor() {
return backgroundExecutor;
}

public ProgressPanel getProgressPane() {
return progressPane;
}

public JRoot getTreeRoot() {
return treeRoot;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.awt.event.KeyEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.Objects;

import javax.swing.AbstractAction;
import javax.swing.Action;
Expand Down Expand Up @@ -75,7 +76,7 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {

public AbstractCodeArea(ContentPanel contentPanel, JNode node) {
this.contentPanel = contentPanel;
this.node = node;
this.node = Objects.requireNonNull(node);

setMarkOccurrences(false);
setEditable(false);
Expand Down
Loading

0 comments on commit 65f7c80

Please sign in to comment.