Documentation
¶
Index ¶
- Constants
- func HasRetainedSource(versionDir string) (bool, error)
- func MarkFileComplete(versionDir, fileID string) error
- func MarkFileCompleteWithMembers(versionDir, fileID string, members []string) error
- func MergeFingerprintPath(versionDir string) string
- func PruneUnclaimed(versionDir string) error
- func RetainedSourceName(fileID string) string
- func VerifiableFileID(fileID string) bool
- type Cache
- func (c *Cache) CloneMod(dest *Cache, gameID, sourceID, modID, version string) error
- func (c *Cache) Delete(gameID, sourceID, modID, version string) error
- func (c *Cache) Exists(gameID, sourceID, modID, version string) bool
- func (c *Cache) FileManifests(gameID, sourceID, modID, version string) (map[string]FileManifest, error)
- func (c *Cache) GetFilePath(gameID, sourceID, modID, version, relativePath string) string
- func (c *Cache) HasFileIDs(gameID, sourceID, modID, version string, fileIDs []string) bool
- func (c *Cache) ListFiles(gameID, sourceID, modID, version string) ([]string, error)
- func (c *Cache) ModPath(gameID, sourceID, modID, version string) string
- func (c *Cache) Size(gameID, sourceID, modID, version string) (int64, error)
- func (c *Cache) Store(gameID, sourceID, modID, version, relativePath string, content []byte) error
- type FileManifest
Constants ¶
const ReservedPrefix = ".lmm-"
ReservedPrefix marks lmm's own bookkeeping entries inside a cache version directory. Nothing under it is mod content: reserved entries are excluded from every enumerator of a version directory (ListFiles, Size) so they can never be deployed, counted, checksummed, or conflict-matched.
It is exported so the archive extractor (internal/core) can enforce the same namespace from the other side: mod archives are untrusted content, and a member shipped under this prefix could otherwise forge a completion marker (making the cache-first guard skip a real download) or hide itself from deploy. One constant, both sides.
Variables ¶
This section is empty.
Functions ¶
func HasRetainedSource ¶ added in v1.29.0
HasRetainedSource reports whether versionDir contains at least one retained compile source (any entry named by RetainedSourceName, for any fileID). It is deployableFiles' narrowing gate (#210): a retained source is the validate+retain compile model's signature, present in every real #210 entry (a merged-pak deploy that keeps its .exmodz for offline recompile) and the same signal verify's retained-source carve-out trusts. Its absence means unattributed content on disk cannot be distinguished from an unmanifested contributor (#144, e.g. `lmm import`), so callers must fall back to the full union rather than narrow.
A missing versionDir is not an error - it reports (false, nil), same as an empty directory.
func MarkFileComplete ¶ added in v1.25.0
MarkFileComplete writes the zero-byte completion marker for fileID into versionDir, recording that this source file's content has been fully committed to that cache entry. It is the write side of HasFileIDs.
A bare (zero-byte) marker vouches for completion but records no member manifest - FileManifests reports it as Recorded=false. Production commits go through MarkFileCompleteWithMembers instead; this remains the legacy shape every pre-manifest cache entry already has on disk.
versionDir is a raw directory path rather than a cache key because the marker is written into the STAGING directory just before it is swapped into place (see internal/core/service.go's commitStagedCacheWithMarker), so the marker and the content it vouches for become visible in the same atomic rename - a marker can never appear without its content.
An unverifiable fileID (blank, or carrying a path separator - see VerifiableFileID) is skipped rather than rejected: HasFileIDs refuses those same IDs, so the entry simply reads as incomplete and costs a redundant re-download, which is the safe direction and never a write outside versionDir.
func MarkFileCompleteWithMembers ¶ added in v1.27.0
MarkFileCompleteWithMembers is MarkFileComplete plus provenance: the marker body records WHICH versionDir-relative members this file ID contributed (slash-separated, one per line, under the manifestHeader line), so the same-version update path can later undeploy members owned solely by a superseded file (#144 item 4). Read back via FileManifests.
A member name a line-oriented body cannot represent faithfully (embedded newline/carriage return) degrades the WHOLE write to a bare legacy marker: completion is still vouched for, but no manifest is recorded, so consumers fall back to today's union behavior rather than ever trusting a corrupted member list.
func MergeFingerprintPath ¶ added in v1.28.0
MergeFingerprintPath returns the reserved on-disk path for versionDir's merge-fingerprint marker. Pure naming, like RetainedSourceName - callers (internal/core, which owns the MergedFingerprint type and its JSON encoding) read/write the actual bytes with ordinary file I/O.
func PruneUnclaimed ¶ added in v1.29.0
PruneUnclaimed deletes non-reserved regular files in versionDir that no recorded manifest claims, then removes directories the deletions emptied (#210). It is a no-op unless EVERY marker carries a recorded manifest AND the entry holds a retained source (.lmm-source-*) - the validate+retain model's signature (#210); pruning on anything less could delete legacy content no manifest attributes (#144, e.g. an entry `lmm import` populated directly). A bare marker means unknown provenance - pruning on guesswork could delete a legacy file's live content, so any bare marker anywhere in versionDir makes the whole call a no-op. Reserved (ReservedPrefix) entries are never candidates. Callers invoke it on a STAGING directory at commit time, so a prune can never race a deploy.
An unclaimed file whose content matches a retained source is EXEMPT (#250): "unclaimed" has two distinct causes prune must tell apart. Stale/superseded content - #210's actual target - matches no retained source. A converted pak's deployable copy, by contrast, is unclaimed only because a successful merge suppressed it (its manifest reads members=nil; the merged pak claims its content), yet it remains the designated raw-fallback artifact an opt-out or failed merge must be able to redeploy. Content identity with the retained source is the one signal that separates the two: ingest stages the deployable copy from the SAME bytes as the retained source, and the convert flip erases the manifest that once recorded the mapping (see internal/core's rawPakMembers, which relies on the same attribution to find the way back to raw).
func RetainedSourceName ¶ added in v1.28.0
RetainedSourceName returns the reserved on-disk filename for fileID's retained compile source. It is a pure naming function - like GetFilePath, callers join it against a staging or cache directory themselves and read/write/copy the actual bytes with ordinary file I/O (see internal/core's ingest/merge paths).
fileID is Base'd first (#197 hardening): it is source-controlled (a ModSource's own DownloadableFile.ID, or - for an import - the archive's own filename) exactly like the FileName fields #196's review already found needed filepath.Base sanitization at their own join sites - a fileID containing "../" must not be able to escape the staging/cache directory this name gets joined against downstream.
func VerifiableFileID ¶ added in v1.27.0
VerifiableFileID reports whether a source file ID can be round-tripped through a marker filename. A blank ID has nothing to name, and one carrying a path separator would place (or look for) the marker outside the version directory entirely. Both are refused rather than sanitized: two distinct IDs sanitizing to the same marker would let one file's completion vouch for another's.
Types ¶
type Cache ¶
type Cache struct {
// contains filtered or unexported fields
}
Cache manages the central mod file cache
func New ¶
New creates a new cache manager for the global cache (basePath/gameID/source-mod/version).
func NewGameScoped ¶ added in v1.1.0
NewGameScoped creates a cache for a per-game cache_path. Paths are basePath/source-mod/version (no gameID); the base is already game-specific.
func (*Cache) CloneMod ¶ added in v1.3.3
CloneMod copies a cached mod version into another cache.
Unlike every other walker it deliberately INCLUDES lmm's own .lmm-* bookkeeping entries: a clone is meant to reproduce the entry itself, not enumerate its mod content, and the reinstall cache transaction (internal/core/flows.go) round-trips a live entry through a staged/snapshot cache and back. Dropping the completion markers on that round trip would silently downgrade a complete entry to a pre-marker one and cost a redundant redownload on the next cache-first check.
func (*Cache) Delete ¶
Delete removes a cached mod version, then removes the mod's per-mod container directory (ModPath's parent - the "<source>-<modID>" directory version subdirectories live under) if this was its last remaining version. The container has no meaning once nothing is left under it, so leaving it behind after every version is gone is just litter (#190 item 4). A container that still holds another version, or that never existed, is left alone either way - never an error.
func (*Cache) FileManifests ¶ added in v1.27.0
func (c *Cache) FileManifests(gameID, sourceID, modID, version string) (map[string]FileManifest, error)
FileManifests reads every completion marker in the version directory and returns each file ID's manifest. A directory (or entry) with no markers returns an empty map, never an error. A marker whose body is empty or carries an unrecognized header parses as Recorded=false - the safe, union-fallback direction - so pre-manifest entries and any future format revision both degrade silently rather than misreport provenance.
func (*Cache) GetFilePath ¶
GetFilePath returns the full path to a cached file
func (*Cache) HasFileIDs ¶ added in v1.25.0
HasFileIDs reports whether every named source file has been fully committed to the given (gameID, sourceID, modID, version) cache entry - a stronger guard than Exists alone, which only checks the version directory's presence and can return true for a PARTIALLY populated entry (e.g. a previous download run that stored file 1 of 2 before failing - each file is committed to the cache individually, so a broken-off run leaves the directory present but incomplete). Callers doing a "cache already has this, skip downloading" check (see internal/core/flows.go's ApplyProfileSwitch and cmd/lmm/profile.go's doProfileApply, both #96) should use this instead of Exists to decide whether a re-download is genuinely unnecessary.
Completeness is judged by the per-file markers MarkFileComplete writes, NOT by on-disk filenames: under the default DeployExtract mode a cache version directory holds an archive's EXTRACTED MEMBERS, whose names have nothing to do with the DownloadableFile's own FileName, so a filename-based check would report false for essentially every archive-based mod and redownload despite a complete cache.
LEGACY ENTRIES: a cache directory populated before markers existed (or by an `lmm import` that could not resolve the archive against its source's file listing - source-linked imports that do resolve stamp the marker at import time, #139) carries no markers and therefore reads as incomplete. That costs exactly one redundant redownload, which commits markers on the way through and makes every later check a hit.
An empty/nil fileIDs degrades to Exists - there is nothing left to verify beyond the directory's presence. An unverifiable ID (blank, or carrying a path separator) reports false rather than being skipped.
func (*Cache) ListFiles ¶
ListFiles returns all files in a cached mod version.
This is the choke point every consumer of a cache entry's contents goes through - Installer.Install/Replace/Undeploy, DetectConflicts, `lmm verify`'s file-count check, DownloadModResult.FilesExtracted - so lmm's own bookkeeping entries (the .lmm-* completion markers, see MarkFileComplete) are excluded here and never reach any of them. A version directory holding ONLY markers correctly reports zero files.
CloneMod is the sole exception and walks with includeReserved - see there.
type FileManifest ¶ added in v1.27.0
type FileManifest struct {
// Members holds the version-dir-relative paths (OS separators) this file
// ID contributed to the cache entry. Meaningful only when Recorded.
Members []string
// Recorded is false for a legacy bare marker: the file's completion is
// vouched for, but WHICH members it contributed was never written down.
// Consumers must treat that as unknown provenance - never as "none".
Recorded bool
}
FileManifest is one completion marker's recorded member manifest.