提交 9c88f707 编写于 作者: S Skylot

fix(gui): load file in background thread and show progress indicator

上级 9ab003df
package jadx.gui.jobs;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.swing.SwingWorker;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.ProgressPanel;
/**
* Class for run tasks in background with progress bar indication.
* Use instance created in {@link MainWindow}.
*/
public class BackgroundExecutor {
private static final Logger LOG = LoggerFactory.getLogger(BackgroundExecutor.class);
private final MainWindow mainWindow;
private final ProgressPanel progressPane;
private ThreadPoolExecutor taskQueueExecutor;
public BackgroundExecutor(MainWindow mainWindow) {
this.mainWindow = mainWindow;
this.progressPane = mainWindow.getProgressPane();
this.taskQueueExecutor = makeTaskQueueExecutor();
}
public Future<Boolean> execute(IBackgroundTask task) {
TaskWorker taskWorker = new TaskWorker(task);
taskWorker.init();
taskQueueExecutor.execute(taskWorker);
return taskWorker;
}
public void cancelAll() {
try {
taskQueueExecutor.shutdownNow();
taskQueueExecutor.awaitTermination(1, TimeUnit.SECONDS);
} catch (Exception e) {
LOG.error("Error terminating task executor", e);
} finally {
taskQueueExecutor = makeTaskQueueExecutor();
}
}
public void execute(String title, Runnable backgroundRunnable, Runnable onFinishUiRunnable) {
execute(new SimpleTask(title, backgroundRunnable, onFinishUiRunnable));
}
public void execute(String title, Runnable backgroundRunnable) {
execute(new SimpleTask(title, backgroundRunnable, null));
}
private ThreadPoolExecutor makeTaskQueueExecutor() {
return (ThreadPoolExecutor) Executors.newFixedThreadPool(1);
}
private final class TaskWorker extends SwingWorker<Boolean, Void> {
private final IBackgroundTask task;
private long jobsCount;
public TaskWorker(IBackgroundTask task) {
this.task = task;
}
public void init() {
addPropertyChangeListener(progressPane);
progressPane.reset();
}
@Override
protected Boolean doInBackground() throws Exception {
progressPane.changeLabel(this, task.getTitle() + ':');
progressPane.changeCancelBtnVisible(this, task.canBeCanceled());
progressPane.changeVisibility(this, true);
List<Runnable> jobs = task.scheduleJobs();
jobsCount = jobs.size();
if (jobsCount == 1) {
jobs.get(0).run();
return true;
}
int threadsCount = mainWindow.getSettings().getThreadsCount();
if (threadsCount == 1) {
return runInCurrentThread(jobs);
}
return runInExecutor(jobs, threadsCount);
}
private boolean runInCurrentThread(List<Runnable> jobs) {
int k = 0;
for (Runnable job : jobs) {
job.run();
k++;
setProgress(calcProgress(k));
if (isCancelled()) {
return false;
}
}
return true;
}
private boolean runInExecutor(List<Runnable> jobs, int threadsCount) throws InterruptedException {
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(threadsCount);
for (Runnable job : jobs) {
executor.execute(job);
}
executor.shutdown();
return waitTermination(executor);
}
private boolean waitTermination(ThreadPoolExecutor executor) throws InterruptedException {
while (true) {
if (executor.isTerminated()) {
return true;
}
if (isCancelled()) {
executor.shutdownNow();
progressPane.changeLabel(this, task.getTitle() + " (Canceling):");
// force termination
executor.awaitTermination(5, TimeUnit.SECONDS);
return false;
}
setProgress(calcProgress(executor.getCompletedTaskCount()));
Thread.sleep(500);
}
}
private int calcProgress(long done) {
return Math.round(done * 100 / (float) jobsCount);
}
@Override
protected void done() {
progressPane.setVisible(false);
task.onFinish();
}
}
private static final class SimpleTask implements IBackgroundTask {
private final String title;
private final Runnable runnable;
private final Runnable onFinish;
public SimpleTask(String title, Runnable runnable, @Nullable Runnable onFinish) {
this.title = title;
this.runnable = runnable;
this.onFinish = onFinish;
}
@Override
public String getTitle() {
return title;
}
@Override
public List<Runnable> scheduleJobs() {
return Collections.singletonList(runnable);
}
@Override
public boolean canBeCanceled() {
return false;
}
@Override
public void onFinish() {
if (onFinish != null) {
onFinish.run();
}
}
}
}
......@@ -2,7 +2,9 @@ package jadx.gui.jobs;
import java.util.concurrent.Future;
import javax.swing.*;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -13,6 +15,9 @@ import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.search.TextSearchIndex;
/**
* Deprecated. Use {@link BackgroundExecutor} instead.
*/
public class BackgroundWorker extends SwingWorker<Void, Void> {
private static final Logger LOG = LoggerFactory.getLogger(BackgroundWorker.class);
......
......@@ -2,6 +2,7 @@ package jadx.gui.jobs;
import jadx.api.JavaClass;
import jadx.gui.JadxWrapper;
import jadx.gui.utils.NLS;
public class DecompileJob extends BackgroundJob {
......@@ -9,6 +10,7 @@ public class DecompileJob extends BackgroundJob {
super(wrapper, threadsCount);
}
@Override
protected void runJob() {
for (final JavaClass cls : wrapper.getIncludedClasses()) {
addTask(cls::decompile);
......@@ -17,6 +19,6 @@ public class DecompileJob extends BackgroundJob {
@Override
public String getInfoString() {
return "Decompiling: ";
return NLS.str("progress.decompile");
}
}
package jadx.gui.jobs;
import java.util.List;
public interface IBackgroundTask {
String getTitle();
List<Runnable> scheduleJobs();
void onFinish();
boolean canBeCanceled();
}
......@@ -13,6 +13,7 @@ import jadx.gui.utils.CacheObject;
import jadx.gui.utils.CodeLinesInfo;
import jadx.gui.utils.CodeUsageInfo;
import jadx.gui.utils.JNodeCache;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.search.StringRef;
import jadx.gui.utils.search.TextSearchIndex;
......@@ -27,6 +28,7 @@ public class IndexJob extends BackgroundJob {
this.cache = cache;
}
@Override
protected void runJob() {
JNodeCache nodeCache = cache.getNodeCache();
final TextSearchIndex index = new TextSearchIndex(nodeCache);
......@@ -66,6 +68,6 @@ public class IndexJob extends BackgroundJob {
@Override
public String getInfoString() {
return "Indexing: ";
return NLS.str("progress.index");
}
}
......@@ -78,6 +78,7 @@ import jadx.api.JavaClass;
import jadx.api.JavaNode;
import jadx.api.ResourceFile;
import jadx.gui.JadxWrapper;
import jadx.gui.jobs.BackgroundExecutor;
import jadx.gui.jobs.BackgroundWorker;
import jadx.gui.jobs.DecompileJob;
import jadx.gui.jobs.IndexJob;
......@@ -156,6 +157,7 @@ public class MainWindow extends JFrame {
private transient Link updateLink;
private transient ProgressPanel progressPane;
private transient BackgroundWorker backgroundWorker;
private transient BackgroundExecutor backgroundExecutor;
private transient Theme editorTheme;
public MainWindow(JadxSettings settings) {
......@@ -172,6 +174,8 @@ public class MainWindow extends JFrame {
loadSettings();
checkForUpdate();
newProject();
this.backgroundExecutor = new BackgroundExecutor(this);
}
public void init() {
......@@ -252,14 +256,6 @@ public class MainWindow extends JFrame {
clearTree();
}
private void clearTree() {
tabbedPane.closeAllTabs();
resetCache();
treeRoot = null;
treeModel.setRoot(treeRoot);
treeModel.reload();
}
private void saveProject() {
if (project.getProjectPath() == null) {
saveProjectAs();
......@@ -310,13 +306,15 @@ public class MainWindow extends JFrame {
openProject(path);
} else {
project.setFilePath(path);
tabbedPane.closeAllTabs();
resetCache();
wrapper.openFile(path.toFile());
deobfToggleBtn.setSelected(settings.isDeobfuscationOn());
initTree();
update();
runBackgroundJobs();
clearTree();
backgroundExecutor.execute(NLS.str("progress.load"),
() -> wrapper.openFile(path.toFile()),
() -> {
deobfToggleBtn.setSelected(settings.isDeobfuscationOn());
initTree();
update();
runBackgroundJobs();
});
}
}
......@@ -398,6 +396,7 @@ public class MainWindow extends JFrame {
}
public synchronized void cancelBackgroundJobs() {
backgroundExecutor.cancelAll();
if (backgroundWorker != null) {
backgroundWorker.stop();
backgroundWorker = new BackgroundWorker(cacheObject, progressPane);
......@@ -492,6 +491,14 @@ public class MainWindow extends JFrame {
reloadTree();
}
private void clearTree() {
tabbedPane.closeAllTabs();
resetCache();
treeRoot = null;
treeModel.setRoot(treeRoot);
treeModel.reload();
}
private void reloadTree() {
treeReloading = true;
......@@ -1101,6 +1108,10 @@ public class MainWindow extends JFrame {
return backgroundWorker;
}
public ProgressPanel getProgressPane() {
return progressPane;
}
private class RecentProjectsMenuListener implements MenuListener {
private final JMenu recentProjects;
......
package jadx.gui.ui;
import java.awt.*;
import java.awt.Dimension;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import jadx.gui.utils.UiUtils;
......@@ -16,8 +23,12 @@ public class ProgressPanel extends JPanel implements PropertyChangeListener {
private final JProgressBar progressBar;
private final JLabel progressLabel;
private final JButton cancelButton;
private final boolean showCancelButton;
public ProgressPanel(final MainWindow mainWindow, boolean showCancelButton) {
this.showCancelButton = showCancelButton;
progressLabel = new JLabel();
progressBar = new JProgressBar(0, 100);
progressBar.setIndeterminate(true);
......@@ -30,28 +41,47 @@ public class ProgressPanel extends JPanel implements PropertyChangeListener {
add(progressLabel);
add(progressBar);
if (showCancelButton) {
JButton cancelButton = new JButton(ICON_CANCEL);
cancelButton.setPreferredSize(new Dimension(ICON_CANCEL.getIconWidth(), ICON_CANCEL.getIconHeight()));
cancelButton.setToolTipText("Cancel background jobs");
cancelButton.setBorderPainted(false);
cancelButton.setFocusPainted(false);
cancelButton.setContentAreaFilled(false);
cancelButton.addActionListener(e -> mainWindow.cancelBackgroundJobs());
add(cancelButton);
}
cancelButton = new JButton(ICON_CANCEL);
cancelButton.setPreferredSize(new Dimension(ICON_CANCEL.getIconWidth(), ICON_CANCEL.getIconHeight()));
cancelButton.setToolTipText("Cancel background jobs");
cancelButton.setBorderPainted(false);
cancelButton.setFocusPainted(false);
cancelButton.setContentAreaFilled(false);
cancelButton.addActionListener(e -> mainWindow.cancelBackgroundJobs());
cancelButton.setVisible(showCancelButton);
add(cancelButton);
}
public void reset() {
cancelButton.setVisible(showCancelButton);
progressBar.setIndeterminate(true);
progressBar.setValue(0);
progressBar.setString("");
progressBar.setStringPainted(true);
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
int progress = (Integer) evt.getNewValue();
progressBar.setIndeterminate(false);
progressBar.setValue(progress);
progressBar.setString(progress + "%");
progressBar.setStringPainted(true);
} else if ("label".equals(evt.getPropertyName())) {
setLabel((String) evt.getNewValue());
switch (evt.getPropertyName()) {
case "progress":
int progress = (Integer) evt.getNewValue();
progressBar.setIndeterminate(false);
progressBar.setValue(progress);
progressBar.setString(progress + "%");
progressBar.setStringPainted(true);
break;
case "label":
setLabel((String) evt.getNewValue());
break;
case "visible":
setVisible(((Boolean) evt.getNewValue()));
break;
case "cancel-visible":
cancelButton.setVisible(((Boolean) evt.getNewValue()));
break;
}
}
......@@ -66,4 +96,12 @@ public class ProgressPanel extends JPanel implements PropertyChangeListener {
public void changeLabel(SwingWorker<?, ?> task, String label) {
task.firePropertyChange("label", null, label);
}
public void changeVisibility(SwingWorker<?, ?> task, boolean visible) {
task.firePropertyChange("visible", null, visible);
}
public void changeCancelBtnVisible(SwingWorker<?, ?> task, boolean visible) {
task.firePropertyChange("cancel-visible", null, visible);
}
}
......@@ -62,6 +62,14 @@ public class NLS {
LANG_LOCALES_MAP.put(locale, bundle);
}
public static String str(String key) {
try {
return localizedMessagesMap.getString(key);
} catch (Exception e) {
return FALLBACK_MESSAGES_MAP.getString(key);
}
}
public static String str(String key, Object... parameters) {
String value;
try {
......
......@@ -32,6 +32,10 @@ tree.sources_title=Quellcode
tree.resources_title=Ressourcen
tree.loading=Laden…
progress.load=Laden
progress.decompile=Decompiling
progress.index=Indexing
error_dialog.title=Fehler
search.previous=Zurück
......@@ -133,7 +137,7 @@ msg.project_error_title=Fehler
msg.project_error=Projekt konnte nicht geladen werden
msg.rename_disabled_title=Umbenennen deaktiviert
msg.rename_disabled=Einige der Umbenennungseinstellungen sind deaktiviert, bitte beachten Sie dies.
msg.cmd_select_class_error=Klasse\n%s auswählen nicht möglich\nSie existiert nicht.
msg.cmd_select_class_error=Klasse\n%s auswählen nicht möglich\nSie existiert nicht.
popup.undo=Rückgängig
popup.redo=Wiederholen
......
......@@ -32,6 +32,10 @@ tree.sources_title=Source code
tree.resources_title=Resources
tree.loading=Loading...
progress.load=Loading
progress.decompile=Decompiling
progress.index=Indexing
error_dialog.title=Error
search.previous=Previous
......@@ -133,7 +137,7 @@ msg.project_error_title=Error
msg.project_error=Project could not be loaded
msg.rename_disabled_title=Rename disabled
msg.rename_disabled=Some of rename settings are disabled, please take this into consideration
msg.cmd_select_class_error=Failed to select the class\n%s\nThe class does not exist.
msg.cmd_select_class_error=Failed to select the class\n%s\nThe class does not exist.
popup.undo=Undo
popup.redo=Redo
......
......@@ -32,6 +32,10 @@ tree.sources_title=Código fuente
tree.resources_title=Recursos
tree.loading=Cargando...
progress.load=Cargando
progress.decompile=Decompiling
progress.index=Indexing
#error_dialog.title=
search.previous=Anterior
......
......@@ -32,6 +32,10 @@ tree.sources_title=源代码
tree.resources_title=资源文件
tree.loading=稍等...
progress.load=稍等
progress.decompile=Decompiling
progress.index=Indexing
error_dialog.title=错误
search.previous=上一个
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册