Interface EnderChestStorage
- All Known Implementing Classes:
CachedStorage
com.enhancedechest.service layer,
via DbExecutor) are responsible for dispatching these onto an async executor and never
blocking a region/main thread.
Ownership model: a player owns a chest iff a row exists for (player_uuid, chest_index). Chests are created explicitly (admin command, API, or the auto-bootstrap of the first chest).
The sole implementation is the lazy write-back CachedStorage (per-player load on first
touch, dirty-row flush); the SQL side of the plugin implements the much narrower
StorageBackend instead (per-player reads, batched flush, import/backup — no per-row writes).
-
Nested Class Summary
Nested ClassesModifier and TypeInterfaceDescriptionstatic final recordLightweight reference to an expired chest, returned byfindExpired(long).static final recordOne known player for the in-memory name index: their UUID, last recorded name, and last-seen time.static final recordOneenderchestsrow read verbatim from a source database forimportRows(java.util.List<com.enhancedechest.storage.EnderChestStorage.RawPlayerRow>, java.util.List<com.enhancedechest.storage.EnderChestStorage.RawChestRow>).static final recordOneplayersrow read verbatim from a source database forimportRows(java.util.List<com.enhancedechest.storage.EnderChestStorage.RawPlayerRow>, java.util.List<com.enhancedechest.storage.EnderChestStorage.RawChestRow>). -
Method Summary
Modifier and TypeMethodDescriptiondefault voidWrites a consistent snapshot of the entire database totarget(which must not already exist).voidclearChestContents(UUID owner, int index) Empties a chest's contents (sets the stored bytes to NULL), keeping its size, name, icon and kind.voidclearPrimary(UUID owner) Clears the primary flag from all of the player's chests, leaving them with no main chest.voidclose()Closes all connections.voidcompleteMigration(UUID owner, byte[] containerData, @org.jetbrains.annotations.Nullable byte[] overflow, int tempSize, long tempExpiresAt) Commits a vanilla migration in one transaction: writes chest #1's contents and sets its migrated flag (one single-row UPDATE — chest #1's size is never changed), plus, whenoverflowis non-null, inserts a temp chest at the next free index carrying the vanilla items that did not fit.longTotal number of rows in theendercheststable across every player and chest kind.default intcreateChest(UUID owner, int size) Creates a new permanent chest at the lowest free index, so a number freed by a delete is reused instead of the numbering climbing forever (the index is the number the player reads on the chest).intcreateChest(UUID owner, int size, @Nullable Long expiresAt) Creates a new NORMAL chest at the lowest free index.intcreatePermChest(UUID owner, int size) Creates a permission-granted chest (kind=PERM) at the lowest free index.voiddeleteChest(UUID owner, int index) Deletes a chest.voidensureChest(UUID owner, int index, int size) Creates a chest at a specific index if it does not already exist (used by migration).findExpired(long now) Returns every chest whose expiry is set and at or beforenow.@Nullable UUIDfindUuidByName(String name) Resolves a stored in-game name to its UUID, case-insensitively, ornullif no player with that name has been recorded.intgetAppliedDefaultSize(UUID owner) Reads the persistedapplied_default_sizebaseline — the base-chest size currently dictated by the player'senhancedechest.default_size.<size>permission, or0when the base chest is not permission-managed.intgetPrimaryIndex(UUID owner) Returns the index of the chest /ec opens: the primary if one is flagged, otherwise the lowest-indexed chest.int[]importRows(List<EnderChestStorage.RawPlayerRow> players, List<EnderChestStorage.RawChestRow> chests) Bulk-inserts rawplayersandenderchestsrows verbatim into this (fresh, active) backend, in a single transaction.voidinit()Creates schema and prepares connections.booleanisMigrated(UUID owner) Returns true if the player's chest #1 has its migrated flag set.listChests(UUID owner) Returns the player's chests ordered by index.Returns every recorded player with a non-null username, with the epoch-ms they were last seen (0when never recorded).@Nullable EnderChestDataLoads a single chest, or null if no such (owner, index) row exists.loadSettings(UUID owner) Loads the player's settings, orPlayerSettings.defaults()if they have no row yet.booleanreclaimTemp(UUID owner, int tempIndex, int targetIndex) Moves a temp chest's contents into an empty chest and deletes the temp row, in one transaction — the inverse of a spill, used when a player is granted a new chest that can hold a temp chest whole (seeChestSpillService#reclaimTempInto).voidrecordPlayerSeen(UUID owner, String name, long lastOnline) Records that a player was seen: their current in-game name against their UUID (so name→UUID resolution works for offline players from the plugin's own data, with no server usercache or Mojang lookup), andlastOnlineas epoch-ms.voidrenameChest(UUID owner, int index, @Nullable String name) Sets or clears a chest's custom display name (null resets to the default numbered title).voidresizeChest(UUID owner, int index, int size) Changes a chest's slot count.voidUpdates the container bytes of an existing chest.voidsaveSettings(UUID owner, PlayerSettings settings) Upserts the player'seditMode/appliedDefaultSize(whole-object save): updates the existing row, or inserts one if none exists.voidsetAppliedDefaultSize(UUID owner, int size) Targeted single-field upsert of just theapplied_default_sizebaseline (no preceding read), leaving every other setting untouched.voidsetEditMode(UUID owner, boolean editMode) Targeted single-field upsert of just the edit-mode preference.voidSets or clears a chest's icon (material key, e.g.voidsetMigrated(UUID owner, boolean migrated) Updates the migrated flag on the player's chest #1.voidsetPrimary(UUID owner, int index) Makes the given chest the player's primary, clearing the flag from all others.voidspillRemove(UUID owner, int index, @org.jetbrains.annotations.Nullable byte[] items, int tempSize, long tempExpiresAt) Deletes a chest, optionally spilling its items into a new temp chest, in one transaction.voidspillShrink(UUID owner, int index, int newSize, byte[] visible, @org.jetbrains.annotations.Nullable byte[] overflow, int tempSize, long tempExpiresAt) Shrinks a chest and spills any cut-off items into a new temp chest, in one transaction.default booleanTrue if this backend can produce a file snapshot viabackup(Path).inttransferChests(UUID from, UUID to, @Nullable Integer onlyIndex, Set<Integer> preserveDestIndices, long tempExpiresAt) Moves a player's NORMAL chests onto another player in a single transaction (the account-switch primitive behind/ee transfer).
-
Method Details
-
init
void init()Creates schema and prepares connections. Must be called once before any other method. -
close
void close()Closes all connections. Safe to call even if init() was not completed. -
supportsBackup
default boolean supportsBackup()True if this backend can produce a file snapshot viabackup(Path). Only the file-based SQLite backend supports it today; remote backends (MySQL/MariaDB/Postgres) return false and must be backed up with the database server's own tooling. -
backup
Writes a consistent snapshot of the entire database totarget(which must not already exist). The snapshot is safe to take while players are saving — it does not interrupt writes. Only valid whensupportsBackup()is true.- Throws:
Exception- if the snapshot fails; the caller logs it and leaves the live DB untouched.
-
listChests
Returns the player's chests ordered by index. Empty list if the player owns none. -
countChests
long countChests()Total number of rows in theendercheststable across every player and chest kind. Used by/ee importto refuse converting into a non-empty destination (import only into a fresh DB). -
importRows
int[] importRows(List<EnderChestStorage.RawPlayerRow> players, List<EnderChestStorage.RawChestRow> chests) Bulk-inserts rawplayersandenderchestsrows verbatim into this (fresh, active) backend, in a single transaction. Every column is copied as-is — including thecontainer_databytes — so no item (de)serialization happens; this is the DB→DB conversion primitive behind/ee import.Inserts are batched per table (one reused
PreparedStatementeach, flushed in chunks) with autocommit off, so the whole copy commits once. A duplicate primary key (or any other failure) rolls the whole transaction back and throws — the caller guards that the destination is empty first, so a duplicate signals a real problem rather than silently merging.- Returns:
[playersInserted, chestsInserted]
-
getPrimaryIndex
Returns the index of the chest /ec opens: the primary if one is flagged, otherwise the lowest-indexed chest. Returns -1 if the player owns no chests. -
loadChest
Loads a single chest, or null if no such (owner, index) row exists. -
saveChest
Updates the container bytes of an existing chest. No-op if the row was deleted while open. Size, name and primary flag are never touched here. -
createChest
Creates a new permanent chest at the lowest free index, so a number freed by a delete is reused instead of the numbering climbing forever (the index is the number the player reads on the chest). No chest is ever auto-flagged primary; the main chest is set only viasetPrimary(java.util.UUID, int).- Returns:
- the index assigned to the new chest
-
createChest
Creates a new NORMAL chest at the lowest free index. IfexpiresAtis non-null the chest expires at that epoch-millis instant (an expirable granted chest); null = never expires. No chest is ever auto-flagged primary; the main chest is set only viasetPrimary(java.util.UUID, int).- Returns:
- the index assigned to the new chest
-
createPermChest
Creates a permission-granted chest (kind=PERM) at the lowest free index. PERM chests carry no expiry and are never auto-flagged primary; they are granted/removed by the reconcile ofcom.enhancedechest.service.PermissionChestServiceand are invisible to admin commands.- Returns:
- the index assigned to the new chest
-
spillShrink
void spillShrink(UUID owner, int index, int newSize, byte[] visible, @Nullable @org.jetbrains.annotations.Nullable byte[] overflow, int tempSize, long tempExpiresAt) Shrinks a chest and spills any cut-off items into a new temp chest, in one transaction. The original row is updated tonewSizewithvisibleas its new contents; ifoverflowis non-null a temp chest (kind=TEMP, appended after the highest index so it sorts last, expiring attempExpiresAt) is inserted holding it. Never leaves items in two rows at once.- Parameters:
tempSize- slot count of the temp chest created for the overflow (ignored if no overflow)
-
spillRemove
void spillRemove(UUID owner, int index, @Nullable @org.jetbrains.annotations.Nullable byte[] items, int tempSize, long tempExpiresAt) Deletes a chest, optionally spilling its items into a new temp chest, in one transaction. Ifitemsis non-null a temp chest holding them is inserted before the original row is deleted. No survivor is promoted to primary (the main is an explicit player choice).- Parameters:
tempSize- slot count of the temp chest created for the items (ignored ifitemsis null)
-
reclaimTemp
Moves a temp chest's contents into an empty chest and deletes the temp row, in one transaction — the inverse of a spill, used when a player is granted a new chest that can hold a temp chest whole (seeChestSpillService#reclaimTempInto).The stored bytes are copied verbatim: nothing is decoded, merged or re-packed, so every item keeps the exact slot it occupied in the temp chest. That is only sound while the target is at least as large as the temp chest and is itself empty, so both are re-checked here under the lock along with the two kinds — the caller picked the pair from a list it fetched earlier, and an expiry sweep or a concurrent op may have invalidated it since. Any failed check is a no-op returning
false, never a partial move.- Returns:
- true if the contents were moved and the temp row deleted, false if a check failed
-
findExpired
Returns every chest whose expiry is set and at or beforenow. -
ensureChest
Creates a chest at a specific index if it does not already exist (used by migration). No-op if the row already exists. Never auto-flags primary. -
resizeChest
Changes a chest's slot count. Caller validates size (multiple of 9, 9..54). -
deleteChest
Deletes a chest. No survivor is promoted: if the deleted chest was the main, the player simply has no main until they choose one again. -
clearChestContents
Empties a chest's contents (sets the stored bytes to NULL), keeping its size, name, icon and kind. -
transferChests
int transferChests(UUID from, UUID to, @Nullable @Nullable Integer onlyIndex, Set<Integer> preserveDestIndices, long tempExpiresAt) Moves a player's NORMAL chests onto another player in a single transaction (the account-switch primitive behind/ee transfer). For each source NORMAL chest (all of them, or onlyonlyIndexwhen non-null) a copy is written totoat the same index, carrying its size, custom name, icon and contents (and, on a full transfer, the primary flag); the source row is then deleted, so the items live on exactly one account.The destination's pre-existing NORMAL chests in scope are removed first so the destination ends up with exactly the source's chest count and nothing stacked on top. Any destination index listed in
preserveDestIndicesis re-inserted as a TEMP chest (carrying its items and expiring attempExpiresAt) before removal, so those items remain recoverable; indices not listed are discarded. Destination PERM/TEMP chests sitting on an index the source needs are relocated to a free index rather than dropped. The whole thing is one transaction, so a failure leaves both players untouched.- Parameters:
onlyIndex- a single source index to transfer, or null for every NORMAL chestpreserveDestIndices- destination indices whose items must be kept (spilled to a temp chest); empty to discard every replaced destination chesttempExpiresAt- epoch-millis expiry stamped on any temp chest created for preserved items- Returns:
- the number of chests transferred
-
renameChest
Sets or clears a chest's custom display name (null resets to the default numbered title). -
setIcon
Sets or clears a chest's icon (material key, e.g.minecraft:diamond; null resets to default). -
setPrimary
Makes the given chest the player's primary, clearing the flag from all others. -
clearPrimary
Clears the primary flag from all of the player's chests, leaving them with no main chest. -
isMigrated
Returns true if the player's chest #1 has its migrated flag set. -
setMigrated
Updates the migrated flag on the player's chest #1. No-op if chest #1 does not exist. -
completeMigration
void completeMigration(UUID owner, byte[] containerData, @Nullable @org.jetbrains.annotations.Nullable byte[] overflow, int tempSize, long tempExpiresAt) Commits a vanilla migration in one transaction: writes chest #1's contents and sets its migrated flag (one single-row UPDATE — chest #1's size is never changed), plus, whenoverflowis non-null, inserts a temp chest at the next free index carrying the vanilla items that did not fit. Used by the vanilla migration so no crash or concurrent runner can ever observe "contents written but not yet flagged" — the migration merge is not idempotent, so a re-run against an unflagged row would duplicate items. No-op on chest #1 if it does not exist.- Parameters:
overflow- encoded bytes of the vanilla items that did not fit, or null when all fittempSize- slot count of the temp chest (ignored whenoverflowis null)tempExpiresAt- epoch-ms expiry of the temp chest (ignored whenoverflowis null)
-
loadSettings
Loads the player's settings, orPlayerSettings.defaults()if they have no row yet. Never returns null — an absent row is indistinguishable from an all-defaults one. -
saveSettings
Upserts the player'seditMode/appliedDefaultSize(whole-object save): updates the existing row, or inserts one if none exists. Does not touchusername— that field is written only byrecordPlayerSeen(java.util.UUID, java.lang.String, long), so a save built from a stale in-memory copy never clobbers a name recorded since it was loaded. -
setEditMode
Targeted single-field upsert of just the edit-mode preference. Cheaper thanloadSettings(java.util.UUID)+saveSettings(java.util.UUID, com.enhancedechest.model.PlayerSettings)for a one-field toggle (no preceding read) and never clobbers other settings. UsesaveSettings(java.util.UUID, com.enhancedechest.model.PlayerSettings)when persisting the whole object. -
getAppliedDefaultSize
Reads the persistedapplied_default_sizebaseline — the base-chest size currently dictated by the player'senhancedechest.default_size.<size>permission, or0when the base chest is not permission-managed. Returns0for a player with no settings row. Used by/ee resizeto decide (even for an offline owner) whether the base chest is off-limits. -
setAppliedDefaultSize
Targeted single-field upsert of just theapplied_default_sizebaseline (no preceding read), leaving every other setting untouched. Written by the default-size reconcile whenever the player's permission-derived base size changes.0records "not permission-managed". -
recordPlayerSeen
Records that a player was seen: their current in-game name against their UUID (so name→UUID resolution works for offline players from the plugin's own data, with no server usercache or Mojang lookup), andlastOnlineas epoch-ms. Called on join and quit, and fromChestOpener's open prelude when the name it already loaded differs.Cheap to call often: this only mutates the in-memory row and marks it dirty, so the SQL write rides the next batched flush rather than costing a statement per call.
-
findUuidByName
Resolves a stored in-game name to its UUID, case-insensitively, ornullif no player with that name has been recorded. Never blocks on the network — this reads only the plugin's own table. -
loadAllPlayerNames
List<EnderChestStorage.PlayerNameRecord> loadAllPlayerNames()Returns every recorded player with a non-null username, with the epoch-ms they were last seen (0when never recorded). Used once at startup to populate the in-memoryPlayerNameIndexso offline-player tab-completion and resolution can answer from memory instead of hitting the DB on every keystroke.
-