GP-4341 Force retained checkout if file is in-use during checkin or add-to-version-control. Deprecated upgrade concept during checkin. Revised manner in which file open for update is updated following a version control operation (perform DBHandle update).

This commit is contained in:
ghidra1 2024-03-20 17:43:49 -04:00
parent 74a5b6f0e1
commit 2dff876f0f
46 changed files with 695 additions and 852 deletions

View file

@ -32,7 +32,8 @@ import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
import ghidra.app.plugin.core.debug.event.TraceOpenedPluginEvent; import ghidra.app.plugin.core.debug.event.TraceOpenedPluginEvent;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingProposals.ModuleMapProposalGenerator; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingProposals.ModuleMapProposalGenerator;
import ghidra.app.plugin.core.debug.utils.*; import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils;
import ghidra.app.plugin.core.debug.utils.ProgramURLUtils;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.async.AsyncDebouncer; import ghidra.async.AsyncDebouncer;
import ghidra.async.AsyncTimer; import ghidra.async.AsyncTimer;
@ -60,6 +61,7 @@ import ghidra.util.datastruct.ListenerSet;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
//@formatter:off
@PluginInfo( @PluginInfo(
shortDescription = "Debugger static mapping manager", shortDescription = "Debugger static mapping manager",
description = "Track and manage static mappings (program-trace relocations)", description = "Track and manage static mappings (program-trace relocations)",
@ -70,8 +72,9 @@ import ghidra.util.task.TaskMonitor;
TraceOpenedPluginEvent.class, TraceClosedPluginEvent.class, }, TraceOpenedPluginEvent.class, TraceClosedPluginEvent.class, },
servicesRequired = { ProgramManager.class, DebuggerTraceManagerService.class, }, servicesRequired = { ProgramManager.class, DebuggerTraceManagerService.class, },
servicesProvided = { DebuggerStaticMappingService.class, }) servicesProvided = { DebuggerStaticMappingService.class, })
//@formatter:on
public class DebuggerStaticMappingServicePlugin extends Plugin public class DebuggerStaticMappingServicePlugin extends Plugin
implements DebuggerStaticMappingService, DomainFolderChangeAdapter { implements DebuggerStaticMappingService, DomainFolderChangeListener {
protected class MappingEntry { protected class MappingEntry {
private final TraceStaticMapping mapping; private final TraceStaticMapping mapping;

View file

@ -22,7 +22,6 @@ import java.util.stream.Collectors;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import ghidra.app.plugin.core.debug.utils.DomainFolderChangeAdapter;
import ghidra.app.plugin.core.debug.utils.ProgramURLUtils; import ghidra.app.plugin.core.debug.utils.ProgramURLUtils;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.options.Options; import ghidra.framework.options.Options;
@ -33,7 +32,7 @@ import ghidra.program.model.listing.Program;
import ghidra.trace.model.modules.TraceModule; import ghidra.trace.model.modules.TraceModule;
// TODO: Consider making this a front-end plugin? // TODO: Consider making this a front-end plugin?
public class ProgramModuleIndexer implements DomainFolderChangeAdapter { public class ProgramModuleIndexer implements DomainFolderChangeListener {
public static final String MODULE_PATHS_PROPERTY = "Module Paths"; public static final String MODULE_PATHS_PROPERTY = "Module Paths";
private static final Gson JSON = new Gson(); private static final Gson JSON = new Gson();
@ -288,11 +287,6 @@ public class ProgramModuleIndexer implements DomainFolderChangeAdapter {
refreshIndex(file); refreshIndex(file);
} }
@Override
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
refreshIndex(file);
}
@Override @Override
public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) { public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) {
if (disposed) { if (disposed) {

View file

@ -1,72 +0,0 @@
/* ###
* 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.utils;
import ghidra.framework.model.*;
public interface DomainFolderChangeAdapter extends DomainFolderChangeListener {
@Override
default void domainFileAdded(DomainFile file) {
}
@Override
default void domainFolderAdded(DomainFolder folder) {
}
@Override
default void domainFolderRemoved(DomainFolder parent, String name) {
}
@Override
default void domainFileRemoved(DomainFolder parent, String name, String fileID) {
}
@Override
default void domainFolderRenamed(DomainFolder folder, String oldName) {
}
@Override
default void domainFileRenamed(DomainFile file, String oldName) {
}
@Override
default void domainFolderMoved(DomainFolder folder, DomainFolder oldParent) {
}
@Override
default void domainFileMoved(DomainFile file, DomainFolder oldParent, String oldName) {
}
@Override
default void domainFolderSetActive(DomainFolder folder) {
}
@Override
default void domainFileStatusChanged(DomainFile file, boolean fileIDset) {
}
@Override
default void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
}
@Override
default void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) {
}
@Override
default void domainFileObjectClosed(DomainFile file, DomainObject object) {
}
}

View file

@ -131,11 +131,6 @@ public class ProjectExperimentsTest extends AbstractGhidraHeadedIntegrationTest
log("File status changed: file=" + file + " idSet=" + fileIDset); log("File status changed: file=" + file + " idSet=" + fileIDset);
} }
@Override
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
log("File object replaced: file=" + file + " oldObject=" + obj(oldObject));
}
@Override @Override
public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) { public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) {
log("File object opened for update: file=" + file + " object=" + obj(object)); log("File object opened for update: file=" + file + " object=" + obj(object));

View file

@ -1045,12 +1045,6 @@ public class DBTraceProgramView implements TraceProgramView {
return language.getAddressFactory().getAllAddresses(addrStr, caseSensitive); return language.getAddressFactory().getAllAddresses(addrStr, caseSensitive);
} }
@Override
public void invalidate() {
// TODO: I imagine I'll find out who uses this pretty quick....
throw new UnsupportedOperationException();
}
@Override @Override
public Register getRegister(String name) { public Register getRegister(String name) {
return language.getRegister(name); return language.getRegister(name);

View file

@ -260,11 +260,6 @@ public class DBTraceProgramViewRegisters implements TraceProgramView {
return view.parseAddress(addrStr, caseSensitive); return view.parseAddress(addrStr, caseSensitive);
} }
@Override
public void invalidate() {
view.invalidate();
}
@Override @Override
public Register getRegister(String name) { public Register getRegister(String name) {
return view.getRegister(name); return view.getRegister(name);
@ -685,7 +680,7 @@ public class DBTraceProgramViewRegisters implements TraceProgramView {
} }
@Override @Override
public TraceProgramView getViewRegisters(TraceThread thread, boolean createIfAbsent) { public TraceProgramView getViewRegisters(TraceThread t, boolean createIfAbsent) {
return view.getViewRegisters(thread, createIfAbsent); return view.getViewRegisters(t, createIfAbsent);
} }
} }

View file

@ -208,7 +208,7 @@ public class RepositoryFileUpgradeScript extends GhidraScript {
dobj.release(this); dobj.release(this);
dobj = null; dobj = null;
if (df.isVersioned()) { if (df.isVersioned()) {
df.checkin(checkinHandler, false, monitor); df.checkin(checkinHandler, monitor);
println("Repository file upgraded: " + df.getPathname()); println("Repository file upgraded: " + df.getPathname());
} }
else { else {

View file

@ -1639,22 +1639,6 @@ public class DataTypeManagerHandler {
// ignore // ignore
} }
@Override
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
if (oldObject instanceof DataTypeArchiveDB) {
for (Archive archive : openArchives) {
if (archive instanceof ProjectArchive) {
ProjectArchive projectArchive = (ProjectArchive) archive;
DomainObject domainObject = projectArchive.getDomainObject();
if (domainObject == oldObject) {
replaceArchiveWithFile(projectArchive, file);
return;
}
}
}
}
}
@Override @Override
public void domainFileRenamed(DomainFile file, String oldName) { public void domainFileRenamed(DomainFile file, String oldName) {
if (!DataTypeArchiveContentHandler.DATA_TYPE_ARCHIVE_CONTENT_TYPE if (!DataTypeArchiveContentHandler.DATA_TYPE_ARCHIVE_CONTENT_TYPE

View file

@ -23,13 +23,9 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.jdom.Element;
import ghidra.app.events.*; import ghidra.app.events.*;
import ghidra.app.nav.Navigatable; import ghidra.app.nav.Navigatable;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.app.util.task.OpenProgramRequest;
import ghidra.app.util.task.OpenProgramTask;
import ghidra.framework.data.DomainObjectAdapterDB; import ghidra.framework.data.DomainObjectAdapterDB;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
@ -38,7 +34,6 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.Swing; import ghidra.util.Swing;
import ghidra.util.task.TaskLauncher;
/** /**
* Class for tracking open programs in the tool. * Class for tracking open programs in the tool.
@ -49,7 +44,6 @@ class MultiProgramManager implements TransactionListener {
private PluginTool tool; private PluginTool tool;
private ProgramInfo currentInfo; private ProgramInfo currentInfo;
private TransactionMonitor txMonitor; private TransactionMonitor txMonitor;
private MyFolderListener folderListener;
private Runnable programChangedRunnable; private Runnable programChangedRunnable;
private boolean hasUnsavedPrograms; private boolean hasUnsavedPrograms;
@ -72,8 +66,6 @@ class MultiProgramManager implements TransactionListener {
txMonitor = new TransactionMonitor(); txMonitor = new TransactionMonitor();
txMonitor.setName("Transaction Open (Program being modified)"); txMonitor.setName("Transaction Open (Program being modified)");
tool.addStatusComponent(txMonitor, true, true); tool.addStatusComponent(txMonitor, true, true);
folderListener = new MyFolderListener();
tool.getProject().getProjectData().addDomainFolderChangeListener(folderListener);
programChangedRunnable = () -> { programChangedRunnable = () -> {
if (tool == null) { if (tool == null) {
@ -111,7 +103,6 @@ class MultiProgramManager implements TransactionListener {
} }
void dispose() { void dispose() {
tool.getProject().getProjectData().removeDomainFolderChangeListener(folderListener);
fireActivatedEvent(null); fireActivatedEvent(null);
for (Program p : programMap.keySet()) { for (Program p : programMap.keySet()) {
@ -442,41 +433,6 @@ class MultiProgramManager implements TransactionListener {
//================================================================================================== //==================================================================================================
// Inner Classes // Inner Classes
//================================================================================================== //==================================================================================================
private class MyFolderListener extends DomainFolderListenerAdapter {
@Override
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
/**
* Special handling for when a file is checked-in. The existing program has be moved
* to a proxy file (no longer in the project) so that it can be closed and the program
* re-opened with the new version after the check-in merge.
*/
if (!programMap.containsKey(oldObject)) {
return;
}
Element dataState = null;
if (currentInfo != null && currentInfo.program == oldObject) {
// save dataState as though the project state was saved and re-opened to simulate
// recovering after closing the program during this swap
dataState = tool.saveDataStateToXml(true);
}
OpenProgramTask openTask = new OpenProgramTask(file, DomainFile.DEFAULT_VERSION, this);
openTask.setSilent();
new TaskLauncher(openTask, tool.getToolFrame());
OpenProgramRequest openProgramReq = openTask.getOpenProgram();
if (openProgramReq != null) {
plugin.openProgram(openProgramReq.getProgram(),
dataState != null ? ProgramManager.OPEN_CURRENT : ProgramManager.OPEN_VISIBLE);
openProgramReq.release();
removeProgram((Program) oldObject);
if (dataState != null) {
tool.restoreDataStateFromXml(dataState);
}
}
}
}
class ProgramInfo implements Comparable<ProgramInfo> { class ProgramInfo implements Comparable<ProgramInfo> {

View file

@ -132,11 +132,6 @@ public class DomainFolderChangesDisplayPlugin extends Plugin
Boolean.toString(fileIDset)); Boolean.toString(fileIDset));
} }
@Override
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
provider.addText("domainFileObjectReplaced: " + file.getPathname());
}
@Override @Override
public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) { public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) {
provider.addText("domainFileObjectOpenedForUpdate: " + file.getPathname()); provider.addText("domainFileObjectOpenedForUpdate: " + file.getPathname());

View file

@ -1488,7 +1488,7 @@ public class HeadlessAnalyzer {
public boolean createKeepFile() throws CancelledException { public boolean createKeepFile() throws CancelledException {
return false; return false;
} }
}, true, TaskMonitor.DUMMY); }, TaskMonitor.DUMMY);
Msg.info(this, "REPORT: Committed file changes to repository: " + df.getPathname()); Msg.info(this, "REPORT: Committed file changes to repository: " + df.getPathname());
} }
catch (IOException e) { catch (IOException e) {

View file

@ -701,20 +701,6 @@ public class DefaultProjectDataTest extends AbstractGhidraHeadedIntegrationTest
oldParent.getPathname(), oldName)); oldParent.getPathname(), oldName));
} }
@Override
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
// not tested
}
@Override
public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) {
// not tested
}
@Override
public void domainFileObjectClosed(DomainFile file, DomainObject object) {
// not tested
}
} }
} }

View file

