From 2dff876f0f02fa3a440a434f0db6fb7c671ee446 Mon Sep 17 00:00:00 2001 From: ghidra1 Date: Wed, 20 Mar 2024 17:43:49 -0400 Subject: [PATCH] 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). --- .../DebuggerStaticMappingServicePlugin.java | 7 +- .../service/modules/ProgramModuleIndexer.java | 8 +- .../utils/DomainFolderChangeAdapter.java | 72 --- .../service/url/ProjectExperimentsTest.java | 5 - .../database/program/DBTraceProgramView.java | 6 - .../program/DBTraceProgramViewRegisters.java | 9 +- .../RepositoryFileUpgradeScript.java | 2 +- .../archive/DataTypeManagerHandler.java | 16 - .../core/progmgr/MultiProgramManager.java | 44 -- .../DomainFolderChangesDisplayPlugin.java | 5 - .../app/util/headless/HeadlessAnalyzer.java | 2 +- .../data/DefaultProjectDataTest.java | 14 - .../base/project/FakeSharedProject.java | 16 +- .../vt/api/db/VTSessionContentHandler.java | 21 +- .../vt/gui/plugin/VTControllerImpl.java | 83 --- .../DB/src/main/java/db/DBHandle.java | 39 +- .../src/main/java/db/buffers/BufferMgr.java | 148 +++-- .../db/buffers/LocalManagedBufferFile.java | 5 +- .../ghidra/framework/data/ContentHandler.java | 58 +- .../framework/data/DBContentHandler.java | 7 +- .../framework/data/DefaultCheckinHandler.java | 2 +- .../framework/data/DomainFileIndex.java | 36 +- .../framework/data/DomainFileProxy.java | 2 +- .../data/DomainFolderChangeListenerList.java | 16 +- .../framework/data/DomainObjectAdapter.java | 13 +- .../framework/data/DomainObjectAdapterDB.java | 40 +- .../ghidra/framework/data/GhidraFile.java | 10 +- .../ghidra/framework/data/GhidraFileData.java | 516 ++++++++++-------- .../framework/data/LinkedGhidraFile.java | 2 +- .../main/datatable/ProjectDataTablePanel.java | 26 +- .../main/datatree/ChangeManager.java | 29 - .../framework/main/datatree/CheckInTask.java | 31 +- .../main/datatree/VersionControlTask.java | 30 +- .../actions/VersionControlAddAction.java | 3 +- .../ghidra/framework/model/DomainFile.java | 52 +- .../model/DomainFolderChangeListener.java | 62 ++- .../model/DomainFolderListenerAdapter.java | 19 - .../framework/model/TestDummyDomainFile.java | 2 +- .../DataTypeArchiveContentHandler.java | 24 +- .../program/database/DataTypeArchiveDB.java | 3 - .../database/ProgramContentHandler.java | 27 +- .../ghidra/program/database/ProgramDB.java | 6 - .../ghidra/program/model/listing/Program.java | 6 - .../ghidra/program/model/StubProgram.java | 5 - .../ProgramManagerPluginScreenShots.java | 4 +- .../datatree/VersionControlAction1Test.java | 14 +- 46 files changed, 695 insertions(+), 852 deletions(-) delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/DomainFolderChangeAdapter.java diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java index c57aa2173b..f4be7da5fb 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java @@ -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; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/ProgramModuleIndexer.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/ProgramModuleIndexer.java index ba48220be5..20e8230328 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/ProgramModuleIndexer.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/ProgramModuleIndexer.java @@ -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) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/DomainFolderChangeAdapter.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/DomainFolderChangeAdapter.java deleted file mode 100644 index 9528dc4a2e..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/DomainFolderChangeAdapter.java +++ /dev/null @@ -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) { - } -} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/url/ProjectExperimentsTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/url/ProjectExperimentsTest.java index e0c6d45e19..adae9bb769 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/url/ProjectExperimentsTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/url/ProjectExperimentsTest.java @@ -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)); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramView.java index 74bc0e318e..b8c3e62b49 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramView.java @@ -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); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegisters.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegisters.java index 2e0bd8c1e6..b707c517b3 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegisters.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegisters.java @@ -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); } } diff --git a/Ghidra/Features/Base/ghidra_scripts/RepositoryFileUpgradeScript.java b/Ghidra/Features/Base/ghidra_scripts/RepositoryFileUpgradeScript.java index 78e207546c..774e2e277a 100644 --- a/Ghidra/Features/Base/ghidra_scripts/RepositoryFileUpgradeScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/RepositoryFileUpgradeScript.java @@ -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 { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeManagerHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeManagerHandler.java index 85e41b4365..2306fb02f6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeManagerHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeManagerHandler.java @@ -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 diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiProgramManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiProgramManager.java index 72862acd04..acd7db4b74 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiProgramManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiProgramManager.java @@ -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 { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/DomainFolderChangesDisplayPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/DomainFolderChangesDisplayPlugin.java index 93e980b94b..86b57105bb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/DomainFolderChangesDisplayPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/DomainFolderChangesDisplayPlugin.java @@ -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()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java index 33f5fb2f0c..66163fc318 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java @@ -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) { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/DefaultProjectDataTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/DefaultProjectDataTest.java index 09e77fbbaa..9be87dcce7 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/DefaultProjectDataTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/DefaultProjectDataTest.java @@ -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 - } } } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/base/project/FakeSharedProject.java b/Ghidra/Features/Base/src/test/java/ghidra/base/project/FakeSharedProject.java index 3de5d6e918..9197c68465 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/base/project/FakeSharedProject.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/base/project/FakeSharedProject.java @@ -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)) { diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/db/VTSessionContentHandler.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/db/VTSessionContentHandler.java index 7f7be2ff35..a65b77307f 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/db/VTSessionContentHandler.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/db/VTSessionContentHandler.java @@ -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 { 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); + } + } diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/plugin/VTControllerImpl.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/plugin/VTControllerImpl.java index 8a3deba1dd..2473ee06f8 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/plugin/VTControllerImpl.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/plugin/VTControllerImpl.java @@ -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 events = new ArrayList(); -// 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; diff --git a/Ghidra/Framework/DB/src/main/java/db/DBHandle.java b/Ghidra/Framework/DB/src/main/java/db/DBHandle.java index 98909d6ee4..1f00f89d95 100644 --- a/Ghidra/Framework/DB/src/main/java/db/DBHandle.java +++ b/Ghidra/Framework/DB/src/main/java/db/DBHandle.java @@ -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 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)) { diff --git a/Ghidra/Framework/DB/src/main/java/db/buffers/BufferMgr.java b/Ghidra/Framework/DB/src/main/java/db/buffers/BufferMgr.java index 978131edd5..88eda4b1ba 100644 --- a/Ghidra/Framework/DB/src/main/java/db/buffers/BufferMgr.java +++ b/Ghidra/Framework/DB/src/main/java/db/buffers/BufferMgr.java @@ -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 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 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) { diff --git a/Ghidra/Framework/DB/src/main/java/db/buffers/LocalManagedBufferFile.java b/Ghidra/Framework/DB/src/main/java/db/buffers/LocalManagedBufferFile.java index 7ce4ed857e..376e9c3cd1 100644 --- a/Ghidra/Framework/DB/src/main/java/db/buffers/LocalManagedBufferFile.java +++ b/Ghidra/Framework/DB/src/main/java/db/buffers/LocalManagedBufferFile.java @@ -157,7 +157,6 @@ public class LocalManagedBufferFile extends LocalBufferFile implements ManagedBu * preSaveThread 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)); } } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/ContentHandler.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/ContentHandler.java index f8406044b0..78dda0a7de 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/ContentHandler.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/ContentHandler.java @@ -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 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 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 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 extends Extension return null; } + /** + * Determine if this content handler supports the use of + * {@link #resetDBSourceFile(FolderItem, DomainObjectAdapterDB)} . + *

