Merge remote-tracking branch 'origin/GP-6782_Dan_saveTraceAsAction--SQUASHED'

This commit is contained in:
Ryan Kurtz
2026-05-11 05:10:53 -04:00
8 changed files with 425 additions and 124 deletions
@@ -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
*
@@ -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;
@@ -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) {
@@ -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);
}
}
}
@@ -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
@@ -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);
}
}
@@ -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.
*/
}
}
@@ -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);
}
}