@ -58,6 +58,7 @@ public class FakeSharedProject {
private GhidraProject gProject; private GhidraProject gProject;
private TestProgramManager programManager = new TestProgramManager(); private TestProgramManager programManager = new TestProgramManager();
private FakeRepository repo; private FakeRepository repo;
private boolean isFileSharingEnabled; // set true if multiple projects share repo files
public FakeSharedProject(FakeRepository repo, User user) throws IOException { public FakeSharedProject(FakeRepository repo, User user) throws IOException {
@ -85,6 +86,14 @@ public class FakeSharedProject {
invokeInstanceMethod("setVersionedFileSystem", pd, argTypes(FileSystem.class), args(fs)); invokeInstanceMethod("setVersionedFileSystem", pd, argTypes(FileSystem.class), args(fs));
} }
/**
* Mark project as sharing file with another project via a common repo.
* This is needed to bypass check performed by assertFileInProject
*/
public void enableFileSharing() {
isFileSharingEnabled = true;
}
/** /**
* Get the ghidra project * Get the ghidra project
* @return the ghidra project * @return the ghidra project
@ -162,6 +171,7 @@ public class FakeSharedProject {
public DomainFile getDomainFile(String filepath) { public DomainFile getDomainFile(String filepath) {
Project project = getGhidraProject().getProject(); Project project = getGhidraProject().getProject();
ProjectData projectData = project.getProjectData(); ProjectData projectData = project.getProjectData();
refresh(); // force refresh since we do not employ repo listener
DomainFile df; DomainFile df;
if (filepath.startsWith("/")) { if (filepath.startsWith("/")) {
df = projectData.getFile(filepath); df = projectData.getFile(filepath);
@ -287,7 +297,7 @@ public class FakeSharedProject {
} }
}; };
df.checkin(ch, false, TaskMonitor.DUMMY); df.checkin(ch, TaskMonitor.DUMMY);
repo.refresh(); repo.refresh();
} }
@ -387,6 +397,10 @@ public class FakeSharedProject {
throw new IllegalArgumentException("DomainFile cannot be null"); throw new IllegalArgumentException("DomainFile cannot be null");
} }
if (isFileSharingEnabled) {
return;
}
ProjectLocator pl = df.getProjectLocator(); ProjectLocator pl = df.getProjectLocator();
ProjectLocator mypl = getProjectData().getProjectLocator(); ProjectLocator mypl = getProjectData().getProjectLocator();
if (!pl.equals(mypl)) { if (!pl.equals(mypl)) {

View file

@ -21,12 +21,13 @@ import javax.swing.Icon;
import db.DBHandle; import db.DBHandle;
import db.buffers.BufferFile; import db.buffers.BufferFile;
import db.buffers.LocalManagedBufferFile;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.framework.data.DBContentHandler; import ghidra.framework.data.*;
import ghidra.framework.data.DomainObjectMergeManager;
import ghidra.framework.model.ChangeSet; import ghidra.framework.model.ChangeSet;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.store.*; import ghidra.framework.store.*;
import ghidra.framework.store.local.LocalDatabaseItem;
import ghidra.util.*; import ghidra.util.*;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException; import ghidra.util.exception.VersionException;
@ -168,4 +169,20 @@ public class VTSessionContentHandler extends DBContentHandler<VTSessionDB> {
return false; return false;
} }
@Override
public boolean canResetDBSourceFile() {
return true;
}
@Override
public void resetDBSourceFile(FolderItem item, DomainObjectAdapterDB domainObj)
throws IOException {
if (!(item instanceof LocalDatabaseItem dbItem) ||
!(domainObj instanceof VTSessionDB vtSession)) {
throw new IllegalArgumentException("LocalDatabaseItem and VTSessionDB required");
}
LocalManagedBufferFile bf = dbItem.openForUpdate(FolderItem.DEFAULT_CHECKOUT_ID);
vtSession.getDBHandle().setDBVersionedSourceFile(bf);
}
} }

View file

@ -28,7 +28,6 @@ import ghidra.app.plugin.core.colorizer.ColorizingService;
import ghidra.feature.vt.api.db.VTAssociationDB; import ghidra.feature.vt.api.db.VTAssociationDB;
import ghidra.feature.vt.api.db.VTSessionDB; import ghidra.feature.vt.api.db.VTSessionDB;
import ghidra.feature.vt.api.main.*; import ghidra.feature.vt.api.main.*;
import ghidra.feature.vt.api.util.VTSessionFileUtil;
import ghidra.feature.vt.gui.duallisting.VTListingContext; import ghidra.feature.vt.gui.duallisting.VTListingContext;
import ghidra.feature.vt.gui.provider.markuptable.VTMarkupItemContext; import ghidra.feature.vt.gui.provider.markuptable.VTMarkupItemContext;
import ghidra.feature.vt.gui.task.SaveTask; import ghidra.feature.vt.gui.task.SaveTask;
@ -69,7 +68,6 @@ public class VTControllerImpl
private ToolOptions vtOptions; private ToolOptions vtOptions;
private MatchInfo currentMatchInfo; private MatchInfo currentMatchInfo;
private MyFolderListener folderListener;
public VTControllerImpl(VTPlugin plugin) { public VTControllerImpl(VTPlugin plugin) {
this.plugin = plugin; this.plugin = plugin;
@ -77,11 +75,6 @@ public class VTControllerImpl
matchInfoFactory = new MatchInfoFactory(); matchInfoFactory = new MatchInfoFactory();
vtOptions = plugin.getTool().getOptions(VERSION_TRACKING_OPTIONS_NAME); vtOptions = plugin.getTool().getOptions(VERSION_TRACKING_OPTIONS_NAME);
vtOptions.addOptionsChangeListener(this); vtOptions.addOptionsChangeListener(this);
folderListener = new MyFolderListener();
plugin.getTool()
.getProject()
.getProjectData()
.addDomainFolderChangeListener(folderListener);
} }
@Override @Override
@ -763,82 +756,6 @@ public class VTControllerImpl
// Inner Classes // Inner Classes
//================================================================================================== //==================================================================================================
private void updateProgram(DomainFile file, boolean isSource) {
String type = isSource ? "Source" : "Destination";
Program newProgram;
try {
newProgram = (Program) file.getDomainObject(this, false, false, TaskMonitor.DUMMY);
}
catch (Exception e) {
Msg.showError(this, getParentComponent(),
"Error opening VT " + type + " Program: " + file, e);
return;
}
if (isSource) {
session.updateSourceProgram(newProgram);
}
else {
session.updateDestinationProgram(newProgram);
}
// List<DomainObjectChangeRecord> events = new ArrayList<DomainObjectChangeRecord>();
// events.add(new DomainObjectChangeRecord(DomainObjectEvent.RESTORED));
// domainObjectChanged(new DomainObjectChangedEvent(newProgram, events));
matchInfoFactory.clearCache();
fireSessionChanged();
}
private class MyFolderListener extends DomainFolderListenerAdapter {
@Override
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
if (session == null) {
return;
}
if (session.getSourceProgram() == oldObject) {
updateProgram(file, true);
return;
}
String type;
if (session == oldObject) {
type = "VT Session";
}
else if (session.getDestinationProgram() == oldObject) {
if (VTSessionFileUtil.canUpdate(file)) {
updateProgram(file, false);
return;
}
type = "Destination Program";
}
else {
return;
}
// Session or destination program can no longer be saved to project so we
// have no choice but to close session.
// Since we are already in the Swing thread we need to delay closing so we do
// not continue to block the Swing thread and the checkin which is in progress.
// This allows the DomainFile checkin to complete its processing first.
SwingUtilities.invokeLater(() -> {
Msg.showInfo(this, plugin.getTool().getToolFrame(), "Closing VT Session",
type + " checkin has forced session close.\n" +
"You will be prompted to save any other changes if needed, after which\n" +
"you may reopen the VT Session.");
closeVersionTrackingSession();
// NOTE: a future convenience could be added to attempt reopening of session
});
}
}
private class OpenSessionTask extends Task { private class OpenSessionTask extends Task {
private final VTSession newSession; private final VTSession newSession;

View file

@ -18,7 +18,6 @@ package db;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.Iterator;
import db.buffers.*; import db.buffers.*;
import db.util.ErrorHandler; import db.util.ErrorHandler;
@ -439,8 +438,7 @@ public class DBHandle {
* @throws IllegalStateException if transaction is already active or this {@link DBHandle} has * @throws IllegalStateException if transaction is already active or this {@link DBHandle} has
* already been closed. * already been closed.
*/ */
public Transaction openTransaction(ErrorHandler errorHandler) public Transaction openTransaction(ErrorHandler errorHandler) throws IllegalStateException {
throws IllegalStateException {
return new Transaction() { return new Transaction() {
long txId = startTransaction(); long txId = startTransaction();
@ -542,6 +540,33 @@ public class DBHandle {
return (bufferMgr != null && !bufferMgr.atCheckpoint()); return (bufferMgr != null && !bufferMgr.atCheckpoint());
} }
/**
* Set the DB source buffer file with a newer local buffer file version.
* Intended for use following a merge or commit operation only where a local checkout has been
* retained.
* @param versionedSourceBufferFile updated local DB source buffer file opened for versioning
* update (NOTE: file itself is read-only). File must be an instance of
* {@link LocalManagedBufferFile}.
* @throws IOException if an IO error occurs
*/
public void setDBVersionedSourceFile(BufferFile versionedSourceBufferFile) throws IOException {
if (!(versionedSourceBufferFile instanceof LocalManagedBufferFile bf) ||
!versionedSourceBufferFile.isReadOnly()) {
throw new IllegalArgumentException(
"Requires local versioned buffer file opened for versioning update");
}
synchronized (this) {
if (isTransactionActive()) {
throw new IOException("transaction is active");
}
bufferMgr.clearRecoveryFiles();
bufferMgr.setDBVersionedSourceFile(bf);
++checkpointNum;
reloadTables();
}
notifyDbRestored();
}
/** /**
* Terminate current transaction. If commit is false a rollback may occur followed by * Terminate current transaction. If commit is false a rollback may occur followed by
* {@link DBListener#dbRestored(DBHandle)} notification to listeners. This method is very * {@link DBListener#dbRestored(DBHandle)} notification to listeners. This method is very
@ -1022,10 +1047,9 @@ public class DBHandle {
public Table[] getTables() { public Table[] getTables() {
Table[] t = new Table[tables.size()]; Table[] t = new Table[tables.size()];
Iterator<Table> it = tables.values().iterator();
int i = 0; int i = 0;
while (it.hasNext()) { for (Table element : tables.values()) {
t[i++] = it.next(); t[i++] = element;
} }
return t; return t;
} }
@ -1050,8 +1074,7 @@ public class DBHandle {
* @return new table instance * @return new table instance
* @throws IOException if IO error occurs during table creation * @throws IOException if IO error occurs during table creation
*/ */
public Table createTable(String name, Schema schema, int[] indexedColumns) public Table createTable(String name, Schema schema, int[] indexedColumns) throws IOException {
throws IOException {
Table table; Table table;
synchronized (this) { synchronized (this) {
if (tables.containsKey(name)) { if (tables.containsKey(name)) {

View file

@ -223,6 +223,22 @@ public class BufferMgr {
approxCacheSize < MINIMUM_CACHE_SIZE ? MINIMUM_CACHE_SIZE : approxCacheSize; approxCacheSize < MINIMUM_CACHE_SIZE ? MINIMUM_CACHE_SIZE : approxCacheSize;
maxCacheSize = (int) (approxCacheSize / bufferSize); maxCacheSize = (int) (approxCacheSize / bufferSize);
// Setup baseline - checkpoint 0
startCheckpoint();
baselineCheckpointHead = currentCheckpointHead;
currentCheckpointHead = null;
initializeCache();
addInstance(this);
}
private void initializeCache() throws IOException {
if (cacheFile != null) {
cacheFile.delete();
}
// Setup memory cache list // Setup memory cache list
cacheHead = new BufferNode(HEAD, -1); cacheHead = new BufferNode(HEAD, -1);
cacheTail = new BufferNode(TAIL, -1); cacheTail = new BufferNode(TAIL, -1);
@ -234,11 +250,6 @@ public class BufferMgr {
cacheIndexProvider = new IndexProvider(); cacheIndexProvider = new IndexProvider();
// Setup baseline - checkpoint 0
startCheckpoint();
baselineCheckpointHead = currentCheckpointHead;
currentCheckpointHead = null;
// Copy file parameters into cache file // Copy file parameters into cache file
if (sourceFile != null) { if (sourceFile != null) {
String[] parmNames = sourceFile.getParameterNames(); String[] parmNames = sourceFile.getParameterNames();
@ -247,8 +258,6 @@ public class BufferMgr {
} }
} }
addInstance(this);
if (alwaysPreCache) { if (alwaysPreCache) {
startPreCacheIfNeeded(); startPreCacheIfNeeded();
} }
@ -417,9 +426,7 @@ public class BufferMgr {
// Dispose all buffer nodes - speeds up garbage collection // Dispose all buffer nodes - speeds up garbage collection
if (checkpointHeads != null) { if (checkpointHeads != null) {
Iterator<BufferNode> iter = checkpointHeads.iterator(); for (BufferNode node : checkpointHeads) {
while (iter.hasNext()) {
BufferNode node = iter.next();
while (node != null) { while (node != null) {
BufferNode next = node.nextInCheckpoint; BufferNode next = node.nextInCheckpoint;
node.buffer = null; node.buffer = null;
@ -435,9 +442,7 @@ public class BufferMgr {
checkpointHeads = null; checkpointHeads = null;
} }
if (redoCheckpointHeads != null) { if (redoCheckpointHeads != null) {
Iterator<BufferNode> iter = redoCheckpointHeads.iterator(); for (BufferNode node : redoCheckpointHeads) {
while (iter.hasNext()) {
BufferNode node = iter.next();
while (node != null) { while (node != null) {
BufferNode next = node.nextInCheckpoint; BufferNode next = node.nextInCheckpoint;
node.buffer = null; node.buffer = null;
@ -702,24 +707,7 @@ public class BufferMgr {
return; // only pre-cache remote buffer files return; // only pre-cache remote buffer files
} }
synchronized (preCacheLock) { synchronized (preCacheLock) {
preCacheThread = new Thread(() -> { preCacheThread = new Thread(() -> preCacheSourceFile());
try {
preCacheSourceFile();
}
catch (InterruptedIOException e) {
// ignore
}
catch (IOException e) {
Msg.error(BufferMgr.this, "pre-cache failure: " + e.getMessage(), e);
}
finally {
synchronized (preCacheLock) {
preCacheStatus = PreCacheStatus.STOPPED;
preCacheThread = null;
preCacheLock.notifyAll();
}
}
});
preCacheThread.setName("Pre-Cache"); preCacheThread.setName("Pre-Cache");
preCacheThread.setPriority(Thread.MIN_PRIORITY); preCacheThread.setPriority(Thread.MIN_PRIORITY);
preCacheThread.start(); preCacheThread.start();
@ -731,7 +719,8 @@ public class BufferMgr {
* Pre-cache source file into cache file. This is intended to be run in a * Pre-cache source file into cache file. This is intended to be run in a
* dedicated thread when the source file is remote. * dedicated thread when the source file is remote.
*/ */
private void preCacheSourceFile() throws IOException { private void preCacheSourceFile() {
try {
if (!(sourceFile instanceof BufferFileAdapter)) { if (!(sourceFile instanceof BufferFileAdapter)) {
throw new UnsupportedOperationException("unsupported use of preCacheSourceFile"); throw new UnsupportedOperationException("unsupported use of preCacheSourceFile");
} }
@ -750,6 +739,20 @@ public class BufferMgr {
sourceFile.getIndexCount() + " buffers to cache"); sourceFile.getIndexCount() + " buffers to cache");
} }
} }
catch (InterruptedIOException e) {
// ignore
}
catch (IOException e) {
Msg.error(BufferMgr.this, "pre-cache failure: " + e.getMessage(), e);
}
finally {
synchronized (preCacheLock) {
preCacheStatus = PreCacheStatus.STOPPED;
preCacheThread = null;
preCacheLock.notifyAll();
}
}
}
/** /**
* Pre-cache an non-requested buffer from the sourceFile * Pre-cache an non-requested buffer from the sourceFile
@ -1744,6 +1747,51 @@ public class BufferMgr {
return false; return false;
} }
/**
* Set the source buffer file with a newer local buffer file version.
* Intended for use following a merge or commit operation only where a local checkout has been
* retained.
* @param versionedSourceBufferFile updated local source buffer file opened for versioning
* update (NOTE: file itself is read-only).
* @throws IOException if an IO error occurs
*/
public void setDBVersionedSourceFile(LocalManagedBufferFile versionedSourceBufferFile)
throws IOException {
synchronized (snapshotLock) {
synchronized (this) {
if (!(sourceFile instanceof LocalManagedBufferFile)) {
throw new UnsupportedOperationException(getClass().getSimpleName() +
".setDBSourceFile not allowed: " + sourceFile.getClass());
}
if (bufferSize != sourceFile.getBufferSize()) {
throw new IllegalArgumentException("Buffer size mismatch");
}
if (corruptedState) {
throw new IOException("Corrupted BufferMgr state");
}
if (lockCount != 0) {
throw new IOException("Attempted checkout update while buffers are locked");
}
stopPreCache();
clearCheckpoints();
doSetSourceFile(versionedSourceBufferFile);
// re-initialize cached file data
int cnt = sourceFile.getIndexCount();
indexProvider = new IndexProvider(cnt, sourceFile.getFreeIndexes());
bufferTable = new ObjectArray(cnt + INITIAL_BUFFER_TABLE_SIZE);
initializeCache();
}
}
}
/** /**
* Save the current set of buffers to a new version of the source buffer file. * Save the current set of buffers to a new version of the source buffer file.
* If the buffer manager was not instantiated with a source file an * If the buffer manager was not instantiated with a source file an
@ -1825,7 +1873,7 @@ public class BufferMgr {
monitor.setCancelEnabled(oldCancelState & !monitor.isCancelled()); monitor.setCancelEnabled(oldCancelState & !monitor.isCancelled());
} }
setSourceFile(outFile); doSetSourceFile(outFile);
} }
} }
} }
@ -1881,7 +1929,7 @@ public class BufferMgr {
monitor.setCancelEnabled(true); monitor.setCancelEnabled(true);
} }
if (associateWithNewFile) { if (associateWithNewFile) {
setSourceFile(outFile); doSetSourceFile(outFile);
} }
} }
} }
@ -1965,7 +2013,7 @@ public class BufferMgr {
} }
} }
private void setSourceFile(BufferFile newFile) { private void doSetSourceFile(BufferFile newFile) {
// Close buffer file // Close buffer file
if (sourceFile != null) { if (sourceFile != null) {

View file

@ -157,7 +157,6 @@ public class LocalManagedBufferFile extends LocalBufferFile implements ManagedBu
* <code>preSaveThread</code> corresponds to the PreSaveTask which creates the * <code>preSaveThread</code> corresponds to the PreSaveTask which creates the
* preSaveFile when this buffer file is updateable. * preSaveFile when this buffer file is updateable.
*/ */
//private Thread preSaveThread;
private PreSaveTask preSaveTask; private PreSaveTask preSaveTask;
/** /**
@ -228,8 +227,8 @@ public class LocalManagedBufferFile extends LocalBufferFile implements ManagedBu
setFreeIndexes(versionFileHandler.getFreeIndexList()); setFreeIndexes(versionFileHandler.getFreeIndexList());
String[] names = versionFileHandler.getOldParameterNames(); String[] names = versionFileHandler.getOldParameterNames();
clearParameters(); clearParameters();
for (int i = 0; i < names.length; i++) { for (String name : names) {
setParameter(names[i], versionFileHandler.getOldParameter(names[i])); setParameter(name, versionFileHandler.getOldParameter(name));
} }
} }

View file

@ -17,6 +17,7 @@ package ghidra.framework.data;
import java.io.IOException; import java.io.IOException;
import javax.help.UnsupportedOperationException;
import javax.swing.Icon; import javax.swing.Icon;
import ghidra.framework.model.*; import ghidra.framework.model.*;
@ -83,9 +84,8 @@ public interface ContentHandler<T extends DomainObjectAdapter> extends Extension
* @throws VersionException if unable to handle file content due to version * @throws VersionException if unable to handle file content due to version
* difference which could not be handled. * difference which could not be handled.
*/ */
T getImmutableObject(FolderItem item, Object consumer, int version, T getImmutableObject(FolderItem item, Object consumer, int version, int minChangeVersion,
int minChangeVersion, TaskMonitor monitor) TaskMonitor monitor) throws IOException, CancelledException, VersionException;
throws IOException, CancelledException, VersionException;
/** /**
* Open a folder item for read-only use. While changes are permitted on the * Open a folder item for read-only use. While changes are permitted on the
@ -104,9 +104,8 @@ public interface ContentHandler<T extends DomainObjectAdapter> extends Extension
* @throws VersionException if unable to handle file content due to version * @throws VersionException if unable to handle file content due to version
* difference which could not be handled. * difference which could not be handled.
*/ */
T getReadOnlyObject(FolderItem item, int version, boolean okToUpgrade, T getReadOnlyObject(FolderItem item, int version, boolean okToUpgrade, Object consumer,
Object consumer, TaskMonitor monitor) TaskMonitor monitor) throws IOException, VersionException, CancelledException;
throws IOException, VersionException, CancelledException;
/** /**
* Open a folder item for update. Changes made to the returned object may be * Open a folder item for update. Changes made to the returned object may be
@ -127,8 +126,8 @@ public interface ContentHandler<T extends DomainObjectAdapter> extends Extension
* @throws VersionException if unable to handle file content due to version * @throws VersionException if unable to handle file content due to version
* difference which could not be handled. * difference which could not be handled.
*/ */
T getDomainObject(FolderItem item, FileSystem userfs, long checkoutId, T getDomainObject(FolderItem item, FileSystem userfs, long checkoutId, boolean okToUpgrade,
boolean okToUpgrade, boolean okToRecover, Object consumer, TaskMonitor monitor) boolean okToRecover, Object consumer, TaskMonitor monitor)
throws IOException, CancelledException, VersionException; throws IOException, CancelledException, VersionException;
/** /**
@ -204,4 +203,47 @@ public interface ContentHandler<T extends DomainObjectAdapter> extends Extension
return null; return null;
} }
/**
* Determine if this content handler supports the use of
* {@link #resetDBSourceFile(FolderItem, DomainObjectAdapterDB)} .
* <p>
* A versioned {@link DomainObjectAdapterDB domain object} open for update may have its
* underlying database reset to the latest buffer file version:
* <ol>
* <li>The {@link #resetDBSourceFile(FolderItem, DomainObjectAdapterDB)} method is
* invoked (synchronized on filesystem) to reset the underlying database source file and
* and any corresponding change sets held by the specified domain object to the latest
* version,</li>
* <li>afterwhich the caller must {@link DomainObjectAdapter#invalidate() invalidate} the domain
* object instance which will clear all caches and generate a {@link DomainObjectEvent#RESTORED}
* event.</li>
* </ol>
* @return true if this content handler supports DB source file replacement, else false
*/
public default boolean canResetDBSourceFile() {
return false;
}
/**
* Reset the database for the specified domain object to its latest buffer file version.
* It is very important that the specified folder item matches the item which was used to
* originally open the specified domain object. This method should be invoked with a
* filesystem lock.
* <p>
* Following the invocation of this method, the specified domain object should be
* {@link DomainObjectAdapter#invalidate() invalidated} without a filesystem lock.
*
* @param item local versioned database folder item currently checked-out. An error will be
* thrown if not an instanceof LocalDatabaseItem. This should always be the case for an item
* which has just processed a versioning action with a retained checkout (e.g., checkin,
* merge, add-to-version-control).
* @param domainObj domain object which is currently open for update
* @throws IOException if an IO error occurs
* @throws IllegalArgumentException if invalid or unsupported arguments are provided
*/
public default void resetDBSourceFile(FolderItem item, DomainObjectAdapterDB domainObj)
throws IOException {
throw new UnsupportedOperationException();
}
} }

View file

@ -17,6 +17,8 @@ package ghidra.framework.data;
import java.io.IOException; import java.io.IOException;
import javax.help.UnsupportedOperationException;
import db.DBHandle; import db.DBHandle;
import db.buffers.ManagedBufferFile; import db.buffers.ManagedBufferFile;
import ghidra.framework.store.*; import ghidra.framework.store.*;
@ -55,9 +57,8 @@ public abstract class DBContentHandler<T extends DomainObjectAdapterDB>
FileSystem fs, String path, String name, TaskMonitor monitor) FileSystem fs, String path, String name, TaskMonitor monitor)
throws InvalidNameException, CancelledException, IOException { throws InvalidNameException, CancelledException, IOException {
DBHandle dbh = domainObj.getDBHandle(); DBHandle dbh = domainObj.getDBHandle();
ManagedBufferFile bf = ManagedBufferFile bf = fs.createDatabase(path, name, FileIDFactory.createFileID(),
fs.createDatabase(path, name, FileIDFactory.createFileID(), contentType, contentType, dbh.getBufferSize(), SystemUtilities.getUserName(), null);
dbh.getBufferSize(), SystemUtilities.getUserName(), null);
long checkoutId = bf.getCheckinID(); // item remains checked-out after saveAs long checkoutId = bf.getCheckinID(); // item remains checked-out after saveAs
boolean success = false; boolean success = false;
try { try {

View file

@ -21,7 +21,7 @@ import ghidra.util.exception.CancelledException;
/** /**
* <code>DefaultCheckinHandler</code> provides a simple * <code>DefaultCheckinHandler</code> provides a simple
* check-in handler for use with * check-in handler for use with
* {@link DomainFile#checkin(CheckinHandler, boolean, ghidra.util.task.TaskMonitor)} * {@link DomainFile#checkin(CheckinHandler, ghidra.util.task.TaskMonitor)}
*/ */
public class DefaultCheckinHandler implements CheckinHandler { public class DefaultCheckinHandler implements CheckinHandler {

View file

@ -181,57 +181,31 @@ class DomainFileIndex implements DomainFolderChangeListener {
return null; return null;
} }
@Override
public void domainFileAdded(DomainFile file) { public void domainFileAdded(DomainFile file) {
updateFileEntry((GhidraFile) file); updateFileEntry((GhidraFile) file);
} }
@Override
public void domainFileMoved(DomainFile file, DomainFolder oldParent, String oldName) { public void domainFileMoved(DomainFile file, DomainFolder oldParent, String oldName) {
updateFileEntry((GhidraFile) file); updateFileEntry((GhidraFile) file);
} }
public void domainFileObjectClosed(DomainFile file, DomainObject object) { @Override
// no-op
}
public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) {
// no-op
}
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
// no-op
}
public void domainFileRemoved(DomainFolder parent, String name, String fileID) { public void domainFileRemoved(DomainFolder parent, String name, String fileID) {
fileIdToPathIndex.remove(fileID); fileIdToPathIndex.remove(fileID);
} }
@Override
public void domainFileRenamed(DomainFile file, String oldName) { public void domainFileRenamed(DomainFile file, String oldName) {
updateFileEntry((GhidraFile) file); updateFileEntry((GhidraFile) file);
} }
@Override
public void domainFileStatusChanged(DomainFile file, boolean fileIDset) { public void domainFileStatusChanged(DomainFile file, boolean fileIDset) {
if (fileIDset) { if (fileIDset) {
updateFileEntry((GhidraFile) file); updateFileEntry((GhidraFile) file);
} }
} }
public void domainFolderAdded(DomainFolder folder) {
// no-op
}
public void domainFolderMoved(DomainFolder folder, DomainFolder oldParent) {
// no-op
}
public void domainFolderRemoved(DomainFolder parent, String name) {
// no-op
}
public void domainFolderRenamed(DomainFolder folder, String oldName) {
// no-op
}
public void domainFolderSetActive(DomainFolder folder) {
// no-op
}
} }

View file

@ -398,7 +398,7 @@ public class DomainFileProxy implements DomainFile {
} }
@Override @Override
public void checkin(CheckinHandler checkinHandler, boolean okToUpgrade, TaskMonitor monitor) public void checkin(CheckinHandler checkinHandler, TaskMonitor monitor)
throws IOException, VersionException, CancelledException { throws IOException, VersionException, CancelledException {
throw new UnsupportedOperationException("Repository operations not supported"); throw new UnsupportedOperationException("Repository operations not supported");
} }

View file

@ -26,8 +26,7 @@ class DomainFolderChangeListenerList implements DomainFolderChangeListener {
private DomainFileIndex fileIndex; private DomainFileIndex fileIndex;
/** CopyOnWriteArrayList prevents the need for synchronization */ /** CopyOnWriteArrayList prevents the need for synchronization */
private List<DomainFolderChangeListener> list = private List<DomainFolderChangeListener> list = new CopyOnWriteArrayList<>();
new CopyOnWriteArrayList<>();
DomainFolderChangeListenerList(DomainFileIndex fileIndex) { DomainFolderChangeListenerList(DomainFileIndex fileIndex) {
this.fileIndex = fileIndex; this.fileIndex = fileIndex;
@ -199,19 +198,6 @@ class DomainFolderChangeListenerList implements DomainFolderChangeListener {
}); });
} }
@Override
public void domainFileObjectReplaced(final DomainFile file, final DomainObject oldObject) {
fileIndex.domainFileObjectReplaced(file, oldObject);
if (list.isEmpty()) {
return;
}
Swing.runNow(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFileObjectReplaced(file, oldObject);
}
});
}
public void clearAll() { public void clearAll() {
list.clear(); list.clear();
} }

View file

@ -81,8 +81,8 @@ public abstract class DomainObjectAdapter implements DomainObject {
* with consumer. * with consumer.
* *
* @param name name of the object * @param name name of the object
* @param timeInterval the time (in milliseconds) to wait before the event queue is flushed. If * @param timeInterval the time (in milliseconds) to wait before the event queue is flushed.
* a new event comes in before the time expires, the timer is reset. * If a new event comes in before the time expires the timer is reset.
* @param consumer the object that created this domain object * @param consumer the object that created this domain object
*/ */
protected DomainObjectAdapter(String name, int timeInterval, Object consumer) { protected DomainObjectAdapter(String name, int timeInterval, Object consumer) {
@ -98,6 +98,15 @@ public abstract class DomainObjectAdapter implements DomainObject {
} }
} }
/**
* Invalidates any caching in a program and generate a {@link DomainObjectEvent#RESTORED}
* event.
* NOTE: Over-using this method can adversely affect system performance.
*/
public void invalidate() {
fireEvent(new DomainObjectChangeRecord(DomainObjectEvent.RESTORED));
}
@Override @Override
public void release(Object consumer) { public void release(Object consumer) {
synchronized (consumers) { synchronized (consumers) {

View file

@ -37,32 +37,6 @@ import ghidra.util.task.TaskMonitor;
* concept of starting a transaction before a change is made to the * concept of starting a transaction before a change is made to the
* domain object and ending the transaction. The transaction allows for * domain object and ending the transaction. The transaction allows for
* undo/redo changes. * undo/redo changes.
*
* The implementation class must also satisfy the following requirements:
* <pre>
*
* 1. The following constructor signature must be implemented:
*
* **
* * Constructs new Domain Object
* * @param dbh a handle to an open domain object database.
* * @param openMode one of:
* * READ_ONLY: the original database will not be modified
* * UPDATE: the database can be written to.
* * UPGRADE: the database is upgraded to the latest schema as it is opened.
* * @param monitor TaskMonitor that allows the open to be cancelled.
* * @param consumer the object that keeping the program open.
* *
* * @throws IOException if an error accessing the database occurs.
* * @throws VersionException if database version does not match implementation. UPGRADE may be possible.
* **
* public DomainObjectAdapterDB(DBHandle dbh, int openMode, TaskMonitor monitor, Object consumer) throws IOException, VersionException
*
* 2. The following static field must be provided:
*
* public static final String CONTENT_TYPE
*
* </pre>
*/ */
public abstract class DomainObjectAdapterDB extends DomainObjectAdapter public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
implements ErrorHandler, DBConstants { implements ErrorHandler, DBConstants {
@ -100,12 +74,10 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
/** /**
* Construct a new DomainObjectAdapterDB object. * Construct a new DomainObjectAdapterDB object.
* If construction of this object fails, be sure to release with consumer
* @param dbh database handle * @param dbh database handle
* @param name name of the domain object * @param name name of the domain object
* @param timeInterval the time (in milliseconds) to wait before the * @param timeInterval the time (in milliseconds) to wait before the event queue is flushed.
* event queue is flushed. If a new event comes in before the time expires, * If a new event comes in before the time expires the timer is reset.
* the timer is reset.
* @param consumer the object that created this domain object * @param consumer the object that created this domain object
*/ */
protected DomainObjectAdapterDB(DBHandle dbh, String name, int timeInterval, Object consumer) { protected DomainObjectAdapterDB(DBHandle dbh, String name, int timeInterval, Object consumer) {
@ -129,6 +101,7 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
* prior to closing a transaction. * prior to closing a transaction.
*/ */
public void flushWriteCache() { public void flushWriteCache() {
// do nothing
} }
/** /**
@ -137,6 +110,7 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
* prior to aborting a transaction. * prior to aborting a transaction.
*/ */
public void invalidateWriteCache() { public void invalidateWriteCache() {
// do nothing
} }
/** /**
@ -491,6 +465,12 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
transactionMgr.clearUndo(notifyListeners); transactionMgr.clearUndo(notifyListeners);
} }
@Override
public void invalidate() {
clearCache(false);
super.invalidate();
}
protected void clearCache(boolean all) { protected void clearCache(boolean all) {
options.clearCache(); options.clearCache();
} }

View file

@ -382,10 +382,11 @@ public class GhidraFile implements DomainFile {
@Override @Override
public boolean canAddToRepository() { public boolean canAddToRepository() {
try { try {
return getFileData().canAddToRepository(); getFileData().checkCanAddToRepository();
return true;
} }
catch (IOException e) { catch (IOException e) {
fileError(e); // ignore
} }
return false; return false;
} }
@ -473,10 +474,9 @@ public class GhidraFile implements DomainFile {
} }
@Override @Override
public void checkin(CheckinHandler checkinHandler, boolean okToUpgrade, TaskMonitor monitor) public void checkin(CheckinHandler checkinHandler, TaskMonitor monitor)
throws IOException, VersionException, CancelledException { throws IOException, VersionException, CancelledException {
getFileData().checkin(checkinHandler, okToUpgrade, getFileData().checkin(checkinHandler, monitor != null ? monitor : TaskMonitor.DUMMY);
monitor != null ? monitor : TaskMonitor.DUMMY);
} }
@Override @Override

View file

@ -436,9 +436,8 @@ public class GhidraFileData {
ChangeSet getChangesByOthersSinceCheckout() throws VersionException, IOException { ChangeSet getChangesByOthersSinceCheckout() throws VersionException, IOException {
synchronized (fileSystem) { synchronized (fileSystem) {
if (versionedFolderItem != null && folderItem != null && folderItem.isCheckedOut()) { if (versionedFolderItem != null && folderItem != null && folderItem.isCheckedOut()) {
ContentHandler<?> ch = getContentHandler(); return getContentHandler().getChangeSet(versionedFolderItem,
return ch.getChangeSet(versionedFolderItem, folderItem.getCheckoutVersion(), folderItem.getCheckoutVersion(), versionedFolderItem.getCurrentVersion());
versionedFolderItem.getCurrentVersion());
} }
return null; return null;
} }
@ -478,6 +477,12 @@ public class GhidraFileData {
*/ */
DomainObject getDomainObject(Object consumer, boolean okToUpgrade, boolean okToRecover, DomainObject getDomainObject(Object consumer, boolean okToUpgrade, boolean okToRecover,
TaskMonitor monitor) throws VersionException, IOException, CancelledException { TaskMonitor monitor) throws VersionException, IOException, CancelledException {
// Don't allow this call while versioning operation is on-going
if (busy.get()) {
throw new FileInUseException("Cannot open during versioning operation");
}
FolderItem myFolderItem; FolderItem myFolderItem;
DomainObjectAdapter domainObj = null; DomainObjectAdapter domainObj = null;
synchronized (fileSystem) { synchronized (fileSystem) {
@ -494,9 +499,9 @@ public class GhidraFileData {
return domainObj; return domainObj;
} }
} }
ContentHandler<?> ch = getContentHandler(); ContentHandler<?> contentHandler = getContentHandler();
if (folderItem == null) { if (folderItem == null) {
DomainObjectAdapter doa = ch.getReadOnlyObject(versionedFolderItem, DomainObjectAdapter doa = contentHandler.getReadOnlyObject(versionedFolderItem,
DomainFile.DEFAULT_VERSION, true, consumer, monitor); DomainFile.DEFAULT_VERSION, true, consumer, monitor);
doa.setChanged(false); doa.setChanged(false);
DomainFileProxy proxy = new DomainFileProxy(name, parent.getPathname(), doa, DomainFileProxy proxy = new DomainFileProxy(name, parent.getPathname(), doa,
@ -506,7 +511,7 @@ public class GhidraFileData {
} }
myFolderItem = folderItem; myFolderItem = folderItem;
domainObj = ch.getDomainObject(myFolderItem, parent.getUserFileSystem(), domainObj = contentHandler.getDomainObject(myFolderItem, parent.getUserFileSystem(),
FolderItem.DEFAULT_CHECKOUT_ID, okToUpgrade, okToRecover, consumer, monitor); FolderItem.DEFAULT_CHECKOUT_ID, okToUpgrade, okToRecover, consumer, monitor);
projectData.setDomainObject(getPathname(), domainObj); projectData.setDomainObject(getPathname(), domainObj);
@ -567,8 +572,8 @@ public class GhidraFileData {
FolderItem item = FolderItem item =
(folderItem != null && version == DomainFile.DEFAULT_VERSION) ? folderItem (folderItem != null && version == DomainFile.DEFAULT_VERSION) ? folderItem
: versionedFolderItem; : versionedFolderItem;
ContentHandler<?> ch = getContentHandler(); DomainObjectAdapter doa =
DomainObjectAdapter doa = ch.getReadOnlyObject(item, version, true, consumer, monitor); getContentHandler().getReadOnlyObject(item, version, true, consumer, monitor);
doa.setChanged(false); doa.setChanged(false);
// Notify file manager of in-use domain object. // Notify file manager of in-use domain object.
@ -606,13 +611,14 @@ public class GhidraFileData {
throws VersionException, IOException, CancelledException { throws VersionException, IOException, CancelledException {
synchronized (fileSystem) { synchronized (fileSystem) {
DomainObjectAdapter obj = null; DomainObjectAdapter obj = null;
ContentHandler<?> ch = getContentHandler(); ContentHandler<?> contentHandler = getContentHandler();
if (versionedFolderItem == null || if (versionedFolderItem == null ||
(version == DomainFile.DEFAULT_VERSION && folderItem != null) || isHijacked()) { (version == DomainFile.DEFAULT_VERSION && folderItem != null) || isHijacked()) {
obj = ch.getImmutableObject(folderItem, consumer, version, -1, monitor); obj = contentHandler.getImmutableObject(folderItem, consumer, version, -1, monitor);
} }
else { else {
obj = ch.getImmutableObject(versionedFolderItem, consumer, version, -1, monitor); obj = contentHandler.getImmutableObject(versionedFolderItem, consumer, version, -1,
monitor);
} }
// Notify file manager of in-use domain object. // Notify file manager of in-use domain object.
@ -870,33 +876,6 @@ public class GhidraFileData {
} }
} }
/**
* Returns true if this private file may be added to the associated repository.
* @return true if can add to the repository
*/
boolean canAddToRepository() {
synchronized (fileSystem) {
try {
if (fileSystem.isReadOnly() || versionedFileSystem.isReadOnly()) {
return false;
}
if (folderItem == null || versionedFolderItem != null) {
return false;
}
if (folderItem.isCheckedOut()) {
return false;
}
if (isLinkFile()) {
return GhidraURL.isServerRepositoryURL(LinkHandler.getURL(folderItem));
}
return !getContentHandler().isPrivateContentType();
}
catch (IOException e) {
return false;
}
}
}
/** /**
* Returns true if this file may be checked-out from the associated repository. * Returns true if this file may be checked-out from the associated repository.
* User's with read-only repository access will not have checkout ability. * User's with read-only repository access will not have checkout ability.
@ -1021,6 +1000,32 @@ public class GhidraFileData {
} }
} }
/**
* Perform neccessary check to ensure this file may be added to version control.
* @throws IOException if any checks fail or other IO error occurs
*/
void checkCanAddToRepository() throws IOException {
if (!versionedFileSystem.isOnline()) {
throw new NotConnectedException("Not connected to repository server");
}
if (fileSystem.isReadOnly() || versionedFileSystem.isReadOnly()) {
throw new ReadOnlyException(
"versioning permitted within writeable project and repository only");
}
if (folderItem == null) {
throw new FileNotFoundException("File not found");
}
if (folderItem.isCheckedOut() || versionedFolderItem != null) {
throw new IOException("File already versioned");
}
if (isLinkFile() && !GhidraURL.isServerRepositoryURL(LinkHandler.getURL(folderItem))) {
throw new IOException("Local project link-file may not be versioned");
}
if (getContentHandler().isPrivateContentType()) {
throw new IOException("Content may not be versioned: " + getContentType());
}
}
/** /**
* Adds this private file to version control. * Adds this private file to version control.
* @param comment new version comment * @param comment new version comment
@ -1033,18 +1038,28 @@ public class GhidraFileData {
*/ */
void addToVersionControl(String comment, boolean keepCheckedOut, TaskMonitor monitor) void addToVersionControl(String comment, boolean keepCheckedOut, TaskMonitor monitor)
throws IOException, CancelledException { throws IOException, CancelledException {
DomainObjectAdapter oldDomainObj = null;
synchronized (fileSystem) { checkCanAddToRepository();
if (!canAddToRepository()) {
if (fileSystem.isReadOnly() || versionedFileSystem.isReadOnly()) { if (busy.getAndSet(true)) {
throw new ReadOnlyException( throw new FileInUseException(name + " is busy");
"addToVersionControl permitted within writeable project and repository only");
}
throw new IOException("addToVersionControl not allowed for file");
} }
DomainObjectAdapterDB inUseDomainObj = null;
projectData.mergeStarted();
try {
inUseDomainObj = getAndLockInUseDomainObjectForMergeUpdate("checkin");
if (isLinkFile()) { if (isLinkFile()) {
keepCheckedOut = false; keepCheckedOut = false;
} }
else if (inUseDomainObj != null && !keepCheckedOut) {
keepCheckedOut = true;
Msg.warn(this, "File currently open - must keep checked-out: " + name);
}
synchronized (fileSystem) {
String parentPath = parent.getPathname(); String parentPath = parent.getPathname();
String user = ClientUtil.getUserName(); String user = ClientUtil.getUserName();
try { try {
@ -1052,8 +1067,8 @@ public class GhidraFileData {
DatabaseItem databaseItem = (DatabaseItem) folderItem; DatabaseItem databaseItem = (DatabaseItem) folderItem;
BufferFile bufferFile = databaseItem.open(); BufferFile bufferFile = databaseItem.open();
try { try {
versionedFolderItem = versionedFileSystem.createDatabase(parentPath, name, versionedFolderItem = versionedFileSystem.createDatabase(parentPath,
folderItem.getFileID(), bufferFile, comment, name, folderItem.getFileID(), bufferFile, comment,
folderItem.getContentType(), false, monitor, user); folderItem.getContentType(), false, monitor, user);
} }
finally { finally {
@ -1064,8 +1079,8 @@ public class GhidraFileData {
DataFileItem dataFileItem = (DataFileItem) folderItem; DataFileItem dataFileItem = (DataFileItem) folderItem;
InputStream istream = dataFileItem.getInputStream(); InputStream istream = dataFileItem.getInputStream();
try { try {
versionedFolderItem = versionedFileSystem.createDataFile(parentPath, name, versionedFolderItem = versionedFileSystem.createDataFile(parentPath,
istream, comment, folderItem.getContentType(), monitor); name, istream, comment, folderItem.getContentType(), monitor);
} }
finally { finally {
istream.close(); istream.close();
@ -1079,10 +1094,11 @@ public class GhidraFileData {
throw new AssertException("Unexpected error", e); throw new AssertException("Unexpected error", e);
} }
oldDomainObj = getOpenedDomainObject();
if (keepCheckedOut) { if (keepCheckedOut) {
boolean exclusive = !versionedFileSystem.isShared();
// Maintain exclusive chekout if private repository or file is open for update
boolean exclusive = !versionedFileSystem.isShared() || (inUseDomainObj != null);
ProjectLocator projectLocator = parent.getProjectLocator(); ProjectLocator projectLocator = parent.getProjectLocator();
CheckoutType checkoutType; CheckoutType checkoutType;
if (projectLocator.isTransient()) { if (projectLocator.isTransient()) {
@ -1102,7 +1118,7 @@ public class GhidraFileData {
checkout.getCheckoutVersion(), folderItem.getCurrentVersion()); checkout.getCheckoutVersion(), folderItem.getCurrentVersion());
} }
else { else {
if (oldDomainObj == null) { // NOTE: file open read-only may prevent removal and result in hijack
try { try {
folderItem.delete(-1, ClientUtil.getUserName()); folderItem.delete(-1, ClientUtil.getUserName());
folderItem = null; folderItem = null;
@ -1111,27 +1127,23 @@ public class GhidraFileData {
// Ignore - should result in Hijacked file // Ignore - should result in Hijacked file
} }
} }
}
if (oldDomainObj != null) {
// TODO: Develop way to re-use and re-init domain object instead of a switch-a-roo approach if (inUseDomainObj != null) {
getContentHandler().resetDBSourceFile(folderItem, inUseDomainObj);
}
} // end of synchronized block
projectData.clearDomainObject(getPathname()); if (inUseDomainObj != null) {
inUseDomainObj.invalidate();
oldDomainObj.setDomainFile(new DomainFileProxy("~" + name, oldDomainObj));
oldDomainObj.setTemporary(true);
} }
} }
if (oldDomainObj != null) { finally {
// Complete re-open of file unlockDomainObject(inUseDomainObj);
DomainFile df = getDomainFile(); busy.set(false);
listener.domainFileObjectClosed(df, oldDomainObj); projectData.mergeEnded();
listener.domainFileObjectReplaced(df, oldDomainObj);
}
if (!keepCheckedOut) {
parent.deleteLocalFolderIfEmpty(); parent.deleteLocalFolderIfEmpty();
parent.fileChanged(name);
} }
statusChanged();
} }
/** /**
@ -1258,7 +1270,8 @@ public class GhidraFileData {
if (versionedFolderItem.getCurrentVersion() != folderItem.getCheckoutVersion()) { if (versionedFolderItem.getCurrentVersion() != folderItem.getCheckoutVersion()) {
return false; return false;
} }
// TODO: assumes folderItem is local - should probably defer createNewVersion to folderItem if possible (requires refactor) // TODO: assumes folderItem is local - should probably defer createNewVersion
// to folderItem if possible (requires refactor)
srcFile = (LocalManagedBufferFile) ((DatabaseItem) folderItem).open(); srcFile = (LocalManagedBufferFile) ((DatabaseItem) folderItem).open();
} }
@ -1266,18 +1279,15 @@ public class GhidraFileData {
if (checkinHandler.createKeepFile()) { if (checkinHandler.createKeepFile()) {
DomainObject sourceObj = null; DomainObject sourceObj = null;
try { try {
ContentHandler<?> ch = getContentHandler(); sourceObj = getContentHandler().getImmutableObject(folderItem, this,
sourceObj = ch.getImmutableObject(folderItem, this, DomainFile.DEFAULT_VERSION, DomainFile.DEFAULT_VERSION, -1, monitor);
-1, monitor);
createKeepFile(sourceObj, monitor); createKeepFile(sourceObj, monitor);
} }
catch (VersionException e) { catch (VersionException e) {
// ignore - unable to create keep file // ignore - unable to create keep file
} }
finally { finally {
if (sourceObj != null) { release(sourceObj);
sourceObj.release(this);
}
} }
} }
monitor.checkCancelled(); monitor.checkCancelled();
@ -1298,13 +1308,13 @@ public class GhidraFileData {
} }
/** /**
* Verify that current user is the checkout user for this file * Verify checkout status and that current user is the checkout user for this file
* @param operationName name of user case (e.g., checkin) * @param operationName name of user case (e.g., checkin)
* @throws IOException if server/repository will not permit current user to checkin, * @throws IOException if server/repository will not permit current user to checkin,
* or update checkout version of current file. (i.e., server login does not match * or update checkout version of current file. (i.e., server login does not match
* user name used at time of initial checkout) * user name used at time of initial checkout)
*/ */
private void verifyRepoUser(String operationName) throws IOException { private void verifyCheckout(String operationName) throws IOException {
if (versionedFileSystem instanceof LocalFileSystem) { if (versionedFileSystem instanceof LocalFileSystem) {
return; // rely on local project ownership return; // rely on local project ownership
} }
@ -1327,15 +1337,13 @@ public class GhidraFileData {
* Performs check in to associated repository. File must be checked-out * Performs check in to associated repository. File must be checked-out
* and modified since checkout. * and modified since checkout.
* @param checkinHandler provides user input data to complete checkin process. * @param checkinHandler provides user input data to complete checkin process.
* @param okToUpgrade if true an upgrade will be performed if needed
* @param monitor the TaskMonitor. * @param monitor the TaskMonitor.
* @throws IOException if an IO or access error occurs * @throws IOException if an IO or access error occurs
* @throws VersionException if unable to handle domain object version in versioned filesystem. * @throws VersionException if unable to handle domain object version in versioned filesystem.
* If okToUpgrade was false, check exception to see if it can be upgraded * We are unable to upgrade since this would only occur if checkout is not exclusive.
* sometime after doing a checkout.
* @throws CancelledException if task monitor cancelled operation * @throws CancelledException if task monitor cancelled operation
*/ */
void checkin(CheckinHandler checkinHandler, boolean okToUpgrade, TaskMonitor monitor) void checkin(CheckinHandler checkinHandler, TaskMonitor monitor)
throws IOException, VersionException, CancelledException { throws IOException, VersionException, CancelledException {
if (!versionedFileSystem.isOnline()) { if (!versionedFileSystem.isOnline()) {
@ -1357,15 +1365,29 @@ public class GhidraFileData {
if (!modifiedSinceCheckout()) { if (!modifiedSinceCheckout()) {
throw new IOException("File has not been modified since checkout"); throw new IOException("File has not been modified since checkout");
} }
verifyRepoUser("checkin"); verifyCheckout("checkin");
if (monitor == null) { if (monitor == null) {
monitor = TaskMonitor.DUMMY; monitor = TaskMonitor.DUMMY;
} }
if (busy.getAndSet(true)) { if (busy.getAndSet(true)) {
throw new FileInUseException(name + " is busy"); throw new FileInUseException(name + " is busy");
} }
DomainObjectAdapterDB inUseDomainObj = null;
projectData.mergeStarted(); projectData.mergeStarted();
try { try {
ContentHandler<?> contentHandler = getContentHandler();
inUseDomainObj = getAndLockInUseDomainObjectForMergeUpdate("checkin");
boolean keepCheckedOut = checkinHandler.keepCheckedOut();
if (inUseDomainObj != null && !keepCheckedOut) {
keepCheckedOut = true;
Msg.warn(this, "File currently open - must keep checked-out: " + name);
}
boolean quickCheckin = ALWAYS_MERGE ? false : quickCheckin(checkinHandler, monitor); boolean quickCheckin = ALWAYS_MERGE ? false : quickCheckin(checkinHandler, monitor);
if (!quickCheckin) { if (!quickCheckin) {
@ -1377,9 +1399,8 @@ public class GhidraFileData {
Msg.info(this, "Checkin with merge for " + name); Msg.info(this, "Checkin with merge for " + name);
ContentHandler<?> ch = getContentHandler(); DomainObjectAdapter checkinObj = contentHandler.getDomainObject(versionedFolderItem,
DomainObjectAdapter checkinObj = ch.getDomainObject(versionedFolderItem, null, null, folderItem.getCheckoutId(), false, false, this, monitor);
folderItem.getCheckoutId(), okToUpgrade, false, this, monitor);
checkinObj.setDomainFile(new DomainFileProxy(name, getParent().getPathname(), checkinObj.setDomainFile(new DomainFileProxy(name, getParent().getPathname(),
checkinObj, versionedFolderItem.getCurrentVersion() + 1, fileID, checkinObj, versionedFolderItem.getCurrentVersion() + 1, fileID,
parent.getProjectLocator())); parent.getProjectLocator()));
@ -1390,15 +1411,15 @@ public class GhidraFileData {
try { try {
synchronized (fileSystem) { synchronized (fileSystem) {
int coVer = folderItem.getCheckoutVersion(); int coVer = folderItem.getCheckoutVersion();
sourceObj = ch.getImmutableObject(folderItem, this, sourceObj = contentHandler.getImmutableObject(folderItem, this,
DomainFile.DEFAULT_VERSION, -1, monitor); DomainFile.DEFAULT_VERSION, -1, monitor);
originalObj = originalObj = contentHandler.getImmutableObject(versionedFolderItem, this,
ch.getImmutableObject(versionedFolderItem, this, coVer, -1, monitor); coVer, -1, monitor);
latestObj = ch.getImmutableObject(versionedFolderItem, this, latestObj = contentHandler.getImmutableObject(versionedFolderItem, this,
DomainFile.DEFAULT_VERSION, coVer, monitor); DomainFile.DEFAULT_VERSION, coVer, monitor);
} }
DomainObjectMergeManager mergeMgr = DomainObjectMergeManager mergeMgr = contentHandler.getMergeManager(checkinObj,
ch.getMergeManager(checkinObj, sourceObj, originalObj, latestObj); sourceObj, originalObj, latestObj);
if (!mergeMgr.merge(monitor)) { if (!mergeMgr.merge(monitor)) {
Msg.info(this, "Checkin with merge terminated for " + name); Msg.info(this, "Checkin with merge terminated for " + name);
@ -1417,27 +1438,14 @@ public class GhidraFileData {
} }
finally { finally {
checkinObj.release(this); checkinObj.release(this);
if (sourceObj != null) { release(sourceObj);
sourceObj.release(this); release(originalObj);
} release(latestObj);
if (originalObj != null) {
originalObj.release(this);
}
if (latestObj != null) {
latestObj.release(this);
} }
} }
}
DomainObjectAdapter oldDomainObj = null;
FolderItem oldLocalItem = null;
boolean keepCheckedOut = checkinHandler.keepCheckedOut();
synchronized (fileSystem) { synchronized (fileSystem) {
oldDomainObj = getOpenedDomainObject();
versionedFolderItem = versionedFileSystem.getItem(parent.getPathname(), name); versionedFolderItem = versionedFileSystem.getItem(parent.getPathname(), name);
if (versionedFolderItem == null) { if (versionedFolderItem == null) {
throw new IOException("Checkin failed, versioned item not found"); throw new IOException("Checkin failed, versioned item not found");
@ -1456,7 +1464,17 @@ public class GhidraFileData {
} }
finally { finally {
if (!success) { if (!success) {
// Failed to update checkout for unknown reason
try { try {
if (inUseDomainObj != null) {
// On error disassociate open domain object from this file
projectData.clearDomainObject(getPathname());
// An invalid version (-2) is specified to avoid file match
inUseDomainObj.setDomainFile(new DomainFileProxy(name,
parent.getPathname(), inUseDomainObj, -2, fileID,
parent.getProjectLocator()));
inUseDomainObj.setTemporary(true);
}
undoCheckout(false, true); undoCheckout(false, true);
} }
catch (IOException e) { catch (IOException e) {
@ -1465,54 +1483,72 @@ public class GhidraFileData {
} }
} }
} }
else {
if (oldDomainObj != null) {
oldLocalItem = folderItem;
folderItem = null;
}
else { else {
undoCheckout(false, true); undoCheckout(false, true);
} }
}
if (oldDomainObj != null) {
// TODO: Develop way to re-use and re-init domain object instead of a switch-a-roo approach if (inUseDomainObj != null) {
contentHandler.resetDBSourceFile(folderItem, inUseDomainObj);
projectData.clearDomainObject(getPathname());
oldDomainObj.setDomainFile(new DomainFileProxy(name, parent.getPathname(),
oldDomainObj, -2, fileID, parent.getProjectLocator())); // invalid version (-2) specified to avoid file match
oldDomainObj.setTemporary(true);
}
} }
if (oldDomainObj != null) { } // end of synchronized block
// complete re-open of domain file
DomainFile df = getDomainFile();
listener.domainFileObjectClosed(df, oldDomainObj);
listener.domainFileObjectReplaced(df, oldDomainObj);
}
if (oldLocalItem != null) { if (inUseDomainObj != null) {
synchronized (fileSystem) { inUseDomainObj.invalidate();
// Undo checkout of old item - this will fail on Windows if item is open
long checkoutId = oldLocalItem.getCheckoutId();
oldLocalItem.delete(-1, ClientUtil.getUserName());
versionedFolderItem.terminateCheckout(checkoutId, true);
}
} }
} }
finally { finally {
unlockDomainObject(inUseDomainObj);
busy.set(false); busy.set(false);
try { projectData.mergeEnded();
parent.deleteLocalFolderIfEmpty(); parent.deleteLocalFolderIfEmpty();
parent.fileChanged(name); parent.fileChanged(name);
} }
finally { }
projectData.mergeEnded();
private void release(DomainObject domainObj) {
if (domainObj != null) {
domainObj.release(this);
} }
} }
private void unlockDomainObject(DomainObjectAdapterDB lockedDomainObject) {
try {
if (lockedDomainObject != null) {
lockedDomainObject.unlock();
}
}
catch (Exception e) {
Msg.error(this, "Unexpected " + getContentType() + " lock error: " + getName());
}
}
private DomainObjectAdapterDB getAndLockInUseDomainObjectForMergeUpdate(String operation)
throws IOException {
DomainObjectAdapterDB inUseDomainObj;
synchronized (fileSystem) {
DomainObjectAdapter domainObj = getOpenedDomainObject();
if (domainObj == null) {
return null;
}
// If we proceed with file in-use it must be instance of DomainObjectAdapterDB
if (!(domainObj instanceof DomainObjectAdapterDB)) {
throw new FileInUseException(name + " is in use");
}
inUseDomainObj = (DomainObjectAdapterDB) domainObj;
if (inUseDomainObj.isChanged()) {
throw new FileInUseException(name + " is in use w/ unsaved changes");
}
}
// Ensure that existing domain object will support DB merge update and is can be locked
ContentHandler<?> contentHandler = getContentHandler();
if (!contentHandler.canResetDBSourceFile() || !inUseDomainObj.lock(operation) ||
inUseDomainObj.getDBHandle().hasUncommittedChanges()) {
throw new FileInUseException(name + " is in use");
}
return inUseDomainObj;
} }
/** /**
@ -1619,7 +1655,7 @@ public class GhidraFileData {
throw new IOException("File not checked out"); throw new IOException("File not checked out");
} }
if (!doForce) { if (!doForce) {
verifyRepoUser("undo-checkout"); verifyCheckout("undo-checkout");
long checkoutId = folderItem.getCheckoutId(); long checkoutId = folderItem.getCheckoutId();
versionedFolderItem.terminateCheckout(checkoutId, true); versionedFolderItem.terminateCheckout(checkoutId, true);
} }
@ -1666,7 +1702,7 @@ public class GhidraFileData {
return false; return false;
} }
private void createKeepFile(DomainObject oldDomainObj, TaskMonitor monitor) { private void createKeepFile(DomainObject sourceObj, TaskMonitor monitor) {
String keepName = name + ".keep"; String keepName = name + ".keep";
try { try {
GhidraFileData keepFileData = parent.getFileData(keepName, false); GhidraFileData keepFileData = parent.getFileData(keepName, false);
@ -1683,7 +1719,7 @@ public class GhidraFileData {
} }
keepName = getKeepName(); keepName = getKeepName();
Msg.info(this, "Creating old version keep file: " + keepName); Msg.info(this, "Creating old version keep file: " + keepName);
parent.createFile(keepName, oldDomainObj, monitor); parent.createFile(keepName, sourceObj, monitor);
} }
catch (InvalidNameException e) { catch (InvalidNameException e) {
throw new AssertException("Unexpected error", e); throw new AssertException("Unexpected error", e);
@ -1765,10 +1801,10 @@ public class GhidraFileData {
private void removeAssociatedUserDataFile() { private void removeAssociatedUserDataFile() {
try { try {
ContentHandler<?> ch = getContentHandler(); ContentHandler<?> contentHandler = getContentHandler();
if (ch instanceof DBWithUserDataContentHandler) { if (contentHandler instanceof DBWithUserDataContentHandler) {
FolderItem item = folderItem != null ? folderItem : versionedFolderItem; FolderItem item = folderItem != null ? folderItem : versionedFolderItem;
((DBWithUserDataContentHandler<?>) ch).removeUserDataFile(item, ((DBWithUserDataContentHandler<?>) contentHandler).removeUserDataFile(item,
parent.getUserFileSystem()); parent.getUserFileSystem());
} }
} }
@ -1812,7 +1848,7 @@ public class GhidraFileData {
if (canRecover()) { if (canRecover()) {
throw new IOException("File recovery data exists"); throw new IOException("File recovery data exists");
} }
verifyRepoUser("merge"); verifyCheckout("merge");
if (monitor == null) { if (monitor == null) {
monitor = TaskMonitor.DUMMY; monitor = TaskMonitor.DUMMY;
} }
@ -1821,8 +1857,11 @@ public class GhidraFileData {
} }
FolderItem tmpItem = null; FolderItem tmpItem = null;
DomainObjectAdapterDB inUseDomainObj = null;
projectData.mergeStarted(); projectData.mergeStarted();
try { try {
inUseDomainObj = getAndLockInUseDomainObjectForMergeUpdate("merge");
if (!modifiedSinceCheckout()) { if (!modifiedSinceCheckout()) {
// Quick merge // Quick merge
folderItem.updateCheckout(versionedFolderItem, true, monitor); folderItem.updateCheckout(versionedFolderItem, true, monitor);
@ -1830,17 +1869,17 @@ public class GhidraFileData {
else { else {
if (SystemUtilities.isInHeadlessMode()) { if (SystemUtilities.isInHeadlessMode()) {
throw new IOException( throw new IOException("Merge failed, merge is not supported in headless mode");
"Merge failed, file merge is not supported in headless mode");
} }
ContentHandler<?> ch = getContentHandler(); ContentHandler<?> contentHandler = getContentHandler();
// Test versioned file for VersionException // Test versioned file for VersionException
int mergeVer = versionedFolderItem.getCurrentVersion(); int mergeVer = versionedFolderItem.getCurrentVersion();
if (!okToUpgrade) { if (!okToUpgrade) {
DomainObject testObj = // verify remote version can be opened without verion error
ch.getReadOnlyObject(versionedFolderItem, mergeVer, false, this, monitor); DomainObject testObj = contentHandler.getReadOnlyObject(versionedFolderItem,
mergeVer, false, this, monitor);
testObj.release(this); testObj.release(this);
} }
@ -1866,21 +1905,21 @@ public class GhidraFileData {
tmpItem.setCheckout(checkoutId, folderItem.isCheckedOutExclusive(), mergeVer, 0); tmpItem.setCheckout(checkoutId, folderItem.isCheckedOutExclusive(), mergeVer, 0);
DomainObject mergeObj = DomainObject mergeObj = contentHandler.getDomainObject(tmpItem, null, -1,
ch.getDomainObject(tmpItem, null, -1, okToUpgrade, false, this, monitor); okToUpgrade, false, this, monitor);
DomainObject sourceObj = null; DomainObject sourceObj = null;
DomainObject originalObj = null; DomainObject originalObj = null;
DomainObject latestObj = null; // TODO: Is there some way to leverage the buffer file we already copied into tmpItem? Missing required change set DomainObject latestObj = null; // TODO: Is there some way to leverage the buffer file we already copied into tmpItem? Missing required change set
try { try {
sourceObj = ch.getImmutableObject(folderItem, this, DomainFile.DEFAULT_VERSION, sourceObj = contentHandler.getImmutableObject(folderItem, this,
-1, monitor); DomainFile.DEFAULT_VERSION, -1, monitor);
originalObj = originalObj = contentHandler.getImmutableObject(versionedFolderItem, this,
ch.getImmutableObject(versionedFolderItem, this, coVer, -1, monitor); coVer, -1, monitor);
latestObj = latestObj = contentHandler.getImmutableObject(versionedFolderItem, this,
ch.getImmutableObject(versionedFolderItem, this, mergeVer, coVer, monitor); mergeVer, coVer, monitor);
DomainObjectMergeManager mergeMgr = DomainObjectMergeManager mergeMgr =
ch.getMergeManager(mergeObj, sourceObj, originalObj, latestObj); contentHandler.getMergeManager(mergeObj, sourceObj, originalObj, latestObj);
if (!mergeMgr.merge(monitor)) { if (!mergeMgr.merge(monitor)) {
Msg.info(this, "Merge terminated for " + name); Msg.info(this, "Merge terminated for " + name);
@ -1888,19 +1927,14 @@ public class GhidraFileData {
} }
mergeObj.save("Merge with version " + mergeVer, monitor); mergeObj.save("Merge with version " + mergeVer, monitor);
createKeepFile(sourceObj, monitor); createKeepFile(sourceObj, monitor);
} }
finally { finally {
mergeObj.release(this); release(mergeObj);
if (sourceObj != null) { release(sourceObj);
sourceObj.release(this); release(originalObj);
} release(latestObj);
if (originalObj != null) {
originalObj.release(this);
}
if (latestObj != null) {
latestObj.release(this);
}
} }
// Update folder item // Update folder item
@ -1909,29 +1943,18 @@ public class GhidraFileData {
ClientUtil.getUserName()); ClientUtil.getUserName());
tmpItem = null; tmpItem = null;
Msg.info(this, "Merge completed for " + name); Msg.info(this, "Merge completed for " + name);
}
DomainObjectAdapter oldDomainObj = null; if (inUseDomainObj != null) {
contentHandler.resetDBSourceFile(folderItem, inUseDomainObj);
// TODO: Develop way to re-use and re-init domain object instead of a switch-a-roo approach
synchronized (fileSystem) {
oldDomainObj = getOpenedDomainObject();
if (oldDomainObj != null) {
projectData.clearDomainObject(getPathname());
oldDomainObj.setDomainFile(new DomainFileProxy("~" + name, oldDomainObj));
oldDomainObj.setTemporary(true);
} }
} }
if (oldDomainObj != null) { if (inUseDomainObj != null) {
// Complete re-open of file inUseDomainObj.invalidate();
DomainFile df = getDomainFile();
listener.domainFileObjectClosed(df, oldDomainObj);
listener.domainFileObjectReplaced(df, oldDomainObj);
} }
} }
finally { finally {
unlockDomainObject(inUseDomainObj);
busy.set(false); busy.set(false);
try { try {
if (tmpItem != null) { if (tmpItem != null) {
@ -2281,20 +2304,23 @@ public class GhidraFileData {
/** /**
* Returns an ordered map containing the metadata stored within a specific {@link FolderItem} * Returns an ordered map containing the metadata stored within a specific {@link FolderItem}
* database. The map contains key,value pairs and are ordered by their insertion order. * database. The map contains key,value pairs and are ordered by their insertion order.
* @param item folder item whose metadata should be read
* @return a map containing the metadata that has been associated with the corresponding domain * @return a map containing the metadata that has been associated with the corresponding domain
* object. Map will be empty for a non-database item. * object. Map will be empty for a non-database item.
*/ */
static Map<String, String> getMetadata(FolderItem item) { static Map<String, String> getMetadata(FolderItem item) {
if (!(item instanceof DatabaseItem databaseItem)) {
return new HashMap<>();
}
ManagedBufferFile bf = null;
DBHandle dbh = null;
GenericDomainObjectDB genericDomainObj = null; GenericDomainObjectDB genericDomainObj = null;
try { try {
if (item instanceof DatabaseItem) { bf = databaseItem.open();
DatabaseItem databaseItem = (DatabaseItem) item; dbh = new DBHandle(bf);
BufferFile bf = databaseItem.open();
DBHandle dbh = new DBHandle(bf);
genericDomainObj = new GenericDomainObjectDB(dbh); genericDomainObj = new GenericDomainObjectDB(dbh);
return genericDomainObj.getMetadata(); return genericDomainObj.getMetadata();
} }
}
catch (FileNotFoundException e) { catch (FileNotFoundException e) {
// file has been deleted, just return an empty map. // file has been deleted, just return an empty map.
} }
@ -2308,6 +2334,12 @@ public class GhidraFileData {
if (genericDomainObj != null) { if (genericDomainObj != null) {
genericDomainObj.release(); genericDomainObj.release();
} }
if (dbh != null) {
dbh.close();
}
if (bf != null) {
bf.dispose();
}
} }
return new HashMap<>(); return new HashMap<>();
} }

View file

@ -302,7 +302,7 @@ class LinkedGhidraFile implements LinkedDomainFile {
} }
@Override @Override
public void checkin(CheckinHandler checkinHandler, boolean okToUpgrade, TaskMonitor monitor) public void checkin(CheckinHandler checkinHandler, TaskMonitor monitor)
throws IOException, VersionException, CancelledException { throws IOException, VersionException, CancelledException {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View file

@ -96,8 +96,7 @@ public class ProjectDataTablePanel extends JPanel {
} }
}); });
gTable.getSelectionModel() gTable.getSelectionModel()
.addListSelectionListener( .addListSelectionListener(e -> plugin.getTool().contextChanged(null));
e -> plugin.getTool().contextChanged(null));
gTable.setDefaultRenderer(Date.class, new DateCellRenderer()); gTable.setDefaultRenderer(Date.class, new DateCellRenderer());
gTable.setDefaultRenderer(DomainFileType.class, new TypeCellRenderer()); gTable.setDefaultRenderer(DomainFileType.class, new TypeCellRenderer());
@ -364,11 +363,6 @@ public class ProjectDataTablePanel extends JPanel {
reload(); reload();
} }
@Override
public void domainFolderSetActive(DomainFolder folder) {
// don't care
}
@Override @Override
public void domainFileStatusChanged(DomainFile file, boolean fileIDset) { public void domainFileStatusChanged(DomainFile file, boolean fileIDset) {
if (ignoreChanges()) { if (ignoreChanges()) {
@ -379,24 +373,6 @@ public class ProjectDataTablePanel extends JPanel {
plugin.getTool().contextChanged(null); plugin.getTool().contextChanged(null);
} }
@Override
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
if (ignoreChanges()) {
return;
}
clearInfo(file);
table.repaint();
}
@Override
public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) {
// don't care
}
@Override
public void domainFileObjectClosed(DomainFile file, DomainObject object) {
// don't care
}
} }
/** /**

View file

@ -140,11 +140,6 @@ class ChangeManager implements DomainFolderChangeListener {
} }
} }
// @Override
// public void domainFileSaved(DomainFile file, DomainObject dobj) {
// treePanel.getActionManager().adjustActions();
// }
@Override @Override
public void domainFileStatusChanged(DomainFile file, boolean fileIDset) { public void domainFileStatusChanged(DomainFile file, boolean fileIDset) {
DomainFileNode fileNode = findDomainFileNode(file, true); DomainFileNode fileNode = findDomainFileNode(file, true);
@ -152,7 +147,6 @@ class ChangeManager implements DomainFolderChangeListener {
fileNode.refresh(); fileNode.refresh();
} }
treePanel.domainChange(); treePanel.domainChange();
// treePanel.getActionManager().adjustActions();
} }
private void getFolderPath(DomainFolder df, List<String> list) { private void getFolderPath(DomainFolder df, List<String> list) {
@ -221,27 +215,4 @@ class ChangeManager implements DomainFolderChangeListener {
} }
} }
/* (non-Javadoc)
* @see ghidra.framework.model.DomainFolderChangeListener#domainFileObjectReplaced(ghidra.framework.model.DomainFile, ghidra.framework.model.DomainObject)
*/
@Override
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
// ignored
}
/*
* @see ghidra.framework.model.DomainFolderChangeListener#domainFileObjectOpenedForUpdate(ghidra.framework.model.DomainFile, ghidra.framework.model.DomainObject)
*/
@Override
public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) {
// ignored
}
/*
* @see ghidra.framework.model.DomainFolderChangeListener#domainFileObjectClosed(ghidra.framework.model.DomainFile, ghidra.framework.model.DomainObject)
*/
@Override
public void domainFileObjectClosed(DomainFile file, DomainObject object) {
// ignored
}
} }

View file

@ -54,11 +54,9 @@ public class CheckInTask extends VersionControlTask implements CheckinHandler {
private void promptUser() throws CancelledException { private void promptUser() throws CancelledException {
if (newFile) { if (newFile) {
newFile = false; newFile = false;
if (monitor.isCancelled()) { monitor.checkCancelled();
throw new CancelledException();
}
if (actionID != VersionControlDialog.APPLY_TO_ALL) { if (actionID != VersionControlDialog.APPLY_TO_ALL) {
showDialog(false, df.getName(), df.isLinkFile()); // false==> checking in vs. showDialog(false, df);
// adding to version control // adding to version control
if (actionID == VersionControlDialog.CANCEL) { if (actionID == VersionControlDialog.CANCEL) {
monitor.cancel(); monitor.cancel();
@ -69,23 +67,16 @@ public class CheckInTask extends VersionControlTask implements CheckinHandler {
} }
} }
/* (non-Javadoc)
* @see ghidra.util.task.Task#run(ghidra.util.task.TaskMonitor)
*/
@Override @Override
public void run(TaskMonitor myMonitor) { public void run(TaskMonitor myMonitor) {
this.monitor = myMonitor; this.monitor = myMonitor;
myMonitor.setMessage("Examining selected file(s)"); myMonitor.setMessage("Examining selected file(s)");
// checkFilesInUse();
String currentName = null; String currentName = null;
String currentContentType = null;
try { try {
for (int i = 0; i < list.size() && actionID != VersionControlDialog.CANCEL; i++) { for (int i = 0; i < list.size() && actionID != VersionControlDialog.CANCEL; i++) {
df = list.get(i); df = list.get(i);
currentName = df.getName(); currentName = df.getName();
currentContentType = df.getContentType();
newFile = true; newFile = true;
if (i != 0) { if (i != 0) {
@ -94,27 +85,23 @@ public class CheckInTask extends VersionControlTask implements CheckinHandler {
Thread.sleep(200); Thread.sleep(200);
} }
catch (InterruptedException e2) { catch (InterruptedException e2) {
break;
} }
} }
myMonitor.setMessage("Initiating Check In for " + currentName); myMonitor.setMessage("Initiating Check In for " + currentName);
try { try {
df.checkin(this, false, myMonitor); df.checkin(this, myMonitor);
} }
catch (VersionException e) { catch (VersionException e) {
if (VersionExceptionHandler.isUpgradeOK(parent, df, "Checkin", e)) { VersionExceptionHandler.showVersionError(parent, df.getName(),
df.checkin(this, true, myMonitor); df.getContentType(), "Checkin", e);
}
} }
if (myMonitor.isCancelled()) { if (myMonitor.isCancelled()) {
break; break;
} }
} }
} }
catch (VersionException e) {
VersionExceptionHandler.showVersionError(parent, df.getName(), currentContentType,
"Checkin", e);
}
catch (CancelledException e) { catch (CancelledException e) {
Msg.info(this, "Check In Process was canceled"); Msg.info(this, "Check In Process was canceled");
wasCanceled = true; wasCanceled = true;
@ -125,18 +112,12 @@ public class CheckInTask extends VersionControlTask implements CheckinHandler {
} }
} }
/*
* @see ghidra.framework.data.CheckinHandler#getComment()
*/
@Override @Override
public String getComment() throws CancelledException { public String getComment() throws CancelledException {
promptUser(); promptUser();
return comments; return comments;
} }
/*
* @see ghidra.framework.data.CheckinHandler#keepCheckedOut()
*/
@Override @Override
public boolean keepCheckedOut() throws CancelledException { public boolean keepCheckedOut() throws CancelledException {
promptUser(); promptUser();

View file

@ -57,22 +57,23 @@ public abstract class VersionControlTask extends Task {
* Show the dialog. * Show the dialog.
* @param addToVersionControl true if the dialog is for * @param addToVersionControl true if the dialog is for
* adding files to version control, false for checking in files. * adding files to version control, false for checking in files.
* @param filename the name of the file currently to be added, whose comment we need. * @param file the file currently to be added or checked-in to version control
* @param isLinkFile true if file is a link file, else false. Link-files may not be checked-out
* so keep-checked-out control disabled if this is true.
*/ */
protected void showDialog(boolean addToVersionControl, String filename, boolean isLinkFile) { protected void showDialog(boolean addToVersionControl, DomainFile file) {
Runnable r = () -> { Runnable r = () -> {
VersionControlDialog vcDialog = new VersionControlDialog(addToVersionControl); VersionControlDialog vcDialog = new VersionControlDialog(addToVersionControl);
vcDialog.setCurrentFileName(filename); vcDialog.setCurrentFileName(file.getName());
vcDialog.setMultiFiles(list.size() > 1); vcDialog.setMultiFiles(list.size() > 1);
if (isLinkFile) { if (file.isLinkFile()) {
vcDialog.setKeepCheckboxEnabled(false, false, "Link files may not be Checked Out"); vcDialog.setKeepCheckboxEnabled(false, false, "Link file may not be Checked Out");
} }
else if (filesInUse) { else {
checkFilesInUse();
if (filesInUse) {
vcDialog.setKeepCheckboxEnabled(false, true, vcDialog.setKeepCheckboxEnabled(false, true,
"Must keep Checked Out because the file is in use"); "Must keep Checked Out because the file is in use");
} }
}
actionID = vcDialog.showDialog(tool, parent); actionID = vcDialog.showDialog(tool, parent);
keepCheckedOut = vcDialog.keepCheckedOut(); keepCheckedOut = vcDialog.keepCheckedOut();
createKeep = vcDialog.shouldCreateKeepFile(); createKeep = vcDialog.shouldCreateKeepFile();
@ -93,9 +94,11 @@ public abstract class VersionControlTask extends Task {
* are still in use. * are still in use.
*/ */
protected void checkFilesInUse() { protected void checkFilesInUse() {
// NOTE: In-use check is currently limited to files open for update but for the purpose of
// maintaining a checkout should really correspond to any file use (e.g., open read-only
// with DomainFileProxy).
filesInUse = false; filesInUse = false;
for (int i = 0; i < list.size(); i++) { for (DomainFile df : list) {
DomainFile df = list.get(i);
if (df.getConsumers().size() > 0) { if (df.getConsumers().size() > 0) {
filesInUse = true; filesInUse = true;
return; return;
@ -104,8 +107,7 @@ public abstract class VersionControlTask extends Task {
} }
protected boolean checkFilesForUnsavedChanges() { protected boolean checkFilesForUnsavedChanges() {
for (int i = 0; i < list.size(); i++) { for (DomainFile df : list) {
DomainFile df = list.get(i);
if (df.modifiedSinceCheckout()) { if (df.modifiedSinceCheckout()) {
return true; return true;
} }

View file

@ -136,14 +136,13 @@ public class VersionControlAddAction extends VersionControlAction {
@Override @Override
public void run(TaskMonitor monitor) { public void run(TaskMonitor monitor) {
checkFilesInUse();
try { try {
for (DomainFile df : list) { for (DomainFile df : list) {
String name = df.getName(); String name = df.getName();
monitor.setMessage("Adding " + name + " to Version Control"); monitor.setMessage("Adding " + name + " to Version Control");
if (actionID != VersionControlDialog.APPLY_TO_ALL) { if (actionID != VersionControlDialog.APPLY_TO_ALL) {
showDialog(true, name, df.isLinkFile()); showDialog(true, df);
} }
if (actionID == VersionControlDialog.CANCEL) { if (actionID == VersionControlDialog.CANCEL) {
return; return;

View file

@ -305,19 +305,34 @@ public interface DomainFile extends Comparable<DomainFile> {
/** /**
* Returns true if this file may be checked-in to the associated repository. * Returns true if this file may be checked-in to the associated repository.
* @return true if can check-in *
* Note: this does not take into consideration cases where the file is currently
* in-use which may cause a failure if a checkin is attempted.
*
* @return true if a check-in can be attempted (i.e., file is checked-out with changes),
* else false
*/ */
public boolean canCheckin(); public boolean canCheckin();
/** /**
* Returns true if this file can be merged with the current versioned file. * Returns true if this file can be merged with the current versioned file.
* @return true if can merge *
* Note: this does not take into consideration cases where the file is currently
* in-use which may cause a failure if a merge is attempted.
*
* @return true if a merge can be attempted (i.e., file is checked-out and a newer
* version exists), else false
*/ */
public boolean canMerge(); public boolean canMerge();
/** /**
* Returns true if this private file may be added to the associated repository. * Returns true if this private file may be added to the associated repository.
* @return true if can add to the repository *
* Note: this does not take into consideration cases where the file is currently
* in-use which may cause a failure if add to repository is attempted.
*
* @return true if add to the repository can be attempted (i.e., file in active project
* is not versioned or hijacked)
*/ */
public boolean canAddToRepository(); public boolean canAddToRepository();
@ -381,7 +396,8 @@ public interface DomainFile extends Comparable<DomainFile> {
/** /**
* Adds this private file to version control. * Adds this private file to version control.
* @param comment new version comment * @param comment new version comment
* @param keepCheckedOut if true, the file will be initially checked-out * @param keepCheckedOut if true, the file will be initially checked-out. This option will be
* ignored if file is currently open in which case file will remain checked-out.
* @param monitor progress monitor * @param monitor progress monitor
* @throws FileInUseException if this file is in-use. * @throws FileInUseException if this file is in-use.
* @throws IOException if an IO or access error occurs. Also if file is not * @throws IOException if an IO or access error occurs. Also if file is not
@ -409,16 +425,38 @@ public interface DomainFile extends Comparable<DomainFile> {
* Performs check in to associated repository. File must be checked-out * Performs check in to associated repository. File must be checked-out
* and modified since checkout. * and modified since checkout.
* @param checkinHandler provides user input data to complete checkin process. * @param checkinHandler provides user input data to complete checkin process.
* @param okToUpgrade if true an upgrade will be performed if needed * The keep-checked-out option supplied by this handler will be ignored if file is currently
* open in which case file will remain checked-out.
* @param monitor the TaskMonitor.
* @throws IOException if an IO or access error occurs
* @throws VersionException if unable to handle domain object version in versioned filesystem.
* We are unable to upgrade since this would only occur if checkout is not exclusive.
* @throws CancelledException if task monitor cancelled operation
*/
public void checkin(CheckinHandler checkinHandler, TaskMonitor monitor)
throws IOException, VersionException, CancelledException;
/**
* Performs check in to associated repository. File must be checked-out
* and modified since checkout.
* @param checkinHandler provides user input data to complete checkin process.
* This keep-checked-out option supplied by this handler will be ignored and forced true
* if file is currently open.
* @param okToUpgrade if true an upgrade will be performed if needed (ignored)
* @param monitor the TaskMonitor. * @param monitor the TaskMonitor.
* @throws IOException if an IO or access error occurs * @throws IOException if an IO or access error occurs
* @throws VersionException if unable to handle domain object version in versioned filesystem. * @throws VersionException if unable to handle domain object version in versioned filesystem.
* If okToUpgrade was false, check exception to see if it can be upgraded * If okToUpgrade was false, check exception to see if it can be upgraded
* sometime after doing a checkout. * sometime after doing a checkout.
* @throws CancelledException if task monitor cancelled operation * @throws CancelledException if task monitor cancelled operation
* @deprecated use alternative {@link #checkin(CheckinHandler, TaskMonitor)} method since
* okToUpgrade cannot be respected and is ignored. Upgrade cannot be performed during checkin.
*/ */
public void checkin(CheckinHandler checkinHandler, boolean okToUpgrade, TaskMonitor monitor) @Deprecated(since = "11.1", forRemoval = true)
throws IOException, VersionException, CancelledException; public default void checkin(CheckinHandler checkinHandler, boolean okToUpgrade,
TaskMonitor monitor) throws IOException, VersionException, CancelledException {
checkin(checkinHandler, monitor);
}
/** /**
* Performs merge from current version of versioned file into local checked-out file. * Performs merge from current version of versioned file into local checked-out file.

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -26,21 +25,27 @@ public interface DomainFolderChangeListener {
* Notification that a folder is added to parent. * Notification that a folder is added to parent.
* @param folder domain folder which was just added. * @param folder domain folder which was just added.
*/ */
public void domainFolderAdded(DomainFolder folder); public default void domainFolderAdded(DomainFolder folder) {
// do nothing
}
/** /**
* Notification that a file is added to parent folder. You can * Notification that a file is added to parent folder. You can
* get the parent from the file. * get the parent from the file.
* @param file domain file which was just added. * @param file domain file which was just added.
*/ */
public void domainFileAdded(DomainFile file); public default void domainFileAdded(DomainFile file) {
// do nothing
}
/** /**
* Notification that a domain folder is removed. * Notification that a domain folder is removed.
* @param parent domain folder which contained the folder that was just removed. * @param parent domain folder which contained the folder that was just removed.
* @param name the name of the folder that was removed. * @param name the name of the folder that was removed.
*/ */
public void domainFolderRemoved(DomainFolder parent, String name); public default void domainFolderRemoved(DomainFolder parent, String name) {
// do nothing
}
/** /**
* Notification that a file was removed * Notification that a file was removed
@ -48,40 +53,54 @@ public interface DomainFolderChangeListener {
* @param name the name of the file that was removed. * @param name the name of the file that was removed.
* @param fileID file ID or null * @param fileID file ID or null
*/ */
public void domainFileRemoved(DomainFolder parent, String name, String fileID); public default void domainFileRemoved(DomainFolder parent, String name, String fileID) {
// do nothing
}
/** /**
* Notify listeners when a domain folder is renamed. * Notify listeners when a domain folder is renamed.
* @param folder folder that was renamed * @param folder folder that was renamed
* @param oldName old name of folder * @param oldName old name of folder
*/ */
public void domainFolderRenamed(DomainFolder folder, String oldName); public default void domainFolderRenamed(DomainFolder folder, String oldName) {
// do nothing
}
/** /**
* Notification that the domain file was renamed. * Notification that the domain file was renamed.
* @param file file that was renamed * @param file file that was renamed
* @param oldName old name of the file * @param oldName old name of the file
*/ */
public void domainFileRenamed(DomainFile file, String oldName); public default void domainFileRenamed(DomainFile file, String oldName) {
// do nothing
}
/** /**
* Notification that the domain folder was moved. * Notification that the domain folder was moved.
* @param folder the folder (after move) * @param folder the folder (after move)
* @param oldParent original parent folder * @param oldParent original parent folder
*/ */
public void domainFolderMoved(DomainFolder folder, DomainFolder oldParent); public default void domainFolderMoved(DomainFolder folder, DomainFolder oldParent) {
// do nothing
}
/** /**
* Notification that the domain file was moved. * Notification that the domain file was moved.
* @param file the file (after move) * @param file the file (after move)
* @param oldParent original parent folder * @param oldParent original parent folder
* @param oldName file name prior to move
*/ */
public void domainFileMoved(DomainFile file, DomainFolder oldParent, String oldName); public default void domainFileMoved(DomainFile file, DomainFolder oldParent, String oldName) {
// do nothing
}
/** /**
* Notification that the setActive() method on the folder was called. * Notification that the setActive() method on the folder was called.
* @param folder folder which was activated/visited
*/ */
public void domainFolderSetActive(DomainFolder folder); public default void domainFolderSetActive(DomainFolder folder) {
// do nothing
}
/** /**
* Notification that the status for a domain file has changed. * Notification that the status for a domain file has changed.
@ -89,30 +108,25 @@ public interface DomainFolderChangeListener {
* @param fileIDset if true indicates that the previously missing fileID has been * @param fileIDset if true indicates that the previously missing fileID has been
* established for the specified file. * established for the specified file.
*/ */
public void domainFileStatusChanged(DomainFile file, boolean fileIDset); public default void domainFileStatusChanged(DomainFile file, boolean fileIDset) {
// do nothing
/** }
* Notification that a new version of the domain object exists and the
* current one is no longer valid. Existing consumers should be immediately
* released and no additional use of the oldObject is permitted once this
* method returns. This is only called for domain objects which were
* opened for update.
* @param file file whose object was replaced
* @param oldObject old object that was replaced
*/
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject);
/** /**
* Notification that a domain file has been opened for update. * Notification that a domain file has been opened for update.
* @param file domain file * @param file domain file
* @param object domain object open for update * @param object domain object open for update
*/ */
public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object); public default void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) {
// do nothing
}
/** /**
* Notification that a domain file previously open for update is in the process of closing. * Notification that a domain file previously open for update is in the process of closing.
* @param file domain file * @param file domain file
* @param object domain object which was open for update * @param object domain object which was open for update
*/ */
public void domainFileObjectClosed(DomainFile file, DomainObject object); public default void domainFileObjectClosed(DomainFile file, DomainObject object) {
// do nothing
}
} }

View file

@ -122,11 +122,6 @@ public abstract class DomainFolderListenerAdapter implements DomainFolderChangeL
stateChanged(file.getPathname(), getPathname(oldParent, oldName), false); stateChanged(file.getPathname(), getPathname(oldParent, oldName), false);
} }
@Override
public void domainFolderSetActive(DomainFolder folder) {
// do nothing
}
@Override @Override
public void domainFileStatusChanged(DomainFile file, boolean fileIDset) { public void domainFileStatusChanged(DomainFile file, boolean fileIDset) {
if (enableStateChangeCallback) { if (enableStateChangeCallback) {
@ -135,18 +130,4 @@ public abstract class DomainFolderListenerAdapter implements DomainFolderChangeL
} }
} }
@Override
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
// do nothing
}
@Override
public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) {
// do nothing
}
@Override
public void domainFileObjectClosed(DomainFile file, DomainObject object) {
// do nothing
}
} }

View file

@ -287,7 +287,7 @@ public class TestDummyDomainFile implements DomainFile {
} }
@Override @Override
public void checkin(CheckinHandler checkinHandler, boolean okToUpgrade, TaskMonitor monitor) public void checkin(CheckinHandler checkinHandler, TaskMonitor monitor)
throws IOException, VersionException, CancelledException { throws IOException, VersionException, CancelledException {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View file

@ -21,14 +21,13 @@ import javax.swing.Icon;
import db.DBConstants; import db.DBConstants;
import db.DBHandle; import db.DBHandle;
import db.buffers.BufferFile; import db.buffers.*;
import db.buffers.ManagedBufferFile;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.framework.data.DBContentHandler; import ghidra.framework.data.*;
import ghidra.framework.data.DomainObjectMergeManager;
import ghidra.framework.model.ChangeSet; import ghidra.framework.model.ChangeSet;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.store.*; import ghidra.framework.store.*;
import ghidra.framework.store.local.LocalDatabaseItem;
import ghidra.util.InvalidNameException; import ghidra.util.InvalidNameException;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
@ -371,4 +370,21 @@ public class DataTypeArchiveContentHandler extends DBContentHandler<DataTypeArch
return linkHandler; return linkHandler;
} }
@Override
public boolean canResetDBSourceFile() {
return true;
}
@Override
public void resetDBSourceFile(FolderItem item, DomainObjectAdapterDB domainObj)
throws IOException {
if (!(item instanceof LocalDatabaseItem dbItem) ||
!(domainObj instanceof DataTypeArchiveDB dataTypeArchive)) {
throw new IllegalArgumentException("LocalDatabaseItem and DataTypeArchiveDB required");
}
LocalManagedBufferFile bf = dbItem.openForUpdate(FolderItem.DEFAULT_CHECKOUT_ID);
dataTypeArchive.getDBHandle().setDBVersionedSourceFile(bf);
getDataTypeArchiveChangeSet(dataTypeArchive, bf);
}
} }

View file

@ -509,9 +509,6 @@ public class DataTypeArchiveDB extends DomainObjectAdapterDB implements DataType
} }
} }
/**
* @see ghidra.program.model.listing.Program#invalidate()
*/
@Override @Override
public void invalidate() { public void invalidate() {
clearCache(false); clearCache(false);

View file

@ -20,14 +20,13 @@ import java.io.IOException;
import javax.swing.Icon; import javax.swing.Icon;
import db.*; import db.*;
import db.buffers.BufferFile; import db.buffers.*;
import db.buffers.ManagedBufferFile;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.framework.data.DBWithUserDataContentHandler; import ghidra.framework.data.*;
import ghidra.framework.data.DomainObjectMergeManager;
import ghidra.framework.model.ChangeSet; import ghidra.framework.model.ChangeSet;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.store.*; import ghidra.framework.store.*;
import ghidra.framework.store.local.LocalDatabaseItem;
import ghidra.util.InvalidNameException; import ghidra.util.InvalidNameException;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
@ -58,8 +57,7 @@ public class ProgramContentHandler extends DBWithUserDataContentHandler<ProgramD
if (!(obj instanceof ProgramDB)) { if (!(obj instanceof ProgramDB)) {
throw new IOException("Unsupported domain object: " + obj.getClass().getName()); throw new IOException("Unsupported domain object: " + obj.getClass().getName());
} }
return createFile((ProgramDB) obj, PROGRAM_CONTENT_TYPE, fs, path, name, return createFile((ProgramDB) obj, PROGRAM_CONTENT_TYPE, fs, path, name, monitor);
monitor);
} }
@Override @Override
@ -363,4 +361,21 @@ public class ProgramContentHandler extends DBWithUserDataContentHandler<ProgramD
return linkHandler; return linkHandler;
} }
@Override
public boolean canResetDBSourceFile() {
return true;
}
@Override
public void resetDBSourceFile(FolderItem item, DomainObjectAdapterDB domainObj)
throws IOException {
if (!(item instanceof LocalDatabaseItem dbItem) ||
!(domainObj instanceof ProgramDB program)) {
throw new IllegalArgumentException("LocalDatabaseItem and ProgramDB required");
}
LocalManagedBufferFile bf = dbItem.openForUpdate(FolderItem.DEFAULT_CHECKOUT_ID);
program.getDBHandle().setDBVersionedSourceFile(bf);
getProgramChangeSet(program, bf);
}
} }

View file

@ -1842,12 +1842,6 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
} }
} }
@Override
public void invalidate() {
clearCache(false);
fireEvent(new DomainObjectChangeRecord(DomainObjectEvent.RESTORED));
}
@Override @Override
public boolean isChangeable() { public boolean isChangeable() {
return changeable; return changeable;

View file

@ -356,12 +356,6 @@ public interface Program extends DataTypeManagerDomainObject, ProgramArchitectur
*/ */
public Address[] parseAddress(String addrStr, boolean caseSensitive); public Address[] parseAddress(String addrStr, boolean caseSensitive);
/**
* Invalidates any caching in a program.
* NOTE: Over-using this method can adversely affect system performance.
*/
public void invalidate();
/** /**
* Create a new overlay space based upon the given base AddressSpace * Create a new overlay space based upon the given base AddressSpace
* @param overlaySpaceName the name of the new overlay space. * @param overlaySpaceName the name of the new overlay space.

View file

@ -536,11 +536,6 @@ public class StubProgram implements Program {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public void invalidate() {
throw new UnsupportedOperationException();
}
@Override @Override
public Register getRegister(String name) { public Register getRegister(String name) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();

View file

@ -126,13 +126,13 @@ public class ProgramManagerPluginScreenShots extends GhidraScreenShotGenerator
checkinComment = "Version 2"; checkinComment = "Version 2";
keepCheckedOut = true; keepCheckedOut = true;
assertTrue(df.canCheckin()); assertTrue(df.canCheckin());
df.checkin(this, false, null); df.checkin(this, null);
changeProgram(p, "bbb"); changeProgram(p, "bbb");
checkinComment = "Version 3"; checkinComment = "Version 3";
keepCheckedOut = true; keepCheckedOut = true;
assertTrue(df.canCheckin()); assertTrue(df.canCheckin());
df.checkin(this, false, null); df.checkin(this, null);
p.release(this); p.release(this);
performAction("Open File", "ProgramManagerPlugin", false); performAction("Open File", "ProgramManagerPlugin", false);

View file

@ -216,21 +216,21 @@ public class VersionControlAction1Test extends AbstractVersionControlActionTest
// make some changes to check in // make some changes to check in
Program program = (Program) ((DomainFileNode) node).getDomainFile() Program program = (Program) ((DomainFileNode) node).getDomainFile()
.getDomainObject(this, .getDomainObject(this, true, false, TaskMonitor.DUMMY);
true, false, TaskMonitor.DUMMY);
editProgram(program, (p) -> { editProgram(program, (p) -> {
SymbolTable symTable = p.getSymbolTable(); SymbolTable symTable = p.getSymbolTable();
symTable.createLabel(p.getMinAddress().getNewAddress(0x010001000), "fred", symTable.createLabel(p.getMinAddress().getNewAddress(0x010001000), "fred",
SourceType.USER_DEFINED); SourceType.USER_DEFINED);
}); });
program.release(this);
program = (Program) ((DomainFileNode) xnode).getDomainFile() program = (Program) ((DomainFileNode) xnode).getDomainFile()
.getDomainObject(this, true, .getDomainObject(this, true, false, TaskMonitor.DUMMY);
false, TaskMonitor.DUMMY);
editProgram(program, (p) -> { editProgram(program, (p) -> {
SymbolTable symTable = p.getSymbolTable(); SymbolTable symTable = p.getSymbolTable();
symTable.createLabel(p.getMinAddress(), "bob", SourceType.USER_DEFINED); symTable.createLabel(p.getMinAddress(), "bob", SourceType.USER_DEFINED);
}); });
program.release(this);
DockingActionIf checkInAction = getAction("CheckIn"); DockingActionIf checkInAction = getAction("CheckIn");
performAction(checkInAction, getDomainFileActionContext(node, xnode), false); performAction(checkInAction, getDomainFileActionContext(node, xnode), false);
@ -254,10 +254,8 @@ public class VersionControlAction1Test extends AbstractVersionControlActionTest
checkout(programNode); checkout(programNode);
Program program = Program program = (Program) ((DomainFileNode) programNode).getDomainFile()
(Program) ((DomainFileNode) programNode).getDomainFile() .getDomainObject(this, true, false, TaskMonitor.DUMMY);
.getDomainObject(this, true,
false, TaskMonitor.DUMMY);
createHistoryEntry(program, "Symbol1"); createHistoryEntry(program, "Symbol1");
frontEnd.checkIn(programNode, "This is checkin 1"); frontEnd.checkIn(programNode, "This is checkin 1");