mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-20 10:37:27 +08:00
Merge remote-tracking branch 'origin/GP-6782_Dan_saveTraceAsAction--SQUASHED'
This commit is contained in:
+15
-5
@@ -17,6 +17,7 @@ package ghidra.app.services;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
|
||||
import ghidra.async.AsyncReference;
|
||||
import ghidra.debug.api.target.Target;
|
||||
@@ -237,17 +238,26 @@ public interface DebuggerTraceManagerService {
|
||||
* <p>
|
||||
* If a different domain file of the trace's name already exists, an incrementing integer is
|
||||
* appended. Errors are handled in the same fashion as saving a program, so there is little/no
|
||||
* need to invoke {@link CompletableFuture#exceptionally(java.util.function.Function)} on the
|
||||
* returned future. The future is returned as a means of registering follow-up actions.
|
||||
*
|
||||
* <p>
|
||||
* TODO: Support save-as, prompting to overwrite, etc?
|
||||
* need to invoke {@link CompletableFuture#exceptionally(Function)} on the returned future. The
|
||||
* future is returned as a means of registering follow-up actions.
|
||||
*
|
||||
* @param trace the trace to save
|
||||
* @return a future which completes when the save is finished
|
||||
*/
|
||||
CompletableFuture<Void> saveTrace(Trace trace);
|
||||
|
||||
/**
|
||||
* Prompt the user and save the trace to a chosen path in the project
|
||||
* <p>
|
||||
* Errors are handled in the same fashion as saving a program, so there is little/no need to
|
||||
* invoke {@link CompletableFuture#exceptionally(Function)} on the returned future. The future
|
||||
* is returned as a means of registering follow-up actions.
|
||||
*
|
||||
* @param trace the trace to (rename and) save
|
||||
* @return a future which completes when the save is finished
|
||||
*/
|
||||
CompletableFuture<Void> saveTraceAs(Trace trace);
|
||||
|
||||
/**
|
||||
* Close the given trace
|
||||
*
|
||||
|
||||
+17
@@ -355,6 +355,23 @@ public interface DebuggerResources {
|
||||
}
|
||||
}
|
||||
|
||||
interface SaveTraceAsAction {
|
||||
String NAME = "Save Trace As";
|
||||
String DESCRIPTION = "Rename and save the selected trace";
|
||||
Icon ICON = DebuggerResources.ICON_SAVE;
|
||||
String GROUP = DebuggerResources.GROUP_TRACE;
|
||||
String HELP_ANCHOR = "save_trace_as";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ActionBuilder(NAME, ownerName).description(DESCRIPTION)
|
||||
.menuPath(DebuggerPluginPackage.NAME, NAME)
|
||||
.menuIcon(ICON)
|
||||
.menuGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractConnectAction extends DockingAction {
|
||||
public static final String NAME = "Connect";
|
||||
public static final Icon ICON = ICON_CONNECTION;
|
||||
|
||||
+21
-1
@@ -41,7 +41,7 @@ import utilities.util.SuppressableCallback;
|
||||
import utilities.util.SuppressableCallback.Suppression;
|
||||
|
||||
public class DebuggerTraceTabPanel extends GTabPanel<Trace>
|
||||
implements PluginEventListener, DomainObjectListener {
|
||||
implements PluginEventListener, DomainObjectListener, DomainFolderChangeListener {
|
||||
|
||||
private class TargetsChangeListener implements TargetPublicationListener {
|
||||
@Override
|
||||
@@ -79,6 +79,7 @@ public class DebuggerTraceTabPanel extends GTabPanel<Trace>
|
||||
tool.addEventListener(TraceOpenedPluginEvent.class, this);
|
||||
tool.addEventListener(TraceActivatedPluginEvent.class, this);
|
||||
tool.addEventListener(TraceClosedPluginEvent.class, this);
|
||||
tool.getProject().getProjectData().addDomainFolderChangeListener(this);
|
||||
|
||||
setNameFunction(this::getNameForTrace);
|
||||
setIconFunction(this::getIconForTrace);
|
||||
@@ -212,6 +213,25 @@ public class DebuggerTraceTabPanel extends GTabPanel<Trace>
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void domainFileRenamed(DomainFile file, String oldName) {
|
||||
if (file.getOpenedDomainObject(this) instanceof Trace trace) {
|
||||
try {
|
||||
refreshTab(trace);
|
||||
}
|
||||
finally {
|
||||
trace.release(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) {
|
||||
if (object instanceof Trace trace) {
|
||||
refreshTab(trace);
|
||||
}
|
||||
}
|
||||
|
||||
private void traceTabSelected(Trace newTrace) {
|
||||
cbCoordinateActivation.invoke(() -> {
|
||||
if (traceManager == null) {
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.tracemgr;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin.AskTraceResult;
|
||||
import ghidra.framework.client.ClientUtil;
|
||||
import ghidra.framework.client.NotConnectedException;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.util.InvalidNameException;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.database.DomainObjectLockHold;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.Task;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public abstract class AbstractSaveTraceTask extends Task {
|
||||
protected final Trace trace;
|
||||
protected final AskTraceResult asked;
|
||||
protected final boolean force;
|
||||
protected final PluginTool tool;
|
||||
|
||||
protected final CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
|
||||
public AbstractSaveTraceTask(String title, PluginTool tool, Trace trace, AskTraceResult asked,
|
||||
boolean force) {
|
||||
super(title, true, true, true);
|
||||
this.tool = tool;
|
||||
this.trace = trace;
|
||||
this.asked = asked;
|
||||
this.force = force;
|
||||
}
|
||||
|
||||
protected DomainObjectLockHold maybeLock(Trace trace, boolean lock) {
|
||||
if (!lock) {
|
||||
return null;
|
||||
}
|
||||
return DomainObjectLockHold.forceLock(trace, false, getTaskTitle());
|
||||
}
|
||||
|
||||
protected abstract void saveTrace(TaskMonitor monitor)
|
||||
throws CancelledException, InvalidNameException, IOException;
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
try (DomainObjectLockHold hold = maybeLock(trace, force)) {
|
||||
saveTrace(monitor);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// Done
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
catch (NotConnectedException | ConnectException e) {
|
||||
ClientUtil.promptForReconnect(tool.getProject().getRepository(), tool.getToolFrame());
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
catch (IOException e) {
|
||||
ClientUtil.handleException(tool.getProject().getRepository(), e, getTaskTitle(),
|
||||
tool.getToolFrame());
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
catch (InvalidNameException e) {
|
||||
Msg.showError(DebuggerTraceManagerServicePlugin.class, null, getTaskTitle(),
|
||||
e.getMessage());
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Msg.showError(DebuggerTraceManagerServicePlugin.class, null, getTaskTitle(),
|
||||
e.getMessage(), e);
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+148
-118
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.tracemgr;
|
||||
|
||||
import static ghidra.framework.main.DataTreeDialogType.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.net.ConnectException;
|
||||
@@ -46,7 +44,9 @@ import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.framework.client.ClientUtil;
|
||||
import ghidra.framework.client.NotConnectedException;
|
||||
import ghidra.framework.data.DomainObjectAdapterDB;
|
||||
import ghidra.framework.data.DomainObjectFileListener;
|
||||
import ghidra.framework.main.DataTreeDialog;
|
||||
import ghidra.framework.main.DataTreeDialogType;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.plugintool.*;
|
||||
@@ -66,7 +66,6 @@ import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
import ghidra.trace.util.TraceEvents;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.database.DomainObjectLockHold;
|
||||
import ghidra.util.exception.*;
|
||||
import ghidra.util.task.*;
|
||||
|
||||
@@ -96,16 +95,17 @@ import ghidra.util.task.*;
|
||||
public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
implements DebuggerTraceManagerService {
|
||||
|
||||
private static final AutoConfigState.ClassHandler<DebuggerTraceManagerServicePlugin> //
|
||||
CONFIG_STATE_HANDLER = AutoConfigState.wireHandler(DebuggerTraceManagerServicePlugin.class,
|
||||
MethodHandles.lookup());
|
||||
private static final AutoConfigState.ClassHandler<
|
||||
DebuggerTraceManagerServicePlugin> CONFIG_STATE_HANDLER = AutoConfigState
|
||||
.wireHandler(DebuggerTraceManagerServicePlugin.class, MethodHandles.lookup());
|
||||
|
||||
private static final String KEY_TRACE_COUNT = "NUM_TRACES";
|
||||
private static final String PREFIX_OPEN_TRACE = "OPEN_TRACE_";
|
||||
private static final String KEY_CURRENT_COORDS = "CURRENT_COORDS";
|
||||
public static final String NEW_TRACES_FOLDER_NAME = "New Traces";
|
||||
|
||||
class ListenerForTraceChanges extends TraceDomainObjectListener {
|
||||
class ListenerForTraceChanges extends TraceDomainObjectListener
|
||||
implements DomainObjectFileListener {
|
||||
private final Trace trace;
|
||||
|
||||
public ListenerForTraceChanges(Trace trace) {
|
||||
@@ -162,6 +162,14 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
}
|
||||
activate(current.object(object), ActivationCause.SYNC_MODEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void domainFileChanged(DomainObject domainObject) {
|
||||
if (current.getTrace() != trace) {
|
||||
return;
|
||||
}
|
||||
contextChanged();
|
||||
}
|
||||
}
|
||||
|
||||
static class TransactionEndFuture extends CompletableFuture<Void>
|
||||
@@ -310,6 +318,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
DockingAction actionCloseOtherTraces;
|
||||
DockingAction actionCloseDeadTraces;
|
||||
DockingAction actionSaveTrace;
|
||||
DockingAction actionSaveTraceAs;
|
||||
DockingAction actionOpenTrace;
|
||||
ToggleDockingAction actionSaveByDefault;
|
||||
ToggleDockingAction actionCloseOnTerminate;
|
||||
@@ -337,6 +346,10 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
.enabledWhen(c -> current.getTrace() != null)
|
||||
.onAction(this::activatedSaveTrace)
|
||||
.buildAndInstall(tool);
|
||||
actionSaveTraceAs = SaveTraceAsAction.builder(this)
|
||||
.enabledWhen(c -> current.getTrace() != null)
|
||||
.onAction(this::activatedSaveTraceAs)
|
||||
.buildAndInstall(tool);
|
||||
actionOpenTrace = OpenTraceAction.builder(this)
|
||||
.enabledWhen(ctx -> true)
|
||||
.onAction(this::activatedOpenTrace)
|
||||
@@ -381,12 +394,26 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
saveTrace(trace);
|
||||
}
|
||||
|
||||
private void activatedOpenTrace(ActionContext ctx) {
|
||||
DomainFile df = askTrace(current.getTrace());
|
||||
if (df != null) {
|
||||
Trace trace = openTrace(df, DomainFile.DEFAULT_VERSION); // TODO: Permit opening a previous revision?
|
||||
activateTrace(trace);
|
||||
private void activatedSaveTraceAs(ActionContext ctx) {
|
||||
Trace trace = current.getTrace();
|
||||
if (trace == null) {
|
||||
return;
|
||||
}
|
||||
saveTraceAs(trace);
|
||||
}
|
||||
|
||||
private void activatedOpenTrace(ActionContext ctx) {
|
||||
AskTraceResult asked =
|
||||
askTrace(tool, OpenTraceAction.NAME, DataTreeDialogType.OPEN, current.getTrace());
|
||||
if (asked == null) {
|
||||
return;
|
||||
}
|
||||
if (asked.file() == null) {
|
||||
return;
|
||||
}
|
||||
// LATER: Permit opening a previous revision?
|
||||
Trace trace = openTrace(asked.file(), DomainFile.DEFAULT_VERSION);
|
||||
activateTrace(trace);
|
||||
}
|
||||
|
||||
private void activatedCloseTrace(ActionContext ctx) {
|
||||
@@ -413,18 +440,55 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
closeDeadTraces();
|
||||
}
|
||||
|
||||
protected DataTreeDialog getTraceChooserDialog() {
|
||||
protected static DataTreeDialog getTraceChooserDialog(String title, DataTreeDialogType type) {
|
||||
DomainFileFilter filter = new DefaultDomainFileFilter(Trace.class, false);
|
||||
return new DataTreeDialog(null, OpenTraceAction.NAME, OPEN, filter);
|
||||
return new DataTreeDialog(null, title, type, filter);
|
||||
}
|
||||
|
||||
public DomainFile askTrace(Trace trace) {
|
||||
DataTreeDialog dialog = getTraceChooserDialog();
|
||||
if (trace != null) {
|
||||
dialog.selectDomainFile(trace.getDomainFile());
|
||||
public record AskTraceResult(DomainFile file, DomainFolder parent, String name) {}
|
||||
|
||||
public static AskTraceResult askTrace(PluginTool tool, String title, DataTreeDialogType type,
|
||||
Trace defaultTrace) {
|
||||
DataTreeDialog dialog = getTraceChooserDialog(title, type);
|
||||
if (defaultTrace != null) {
|
||||
dialog.selectDomainFile(defaultTrace.getDomainFile());
|
||||
}
|
||||
|
||||
dialog.addOkActionListener(ev -> {
|
||||
dialog.setStatusText("");
|
||||
String name = dialog.getNameText();
|
||||
if (name.isBlank()) {
|
||||
dialog.setStatusText("Please enter a name");
|
||||
return;
|
||||
}
|
||||
DomainFolder parent = dialog.getDomainFolder();
|
||||
if (parent == null) {
|
||||
dialog.setStatusText("Please select a folder");
|
||||
return;
|
||||
}
|
||||
DomainFile file = parent.getFile(name);
|
||||
if (type == DataTreeDialogType.SAVE && file != null && file.isReadOnly()) {
|
||||
dialog.setStatusText("Specified file is read-only");
|
||||
return;
|
||||
}
|
||||
if (type == DataTreeDialogType.SAVE && file != null) {
|
||||
String msg = """
|
||||
Domain file %s already exists with type %s. Do you want to overwrite it?\
|
||||
""".formatted(name, file.getContentType());
|
||||
int overwrite = OptionDialog.showYesNoDialog(tool.getToolFrame(), "Overwrite", msg);
|
||||
if (overwrite != OptionDialog.YES_OPTION) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
dialog.close();
|
||||
});
|
||||
|
||||
tool.showDialog(dialog);
|
||||
return dialog.getDomainFile();
|
||||
if (dialog.wasCancelled()) {
|
||||
return null;
|
||||
}
|
||||
return new AskTraceResult(dialog.getDomainFile(), dialog.getDomainFolder(),
|
||||
dialog.getNameText());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -553,9 +617,18 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
return newCurrent;
|
||||
}
|
||||
|
||||
protected String getTraceDisplayName(Trace trace) {
|
||||
DomainFile file = trace.getDomainFile();
|
||||
String name = file.getName();
|
||||
if (!name.isBlank()) {
|
||||
return name;
|
||||
}
|
||||
return trace.getName();
|
||||
}
|
||||
|
||||
protected void contextChanged() {
|
||||
Trace trace = current.getTrace();
|
||||
String itemName = trace == null ? "..." : trace.getName();
|
||||
String itemName = trace == null ? "..." : getTraceDisplayName(trace);
|
||||
actionCloseTrace.getMenuBarData().setMenuItemName(CloseTraceAction.NAME_PREFIX + itemName);
|
||||
actionSaveTrace.getMenuBarData().setMenuItemName(SaveTraceAction.NAME_PREFIX + itemName);
|
||||
tool.contextChanged(null);
|
||||
@@ -793,6 +866,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
ListenerForTraceChanges listener = new ListenerForTraceChanges(trace);
|
||||
listenersByTrace.put(trace, listener);
|
||||
trace.addListener(listener);
|
||||
trace.addDomainFileListener(listener);
|
||||
}
|
||||
contextChanged();
|
||||
firePluginEvent(new TraceOpenedPluginEvent(getName(), trace));
|
||||
@@ -867,7 +941,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
}
|
||||
|
||||
public static DomainFolder createOrGetFolder(PluginTool tool, String operation,
|
||||
DomainFolder parent, String name) throws InvalidNameException {
|
||||
DomainFolder parent, String name) {
|
||||
try {
|
||||
return parent.createFolder(name);
|
||||
}
|
||||
@@ -883,118 +957,70 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
tool.getToolFrame());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected static DomainObjectLockHold maybeLock(Trace trace, boolean lock) {
|
||||
if (!lock) {
|
||||
return null;
|
||||
catch (InvalidNameException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
return DomainObjectLockHold.forceLock(trace, false, "Auto Save");
|
||||
}
|
||||
|
||||
public static CompletableFuture<Void> saveTrace(PluginTool tool, Trace trace, boolean force) {
|
||||
public static AskTraceResult deriveDefaults(PluginTool tool, Trace trace) {
|
||||
DomainFile file = trace.getDomainFile();
|
||||
DomainFolder folder = file.getParent();
|
||||
if (folder != null) {
|
||||
return new AskTraceResult(file, folder, file.getName());
|
||||
}
|
||||
DomainFolder root = tool.getProject().getProjectData().getRootFolder();
|
||||
return new AskTraceResult(file,
|
||||
createOrGetFolder(tool, "Save New Trace", root, NEW_TRACES_FOLDER_NAME),
|
||||
trace.getName());
|
||||
}
|
||||
|
||||
public static CompletableFuture<Void> saveTrace(PluginTool tool, Trace trace,
|
||||
boolean promptPath, boolean force) {
|
||||
AskTraceResult asked = promptPath
|
||||
? askTrace(tool, SaveTraceAsAction.NAME, DataTreeDialogType.SAVE, trace)
|
||||
: deriveDefaults(tool, trace);
|
||||
if (asked == null) {
|
||||
return CompletableFuture.failedFuture(new CancelledException("User cancelled"));
|
||||
}
|
||||
tool.prepareToSave(trace);
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
// TODO: Get all the nuances for this correct...
|
||||
// "Save As" action, Locking, transaction flushing, etc....
|
||||
if (trace.getDomainFile().getParent() != null) {
|
||||
new TaskLauncher(new Task("Save Trace", true, true, true) {
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
try (DomainObjectLockHold hold = maybeLock(trace, force)) {
|
||||
trace.getDomainFile().save(monitor);
|
||||
future.complete(null);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// Done
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
catch (NotConnectedException | ConnectException e) {
|
||||
ClientUtil.promptForReconnect(tool.getProject().getRepository(),
|
||||
tool.getToolFrame());
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
catch (IOException e) {
|
||||
ClientUtil.handleException(tool.getProject().getRepository(), e,
|
||||
"Save Trace", tool.getToolFrame());
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
// LATER: Get all the nuances for this correct: Locking, tx flushing, etc.
|
||||
|
||||
final AbstractSaveTraceTask task;
|
||||
if (promptPath) {
|
||||
if (Objects.equals(trace.getDomainFile(), asked.parent().getFile(asked.name()))) {
|
||||
task = new SaveTraceTask(tool, trace, asked, force);
|
||||
}
|
||||
else {
|
||||
task = new SaveTraceAsTask(tool, trace, asked, force);
|
||||
}
|
||||
}
|
||||
else if (trace.getDomainFile().getParent() == null) {
|
||||
task = new SaveNewTraceTask(tool, trace, asked, force);
|
||||
}
|
||||
else {
|
||||
DomainFolder root = tool.getProject().getProjectData().getRootFolder();
|
||||
DomainFolder traces;
|
||||
try {
|
||||
traces = createOrGetFolder(tool, "Save New Trace", root, NEW_TRACES_FOLDER_NAME);
|
||||
}
|
||||
catch (InvalidNameException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
new TaskLauncher(new Task("Save New Trace", true, true, true) {
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
String filename = trace.getName();
|
||||
try (DomainObjectLockHold hold = maybeLock(trace, force)) {
|
||||
for (int i = 1;; i++) {
|
||||
try {
|
||||
traces.createFile(filename, trace, monitor);
|
||||
break;
|
||||
}
|
||||
catch (DuplicateFileException e) {
|
||||
filename = trace.getName() + "." + i;
|
||||
}
|
||||
}
|
||||
trace.save("Initial save", monitor);
|
||||
future.complete(null);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// Done
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
catch (NotConnectedException | ConnectException e) {
|
||||
ClientUtil.promptForReconnect(tool.getProject().getRepository(),
|
||||
tool.getToolFrame());
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
catch (IOException e) {
|
||||
ClientUtil.handleException(tool.getProject().getRepository(), e,
|
||||
"Save New Trace", tool.getToolFrame());
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
catch (InvalidNameException e) {
|
||||
Msg.showError(DebuggerTraceManagerServicePlugin.class, null,
|
||||
"Save New Trace Error", e.getMessage());
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Msg.showError(DebuggerTraceManagerServicePlugin.class, null,
|
||||
"Save New Trace Error", e.getMessage(), e);
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
task = new SaveTraceTask(tool, trace, asked, force);
|
||||
}
|
||||
return future;
|
||||
|
||||
new TaskLauncher(task);
|
||||
return task.future;
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> saveTrace(Trace trace, boolean force) {
|
||||
public CompletableFuture<Void> saveTrace(Trace trace, boolean promptPath, boolean force) {
|
||||
if (isDisposed()) {
|
||||
Msg.error(this, "Cannot save trace after manager disposal! Data may have been lost.");
|
||||
return AsyncUtils.nil();
|
||||
}
|
||||
return saveTrace(tool, trace, force);
|
||||
return saveTrace(tool, trace, promptPath, force);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> saveTrace(Trace trace) {
|
||||
return saveTrace(trace, false);
|
||||
return saveTrace(trace, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> saveTraceAs(Trace trace) {
|
||||
return saveTrace(trace, true, false);
|
||||
}
|
||||
|
||||
protected void doTraceClosed(Trace trace) {
|
||||
@@ -1003,7 +1029,9 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
}
|
||||
synchronized (listenersByTrace) {
|
||||
lastCoordsByTrace.remove(trace);
|
||||
trace.removeListener(listenersByTrace.remove(trace));
|
||||
ListenerForTraceChanges listener = listenersByTrace.remove(trace);
|
||||
trace.removeListener(listener);
|
||||
trace.removeDomainFileListener(listener);
|
||||
//Msg.debug(this, "Remaining Consumers of " + trace + ": " + trace.getConsumerList());
|
||||
}
|
||||
try {
|
||||
@@ -1089,7 +1117,9 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
Trace trace = it.next();
|
||||
trace.release(this);
|
||||
lastCoordsByTrace.remove(trace);
|
||||
trace.removeListener(listenersByTrace.get(trace));
|
||||
ListenerForTraceChanges listener = listenersByTrace.get(trace);
|
||||
trace.removeListener(listener);
|
||||
trace.removeDomainFileListener(listener);
|
||||
it.remove();
|
||||
}
|
||||
// Be certain
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.tracemgr;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin.AskTraceResult;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.util.InvalidNameException;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.DuplicateFileException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class SaveNewTraceTask extends AbstractSaveTraceTask {
|
||||
public SaveNewTraceTask(PluginTool tool, Trace trace, AskTraceResult asked, boolean force) {
|
||||
super("Save trace " + trace.getName(), tool, trace, asked, force);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveTrace(TaskMonitor monitor)
|
||||
throws CancelledException, InvalidNameException, IOException {
|
||||
String filename = asked.name();
|
||||
for (int i = 1;; i++) {
|
||||
try {
|
||||
asked.parent().createFile(filename, trace, monitor);
|
||||
break; // success, so fall through
|
||||
}
|
||||
catch (DuplicateFileException e) {
|
||||
filename = "%s.%d".formatted(asked.name(), i);
|
||||
}
|
||||
}
|
||||
trace.save("Initial save", monitor);
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.tracemgr;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin.AskTraceResult;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.util.InvalidNameException;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class SaveTraceAsTask extends AbstractSaveTraceTask {
|
||||
public SaveTraceAsTask(PluginTool tool, Trace trace, AskTraceResult asked,
|
||||
boolean force) {
|
||||
super("Save %s as %s".formatted(trace, asked.name()), tool, trace, asked, force);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveTrace(TaskMonitor monitor)
|
||||
throws CancelledException, InvalidNameException, IOException {
|
||||
DomainFile exists = asked.parent().getFile(asked.name());
|
||||
if (exists != null) {
|
||||
exists.delete();
|
||||
}
|
||||
asked.parent().createFile(asked.name(), trace, monitor);
|
||||
trace.setName(asked.name());
|
||||
trace.save("Save As", monitor);
|
||||
/**
|
||||
* NOTE: Refrain from modifying "Trace Information", since that better indicates the
|
||||
* *original* trace name, as reported by the back end debugger.
|
||||
*/
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.tracemgr;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin.AskTraceResult;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.util.InvalidNameException;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class SaveTraceTask extends AbstractSaveTraceTask {
|
||||
public SaveTraceTask(PluginTool tool, Trace trace, AskTraceResult asked, boolean force) {
|
||||
super("Save trace " + trace.getDomainFile().getName(), tool, trace, asked, force);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveTrace(TaskMonitor monitor)
|
||||
throws CancelledException, InvalidNameException, IOException {
|
||||
trace.getDomainFile().save(monitor);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user