+ * A versioned {@link DomainObjectAdapterDB domain object} open for update may have its + * underlying database reset to the latest buffer file version: + *

    + *
  1. 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,
  2. + *
  3. afterwhich the caller must {@link DomainObjectAdapter#invalidate() invalidate} the domain + * object instance which will clear all caches and generate a {@link DomainObjectEvent#RESTORED} + * event.
  4. + *
+ * @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. + *

+ * 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(); + } + } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DBContentHandler.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DBContentHandler.java index 4b62da8926..3136f8cd2c 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DBContentHandler.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DBContentHandler.java @@ -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 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 { diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DefaultCheckinHandler.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DefaultCheckinHandler.java index c985b8b963..4f89e1c0c8 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DefaultCheckinHandler.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DefaultCheckinHandler.java @@ -21,7 +21,7 @@ import ghidra.util.exception.CancelledException; /** * DefaultCheckinHandler 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 { diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFileIndex.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFileIndex.java index a4052a91f6..3965f0411f 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFileIndex.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFileIndex.java @@ -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 - } } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFileProxy.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFileProxy.java index bbaff1ff36..3c558d0d20 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFileProxy.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFileProxy.java @@ -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"); } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFolderChangeListenerList.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFolderChangeListenerList.java index af523cc8be..a2cc74cf27 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFolderChangeListenerList.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFolderChangeListenerList.java @@ -26,8 +26,7 @@ class DomainFolderChangeListenerList implements DomainFolderChangeListener { private DomainFileIndex fileIndex; /** CopyOnWriteArrayList prevents the need for synchronization */ - private List list = - new CopyOnWriteArrayList<>(); + private List 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(); } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapter.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapter.java index b3d0bd6a42..5e9aad26f8 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapter.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapter.java @@ -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) { diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java index ec193d83e0..0205d0e7f8 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java @@ -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: - *

- *
- * 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
- *
- * 
*/ 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(); } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFile.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFile.java index 21c1143b39..398c651354 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFile.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFile.java @@ -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 diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFileData.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFileData.java index 2f06823cf5..811f3ccfdc 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFileData.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFileData.java @@ -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 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<>(); } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LinkedGhidraFile.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LinkedGhidraFile.java index 21e155ae0d..a298700753 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LinkedGhidraFile.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LinkedGhidraFile.java @@ -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(); } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataTablePanel.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataTablePanel.java index f82a8e966f..ab7ddd41d9 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataTablePanel.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataTablePanel.java @@ -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 - } } /** diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/ChangeManager.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/ChangeManager.java index 99da701708..8d62de971b 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/ChangeManager.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/ChangeManager.java @@ -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 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 - } } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/CheckInTask.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/CheckInTask.java index e68dfa1c0e..15a1fd4c98 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/CheckInTask.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/CheckInTask.java @@ -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(); diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/VersionControlTask.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/VersionControlTask.java index 9e21bbd4f3..964519907b 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/VersionControlTask.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/VersionControlTask.java @@ -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; } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/projectdata/actions/VersionControlAddAction.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/projectdata/actions/VersionControlAddAction.java index d89d1637c6..823b122266 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/projectdata/actions/VersionControlAddAction.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/projectdata/actions/VersionControlAddAction.java @@ -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; diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainFile.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainFile.java index d2c5fa2016..b9fdae05f8 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainFile.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainFile.java @@ -305,19 +305,34 @@ public interface DomainFile extends Comparable { /** * 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 { /** * 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 { 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. diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainFolderChangeListener.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainFolderChangeListener.java index 43567d3024..92afd300bc 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainFolderChangeListener.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainFolderChangeListener.java @@ -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 + } } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainFolderListenerAdapter.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainFolderListenerAdapter.java index 21506d4fd5..c56484e9c4 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainFolderListenerAdapter.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainFolderListenerAdapter.java @@ -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 - } } diff --git a/Ghidra/Framework/Project/src/test/java/ghidra/framework/model/TestDummyDomainFile.java b/Ghidra/Framework/Project/src/test/java/ghidra/framework/model/TestDummyDomainFile.java index 1be5916211..bf9239f46c 100644 --- a/Ghidra/Framework/Project/src/test/java/ghidra/framework/model/TestDummyDomainFile.java +++ b/Ghidra/Framework/Project/src/test/java/ghidra/framework/model/TestDummyDomainFile.java @@ -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(); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/DataTypeArchiveContentHandler.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/DataTypeArchiveContentHandler.java index 907f8f83f7..0ffb16c67a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/DataTypeArchiveContentHandler.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/DataTypeArchiveContentHandler.java @@ -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 { 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");