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.TraceOpenedPluginEvent;
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.async.AsyncDebouncer;
import ghidra.async.AsyncTimer;
@ -60,6 +61,7 @@ import ghidra.util.datastruct.ListenerSet;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
//@formatter:off
@PluginInfo(
shortDescription = "Debugger static mapping manager",
description = "Track and manage static mappings (program-trace relocations)",
@ -70,8 +72,9 @@ import ghidra.util.task.TaskMonitor;
TraceOpenedPluginEvent.class, TraceClosedPluginEvent.class, },
servicesRequired = { ProgramManager.class, DebuggerTraceManagerService.class, },
servicesProvided = { DebuggerStaticMappingService.class, })
//@formatter:on
public class DebuggerStaticMappingServicePlugin extends Plugin
implements DebuggerStaticMappingService, DomainFolderChangeAdapter {
implements DebuggerStaticMappingService, DomainFolderChangeListener {
protected class MappingEntry {
private final TraceStaticMapping mapping;

View File

@ -22,7 +22,6 @@ import java.util.stream.Collectors;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import ghidra.app.plugin.core.debug.utils.DomainFolderChangeAdapter;
import ghidra.app.plugin.core.debug.utils.ProgramURLUtils;
import ghidra.framework.model.*;
import ghidra.framework.options.Options;
@ -33,7 +32,7 @@ import ghidra.program.model.listing.Program;
import ghidra.trace.model.modules.TraceModule;
// 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";
private static final Gson JSON = new Gson();
@ -288,11 +287,6 @@ public class ProgramModuleIndexer implements DomainFolderChangeAdapter {
refreshIndex(file);
}
@Override
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
refreshIndex(file);
}
@Override
public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) {
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);
}
@Override
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
log("File object replaced: file=" + file + " oldObject=" + obj(oldObject));
}
@Override
public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject 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);
}
@Override
public void invalidate() {
// TODO: I imagine I'll find out who uses this pretty quick....
throw new UnsupportedOperationException();
}
@Override
public Register getRegister(String name) {
return language.getRegister(name);

View File

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

View File

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

View File

@ -1639,22 +1639,6 @@ public class DataTypeManagerHandler {
// 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
public void domainFileRenamed(DomainFile file, String oldName) {
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.stream.Collectors;
import org.jdom.Element;
import ghidra.app.events.*;
import ghidra.app.nav.Navigatable;
import ghidra.app.services.*;
import ghidra.app.util.task.OpenProgramRequest;
import ghidra.app.util.task.OpenProgramTask;
import ghidra.framework.data.DomainObjectAdapterDB;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginTool;
@ -38,7 +34,6 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.task.TaskLauncher;
/**
* Class for tracking open programs in the tool.
@ -49,7 +44,6 @@ class MultiProgramManager implements TransactionListener {
private PluginTool tool;
private ProgramInfo currentInfo;
private TransactionMonitor txMonitor;
private MyFolderListener folderListener;
private Runnable programChangedRunnable;
private boolean hasUnsavedPrograms;
@ -72,8 +66,6 @@ class MultiProgramManager implements TransactionListener {
txMonitor = new TransactionMonitor();
txMonitor.setName("Transaction Open (Program being modified)");
tool.addStatusComponent(txMonitor, true, true);
folderListener = new MyFolderListener();
tool.getProject().getProjectData().addDomainFolderChangeListener(folderListener);
programChangedRunnable = () -> {
if (tool == null) {
@ -111,7 +103,6 @@ class MultiProgramManager implements TransactionListener {
}
void dispose() {
tool.getProject().getProjectData().removeDomainFolderChangeListener(folderListener);
fireActivatedEvent(null);
for (Program p : programMap.keySet()) {
@ -442,41 +433,6 @@ class MultiProgramManager implements TransactionListener {
//==================================================================================================
// 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> {

View File

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

View File

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

View File

@ -701,20 +701,6 @@ public class DefaultProjectDataTest extends AbstractGhidraHeadedIntegrationTest
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 TestProgramManager programManager = new TestProgramManager();
private FakeRepository repo;
private boolean isFileSharingEnabled; // set true if multiple projects share repo files
public FakeSharedProject(FakeRepository repo, User user) throws IOException {
@ -85,6 +86,14 @@ public class FakeSharedProject {
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
* @return the ghidra project
@ -162,6 +171,7 @@ public class FakeSharedProject {
public DomainFile getDomainFile(String filepath) {
Project project = getGhidraProject().getProject();
ProjectData projectData = project.getProjectData();
refresh(); // force refresh since we do not employ repo listener
DomainFile df;
if (filepath.startsWith("/")) {
df = projectData.getFile(filepath);
@ -287,7 +297,7 @@ public class FakeSharedProject {
}
};
df.checkin(ch, false, TaskMonitor.DUMMY);
df.checkin(ch, TaskMonitor.DUMMY);
repo.refresh();
}
@ -387,6 +397,10 @@ public class FakeSharedProject {
throw new IllegalArgumentException("DomainFile cannot be null");
}
if (isFileSharingEnabled) {
return;
}
ProjectLocator pl = df.getProjectLocator();
ProjectLocator mypl = getProjectData().getProjectLocator();
if (!pl.equals(mypl)) {

View File

@ -21,12 +21,13 @@ import javax.swing.Icon;
import db.DBHandle;
import db.buffers.BufferFile;
import db.buffers.LocalManagedBufferFile;
import generic.theme.GIcon;
import ghidra.framework.data.DBContentHandler;
import ghidra.framework.data.DomainObjectMergeManager;
import ghidra.framework.data.*;
import ghidra.framework.model.ChangeSet;
import ghidra.framework.model.DomainObject;
import ghidra.framework.store.*;
import ghidra.framework.store.local.LocalDatabaseItem;
import ghidra.util.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
@ -168,4 +169,20 @@ public class VTSessionContentHandler extends DBContentHandler<VTSessionDB> {
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.VTSessionDB;
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.provider.markuptable.VTMarkupItemContext;
import ghidra.feature.vt.gui.task.SaveTask;
@ -69,7 +68,6 @@ public class VTControllerImpl
private ToolOptions vtOptions;
private MatchInfo currentMatchInfo;
private MyFolderListener folderListener;
public VTControllerImpl(VTPlugin plugin) {
this.plugin = plugin;
@ -77,11 +75,6 @@ public class VTControllerImpl
matchInfoFactory = new MatchInfoFactory();
vtOptions = plugin.getTool().getOptions(VERSION_TRACKING_OPTIONS_NAME);
vtOptions.addOptionsChangeListener(this);
folderListener = new MyFolderListener();
plugin.getTool()
.getProject()
.getProjectData()
.addDomainFolderChangeListener(folderListener);
}
@Override
@ -763,82 +756,6 @@ public class VTControllerImpl
// 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 final VTSession newSession;

View File

@ -18,7 +18,6 @@ package db;
import java.io.File;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Iterator;
import db.buffers.*;
import db.util.ErrorHandler;
@ -439,8 +438,7 @@ public class DBHandle {
* @throws IllegalStateException if transaction is already active or this {@link DBHandle} has
* already been closed.
*/
public Transaction openTransaction(ErrorHandler errorHandler)
throws IllegalStateException {
public Transaction openTransaction(ErrorHandler errorHandler) throws IllegalStateException {
return new Transaction() {
long txId = startTransaction();
@ -542,6 +540,33 @@ public class DBHandle {
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
* {@link DBListener#dbRestored(DBHandle)} notification to listeners. This method is very
@ -1022,10 +1047,9 @@ public class DBHandle {
public Table[] getTables() {
Table[] t = new Table[tables.size()];
Iterator<Table> it = tables.values().iterator();
int i = 0;
while (it.hasNext()) {
t[i++] = it.next();
for (Table element : tables.values()) {
t[i++] = element;
}
return t;
}
@ -1050,8 +1074,7 @@ public class DBHandle {
* @return new table instance
* @throws IOException if IO error occurs during table creation
*/
public Table createTable(String name, Schema schema, int[] indexedColumns)
throws IOException {
public Table createTable(String name, Schema schema, int[] indexedColumns) throws IOException {
Table table;
synchronized (this) {
if (tables.containsKey(name)) {

View File

@ -223,6 +223,22 @@ public class BufferMgr {
approxCacheSize < MINIMUM_CACHE_SIZE ? MINIMUM_CACHE_SIZE : approxCacheSize;
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
cacheHead = new BufferNode(HEAD, -1);
cacheTail = new BufferNode(TAIL, -1);
@ -234,11 +250,6 @@ public class BufferMgr {
cacheIndexProvider = new IndexProvider();
// Setup baseline - checkpoint 0
startCheckpoint();
baselineCheckpointHead = currentCheckpointHead;
currentCheckpointHead = null;
// Copy file parameters into cache file
if (sourceFile != null) {
String[] parmNames = sourceFile.getParameterNames();
@ -247,8 +258,6 @@ public class BufferMgr {
}
}
addInstance(this);
if (alwaysPreCache) {
startPreCacheIfNeeded();
}
@ -417,9 +426,7 @@ public class BufferMgr {
// Dispose all buffer nodes - speeds up garbage collection
if (checkpointHeads != null) {
Iterator<BufferNode> iter = checkpointHeads.iterator();
while (iter.hasNext()) {
BufferNode node = iter.next();
for (BufferNode node : checkpointHeads) {
while (node != null) {
BufferNode next = node.nextInCheckpoint;
node.buffer = null;
@ -435,9 +442,7 @@ public class BufferMgr {
checkpointHeads = null;
}
if (redoCheckpointHeads != null) {
Iterator<BufferNode> iter = redoCheckpointHeads.iterator();
while (iter.hasNext()) {
BufferNode node = iter.next();
for (BufferNode node : redoCheckpointHeads) {
while (node != null) {
BufferNode next = node.nextInCheckpoint;
node.buffer = null;
@ -702,24 +707,7 @@ public class BufferMgr {
return; // only pre-cache remote buffer files
}
synchronized (preCacheLock) {
preCacheThread = new Thread(() -> {
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 = new Thread(() -> preCacheSourceFile());
preCacheThread.setName("Pre-Cache");
preCacheThread.setPriority(Thread.MIN_PRIORITY);
preCacheThread.start();
@ -731,23 +719,38 @@ public class BufferMgr {
* Pre-cache source file into cache file. This is intended to be run in a
* dedicated thread when the source file is remote.
*/
private void preCacheSourceFile() throws IOException {
if (!(sourceFile instanceof BufferFileAdapter)) {
throw new UnsupportedOperationException("unsupported use of preCacheSourceFile");
}
Msg.trace(BufferMgr.this, "Pre-cache started...");
int cacheCount = 0;
BufferFileAdapter sourceAdapter = (BufferFileAdapter) sourceFile;
try (InputBlockStream inputBlockStream = sourceAdapter.getInputBlockStream()) {
BufferFileBlock block;
while (!Thread.interrupted() && (block = inputBlockStream.readBlock()) != null) {
DataBuffer buf = LocalBufferFile.getDataBuffer(block);
if (buf != null && !buf.isEmpty() && preCacheBuffer(buf)) { // skip head block and empty blocks
++cacheCount;
}
private void preCacheSourceFile() {
try {
if (!(sourceFile instanceof BufferFileAdapter)) {
throw new UnsupportedOperationException("unsupported use of preCacheSourceFile");
}
Msg.trace(BufferMgr.this, "Pre-cache started...");
int cacheCount = 0;
BufferFileAdapter sourceAdapter = (BufferFileAdapter) sourceFile;
try (InputBlockStream inputBlockStream = sourceAdapter.getInputBlockStream()) {
BufferFileBlock block;
while (!Thread.interrupted() && (block = inputBlockStream.readBlock()) != null) {
DataBuffer buf = LocalBufferFile.getDataBuffer(block);
if (buf != null && !buf.isEmpty() && preCacheBuffer(buf)) { // skip head block and empty blocks
++cacheCount;
}
}
Msg.trace(BufferMgr.this, "Pre-cache added " + cacheCount + " of " +
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();
}
Msg.trace(BufferMgr.this, "Pre-cache added " + cacheCount + " of " +
sourceFile.getIndexCount() + " buffers to cache");
}
}
@ -1744,6 +1747,51 @@ public class BufferMgr {
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.
* If the buffer manager was not instantiated with a source file an
@ -1825,7 +1873,7 @@ public class BufferMgr {
monitor.setCancelEnabled(oldCancelState & !monitor.isCancelled());
}
setSourceFile(outFile);
doSetSourceFile(outFile);
}
}
}
@ -1881,7 +1929,7 @@ public class BufferMgr {
monitor.setCancelEnabled(true);
}
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
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
* preSaveFile when this buffer file is updateable.
*/
//private Thread preSaveThread;
private PreSaveTask preSaveTask;
/**
@ -228,8 +227,8 @@ public class LocalManagedBufferFile extends LocalBufferFile implements ManagedBu
setFreeIndexes(versionFileHandler.getFreeIndexList());
String[] names = versionFileHandler.getOldParameterNames();
clearParameters();
for (int i = 0; i < names.length; i++) {
setParameter(names[i], versionFileHandler.getOldParameter(names[i]));
for (String name : names) {
setParameter(name, versionFileHandler.getOldParameter(name));
}
}

View File

@ -17,6 +17,7 @@ package ghidra.framework.data;
import java.io.IOException;
import javax.help.UnsupportedOperationException;
import javax.swing.Icon;
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
* difference which could not be handled.
*/
T getImmutableObject(FolderItem item, Object consumer, int version,
int minChangeVersion, TaskMonitor monitor)
throws IOException, CancelledException, VersionException;
T getImmutableObject(FolderItem item, Object consumer, int version, int minChangeVersion,
TaskMonitor monitor) throws IOException, CancelledException, VersionException;
/**
* 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
* difference which could not be handled.
*/
T getReadOnlyObject(FolderItem item, int version, boolean okToUpgrade,
Object consumer, TaskMonitor monitor)
throws IOException, VersionException, CancelledException;
T getReadOnlyObject(FolderItem item, int version, boolean okToUpgrade, Object consumer,
TaskMonitor monitor) throws IOException, VersionException, CancelledException;
/**
* 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
* difference which could not be handled.
*/
T getDomainObject(FolderItem item, FileSystem userfs, long checkoutId,
boolean okToUpgrade, boolean okToRecover, Object consumer, TaskMonitor monitor)
T getDomainObject(FolderItem item, FileSystem userfs, long checkoutId, boolean okToUpgrade,
boolean okToRecover, Object consumer, TaskMonitor monitor)
throws IOException, CancelledException, VersionException;
/**
@ -204,4 +203,47 @@ public interface ContentHandler<T extends DomainObjectAdapter> extends Extension
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 javax.help.UnsupportedOperationException;
import db.DBHandle;
import db.buffers.ManagedBufferFile;
import ghidra.framework.store.*;
@ -55,9 +57,8 @@ public abstract class DBContentHandler<T extends DomainObjectAdapterDB>
FileSystem fs, String path, String name, TaskMonitor monitor)
throws InvalidNameException, CancelledException, IOException {
DBHandle dbh = domainObj.getDBHandle();
ManagedBufferFile bf =
fs.createDatabase(path, name, FileIDFactory.createFileID(), contentType,
dbh.getBufferSize(), SystemUtilities.getUserName(), null);
ManagedBufferFile bf = fs.createDatabase(path, name, FileIDFactory.createFileID(),
contentType, dbh.getBufferSize(), SystemUtilities.getUserName(), null);
long checkoutId = bf.getCheckinID(); // item remains checked-out after saveAs
boolean success = false;
try {

View File

@ -21,7 +21,7 @@ import ghidra.util.exception.CancelledException;
/**
* <code>DefaultCheckinHandler</code> provides a simple
* 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 {

View File

@ -181,57 +181,31 @@ class DomainFileIndex implements DomainFolderChangeListener {
return null;
}
@Override
public void domainFileAdded(DomainFile file) {
updateFileEntry((GhidraFile) file);
}
@Override
public void domainFileMoved(DomainFile file, DomainFolder oldParent, String oldName) {
updateFileEntry((GhidraFile) file);
}
public void domainFileObjectClosed(DomainFile file, DomainObject object) {
// no-op
}
public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) {
// no-op
}
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
// no-op
}
@Override
public void domainFileRemoved(DomainFolder parent, String name, String fileID) {
fileIdToPathIndex.remove(fileID);
}
@Override
public void domainFileRenamed(DomainFile file, String oldName) {
updateFileEntry((GhidraFile) file);
}
@Override
public void domainFileStatusChanged(DomainFile file, boolean fileIDset) {
if (fileIDset) {
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
public void checkin(CheckinHandler checkinHandler, boolean okToUpgrade, TaskMonitor monitor)
public void checkin(CheckinHandler checkinHandler, TaskMonitor monitor)
throws IOException, VersionException, CancelledException {
throw new UnsupportedOperationException("Repository operations not supported");
}

View File

@ -26,8 +26,7 @@ class DomainFolderChangeListenerList implements DomainFolderChangeListener {
private DomainFileIndex fileIndex;
/** CopyOnWriteArrayList prevents the need for synchronization */
private List<DomainFolderChangeListener> list =
new CopyOnWriteArrayList<>();
private List<DomainFolderChangeListener> list = new CopyOnWriteArrayList<>();
DomainFolderChangeListenerList(DomainFileIndex 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() {
list.clear();
}

View File

@ -81,8 +81,8 @@ public abstract class DomainObjectAdapter implements DomainObject {
* with consumer.
*
* @param name name of the object
* @param timeInterval the time (in milliseconds) to wait before the event queue is flushed. If
* a new event comes in before the time expires, the timer is reset.
* @param timeInterval the time (in milliseconds) to wait before the event queue is flushed.
* If a new event comes in before the time expires the timer is reset.
* @param consumer the object that created this domain object
*/
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
public void release(Object consumer) {
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
* domain object and ending the transaction. The transaction allows for
* 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
implements ErrorHandler, DBConstants {
@ -100,12 +74,10 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
/**
* Construct a new DomainObjectAdapterDB object.
* If construction of this object fails, be sure to release with consumer
* @param dbh database handle
* @param name name of the domain object
* @param timeInterval the time (in milliseconds) to wait before the
* event queue is flushed. If a new event comes in before the time expires,
* the timer is reset.
* @param timeInterval the time (in milliseconds) to wait before the event queue is flushed.
* If a new event comes in before the time expires the timer is reset.
* @param consumer the object that created this domain object
*/
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.
*/
public void flushWriteCache() {
// do nothing
}
/**
@ -137,6 +110,7 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
* prior to aborting a transaction.
*/
public void invalidateWriteCache() {
// do nothing
}
/**
@ -491,6 +465,12 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
transactionMgr.clearUndo(notifyListeners);
}
@Override
public void invalidate() {
clearCache(false);
super.invalidate();
}
protected void clearCache(boolean all) {
options.clearCache();
}

View File

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

View File

@ -436,9 +436,8 @@ public class GhidraFileData {
ChangeSet getChangesByOthersSinceCheckout() throws VersionException, IOException {
synchronized (fileSystem) {
if (versionedFolderItem != null && folderItem != null && folderItem.isCheckedOut()) {
ContentHandler<?> ch = getContentHandler();
return ch.getChangeSet(versionedFolderItem, folderItem.getCheckoutVersion(),
versionedFolderItem.getCurrentVersion());
return getContentHandler().getChangeSet(versionedFolderItem,
folderItem.getCheckoutVersion(), versionedFolderItem.getCurrentVersion());
}
return null;
}
@ -478,6 +477,12 @@ public class GhidraFileData {
*/
DomainObject getDomainObject(Object consumer, boolean okToUpgrade, boolean okToRecover,
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;
DomainObjectAdapter domainObj = null;
synchronized (fileSystem) {
@ -494,9 +499,9 @@ public class GhidraFileData {
return domainObj;
}
}
ContentHandler<?> ch = getContentHandler();
ContentHandler<?> contentHandler = getContentHandler();
if (folderItem == null) {
DomainObjectAdapter doa = ch.getReadOnlyObject(versionedFolderItem,
DomainObjectAdapter doa = contentHandler.getReadOnlyObject(versionedFolderItem,
DomainFile.DEFAULT_VERSION, true, consumer, monitor);
doa.setChanged(false);
DomainFileProxy proxy = new DomainFileProxy(name, parent.getPathname(), doa,
@ -506,7 +511,7 @@ public class GhidraFileData {
}
myFolderItem = folderItem;
domainObj = ch.getDomainObject(myFolderItem, parent.getUserFileSystem(),
domainObj = contentHandler.getDomainObject(myFolderItem, parent.getUserFileSystem(),
FolderItem.DEFAULT_CHECKOUT_ID, okToUpgrade, okToRecover, consumer, monitor);
projectData.setDomainObject(getPathname(), domainObj);
@ -567,8 +572,8 @@ public class GhidraFileData {
FolderItem item =
(folderItem != null && version == DomainFile.DEFAULT_VERSION) ? folderItem
: versionedFolderItem;
ContentHandler<?> ch = getContentHandler();
DomainObjectAdapter doa = ch.getReadOnlyObject(item, version, true, consumer, monitor);
DomainObjectAdapter doa =
getContentHandler().getReadOnlyObject(item, version, true, consumer, monitor);
doa.setChanged(false);
// Notify file manager of in-use domain object.
@ -606,13 +611,14 @@ public class GhidraFileData {
throws VersionException, IOException, CancelledException {
synchronized (fileSystem) {
DomainObjectAdapter obj = null;
ContentHandler<?> ch = getContentHandler();
ContentHandler<?> contentHandler = getContentHandler();
if (versionedFolderItem == null ||
(version == DomainFile.DEFAULT_VERSION && folderItem != null) || isHijacked()) {
obj = ch.getImmutableObject(folderItem, consumer, version, -1, monitor);
obj = contentHandler.getImmutableObject(folderItem, consumer, version, -1, monitor);
}
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.
@ -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.
* 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.
* @param comment new version comment
@ -1033,76 +1038,87 @@ public class GhidraFileData {
*/
void addToVersionControl(String comment, boolean keepCheckedOut, TaskMonitor monitor)
throws IOException, CancelledException {
DomainObjectAdapter oldDomainObj = null;
synchronized (fileSystem) {
if (!canAddToRepository()) {
if (fileSystem.isReadOnly() || versionedFileSystem.isReadOnly()) {
throw new ReadOnlyException(
"addToVersionControl permitted within writeable project and repository only");
}
throw new IOException("addToVersionControl not allowed for file");
}
checkCanAddToRepository();
if (busy.getAndSet(true)) {
throw new FileInUseException(name + " is busy");
}
DomainObjectAdapterDB inUseDomainObj = null;
projectData.mergeStarted();
try {
inUseDomainObj = getAndLockInUseDomainObjectForMergeUpdate("checkin");
if (isLinkFile()) {
keepCheckedOut = false;
}
String parentPath = parent.getPathname();
String user = ClientUtil.getUserName();
try {
if (folderItem instanceof DatabaseItem) {
DatabaseItem databaseItem = (DatabaseItem) folderItem;
BufferFile bufferFile = databaseItem.open();
try {
versionedFolderItem = versionedFileSystem.createDatabase(parentPath, name,
folderItem.getFileID(), bufferFile, comment,
folderItem.getContentType(), false, monitor, user);
}
finally {
bufferFile.dispose();
}
}
else if (folderItem instanceof DataFileItem) {
DataFileItem dataFileItem = (DataFileItem) folderItem;
InputStream istream = dataFileItem.getInputStream();
try {
versionedFolderItem = versionedFileSystem.createDataFile(parentPath, name,
istream, comment, folderItem.getContentType(), monitor);
}
finally {
istream.close();
}
}
else {
throw new AssertException("Unknown folder item type");
}
}
catch (InvalidNameException e) {
throw new AssertException("Unexpected error", e);
else if (inUseDomainObj != null && !keepCheckedOut) {
keepCheckedOut = true;
Msg.warn(this, "File currently open - must keep checked-out: " + name);
}
oldDomainObj = getOpenedDomainObject();
synchronized (fileSystem) {
if (keepCheckedOut) {
boolean exclusive = !versionedFileSystem.isShared();
ProjectLocator projectLocator = parent.getProjectLocator();
CheckoutType checkoutType;
if (projectLocator.isTransient()) {
checkoutType = CheckoutType.TRANSIENT;
exclusive = true;
String parentPath = parent.getPathname();
String user = ClientUtil.getUserName();
try {
if (folderItem instanceof DatabaseItem) {
DatabaseItem databaseItem = (DatabaseItem) folderItem;
BufferFile bufferFile = databaseItem.open();
try {
versionedFolderItem = versionedFileSystem.createDatabase(parentPath,
name, folderItem.getFileID(), bufferFile, comment,
folderItem.getContentType(), false, monitor, user);
}
finally {
bufferFile.dispose();
}
}
else if (folderItem instanceof DataFileItem) {
DataFileItem dataFileItem = (DataFileItem) folderItem;
InputStream istream = dataFileItem.getInputStream();
try {
versionedFolderItem = versionedFileSystem.createDataFile(parentPath,
name, istream, comment, folderItem.getContentType(), monitor);
}
finally {
istream.close();
}
}
else {
throw new AssertException("Unknown folder item type");
}
}
catch (InvalidNameException e) {
throw new AssertException("Unexpected error", e);
}
if (keepCheckedOut) {
// Maintain exclusive chekout if private repository or file is open for update
boolean exclusive = !versionedFileSystem.isShared() || (inUseDomainObj != null);
ProjectLocator projectLocator = parent.getProjectLocator();
CheckoutType checkoutType;
if (projectLocator.isTransient()) {
checkoutType = CheckoutType.TRANSIENT;
exclusive = true;
}
else {
// All checkouts for non-shared versioning are treated as exclusive
checkoutType =
(exclusive || !versionedFileSystem.isShared()) ? CheckoutType.EXCLUSIVE
: CheckoutType.NORMAL;
}
ItemCheckoutStatus checkout = versionedFolderItem.checkout(checkoutType, user,
ItemCheckoutStatus.getProjectPath(projectLocator.toString(),
projectLocator.isTransient()));
folderItem.setCheckout(checkout.getCheckoutId(), exclusive,
checkout.getCheckoutVersion(), folderItem.getCurrentVersion());
}
else {
// All checkouts for non-shared versioning are treated as exclusive
checkoutType =
(exclusive || !versionedFileSystem.isShared()) ? CheckoutType.EXCLUSIVE
: CheckoutType.NORMAL;
}
ItemCheckoutStatus checkout = versionedFolderItem.checkout(checkoutType, user,
ItemCheckoutStatus.getProjectPath(projectLocator.toString(),
projectLocator.isTransient()));
folderItem.setCheckout(checkout.getCheckoutId(), exclusive,
checkout.getCheckoutVersion(), folderItem.getCurrentVersion());
}
else {
if (oldDomainObj == null) {
// NOTE: file open read-only may prevent removal and result in hijack
try {
folderItem.delete(-1, ClientUtil.getUserName());
folderItem = null;
@ -1111,27 +1127,23 @@ public class GhidraFileData {
// 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());
oldDomainObj.setDomainFile(new DomainFileProxy("~" + name, oldDomainObj));
oldDomainObj.setTemporary(true);
if (inUseDomainObj != null) {
inUseDomainObj.invalidate();
}
}
if (oldDomainObj != null) {
// Complete re-open of file
DomainFile df = getDomainFile();
listener.domainFileObjectClosed(df, oldDomainObj);
listener.domainFileObjectReplaced(df, oldDomainObj);
}
if (!keepCheckedOut) {
finally {
unlockDomainObject(inUseDomainObj);
busy.set(false);
projectData.mergeEnded();
parent.deleteLocalFolderIfEmpty();
parent.fileChanged(name);
}
statusChanged();
}
/**
@ -1258,7 +1270,8 @@ public class GhidraFileData {
if (versionedFolderItem.getCurrentVersion() != folderItem.getCheckoutVersion()) {
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();
}
@ -1266,18 +1279,15 @@ public class GhidraFileData {
if (checkinHandler.createKeepFile()) {
DomainObject sourceObj = null;
try {
ContentHandler<?> ch = getContentHandler();
sourceObj = ch.getImmutableObject(folderItem, this, DomainFile.DEFAULT_VERSION,
-1, monitor);
sourceObj = getContentHandler().getImmutableObject(folderItem, this,
DomainFile.DEFAULT_VERSION, -1, monitor);
createKeepFile(sourceObj, monitor);
}
catch (VersionException e) {
// ignore - unable to create keep file
}
finally {
if (sourceObj != null) {
sourceObj.release(this);
}
release(sourceObj);
}
}
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)
* @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
* 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) {
return; // rely on local project ownership
}
@ -1327,15 +1337,13 @@ public class GhidraFileData {
* 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.
* @param okToUpgrade if true an upgrade will be performed if needed
* @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.
* If okToUpgrade was false, check exception to see if it can be upgraded
* sometime after doing a checkout.
* We are unable to upgrade since this would only occur if checkout is not exclusive.
* @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 {
if (!versionedFileSystem.isOnline()) {
@ -1357,15 +1365,29 @@ public class GhidraFileData {
if (!modifiedSinceCheckout()) {
throw new IOException("File has not been modified since checkout");
}
verifyRepoUser("checkin");
verifyCheckout("checkin");
if (monitor == null) {
monitor = TaskMonitor.DUMMY;
}
if (busy.getAndSet(true)) {
throw new FileInUseException(name + " is busy");
}
DomainObjectAdapterDB inUseDomainObj = null;
projectData.mergeStarted();
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);
if (!quickCheckin) {
@ -1377,9 +1399,8 @@ public class GhidraFileData {
Msg.info(this, "Checkin with merge for " + name);
ContentHandler<?> ch = getContentHandler();
DomainObjectAdapter checkinObj = ch.getDomainObject(versionedFolderItem, null,
folderItem.getCheckoutId(), okToUpgrade, false, this, monitor);
DomainObjectAdapter checkinObj = contentHandler.getDomainObject(versionedFolderItem,
null, folderItem.getCheckoutId(), false, false, this, monitor);
checkinObj.setDomainFile(new DomainFileProxy(name, getParent().getPathname(),
checkinObj, versionedFolderItem.getCurrentVersion() + 1, fileID,
parent.getProjectLocator()));
@ -1390,15 +1411,15 @@ public class GhidraFileData {
try {
synchronized (fileSystem) {
int coVer = folderItem.getCheckoutVersion();
sourceObj = ch.getImmutableObject(folderItem, this,
sourceObj = contentHandler.getImmutableObject(folderItem, this,
DomainFile.DEFAULT_VERSION, -1, monitor);
originalObj =
ch.getImmutableObject(versionedFolderItem, this, coVer, -1, monitor);
latestObj = ch.getImmutableObject(versionedFolderItem, this,
originalObj = contentHandler.getImmutableObject(versionedFolderItem, this,
coVer, -1, monitor);
latestObj = contentHandler.getImmutableObject(versionedFolderItem, this,
DomainFile.DEFAULT_VERSION, coVer, monitor);
}
DomainObjectMergeManager mergeMgr =
ch.getMergeManager(checkinObj, sourceObj, originalObj, latestObj);
DomainObjectMergeManager mergeMgr = contentHandler.getMergeManager(checkinObj,
sourceObj, originalObj, latestObj);
if (!mergeMgr.merge(monitor)) {
Msg.info(this, "Checkin with merge terminated for " + name);
@ -1417,27 +1438,14 @@ public class GhidraFileData {
}
finally {
checkinObj.release(this);
if (sourceObj != null) {
sourceObj.release(this);
}
if (originalObj != null) {
originalObj.release(this);
}
if (latestObj != null) {
latestObj.release(this);
}
release(sourceObj);
release(originalObj);
release(latestObj);
}
}
DomainObjectAdapter oldDomainObj = null;
FolderItem oldLocalItem = null;
boolean keepCheckedOut = checkinHandler.keepCheckedOut();
synchronized (fileSystem) {
oldDomainObj = getOpenedDomainObject();
versionedFolderItem = versionedFileSystem.getItem(parent.getPathname(), name);
if (versionedFolderItem == null) {
throw new IOException("Checkin failed, versioned item not found");
@ -1456,7 +1464,17 @@ public class GhidraFileData {
}
finally {
if (!success) {
// Failed to update checkout for unknown reason
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);
}
catch (IOException e) {
@ -1466,53 +1484,71 @@ public class GhidraFileData {
}
}
else {
if (oldDomainObj != null) {
oldLocalItem = folderItem;
folderItem = null;
}
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
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 (inUseDomainObj != null) {
contentHandler.resetDBSourceFile(folderItem, inUseDomainObj);
}
}
if (oldDomainObj != null) {
// complete re-open of domain file
DomainFile df = getDomainFile();
listener.domainFileObjectClosed(df, oldDomainObj);
listener.domainFileObjectReplaced(df, oldDomainObj);
}
} // end of synchronized block
if (oldLocalItem != null) {
synchronized (fileSystem) {
// 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);
}
if (inUseDomainObj != null) {
inUseDomainObj.invalidate();
}
}
finally {
unlockDomainObject(inUseDomainObj);
busy.set(false);
try {
parent.deleteLocalFolderIfEmpty();
parent.fileChanged(name);
projectData.mergeEnded();
parent.deleteLocalFolderIfEmpty();
parent.fileChanged(name);
}
}
private void release(DomainObject domainObj) {
if (domainObj != null) {
domainObj.release(this);
}
}
private void unlockDomainObject(DomainObjectAdapterDB lockedDomainObject) {
try {
if (lockedDomainObject != null) {
lockedDomainObject.unlock();
}
finally {
projectData.mergeEnded();
}
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");
}
if (!doForce) {
verifyRepoUser("undo-checkout");
verifyCheckout("undo-checkout");
long checkoutId = folderItem.getCheckoutId();
versionedFolderItem.terminateCheckout(checkoutId, true);
}
@ -1666,7 +1702,7 @@ public class GhidraFileData {
return false;
}
private void createKeepFile(DomainObject oldDomainObj, TaskMonitor monitor) {
private void createKeepFile(DomainObject sourceObj, TaskMonitor monitor) {
String keepName = name + ".keep";
try {
GhidraFileData keepFileData = parent.getFileData(keepName, false);
@ -1683,7 +1719,7 @@ public class GhidraFileData {
}
keepName = getKeepName();
Msg.info(this, "Creating old version keep file: " + keepName);
parent.createFile(keepName, oldDomainObj, monitor);
parent.createFile(keepName, sourceObj, monitor);
}
catch (InvalidNameException e) {
throw new AssertException("Unexpected error", e);
@ -1765,10 +1801,10 @@ public class GhidraFileData {
private void removeAssociatedUserDataFile() {
try {
ContentHandler<?> ch = getContentHandler();
if (ch instanceof DBWithUserDataContentHandler) {
ContentHandler<?> contentHandler = getContentHandler();
if (contentHandler instanceof DBWithUserDataContentHandler) {
FolderItem item = folderItem != null ? folderItem : versionedFolderItem;
((DBWithUserDataContentHandler<?>) ch).removeUserDataFile(item,
((DBWithUserDataContentHandler<?>) contentHandler).removeUserDataFile(item,
parent.getUserFileSystem());
}
}
@ -1812,7 +1848,7 @@ public class GhidraFileData {
if (canRecover()) {
throw new IOException("File recovery data exists");
}
verifyRepoUser("merge");
verifyCheckout("merge");
if (monitor == null) {
monitor = TaskMonitor.DUMMY;
}
@ -1821,8 +1857,11 @@ public class GhidraFileData {
}
FolderItem tmpItem = null;
DomainObjectAdapterDB inUseDomainObj = null;
projectData.mergeStarted();
try {
inUseDomainObj = getAndLockInUseDomainObjectForMergeUpdate("merge");
if (!modifiedSinceCheckout()) {
// Quick merge
folderItem.updateCheckout(versionedFolderItem, true, monitor);
@ -1830,17 +1869,17 @@ public class GhidraFileData {
else {
if (SystemUtilities.isInHeadlessMode()) {
throw new IOException(
"Merge failed, file merge is not supported in headless mode");
throw new IOException("Merge failed, merge is not supported in headless mode");
}
ContentHandler<?> ch = getContentHandler();
ContentHandler<?> contentHandler = getContentHandler();
// Test versioned file for VersionException
int mergeVer = versionedFolderItem.getCurrentVersion();
if (!okToUpgrade) {
DomainObject testObj =
ch.getReadOnlyObject(versionedFolderItem, mergeVer, false, this, monitor);
// verify remote version can be opened without verion error
DomainObject testObj = contentHandler.getReadOnlyObject(versionedFolderItem,
mergeVer, false, this, monitor);
testObj.release(this);
}
@ -1866,21 +1905,21 @@ public class GhidraFileData {
tmpItem.setCheckout(checkoutId, folderItem.isCheckedOutExclusive(), mergeVer, 0);
DomainObject mergeObj =
ch.getDomainObject(tmpItem, null, -1, okToUpgrade, false, this, monitor);
DomainObject mergeObj = contentHandler.getDomainObject(tmpItem, null, -1,
okToUpgrade, false, this, monitor);
DomainObject sourceObj = 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
try {
sourceObj = ch.getImmutableObject(folderItem, this, DomainFile.DEFAULT_VERSION,
-1, monitor);
originalObj =
ch.getImmutableObject(versionedFolderItem, this, coVer, -1, monitor);
latestObj =
ch.getImmutableObject(versionedFolderItem, this, mergeVer, coVer, monitor);
sourceObj = contentHandler.getImmutableObject(folderItem, this,
DomainFile.DEFAULT_VERSION, -1, monitor);
originalObj = contentHandler.getImmutableObject(versionedFolderItem, this,
coVer, -1, monitor);
latestObj = contentHandler.getImmutableObject(versionedFolderItem, this,
mergeVer, coVer, monitor);
DomainObjectMergeManager mergeMgr =
ch.getMergeManager(mergeObj, sourceObj, originalObj, latestObj);
contentHandler.getMergeManager(mergeObj, sourceObj, originalObj, latestObj);
if (!mergeMgr.merge(monitor)) {
Msg.info(this, "Merge terminated for " + name);
@ -1888,19 +1927,14 @@ public class GhidraFileData {
}
mergeObj.save("Merge with version " + mergeVer, monitor);
createKeepFile(sourceObj, monitor);
}
finally {
mergeObj.release(this);
if (sourceObj != null) {
sourceObj.release(this);
}
if (originalObj != null) {
originalObj.release(this);
}
if (latestObj != null) {
latestObj.release(this);
}
release(mergeObj);
release(sourceObj);
release(originalObj);
release(latestObj);
}
// Update folder item
@ -1909,29 +1943,18 @@ public class GhidraFileData {
ClientUtil.getUserName());
tmpItem = null;
Msg.info(this, "Merge completed for " + name);
}
DomainObjectAdapter oldDomainObj = null;
// 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 (inUseDomainObj != null) {
contentHandler.resetDBSourceFile(folderItem, inUseDomainObj);
}
}
if (oldDomainObj != null) {
// Complete re-open of file
DomainFile df = getDomainFile();
listener.domainFileObjectClosed(df, oldDomainObj);
listener.domainFileObjectReplaced(df, oldDomainObj);
if (inUseDomainObj != null) {
inUseDomainObj.invalidate();
}
}
finally {
unlockDomainObject(inUseDomainObj);
busy.set(false);
try {
if (tmpItem != null) {
@ -2281,19 +2304,22 @@ public class GhidraFileData {
/**
* 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.
* @param item folder item whose metadata should be read
* @return a map containing the metadata that has been associated with the corresponding domain
* object. Map will be empty for a non-database 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;
try {
if (item instanceof DatabaseItem) {
DatabaseItem databaseItem = (DatabaseItem) item;
BufferFile bf = databaseItem.open();
DBHandle dbh = new DBHandle(bf);
genericDomainObj = new GenericDomainObjectDB(dbh);
return genericDomainObj.getMetadata();
}
bf = databaseItem.open();
dbh = new DBHandle(bf);
genericDomainObj = new GenericDomainObjectDB(dbh);
return genericDomainObj.getMetadata();
}
catch (FileNotFoundException e) {
// file has been deleted, just return an empty map.
@ -2308,6 +2334,12 @@ public class GhidraFileData {
if (genericDomainObj != null) {
genericDomainObj.release();
}
if (dbh != null) {
dbh.close();
}
if (bf != null) {
bf.dispose();
}
}
return new HashMap<>();
}

View File

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

View File

@ -96,8 +96,7 @@ public class ProjectDataTablePanel extends JPanel {
}
});
gTable.getSelectionModel()
.addListSelectionListener(
e -> plugin.getTool().contextChanged(null));
.addListSelectionListener(e -> plugin.getTool().contextChanged(null));
gTable.setDefaultRenderer(Date.class, new DateCellRenderer());
gTable.setDefaultRenderer(DomainFileType.class, new TypeCellRenderer());
@ -364,11 +363,6 @@ public class ProjectDataTablePanel extends JPanel {
reload();
}
@Override
public void domainFolderSetActive(DomainFolder folder) {
// don't care
}
@Override
public void domainFileStatusChanged(DomainFile file, boolean fileIDset) {
if (ignoreChanges()) {
@ -379,24 +373,6 @@ public class ProjectDataTablePanel extends JPanel {
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
public void domainFileStatusChanged(DomainFile file, boolean fileIDset) {
DomainFileNode fileNode = findDomainFileNode(file, true);
@ -152,7 +147,6 @@ class ChangeManager implements DomainFolderChangeListener {
fileNode.refresh();
}
treePanel.domainChange();
// treePanel.getActionManager().adjustActions();
}
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 {
if (newFile) {
newFile = false;
if (monitor.isCancelled()) {
throw new CancelledException();
}
monitor.checkCancelled();
if (actionID != VersionControlDialog.APPLY_TO_ALL) {
showDialog(false, df.getName(), df.isLinkFile()); // false==> checking in vs.
showDialog(false, df);
// adding to version control
if (actionID == VersionControlDialog.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
public void run(TaskMonitor myMonitor) {
this.monitor = myMonitor;
myMonitor.setMessage("Examining selected file(s)");
// checkFilesInUse();
String currentName = null;
String currentContentType = null;
try {
for (int i = 0; i < list.size() && actionID != VersionControlDialog.CANCEL; i++) {
df = list.get(i);
currentName = df.getName();
currentContentType = df.getContentType();
newFile = true;
if (i != 0) {
@ -94,27 +85,23 @@ public class CheckInTask extends VersionControlTask implements CheckinHandler {
Thread.sleep(200);
}
catch (InterruptedException e2) {
break;
}
}
myMonitor.setMessage("Initiating Check In for " + currentName);
try {
df.checkin(this, false, myMonitor);
df.checkin(this, myMonitor);
}
catch (VersionException e) {
if (VersionExceptionHandler.isUpgradeOK(parent, df, "Checkin", e)) {
df.checkin(this, true, myMonitor);
}
VersionExceptionHandler.showVersionError(parent, df.getName(),
df.getContentType(), "Checkin", e);
}
if (myMonitor.isCancelled()) {
break;
}
}
}
catch (VersionException e) {
VersionExceptionHandler.showVersionError(parent, df.getName(), currentContentType,
"Checkin", e);
}
catch (CancelledException e) {
Msg.info(this, "Check In Process was canceled");
wasCanceled = true;
@ -125,18 +112,12 @@ public class CheckInTask extends VersionControlTask implements CheckinHandler {
}
}
/*
* @see ghidra.framework.data.CheckinHandler#getComment()
*/
@Override
public String getComment() throws CancelledException {
promptUser();
return comments;
}
/*
* @see ghidra.framework.data.CheckinHandler#keepCheckedOut()
*/
@Override
public boolean keepCheckedOut() throws CancelledException {
promptUser();

View File

@ -57,21 +57,22 @@ public abstract class VersionControlTask extends Task {
* Show the dialog.
* @param addToVersionControl true if the dialog is for
* 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 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.
* @param file the file currently to be added or checked-in to version control
*/
protected void showDialog(boolean addToVersionControl, String filename, boolean isLinkFile) {
protected void showDialog(boolean addToVersionControl, DomainFile file) {
Runnable r = () -> {
VersionControlDialog vcDialog = new VersionControlDialog(addToVersionControl);
vcDialog.setCurrentFileName(filename);
vcDialog.setCurrentFileName(file.getName());
vcDialog.setMultiFiles(list.size() > 1);
if (isLinkFile) {
vcDialog.setKeepCheckboxEnabled(false, false, "Link files may not be Checked Out");
if (file.isLinkFile()) {
vcDialog.setKeepCheckboxEnabled(false, false, "Link file may not be Checked Out");
}
else if (filesInUse) {
vcDialog.setKeepCheckboxEnabled(false, true,
"Must keep Checked Out because the file is in use");
else {
checkFilesInUse();
if (filesInUse) {
vcDialog.setKeepCheckboxEnabled(false, true,
"Must keep Checked Out because the file is in use");
}
}
actionID = vcDialog.showDialog(tool, parent);
keepCheckedOut = vcDialog.keepCheckedOut();
@ -93,9 +94,11 @@ public abstract class VersionControlTask extends Task {
* are still in use.
*/
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;
for (int i = 0; i < list.size(); i++) {
DomainFile df = list.get(i);
for (DomainFile df : list) {
if (df.getConsumers().size() > 0) {
filesInUse = true;
return;
@ -104,8 +107,7 @@ public abstract class VersionControlTask extends Task {
}
protected boolean checkFilesForUnsavedChanges() {
for (int i = 0; i < list.size(); i++) {
DomainFile df = list.get(i);
for (DomainFile df : list) {
if (df.modifiedSinceCheckout()) {
return true;
}

View File

@ -136,14 +136,13 @@ public class VersionControlAddAction extends VersionControlAction {
@Override
public void run(TaskMonitor monitor) {
checkFilesInUse();
try {
for (DomainFile df : list) {
String name = df.getName();
monitor.setMessage("Adding " + name + " to Version Control");
if (actionID != VersionControlDialog.APPLY_TO_ALL) {
showDialog(true, name, df.isLinkFile());
showDialog(true, df);
}
if (actionID == VersionControlDialog.CANCEL) {
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.
* @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();
/**
* 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();
/**
* 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();
@ -381,7 +396,8 @@ public interface DomainFile extends Comparable<DomainFile> {
/**
* Adds this private file to version control.
* @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
* @throws FileInUseException if this file is in-use.
* @throws IOException if an IO or access error occurs. Also if file is not
@ -405,20 +421,42 @@ public interface DomainFile extends Comparable<DomainFile> {
public boolean checkout(boolean exclusive, TaskMonitor monitor)
throws IOException, 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.
* 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.
* @param okToUpgrade if true an upgrade will be performed if needed
* 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.
* @throws IOException if an IO or access error occurs
* @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
* sometime after doing a checkout.
* @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)
throws IOException, VersionException, CancelledException;
@Deprecated(since = "11.1", forRemoval = true)
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.

View File

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (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.
* @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
* get the parent from the file.
* @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.
* @param parent domain folder which contained the folder that was just 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
@ -48,40 +53,54 @@ public interface DomainFolderChangeListener {
* @param name the name of the file that was removed.
* @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.
* @param folder folder that was renamed
* @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.
* @param file file that was renamed
* @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.
* @param folder the folder (after move)
* @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.
* @param file the file (after move)
* @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.
* @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.
@ -89,30 +108,25 @@ public interface DomainFolderChangeListener {
* @param fileIDset if true indicates that the previously missing fileID has been
* established for the specified file.
*/
public void domainFileStatusChanged(DomainFile file, boolean fileIDset);
/**
* 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);
public default void domainFileStatusChanged(DomainFile file, boolean fileIDset) {
// do nothing
}
/**
* Notification that a domain file has been opened for update.
* @param file domain file
* @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.
* @param file domain file
* @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);
}
@Override
public void domainFolderSetActive(DomainFolder folder) {
// do nothing
}
@Override
public void domainFileStatusChanged(DomainFile file, boolean fileIDset) {
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
public void checkin(CheckinHandler checkinHandler, boolean okToUpgrade, TaskMonitor monitor)
public void checkin(CheckinHandler checkinHandler, TaskMonitor monitor)
throws IOException, VersionException, CancelledException {
throw new UnsupportedOperationException();
}

View File

@ -21,14 +21,13 @@ import javax.swing.Icon;
import db.DBConstants;
import db.DBHandle;
import db.buffers.BufferFile;
import db.buffers.ManagedBufferFile;
import db.buffers.*;
import generic.theme.GIcon;
import ghidra.framework.data.DBContentHandler;
import ghidra.framework.data.DomainObjectMergeManager;
import ghidra.framework.data.*;
import ghidra.framework.model.ChangeSet;
import ghidra.framework.model.DomainObject;
import ghidra.framework.store.*;
import ghidra.framework.store.local.LocalDatabaseItem;
import ghidra.util.InvalidNameException;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
@ -371,4 +370,21 @@ public class DataTypeArchiveContentHandler extends DBContentHandler<DataTypeArch
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
public void invalidate() {
clearCache(false);

View File

@ -20,14 +20,13 @@ import java.io.IOException;
import javax.swing.Icon;
import db.*;
import db.buffers.BufferFile;
import db.buffers.ManagedBufferFile;
import db.buffers.*;
import generic.theme.GIcon;
import ghidra.framework.data.DBWithUserDataContentHandler;
import ghidra.framework.data.DomainObjectMergeManager;
import ghidra.framework.data.*;
import ghidra.framework.model.ChangeSet;
import ghidra.framework.model.DomainObject;
import ghidra.framework.store.*;
import ghidra.framework.store.local.LocalDatabaseItem;
import ghidra.util.InvalidNameException;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
@ -58,8 +57,7 @@ public class ProgramContentHandler extends DBWithUserDataContentHandler<ProgramD
if (!(obj instanceof ProgramDB)) {
throw new IOException("Unsupported domain object: " + obj.getClass().getName());
}
return createFile((ProgramDB) obj, PROGRAM_CONTENT_TYPE, fs, path, name,
monitor);
return createFile((ProgramDB) obj, PROGRAM_CONTENT_TYPE, fs, path, name, monitor);
}
@Override
@ -363,4 +361,21 @@ public class ProgramContentHandler extends DBWithUserDataContentHandler<ProgramD
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
public boolean isChangeable() {
return changeable;

View File

@ -356,12 +356,6 @@ public interface Program extends DataTypeManagerDomainObject, ProgramArchitectur
*/
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
* @param overlaySpaceName the name of the new overlay space.

View File

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

View File

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

View File

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