Class ModuleFileTransactions
/upm update replaces a module's JAR (#505, #513).
Observation, not prediction. An update never decides before a restart whether the new
JAR will load. /upm update downloads the new JAR into the transactions folder,
<server root>/.ultikits/upm-transactions -- never into the modules folder -- and writes a
record. At the next start, before the module
class loader is built, applyBeforeLoad() moves the old JAR aside (it is kept) and puts
the new one in place. After the modules have loaded, observeAfterLoad(List, Function)
looks at what actually loaded: the update is committed only when the module is loaded from the
new JAR at the new version, and otherwise rolled back -- the old JAR restored, the new one
removed. Staging never touches the modules folder; the only change while the server runs is a
rollback right after loading, which removes the JAR of a module that did not load and puts the
old JAR back for the next start.
States. PENDING (staged) -> APPLIED (swapped at a start) ->
COMMITTING or ROLLING_BACK (decided) -> record deleted. FAILED (the
swap could not be made; nothing in the modules folder changed) is kept until the next
/upm update of that module reports it. An APPLIED record found at a start means
the previous start ended before the decision; nothing was observed, so it is rolled back before
the modules load.
Crash safety. Every state change is written to the record atomically before the step that depends on it, and each step decides from the record plus which files exist -- the new JAR is recognised by its SHA-256, recorded when it was staged -- so running a step again after a crash completes it rather than repeating it, and never deletes a file that is not the staged JAR. Both versions are kept until the decision.
Deferred removal. An uninstall whose JAR cannot be deleted while the server runs --
Windows holds every module JAR open through the shared module class loader -- records the file
(recordDeferredRemoval(String, List)), and the next start deletes it before any module
loads, if it is still that file (#518). Only a file confirmed to be the recorded one is deleted; one
confirmed to be a different file is left alone and its entry dropped; one whose content cannot be
read at that start stays recorded, unchanged, for the next start to try again -- a failure to read
the file's content never cancels an uninstall the operator asked for. (The size and modification-time
checks use File.length() and File.lastModified(), which report 0 on an I/O error, so a
failure to stat the file is not covered by this guarantee.)
Where the records live. Under the server root, beside the credential store
(transactionsFolder(File)), not under plugins/: the panel's file interface can
write under plugins/ when its file writing is enabled, and a forged removal record there
would delete a module JAR at the next start (maintainer decision, 2026-09-30). The price is that
the start-up swap is an atomic rename between the modules folder and this folder: when
plugins/ is on another file system the rename is refused, the update is not applied, and
one SEVERE line names both folders. There is deliberately no copy fallback -- a copy is not
atomic, and the crash-window guarantees below rely on every move being a rename.
Confinement. A record names files only by their file name. Each name is resolved
against the one folder it belongs to and canonicalised; a name that leaves that folder -- a
separator, .., or a link pointing elsewhere -- makes the whole record refused with a
warning, so a crafted record cannot move or delete an arbitrary file. The transaction's own
working folder is derived from a hash of the record's type and key, never read from the record.
- Since:
- 6.3.0
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic interfaceWhere the latest version of a module, and its download, come from.(package private) static interfaceNamed points of a transaction; a test crashes at one to reproduce that window.static interfaceHow a JAR is downloaded.private static final classA file a destructive step touches, and the recorded hashes it may hold; none means it must be absent.(package private) static interfaceHow files are moved and deleted; a test substitutes a failing one.private static interfaceFile work that may fail with anIOException.static final classThe catalogue keys of every line this class produces.(package private) static final classA step found a file the transaction cannot identify; the record is held for the operator.static final classThrown byModuleFileTransactions.Catalogue.latestVersion(java.lang.String)when the catalogue answers that versions of the module exist but none is compatible with this framework's API level (HTTP 409 from the level-filteredlatest; plan 18-40), so staging can say so instead of "no download".(package private) static final classThe persisted record of one transaction.private static final classWhatrecordedIdentity(com.ultikits.ultitools.utils.ModuleFileTransactions.Removal, java.io.File)found: the same file, a different file, or -- with the read error -- a file whose content could not be read to tell.(package private) static classA record whose names would leave their folder, or that this version does not understand.(package private) static final classOne file aREMOVErecord deletes at the next start, and what it was when recorded.static final classA module an uninstall removed, in every form a transaction can name it by: the identify-strings and runtime names of the instances it unloaded (plus the argument it was given), and the file names of its JARs -- the ones those instances were loaded from, the ones the start-up scan recorded as declaring their main class, and the ones it deleted or recorded for deletion.static final classOne line for the start-up log, with its catalogue key and arguments.private static final classWhatreserve(com.ultikits.ultitools.utils.ModuleFileTransactions.Record, java.io.File, java.lang.String, java.util.List<com.ultikits.ultitools.abstracts.UltiToolsPlugin>, java.util.function.Function<com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.io.File>)decided: the module and JAR to stage an update for, or the refusal.private static enumWhich start-up step a rollback is finished from; it decides what the log line says.static final class -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate final List<ModuleFileTransactions.Record> private static final Stringprivate final ModuleFileTransactions.CrashPointsprivate static final com.google.gson.Gsonprivate StringThe failure of aFAILEDrecordcheckExisting(com.ultikits.ultitools.utils.ModuleFileTransactions.Record)just discarded; guarded byLOCK.private static final ObjectSerialises every change to the transaction folder made while the server runs, so two/upmcommands running on async threads cannot both stage the same module.private static final Logger(package private) static final StringThe "actual hash" a report gives for a staged JAR that is not there.private static final StringThe data-folder child modules are loaded from.private final Fileprivate final ModuleFileTransactions.FileOpsThe modules-folder file names a recorded deletion still has to remove after this start's removals.private static final Stringprivate final List<ModuleFileTransactions.Report> private static final StringThe modules (by identify-string) whose update is being downloaded right now, each with the module's name and old JAR file name an uninstall matches on; guarded byLOCK.private static final Stringprivate static final Stringprivate static final StringTheULTIKITS_FOLDER_NAMEchild that holds transaction records and staged JARs.private final Fileprivate static final StringThe server-root folder the credential store also lives in (CredentialStore). -
Constructor Summary
ConstructorsConstructorDescriptionModuleFileTransactions(File dataFolder) The transactions of the framework whose data folder is given.ModuleFileTransactions(File modulesFolder, File transactionsFolder, ModuleFileTransactions.FileOps ops, ModuleFileTransactions.CrashPoints crashPoints) The seam tests use to inject a failing file operation or a crash at a named point. -
Method Summary
Modifier and TypeMethodDescriptionprivate booleanabandonedBeforeApply(ModuleFileTransactions.Record record, File work, File old, File backup) Abandons the transaction, before anything is moved, when the old JAR is no longer the one staged against.voidCarries out every recorded transaction that has to happen before the module class loader is built.private voidCarries out one record.private voidprivate voidDeletes the JARs an uninstall recorded, before any module loads: each only if it is confirmed to still be the file that was recorded.private voidprivate Fileprivate static voidMarks the downloads running now that belong to the uninstalled module -- by its identify-string, a name it was loaded under, or the JAR it was loaded from -- so staging discards them instead of writing their record.private booleancancelOne(ModuleFileTransactions.Record record, File recordFile) Cancels one staged update: its working folder and record are deleted -- unless the record is held for the operator, or its folder holds a file the transaction cannot identify, which is then held for the operator rather than deleted (foreign-file rule).Cancels every update transaction of a module being uninstalled: its record and its working folder -- the staged JAR and any kept old JAR -- are deleted, so nothing brings the module back at the next start, and a download of its update still running is discarded.cancelStagedUpdates(String moduleName) cancelStagedUpdates(RemovedModule)for a module known by one runtime name only.private static StringcanonicalPath(File file) What an existing record of this module says about staging now, under the lock:nullto go ahead (after discarding aFAILEDrecord, whose failure is left inlastDiscardedFailure), or the refusal to return.static FilecodeSourceOf(UltiToolsPlugin module) Which file a loaded module was loaded from -- the question the uninstall asks, asked the same way here.(package private) static FileResolves a file name a record carries against the one folder it belongs to, refusing any name that would leave it.copiesLoadingFirst(File oldJar, String targetName, String mainClass) The other JARs in the modules folder that would supplymainClassahead of a new JAR namedtargetName: those the start-up would put on the module class path, whoseplugin.ymldeclares it now and whose file name sorts beforetargetNamein the order the module loader reads the folder (#476;loadsAhead(java.io.File, java.lang.String, java.lang.String)).private static StringdeclaredVersion(File jar, String key) The version a staged JAR'splugin.ymldeclares, when it declares the expected identify-string and a version -- identity and version only, read the same wayUltiToolsPluginreads them, and nothing about whether its classes would load.private static booleandeleteQuietly(File file) private voidA crash while writing a record leaves a temporary file; it is never a record.private static booleandeleteTree(File folder) Deletes a folder this class derived itself (never one named by a record), bottom-up.private static voiddeniedAsIo(File file, ModuleFileTransactions.FileWork work) Runswork, turning a denied file access -- aSecurityExceptionfrom any of itsFilescalls -- into anIOExceptionnamingfile, so a caller that handles the checked failure handles this one too.private static Stringprivate StringDiscards aFAILEDrecord before staging again: the apply is undone (undoApply(com.ultikits.ultitools.utils.ModuleFileTransactions.Record): the new JAR out of the modules folder, the old JAR back if it is still aside), then the working folder and the record are deleted.private static voiddiscardStaging(File work) Deletes what staging put in a working folder, and the folder itself only if nothing else is in it -- a kept old JAR is never deleted by staging.download(ModuleFileTransactions.Record record, File work, String identifyString, ModuleFileTransactions.Reservation reservation, ModuleFileTransactions.Catalogue catalogue, ModuleFileTransactions.Downloader downloader, Function<UltiToolsPlugin, String> mainClassOf) The part of staging that touches the network and the staged file, run outside the lock; only the record write at its end takes the lock again.existingOutcome(ModuleFileTransactions.Record existing, File existingFile) What a valid existing record of this module means for staging now: an update already waiting, one in progress, one held for the operator (refused until the operator removes the record), or aFAILEDone, discarded -- unless discarding it would touch a file the transaction cannot identify, which holds it for the operator instead.private static booleanprivate voidfailApply(ModuleFileTransactions.Record record, File file, Throwable error) The apply could not be made.private voidfailApply(ModuleFileTransactions.Record record, File file, Throwable error, ModuleFileTransactions.Report specific) failApply(Record, File, Throwable)with the line to log, when the old JAR is back in place, given by the caller instead of the generic one.private voidfailStagedIdentity(ModuleFileTransactions.Record record, File staged, String actual) The staged JAR is not the file recorded at staging (changed, or gone): the apply fails throughfailApply(com.ultikits.ultitools.utils.ModuleFileTransactions.Record, java.io.File, java.lang.Throwable), so a kept old JAR goes back and the record isFAILED; the line names the staged path and both hashes.private voidfailUnexpectedly(ModuleFileTransactions.Record record, String entryState, RuntimeException error) An unchecked exception ended one transaction.private static Stringfetch(ModuleFileTransactions.Downloader downloader, String link, String targetName, File work, File stagedFolder) Downloads the new JAR into a fresh staged folder.private voidprivate voidRemoves the new JAR -- recognised by content, so nothing else is ever deleted -- and puts the old JAR back.voidflushReports(Logger logger, Function<String, String> i18n) Writes the lines this start collected, translated.voidforgetDeferredRemoval(String fileName) Forgets a recorded deletion of a file name an install is writing again, so the next start does not delete the new install -- even when it is byte-for-byte the JAR that was recorded.private voidforgetDeferredRemovalChecked(String fileName) forgetDeferredRemoval(String), before a denied file access is made a checked failure.The records of this module's updates held for the operator, which/upm uninstallrefuses to go past (foreign-file rule): the module is matched as a cancellation matches it.private voidholdForOperator(ModuleFileTransactions.Record record, String detail) Keeps the record asNEEDS_OPERATOR, says so once, and ends the step.private static StringidOf(ModuleFileTransactions.Record record) The name a record's files take: its type and a hash of its key, never text from the record.private static booleanisAcrossFileSystems(Throwable error) Whether a move failed because its two ends are on different file systems.private static booleanisDirectChild(File folder, File file) Whetherfile, canonicalised, sits directly infolder, canonicalised.private static booleanWhether a file name is one the module loader would load: it ends in.jar.private booleanisLoadedFrom(ModuleFileTransactions.Record record, File target, List<UltiToolsPlugin> loaded, Function<UltiToolsPlugin, File> codeSource) private static booleanisStagedJar(File file, ModuleFileTransactions.Record record) Whetherfileis the JAR this transaction staged, by content.private static booleanloadsAhead(File jar, String oldPath, String mainClass) Whetherjar, sorting before the new JAR, would supplymainClassat the next start: it is not the old JAR, it passes the check the start-up applies before putting a JAR on the module class path (SecurityPolicy.isValidModuleJar(java.io.File), asUltiTools#collectModuleJarUrlsandPluginManager#loadPluginMainClassdo; a JAR failing it is never loaded -- Codex round 17), and itsplugin.ymldeclares the class.locate(String key, String identifyString, List<UltiToolsPlugin> loaded, Function<UltiToolsPlugin, File> codeSource, String previousFailure) The loaded module to update and the JAR in the modules folder it was loaded from.private static StringmainClassOf(ModuleFileTransactions.Reservation reservation, Function<UltiToolsPlugin, String> recorded) The module's main class: as the module loader recorded it, else as the old JAR's ownplugin.ymldeclares it -- the same entry the loader reads.private voidprivate static ModuleFileTransactions.Expectedfilemay be absent, or hold one ofhashes; with none given it must be absent.private static booleanmayHoldFiles(File folder) Whether a folder holds anything, or might:truewhen it lists an entry, and also when it exists (or cannot be shown not to) but cannot be listed -- a failed listing is never read as an empty folder.private List<ModuleFileTransactions.Removal> mergeableRemovals(File recordFile) The entries of an earlier removal record of the same module, to merge into a new one; none when it cannot be read or fails the validation start-up applies (problemWith(com.ultikits.ultitools.utils.ModuleFileTransactions.Record, java.io.File): for example a null entry), and it is then replaced, with a warning naming it (Codex round 18, #561).private static StringThe first field a record of its type needs and lacks, ornullwhen it is complete.static File[]moduleJars(File modulesFolder) The module JARs of a modules folder, in file-name order: the entries whose names end in.jar, the test the module loader applies.static FilemodulesFolder(File dataFolder) The folder modules are loaded from: the framework's data folder plusplugins.private static ModuleFileTransactions.Expectedfilemust be there and holdhash.private static String[]The names in a folder, ornullwhen it does not exist or cannot be listed.private static StringThe identify-string normalisation every install, update and lookup shares (one implementation).voidobserveAfterLoad(List<UltiToolsPlugin> loaded, Function<UltiToolsPlugin, File> codeSource) Decides every update applied at this start from what loaded: committed when the module is loaded from the new JAR at the new version, rolled back otherwise.(package private) List<ModuleFileTransactions.Report> The lines collected and not yet written.private booleanpersist(ModuleFileTransactions.Record record) writeRecord(Record)at start-up, where a failure is a report line, not an abort.private booleanAn existence check of the apply or a rollback, throughModuleFileTransactions.FileOpsso a test can make it fail.private StringproblemWith(ModuleFileTransactions.Record record, File recordFile) Why a record read from a file cannot be carried out, ornullwhen it can: the one validation start-up and staging share.private static ModuleFileTransactions.RecordreadRecord(File recordFile) Reads one record.private ModuleFileTransactions.RecordreadRecordOrReport(File recordFile) private static StringreadSha256(File file) A file's SHA-256, failing with the reason it cannot be read.voidrecordDeferredRemoval(String moduleName, List<File> files) Records module JARs an uninstall could not delete now, so the next start deletes them before any module loads (#518).private voidrecordDeferredRemovalChecked(String moduleName, List<File> files) recordDeferredRemoval(String, List), before a denied file access is made a checked failure.private static ModuleFileTransactions.RecordedIdentityrecordedIdentity(ModuleFileTransactions.Removal removal, File file) Whether a file is still the one an uninstall recorded: same size, same modification time, same content.private FileThe record files, sorted by name.refuseBeforeDownload(String identifyString, String link, String targetName, ModuleFileTransactions.Reservation reservation, Function<UltiToolsPlugin, String> mainClassOf) The refusals decided once the new JAR's file name is known, before anything is downloaded or written: no download offered, or another copy of the module that would load instead of the new JAR at the next start (maintainer follow-up 19).private voidThe working folder and its two subfolders are this class's own; one that is a symbolic link would take every name confined to it somewhere else, so the record is refused.private static ModuleFileTransactions.StageResultrefuseOtherApiLevel(File work, File staged, String previousFailure) A download must declare anapi-versionthis framework installs: fromModuleApiLevels.MIN_INSTALLABLE_API_LEVELto its own level.private static ModuleFileTransactions.StageResultrefuseOtherVersion(File work, String targetName, String stagedVersion, String latest, String previousFailure) A download must be the version the catalogue offered: a stale or misconfigured endpoint can serve a valid JAR of the same module declaring another version, which would otherwise be recorded -- and committed -- under the offered version's file name (round-6 review).private static ModuleFileTransactions.StageResultrefuseSharedOrAmbiguous(UltiToolsPlugin module, File oldJar, String key, String identifyString, List<UltiToolsPlugin> loaded, Function<UltiToolsPlugin, File> codeSource, String previousFailure) The refusal when the JAR to replace is not the selected module's alone, ornull: another loaded module came from the same JAR, or another loaded module in another JAR declares the same identify-string.private ModuleFileTransactions.RecordremovalRecordIn(File recordFile) The removal record inrecordFile, ornullwhen it holds none: a record of another type, or one that cannot be read or fails the validation start-up applies (problemWith(com.ultikits.ultitools.utils.ModuleFileTransactions.Record, java.io.File): for example a null entry).private voidprivate voidrequireRecorded(ModuleFileTransactions.Record record, ModuleFileTransactions.Expected... expected) The foreign-file rule, at every destructive step: the transaction only ever moves, replaces or deletes a file whose SHA-256 matches the record -- the staged new JAR or the kept old JAR -- and only into a place that is free.reserve(ModuleFileTransactions.Record record, File work, String identifyString, List<UltiToolsPlugin> loaded, Function<UltiToolsPlugin, File> codeSource) The checks staging makes before it downloads, underLOCK: no download of this module running, no update already staged, no kept old JAR left without its record.private voidA failed apply found at a later start: make sure it is undone -- the new JAR out of the modules folder and the old JAR back (undoApply(com.ultikits.ultitools.utils.ModuleFileTransactions.Record)) -- and say so if it cannot be.private StringMoves the kept old JAR back into the modules folder when it is not there.private voidresumeWithoutStagedJar(ModuleFileTransactions.Record record, File target, File staged) The staged JAR is not in its folder: either the new JAR was moved in and the start ended beforeAPPLIEDwas recorded, or the staged JAR is gone.private StringMoves the new JAR back to the staged folder when it was already moved into the modules folder -- recognised by its recorded hash, so nothing else is ever moved.private static FileserverRoot(File dataFolder) The server root, found the wayCredentialStorefinds it: the data folder's grandparent (<server root>/plugins/UltiTools).private static String(package private) static StringA file's SHA-256 through the framework's one hashing helper, ornullwhen it cannot be read.private static StringsharedWith(UltiToolsPlugin module, File oldJar, List<UltiToolsPlugin> loaded, Function<UltiToolsPlugin, File> codeSource) The other loaded modules that came from the same JAR, ornullwhen there are none.static File[]sortedByName(File[] files) filessorted by file name, compared as plain strings (#476).private FilestageUpdate(String identifyString, Supplier<List<UltiToolsPlugin>> loaded, Function<UltiToolsPlugin, File> codeSource, ModuleFileTransactions.Catalogue catalogue, ModuleFileTransactions.Downloader downloader, Function<UltiToolsPlugin, String> mainClassOf) Stages an update, asstageUpdate(String, List, Function, Catalogue, Downloader, Function)does, reading the loaded modules only under the lock that an uninstall's cancellation also takes (Codex round 16, #561).stageUpdate(String identifyString, List<UltiToolsPlugin> loaded, Function<UltiToolsPlugin, File> codeSource, ModuleFileTransactions.Catalogue catalogue, ModuleFileTransactions.Downloader downloader) Stages an update of a loaded module: downloads its latest version into the transaction folder and records it, leaving the modules folder untouched.stageUpdate(String identifyString, List<UltiToolsPlugin> loaded, Function<UltiToolsPlugin, File> codeSource, ModuleFileTransactions.Catalogue catalogue, ModuleFileTransactions.Downloader downloader, Function<UltiToolsPlugin, String> mainClassOf) Stages an update of a loaded module, asstageUpdate(String, List, Function, Catalogue, Downloader)does, with the module's main class as the module loader recorded it.private booleanstoppedBeforeMoving(ModuleFileTransactions.Record record, File work, File old, File target, File backup, File staged) The checks an apply makes before it moves anything; each that fails ends the transaction.private static StringstripSuffix(String name) private voidsweepOrphanWorkFolders(List<String> ids) A working folder no record refers to was left by a crash during staging, before its record was written: it can hold only a partial download, which is deleted.static FiletransactionsFolder(File dataFolder) The folder transaction records and staged JARs live in:<server root>/.ultikits/upm-transactions, beside the credential store.private StringUndoes whatever part of an apply happened: the new JAR goes back to the staged folder if it is in the modules folder (returnStagedJar(com.ultikits.ultitools.utils.ModuleFileTransactions.Record), by its recorded hash), then the kept old JAR goes back (restoreBackup(com.ultikits.ultitools.utils.ModuleFileTransactions.Record)) -- in that order, and the second only once the first is done, so the modules folder never holds both.private static Stringunexpected(ModuleFileTransactions.Expected expected) What is wrong with one expected file, ornullwhen it is as recorded.private ModuleFileTransactions.RecordupdateRecordIn(File recordFile) The update record inrecordFile, ornullwhen it cannot be read or is not one.private static ModuleFileTransactions.StageResultverifyStaged(ModuleFileTransactions.Record record, File work, File staged, String latest, String previousFailure) Checks the downloaded JAR -- the size/entry guard, its identity, that it declares the version the catalogue offered, and that itsapi-versionis one this framework installs (ModuleApiLevels; plan 18-40) -- and on success records its version and SHA-256.private static voidwalkAndDelete(File folder) Deletesfolderand everything under it, deepest first.private Fileprivate voidWrites a record atomically: a temporary file in the same folder, then an atomic move over the record, so a crash leaves either the previous record or the new one, never a torn file.writeStaged(ModuleFileTransactions.Record record, File work, String previousFailure) Writes thePENDINGrecord of a staged JAR, under the lock; the staging is discarded when it cannot be.
-
Field Details
-
MODULES_FOLDER_NAME
The data-folder child modules are loaded from.- See Also:
-
ULTIKITS_FOLDER_NAME
The server-root folder the credential store also lives in (CredentialStore).- See Also:
-
TRANSACTIONS_FOLDER_NAME
TheULTIKITS_FOLDER_NAMEchild that holds transaction records and staged JARs.- See Also:
-
RECORD_SUFFIX
- See Also:
-
TEMPORARY_PREFIX
- See Also:
-
TEMPORARY_SUFFIX
- See Also:
-
STAGED_FOLDER
- See Also:
-
BACKUP_FOLDER
- See Also:
-
MISSING
The "actual hash" a report gives for a staged JAR that is not there.- See Also:
-
LOGGER
-
GSON
private static final com.google.gson.Gson GSON -
LOCK
Serialises every change to the transaction folder made while the server runs, so two/upmcommands running on async threads cannot both stage the same module. -
STAGING
The modules (by identify-string) whose update is being downloaded right now, each with the module's name and old JAR file name an uninstall matches on; guarded byLOCK. -
CANCELLED
-
modulesFolder
-
transactionsFolder
-
ops
-
crashPoints
-
reports
-
appliedThisStart
-
lastDiscardedFailure
The failure of aFAILEDrecordcheckExisting(com.ultikits.ultitools.utils.ModuleFileTransactions.Record)just discarded; guarded byLOCK. -
pendingRemovals
The modules-folder file names a recorded deletion still has to remove after this start's removals.
-
-
Constructor Details
-
ModuleFileTransactions
The transactions of the framework whose data folder is given.- Parameters:
dataFolder- the framework's data folder
-
ModuleFileTransactions
ModuleFileTransactions(File modulesFolder, File transactionsFolder, ModuleFileTransactions.FileOps ops, ModuleFileTransactions.CrashPoints crashPoints) The seam tests use to inject a failing file operation or a crash at a named point.- Parameters:
modulesFolder- the folder modules load fromtransactionsFolder- the folder records and staged JARs live inops- how files are moved and deletedcrashPoints- called at every named point of a transaction
-
-
Method Details
-
modulesFolder
The folder modules are loaded from: the framework's data folder plusplugins.The one place this is computed (#517). The module class loader's URLs, the start-up scan, install, update and uninstall all come from here; the transactions folder comes from the same data folder (
transactionsFolder(File)). The data folder follows Bukkit's own plugin-directory option, so this is right wherever the server keeps its plugins -- unlike the JVM's working directory, which a launcher may set anywhere.- Parameters:
dataFolder- the framework's data folder- Returns:
- the modules folder
-
moduleJars
The module JARs of a modules folder, in file-name order: the entries whose names end in.jar, the test the module loader applies. The start-up scan and the module class loader both read this one listing (#476).Sorted because
File#listFilespromises no order (on ext4 it is hash order), and the order decides which of two JARs carrying the same class supplies it and which copy of a duplicated module is read first. Sorting by name, compared as plain strings, gives the same answer for the same folder on every file system and in every locale.- Parameters:
modulesFolder- the modules folder- Returns:
- the JARs sorted by file name, or
nullwhen the folder cannot be listed
-
sortedByName
filessorted by file name, compared as plain strings (#476).- Parameters:
files- a folder listing, ornull- Returns:
- the same array, sorted, or
null
-
transactionsFolder
The folder transaction records and staged JARs live in:<server root>/.ultikits/upm-transactions, beside the credential store. It is outsideplugins/, so the module loader never reads anything in it and the panel's file interface, whose default editable roots do not include it, cannot forge a record.- Parameters:
dataFolder- the framework's data folder- Returns:
- the transactions folder
-
serverRoot
The server root, found the wayCredentialStorefinds it: the data folder's grandparent (<server root>/plugins/UltiTools). Resolved from the absolute data folder, so a relative one gives the same root; never from the JVM's working directory.- Parameters:
dataFolder- the framework's data folder- Returns:
- the server root
- Throws:
IllegalStateException- when the data folder has no grandparent
-
codeSourceOf
Which file a loaded module was loaded from -- the question the uninstall asks, asked the same way here.- Parameters:
module- a loaded module- Returns:
- its code source, or
nullwhen it cannot be determined
-
stageUpdate
public ModuleFileTransactions.StageResult stageUpdate(String identifyString, List<UltiToolsPlugin> loaded, Function<UltiToolsPlugin, File> codeSource, ModuleFileTransactions.Catalogue catalogue, ModuleFileTransactions.Downloader downloader) Stages an update of a loaded module: downloads its latest version into the transaction folder and records it, leaving the modules folder untouched. It takes effect at the next start. One update per module waits at a time; a failed apply recorded by an earlier start is reported throughModuleFileTransactions.StageResult.getPreviousFailure()and cleared before staging again.- Parameters:
identifyString- the module's identify-stringloaded- the loaded modulescodeSource- which JAR a loaded module came fromcatalogue- where the latest version and its download link come fromdownloader- how the new JAR is downloaded- Returns:
- what happened
-
stageUpdate
public ModuleFileTransactions.StageResult stageUpdate(String identifyString, List<UltiToolsPlugin> loaded, Function<UltiToolsPlugin, File> codeSource, ModuleFileTransactions.Catalogue catalogue, ModuleFileTransactions.Downloader downloader, Function<UltiToolsPlugin, String> mainClassOf) Stages an update of a loaded module, asstageUpdate(String, List, Function, Catalogue, Downloader)does, with the module's main class as the module loader recorded it.Once the new JAR's file name is known and before anything is downloaded, the update is refused when another copy of the module would load instead of it at the next start: a JAR in the modules folder whose
plugin.ymldeclares the samemain:and whose file name sorts before the new JAR's (maintainer follow-up 19). All module JARs share one class loader whose class path is in file-name order (#476), so such a copy supplies the module's classes and the observation would roll the update back at every attempt. A copy that sorts after the new JAR does not stop it; the start-up duplicate warning names that copy. A copy added after staging is caught by the observation.- Parameters:
identifyString- the module's identify-stringloaded- the loaded modulescodeSource- which JAR a loaded module came fromcatalogue- where the latest version and its download link come fromdownloader- how the new JAR is downloadedmainClassOf- the main class the module loader recorded for a loaded module, ornullwhen it has none; the old JAR's ownplugin.ymlmain:is used then- Returns:
- what happened
-
stageUpdate
public ModuleFileTransactions.StageResult stageUpdate(String identifyString, Supplier<List<UltiToolsPlugin>> loaded, Function<UltiToolsPlugin, File> codeSource, ModuleFileTransactions.Catalogue catalogue, ModuleFileTransactions.Downloader downloader, Function<UltiToolsPlugin, String> mainClassOf) Stages an update, asstageUpdate(String, List, Function, Catalogue, Downloader, Function)does, reading the loaded modules only under the lock that an uninstall's cancellation also takes (Codex round 16, #561). A list taken before the lock could still name a module an uninstall removed in between, and that uninstall would have found neither a running download nor a record to cancel. Read under the lock, either the module is already gone from the list (refused as not loaded) or the download is marked running before the cancellation can look (and is cancelled).- Parameters:
identifyString- the module's identify-stringloaded- reads the modules loaded now; called once, under the lockcodeSource- which JAR a loaded module came fromcatalogue- where the latest version and its download link come fromdownloader- how the new JAR is downloadedmainClassOf- the main class the module loader recorded for a loaded module, ornull- Returns:
- what happened
-
reserve
private ModuleFileTransactions.Reservation reserve(ModuleFileTransactions.Record record, File work, String identifyString, List<UltiToolsPlugin> loaded, Function<UltiToolsPlugin, File> codeSource) The checks staging makes before it downloads, underLOCK: no download of this module running, no update already staged, no kept old JAR left without its record. -
locate
private ModuleFileTransactions.Reservation locate(String key, String identifyString, List<UltiToolsPlugin> loaded, Function<UltiToolsPlugin, File> codeSource, String previousFailure) The loaded module to update and the JAR in the modules folder it was loaded from. -
download
private ModuleFileTransactions.StageResult download(ModuleFileTransactions.Record record, File work, String identifyString, ModuleFileTransactions.Reservation reservation, ModuleFileTransactions.Catalogue catalogue, ModuleFileTransactions.Downloader downloader, Function<UltiToolsPlugin, String> mainClassOf) The part of staging that touches the network and the staged file, run outside the lock; only the record write at its end takes the lock again. -
refuseBeforeDownload
private ModuleFileTransactions.StageResult refuseBeforeDownload(String identifyString, String link, String targetName, ModuleFileTransactions.Reservation reservation, Function<UltiToolsPlugin, String> mainClassOf) The refusals decided once the new JAR's file name is known, before anything is downloaded or written: no download offered, or another copy of the module that would load instead of the new JAR at the next start (maintainer follow-up 19).- Returns:
- the refusal, or
nullto download
-
mainClassOf
private static String mainClassOf(ModuleFileTransactions.Reservation reservation, Function<UltiToolsPlugin, String> recorded) The module's main class: as the module loader recorded it, else as the old JAR's ownplugin.ymldeclares it -- the same entry the loader reads.nullwhen neither says. -
copiesLoadingFirst
The other JARs in the modules folder that would supplymainClassahead of a new JAR namedtargetName: those the start-up would put on the module class path, whoseplugin.ymldeclares it now and whose file name sorts beforetargetNamein the order the module loader reads the folder (#476;loadsAhead(java.io.File, java.lang.String, java.lang.String)). The old JAR is not one: the update moves it away. Judged by what each file declares now, not by the start-up index alone, which goes stale when a copy is removed or replaced. Reads onlyplugin.yml.- Returns:
- their file names in file-name order; empty when there is none, when the main class is unknown, or when the folder cannot be listed (the observation still backs the update)
-
loadsAhead
Whetherjar, sorting before the new JAR, would supplymainClassat the next start: it is not the old JAR, it passes the check the start-up applies before putting a JAR on the module class path (SecurityPolicy.isValidModuleJar(java.io.File), asUltiTools#collectModuleJarUrlsandPluginManager#loadPluginMainClassdo; a JAR failing it is never loaded -- Codex round 17), and itsplugin.ymldeclares the class. -
verifyStaged
private static ModuleFileTransactions.StageResult verifyStaged(ModuleFileTransactions.Record record, File work, File staged, String latest, String previousFailure) Checks the downloaded JAR -- the size/entry guard, its identity, that it declares the version the catalogue offered, and that itsapi-versionis one this framework installs (ModuleApiLevels; plan 18-40) -- and on success records its version and SHA-256.- Returns:
- the refusal, with the staging discarded, or
nullwhen the JAR is accepted
-
refuseOtherVersion
private static ModuleFileTransactions.StageResult refuseOtherVersion(File work, String targetName, String stagedVersion, String latest, String previousFailure) A download must be the version the catalogue offered: a stale or misconfigured endpoint can serve a valid JAR of the same module declaring another version, which would otherwise be recorded -- and committed -- under the offered version's file name (round-6 review).- Returns:
- the refusal, with the staging discarded, or
nullwhen the versions agree
-
refuseOtherApiLevel
private static ModuleFileTransactions.StageResult refuseOtherApiLevel(File work, File staged, String previousFailure) A download must declare anapi-versionthis framework installs: fromModuleApiLevels.MIN_INSTALLABLE_API_LEVELto its own level. A catalogue that does not filter by level, or a version recorded wrongly, can otherwise stage a module that cannot load here -- one built before 6.3.0 overrides the now-finalunregisterSelf()-- and every start would roll it back (plan 18-40).- Returns:
- the refusal, with the staging discarded, or
nullwhen the level is accepted
-
writeStaged
private ModuleFileTransactions.StageResult writeStaged(ModuleFileTransactions.Record record, File work, String previousFailure) Writes thePENDINGrecord of a staged JAR, under the lock; the staging is discarded when it cannot be. -
fetch
private static String fetch(ModuleFileTransactions.Downloader downloader, String link, String targetName, File work, File stagedFolder) Downloads the new JAR into a fresh staged folder.- Returns:
nullwhen it was downloaded, otherwise the failure, described; the staged folder is then discarded
-
checkExisting
What an existing record of this module says about staging now, under the lock:nullto go ahead (after discarding aFAILEDrecord, whose failure is left inlastDiscardedFailure), or the refusal to return. -
existingOutcome
private ModuleFileTransactions.StageResult existingOutcome(ModuleFileTransactions.Record existing, File existingFile) What a valid existing record of this module means for staging now: an update already waiting, one in progress, one held for the operator (refused until the operator removes the record), or aFAILEDone, discarded -- unless discarding it would touch a file the transaction cannot identify, which holds it for the operator instead. -
cancelDownloads
Marks the downloads running now that belong to the uninstalled module -- by its identify-string, a name it was loaded under, or the JAR it was loaded from -- so staging discards them instead of writing their record. Their version is not known yet, so they are not among the versions a cancellation returns; staging's own reply says it was cancelled. Called underLOCK. -
cancelStagedUpdates
cancelStagedUpdates(RemovedModule)for a module known by one runtime name only.- Parameters:
moduleName- the module's runtime name- Returns:
- the versions whose updates were cancelled
-
cancelStagedUpdates
Cancels every update transaction of a module being uninstalled: its record and its working folder -- the staged JAR and any kept old JAR -- are deleted, so nothing brings the module back at the next start, and a download of its update still running is discarded. The modules folder is not touched; the uninstall owns that.A transaction belongs to the module when its identify-string, the runtime name it was staged under, or the JAR it would replace is one the uninstall resolved (see
ModuleFileTransactions.RemovedModule). The uninstall resolves those before it touches the modules folder, so the match holds whatever happened to the files afterwards -- deleted, deferred, not deletable, or a folder that could not be listed (round-10 review).- Parameters:
removed- the uninstalled module, as the uninstall resolved it- Returns:
- the versions whose updates were cancelled
-
cancelOne
Cancels one staged update: its working folder and record are deleted -- unless the record is held for the operator, or its folder holds a file the transaction cannot identify, which is then held for the operator rather than deleted (foreign-file rule). Called underLOCK.- Returns:
- whether it was cancelled
-
heldForOperator
The records of this module's updates held for the operator, which/upm uninstallrefuses to go past (foreign-file rule): the module is matched as a cancellation matches it.- Parameters:
removed- the module an uninstall would remove- Returns:
- the record files, empty when there is none (or the folder cannot be listed)
-
updateRecordIn
The update record inrecordFile, ornullwhen it cannot be read or is not one. -
recordDeferredRemoval
Records module JARs an uninstall could not delete now, so the next start deletes them before any module loads (#518). On Windows the shared module class loader holds every module JAR open for the life of the server, so this is the only point at which such a JAR can go.Each file is recorded with its size, modification time and SHA-256; the next start deletes it only if it is still that file, so an install under the same name since -- or a copy put back -- is never deleted. An install through
/upmalso forgets the record (forgetDeferredRemoval(String)).- Parameters:
moduleName- the module being uninstalled, as the operator named itfiles- the JARs still on disk, each directly in the modules folder- Throws:
IOException- when the record cannot be written, or a file is not in the modules folder or cannot be read
-
recordDeferredRemovalChecked
recordDeferredRemoval(String, List), before a denied file access is made a checked failure.- Throws:
IOException
-
forgetDeferredRemoval
Forgets a recorded deletion of a file name an install is writing again, so the next start does not delete the new install -- even when it is byte-for-byte the JAR that was recorded.- Parameters:
fileName- the file name the install wrote into the modules folder- Throws:
IOException- when a record cannot be read or rewritten
-
forgetDeferredRemovalChecked
forgetDeferredRemoval(String), before a denied file access is made a checked failure.- Throws:
IOException
-
mergeableRemovals
The entries of an earlier removal record of the same module, to merge into a new one; none when it cannot be read or fails the validation start-up applies (problemWith(com.ultikits.ultitools.utils.ModuleFileTransactions.Record, java.io.File): for example a null entry), and it is then replaced, with a warning naming it (Codex round 18, #561). -
removalRecordIn
The removal record inrecordFile, ornullwhen it holds none: a record of another type, or one that cannot be read or fails the validation start-up applies (problemWith(com.ultikits.ultitools.utils.ModuleFileTransactions.Record, java.io.File): for example a null entry). A record that cannot be used is warned about, naming it, and never stops the others being cleared (round-5 review; Codex round 18, #561). -
applyBeforeLoad
public void applyBeforeLoad()Carries out every recorded transaction that has to happen before the module class loader is built. Called once per start, before any module JAR is opened. Never throws for a problem with one transaction; every problem becomes a report line and the next record is processed. -
applyGuarded
Carries out one record. The transaction is the boundary: an unchecked exception from any file operation in it -- aSecurityException, or anything else unexpected -- fails that transaction and never the start (round-4 review; orchestrator decision 2026-09-30). -
failUnexpectedly
private void failUnexpectedly(ModuleFileTransactions.Record record, String entryState, RuntimeException error) An unchecked exception ended one transaction. An update being applied goes through the same failure path as every other apply failure (failApply(com.ultikits.ultitools.utils.ModuleFileTransactions.Record, java.io.File, java.lang.Throwable)): whatever step already happened is undone, the record isFAILED, and one SEVERE line names the transaction and the exception.A record already past its apply (
APPLIED,COMMITTING,ROLLING_BACK), aFAILEDone, or a removal is not re-marked: its state already says what is left, every step is idempotent, and the next start finishes it, as after a crash at that point. Marking a decided updateFAILEDinstead would put the old JAR back beside a committed new one. It is reported with one SEVERE line. -
applyRecord
private void applyRecord(ModuleFileTransactions.Record record) throws ModuleFileTransactions.RecordRefused -
observeAfterLoad
public void observeAfterLoad(List<UltiToolsPlugin> loaded, Function<UltiToolsPlugin, File> codeSource) Decides every update applied at this start from what loaded: committed when the module is loaded from the new JAR at the new version, rolled back otherwise.- Parameters:
loaded- the modules that loaded at this start; empty when loading failedcodeSource- which JAR a loaded module came from
-
flushReports
Writes the lines this start collected, translated.- Parameters:
logger- where to write themi18n- the translation to apply to each line's key
-
pendingReports
List<ModuleFileTransactions.Report> pendingReports()The lines collected and not yet written.- Returns:
- the lines, in order
-
applyUpdate
private void applyUpdate(ModuleFileTransactions.Record record) throws ModuleFileTransactions.RecordRefused -
stoppedBeforeMoving
private boolean stoppedBeforeMoving(ModuleFileTransactions.Record record, File work, File old, File target, File backup, File staged) throws ModuleFileTransactions.RecordRefused The checks an apply makes before it moves anything; each that fails ends the transaction.- Returns:
- whether the transaction was ended (failed or abandoned)
- Throws:
ModuleFileTransactions.RecordRefused
-
resumeWithoutStagedJar
private void resumeWithoutStagedJar(ModuleFileTransactions.Record record, File target, File staged) throws ModuleFileTransactions.RecordRefused The staged JAR is not in its folder: either the new JAR was moved in and the start ended beforeAPPLIEDwas recorded, or the staged JAR is gone. Its content decides which -- never its name. -
failStagedIdentity
private void failStagedIdentity(ModuleFileTransactions.Record record, File staged, String actual) throws ModuleFileTransactions.RecordRefused The staged JAR is not the file recorded at staging (changed, or gone): the apply fails throughfailApply(com.ultikits.ultitools.utils.ModuleFileTransactions.Record, java.io.File, java.lang.Throwable), so a kept old JAR goes back and the record isFAILED; the line names the staged path and both hashes. Nothing is downloaded again. -
abandonedBeforeApply
private boolean abandonedBeforeApply(ModuleFileTransactions.Record record, File work, File old, File backup) Abandons the transaction, before anything is moved, when the old JAR is no longer the one staged against. Only while the old JAR is still in the modules folder: once it is kept aside, the kept copy is the one that was checked.- The module's JAR left the modules folder after the update was staged -- an uninstall, whatever name it was given, or a hand removal -- or an uninstall recorded it for a deletion that has not happened yet. Installing the update now would bring back a module the operator removed.
- A different file now has the old JAR's name -- an install of the current version, a hand-made hotfix. Moving it aside would make the commit delete it; its content is compared with the SHA-256 recorded at staging, so the same bytes put back still count.
- Returns:
- whether the transaction was abandoned
-
present
An existence check of the apply or a rollback, throughModuleFileTransactions.FileOpsso a test can make it fail. -
markApplied
-
failApply
private void failApply(ModuleFileTransactions.Record record, File file, Throwable error) throws ModuleFileTransactions.RecordRefused The apply could not be made. The old JAR goes back if it was moved, the record is kept asFAILEDfor the next/upm updateto report, and one SEVERE line names the file and the error. -
failApply
private void failApply(ModuleFileTransactions.Record record, File file, Throwable error, ModuleFileTransactions.Report specific) throws ModuleFileTransactions.RecordRefused failApply(Record, File, Throwable)with the line to log, when the old JAR is back in place, given by the caller instead of the generic one.- Parameters:
specific- the line to log when nothing is left out of place, ornullfor the generic one- Throws:
ModuleFileTransactions.RecordRefused
-
undoApply
private String undoApply(ModuleFileTransactions.Record record) throws ModuleFileTransactions.RecordRefused Undoes whatever part of an apply happened: the new JAR goes back to the staged folder if it is in the modules folder (returnStagedJar(com.ultikits.ultitools.utils.ModuleFileTransactions.Record), by its recorded hash), then the kept old JAR goes back (restoreBackup(com.ultikits.ultitools.utils.ModuleFileTransactions.Record)) -- in that order, and the second only once the first is done, so the modules folder never holds both. The one undo of a failed apply: when it fails (failApply(com.ultikits.ultitools.utils.ModuleFileTransactions.Record, java.io.File, java.lang.Throwable)), at every later start while the record isFAILED(restoreAfterFailure(com.ultikits.ultitools.utils.ModuleFileTransactions.Record)), and when/upm updatediscards the record (discardFailed(com.ultikits.ultitools.utils.ModuleFileTransactions.Record)). Restoring only the old JAR at those later points put it back beside a new JAR whose return had failed (Codex round 14, #561).- Returns:
nullwhen nothing of the apply is left in the modules folder and the old JAR is in place (or there was none), otherwise what failed- Throws:
ModuleFileTransactions.RecordRefused
-
may
filemay be absent, or hold one ofhashes; with none given it must be absent. -
must
filemust be there and holdhash. -
requireRecorded
private void requireRecorded(ModuleFileTransactions.Record record, ModuleFileTransactions.Expected... expected) throws ModuleFileTransactions.NeedsOperator The foreign-file rule, at every destructive step: the transaction only ever moves, replaces or deletes a file whose SHA-256 matches the record -- the staged new JAR or the kept old JAR -- and only into a place that is free. Each file the step touches, source and target, is checked here before the step does anything. When one is not what the record says -- another actor replaced the new JAR, dropped a file where the old one is kept, and so on -- nothing is done to any file: the record is kept asNEEDS_OPERATOR, which later starts do not act on and which/upm updateand/upm uninstallof the module refuse to go past, and one SEVERE line names each unexpected file with its expected and actual hash and says how to resolve it (Codex round 19, #561; the maintainer's precedent for ambiguous identity: write nothing, warn). Two expectations for the same path (the old and new JAR sharing a name) are merged.- Throws:
ModuleFileTransactions.NeedsOperator- when a file is not the recorded one; the record is already held
-
unexpected
What is wrong with one expected file, ornullwhen it is as recorded. -
holdForOperator
private void holdForOperator(ModuleFileTransactions.Record record, String detail) throws ModuleFileTransactions.NeedsOperator Keeps the record asNEEDS_OPERATOR, says so once, and ends the step. -
returnStagedJar
private String returnStagedJar(ModuleFileTransactions.Record record) throws ModuleFileTransactions.RecordRefused Moves the new JAR back to the staged folder when it was already moved into the modules folder -- recognised by its recorded hash, so nothing else is ever moved. Part of undoing a failed apply; the old JAR goes back after it.- Returns:
nullwhen the new JAR is not in the modules folder (or is back), otherwise what failed- Throws:
ModuleFileTransactions.RecordRefused
-
isAcrossFileSystems
Whether a move failed because its two ends are on different file systems. -
restoreAfterFailure
private void restoreAfterFailure(ModuleFileTransactions.Record record) throws ModuleFileTransactions.RecordRefused A failed apply found at a later start: make sure it is undone -- the new JAR out of the modules folder and the old JAR back (undoApply(com.ultikits.ultitools.utils.ModuleFileTransactions.Record)) -- and say so if it cannot be. -
restoreBackup
private String restoreBackup(ModuleFileTransactions.Record record) throws ModuleFileTransactions.RecordRefused Moves the kept old JAR back into the modules folder when it is not there.- Returns:
nullwhen the old JAR is in place (or there was none), otherwise what failed- Throws:
ModuleFileTransactions.RecordRefused
-
discardFailed
Discards aFAILEDrecord before staging again: the apply is undone (undoApply(com.ultikits.ultitools.utils.ModuleFileTransactions.Record): the new JAR out of the modules folder, the old JAR back if it is still aside), then the working folder and the record are deleted.- Returns:
nullon success, otherwise why the old JAR could not be put back
-
isLoadedFrom
private boolean isLoadedFrom(ModuleFileTransactions.Record record, File target, List<UltiToolsPlugin> loaded, Function<UltiToolsPlugin, File> codeSource) -
finishCommit
private void finishCommit(ModuleFileTransactions.Record record) throws ModuleFileTransactions.RecordRefused -
finishRollback
private void finishRollback(ModuleFileTransactions.Record record, ModuleFileTransactions.RollbackPhase phase) throws ModuleFileTransactions.RecordRefused Removes the new JAR -- recognised by content, so nothing else is ever deleted -- and puts the old JAR back. A step that fails leaves the record atROLLING_BACKfor the next start, which finishes it before the modules load. -
applyRemoval
private void applyRemoval(ModuleFileTransactions.Record record) throws ModuleFileTransactions.RecordRefused Deletes the JARs an uninstall recorded, before any module loads: each only if it is confirmed to still be the file that was recorded. One that still cannot be deleted stays recorded for the next start and is reported; one confirmed to be a different file -- replaced since -- is left alone, reported, and its entry dropped. One whose content cannot be read to confirm it is not deleted either, and keeps its entry exactly as recorded, reported SEVERE with the read error: whether it is still the recorded file is unknown, and dropping the entry would let a module the operator uninstalled load at every later start. -
recordedIdentity
private static ModuleFileTransactions.RecordedIdentity recordedIdentity(ModuleFileTransactions.Removal removal, File file) Whether a file is still the one an uninstall recorded: same size, same modification time, same content. A copy put back after the uninstall has a new timestamp even when its bytes are the same. Three outcomes, not two: a file whose content cannot be read is neither confirmed the same nor confirmed different. -
readSha256
A file's SHA-256, failing with the reason it cannot be read.sha256Of(File)returnsnullinstead, which its other callers treat as a refusal or a held state; a recorded removal needs the reason, and must not read "cannot be read" as "a different file".- Throws:
IOException
-
isStagedJar
Whetherfileis the JAR this transaction staged, by content. -
refuseLinkedFolders
private void refuseLinkedFolders(ModuleFileTransactions.Record record) throws ModuleFileTransactions.RecordRefused The working folder and its two subfolders are this class's own; one that is a symbolic link would take every name confined to it somewhere else, so the record is refused. (The transactions folder itself, and the modules folder, may be links an operator set up.) -
stagedOf
private File stagedOf(ModuleFileTransactions.Record record) throws ModuleFileTransactions.RecordRefused -
backupOf
private File backupOf(ModuleFileTransactions.Record record) throws ModuleFileTransactions.RecordRefused -
recordFiles
The record files, sorted by name.- Returns:
- the records; empty when the transactions folder does not exist
- Throws:
IOException- when the folder exists -- or cannot be shown not to -- but cannot be listed; a failed listing is never read as "no records"
-
readRecordOrReport
-
problemWith
Why a record read from a file cannot be carried out, ornullwhen it can: the one validation start-up and staging share. -
missingField
The first field a record of its type needs and lacks, ornullwhen it is complete. -
readRecord
Reads one record.- Returns:
- the record, or
nullfor an empty file - Throws:
IOException- when the file cannot be read, or cannot be parsed as a record -- a parse failure (Gson's uncheckedJsonParseException, or any other unchecked exception while parsing) becomes anIOExceptionnaming the file, so every reader handles a malformed record through its checked path
-
writeRecord
Writes a record atomically: a temporary file in the same folder, then an atomic move over the record, so a crash leaves either the previous record or the new one, never a torn file.- Throws:
IOException
-
deniedAsIo
Runswork, turning a denied file access -- aSecurityExceptionfrom any of itsFilescalls -- into anIOExceptionnamingfile, so a caller that handles the checked failure handles this one too.- Throws:
IOException
-
persist
writeRecord(Record)at start-up, where a failure is a report line, not an abort.- Returns:
- whether the record is on disk
-
deleteTemporaryRecords
private void deleteTemporaryRecords()A crash while writing a record leaves a temporary file; it is never a record. -
sweepOrphanWorkFolders
A working folder no record refers to was left by a crash during staging, before its record was written: it can hold only a partial download, which is deleted. One that holds a kept old JAR is never deleted without its record; it is reported instead. -
recordFileOf
-
workFolder
-
idOf
The name a record's files take: its type and a hash of its key, never text from the record. -
stripSuffix
-
confined
Resolves a file name a record carries against the one folder it belongs to, refusing any name that would leave it.- Parameters:
folder- the folder the name belongs toname- the name from the record- Returns:
- the file
- Throws:
ModuleFileTransactions.RecordRefused- when the name is not a plain name insidefolder
-
mayHoldFiles
Whether a folder holds anything, or might:truewhen it lists an entry, and also when it exists (or cannot be shown not to) but cannot be listed -- a failed listing is never read as an empty folder. -
namesIn
The names in a folder, ornullwhen it does not exist or cannot be listed. -
isJarName
Whether a file name is one the module loader would load: it ends in.jar. -
isDirectChild
Whetherfile, canonicalised, sits directly infolder, canonicalised. -
exists
-
canonicalPath
-
deleteQuietly
-
discardStaging
Deletes what staging put in a working folder, and the folder itself only if nothing else is in it -- a kept old JAR is never deleted by staging. -
deleteTree
Deletes a folder this class derived itself (never one named by a record), bottom-up.- Returns:
- whether it is gone
-
walkAndDelete
Deletesfolderand everything under it, deepest first.- Throws:
IOException
-
declaredVersion
The version a staged JAR'splugin.ymldeclares, when it declares the expected identify-string and a version -- identity and version only, read the same wayUltiToolsPluginreads them, and nothing about whether its classes would load. -
normalize
The identify-string normalisation every install, update and lookup shares (one implementation). -
describe
-
sha256
-
sha256Of
A file's SHA-256 through the framework's one hashing helper, ornullwhen it cannot be read. -
report
-