diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java index b9476ba045..cdac1ab3b7 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java @@ -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 { *

* 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. - * - *

- * 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 saveTrace(Trace trace); + /** + * Prompt the user and save the trace to a chosen path in the project + *

+ * 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 saveTraceAs(Trace trace); + /** * Close the given trace * diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java index 1755ee9c86..f66818dbe2 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java @@ -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; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/trace/DebuggerTraceTabPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/trace/DebuggerTraceTabPanel.java index 18eda4023e..97a78b183c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/trace/DebuggerTraceTabPanel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/trace/DebuggerTraceTabPanel.java @@ -41,7 +41,7 @@ import utilities.util.SuppressableCallback; import utilities.util.SuppressableCallback.Suppression; public class DebuggerTraceTabPanel extends GTabPanel - implements PluginEventListener, DomainObjectListener { + implements PluginEventListener, DomainObjectListener, DomainFolderChangeListener { private class TargetsChangeListener implements TargetPublicationListener { @Override @@ -79,6 +79,7 @@ public class DebuggerTraceTabPanel extends GTabPanel 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 } } + @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) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/AbstractSaveTraceTask.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/AbstractSaveTraceTask.java new file mode 100644 index 0000000000..9e4e007e56 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/AbstractSaveTraceTask.java @@ -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 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); + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java index 0825d1328d..baad273a8d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java @@ -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 // - 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 @@ -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 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 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 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 saveTrace(Trace trace, boolean force) { + public CompletableFuture 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 saveTrace(Trace trace) { - return saveTrace(trace, false); + return saveTrace(trace, false, false); + } + + @Override + public CompletableFuture 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 diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/SaveNewTraceTask.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/SaveNewTraceTask.java new file mode 100644 index 0000000000..0a8058e01f --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/SaveNewTraceTask.java @@ -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); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/SaveTraceAsTask.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/SaveTraceAsTask.java new file mode 100644 index 0000000000..6e828204b7 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/SaveTraceAsTask.java @@ -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. + */ + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/SaveTraceTask.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/SaveTraceTask.java new file mode 100644 index 0000000000..8f6781fdef --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/SaveTraceTask.java @@ -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); + } +}