core

package
v1.11.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 21, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ApplyProfileOverrides added in v1.1.0

func ApplyProfileOverrides(game *domain.Game, profile *domain.Profile) error

ApplyProfileOverrides writes a profile's configuration overrides to the game install directory. Each key in profile.Overrides is a path relative to game.InstallPath; the value is written as file content. Used on deploy and profile switch so INI tweaks and other overrides are applied. Paths that escape the game install directory (e.g. ../../../etc/passwd) are rejected to prevent abuse.

func CompareVersions

func CompareVersions(v1, v2 string) int

CompareVersions delegates to domain.CompareVersions. Kept for backward compatibility with existing callers.

func DetectModName added in v0.10.0

func DetectModName(extractedPath, archiveFilename string) string

DetectModName determines a display name for an imported mod. It checks for a single top-level directory in the extracted content, falling back to the archive basename if not found.

func IsNewerVersion

func IsNewerVersion(currentVersion, newVersion string) bool

IsNewerVersion delegates to domain.IsNewerVersion. Kept for backward compatibility with existing callers.

Types

type AggregateSearchResult added in v1.10.0

type AggregateSearchResult struct {
	Mods       []domain.Mod    // merged, ranked; each Mod carries its SourceID
	TotalCount int             // sum of per-source totals (sources reporting 0/unknown contribute 0)
	Warnings   []SourceWarning // per-source failures (design §5: warnings, not errors)
}

AggregateSearchResult is the merged outcome of searching every source configured for a game.

type BatchOptions added in v0.12.0

type BatchOptions struct {
	Hooks       *ResolvedHooks // Hooks to run during batch operation
	HookRunner  *HookRunner    // Runner for executing hooks
	HookContext HookContext    // Base context for hooks (mod-specific fields added per-mod)
	Force       bool           // If true, bypass before_* hook failures
}

BatchOptions configures batch install/uninstall operations

type BatchResult added in v0.12.0

type BatchResult struct {
	Installed   []InstalledModResult   // Successfully installed mods (for InstallBatch)
	Uninstalled []UninstalledModResult // Successfully uninstalled mods (for UninstallBatch)
	Skipped     []SkippedMod           // Mods skipped due to hook failure or error
	Errors      []error                // Non-fatal errors (after_* hook failures)
}

BatchResult contains the results of a batch install/uninstall operation

type Conflict added in v0.9.0

type Conflict struct {
	RelativePath    string
	CurrentSourceID string
	CurrentModID    string
}

Conflict represents a file that would be overwritten by installing a mod

type DependencyResolver

type DependencyResolver struct{}

DependencyResolver resolves mod dependencies and detects cycles

func NewDependencyResolver

func NewDependencyResolver() *DependencyResolver

NewDependencyResolver creates a new dependency resolver

func (*DependencyResolver) GetDependencyTree

func (r *DependencyResolver) GetDependencyTree(mod *domain.Mod, modMap map[string]*domain.Mod) ([]domain.Mod, error)

GetDependencyTree returns all dependencies for a mod (including transitive)

func (*DependencyResolver) Resolve

func (r *DependencyResolver) Resolve(mods []domain.Mod) ([]domain.Mod, error)

Resolve returns mods in dependency order (dependencies first) Returns ErrDependencyLoop if a circular dependency is detected

func (*DependencyResolver) ValidateDependencies

func (r *DependencyResolver) ValidateDependencies(mods []domain.Mod) error

ValidateDependencies checks if all dependencies are satisfied

type DeployOptions added in v1.11.0

type DeployOptions struct {
	Purge bool // --purge: undeploy every installed mod (regardless of ModID/All) before deploying, remembering which were enabled beforehand for the profile-wide selection below.

	// LinkMethod overrides the link method used for this deploy (--method).
	// nil (the zero value) means "use the game's effective link method" via
	// Service.GetGameLinkMethod. A pointer is used, rather than a bare
	// domain.LinkMethod with its zero value as the "unset" sentinel, because
	// domain.LinkMethod's zero value (LinkSymlink) is itself a valid,
	// explicit choice - it cannot double as "no override" without losing
	// the ability to explicitly request symlink. See the task report.
	LinkMethod *domain.LinkMethod

	// ModID/SourceID restrict the deploy to a single mod (`lmm deploy
	// <mod-id>`). Both empty (the default) deploys every mod in profile
	// order, subject to All. SourceID selects which source's copy of ModID
	// to deploy - the CLI's --source flag, default "nexusmods".
	ModID    string
	SourceID string

	All bool // --all: include disabled mods in a full-profile deploy, or allow deploying a disabled ModID.

	// Hook plumbing, mirroring UninstallOptions. Hooks and/or HookRunner may
	// be nil to skip hook execution entirely (e.g. --no-hooks). The deploy
	// pass runs install.* hooks; the purge pass (when Purge is set) runs
	// uninstall.* hooks, matching the pre-extraction CLI's doDeploy/
	// purgeDeployedMods split.
	Hooks       *ResolvedHooks
	HookRunner  *HookRunner
	HookContext HookContext
	Force       bool // continue past a failing before_* hook (warn instead of fail)
}

DeployOptions configures DeployProfile.

type DeployPhase added in v1.11.0

type DeployPhase int

DeployPhase identifies what DeployProfile is doing for the mod named in a DeployProgress event (or, for DeployPurging, for the purge pass as a whole), letting callers (CLI, TUI) render phase-appropriate UI without needing to know how a deploy is actually carried out.

const (
	// DeployPurging fires once, before any purge-phase mod is touched, when
	// Purge is set and there is at least one installed mod to purge. Total
	// is the number of mods being purged; Index and ModName are zero/empty.
	DeployPurging DeployPhase = iota
	// DeployBeforeEachSkipped: install.before_each failed for ModName: the
	// mod is skipped (added to DeployResult.Skipped). Detail is the reason.
	DeployBeforeEachSkipped
	// DeployRedownloading: ModName's cache entry is missing; DeployProfile
	// is re-fetching it from source.
	DeployRedownloading
	// DeployFallbackUsed: ModName's stored file IDs were not found on the
	// source; falling back to the primary file.
	DeployFallbackUsed
	// DeployDownloading: a file for ModName is downloading. Percent is the
	// 0-100 completion (only reported once the source declares a total
	// size, matching the pre-extraction CLI's progress callback gating).
	DeployDownloading
	// DeployDownloadFailed: a file for ModName failed to download; the mod
	// is skipped. Detail is the reason.
	DeployDownloadFailed
	// DeployDownloadDone fires once, after a cache-miss mod's redownload
	// loop finishes without error, mirroring the pre-extraction CLI's
	// unconditional `fmt.Println() // Clear progress line` immediately
	// after the download loop (git show b2ad559:cmd/lmm/deploy.go) - it
	// terminates DeployDownloading's carriage-returned progress line with a
	// real newline before the mod's own DeployDeployed line prints. Unlike
	// its ApplyProfileSwitch analog (SwitchDownloadDone), which fires on
	// both success and failure since doProfileSwitch's equivalent Println
	// sat unconditionally after its own loop, redeployFromSource's failure
	// path returns immediately via a DeployDownloadFailed event instead (see
	// below) without reaching this point - so this phase covers the
	// success path only.
	DeployDownloadDone
	// DeploySkipped: ModName was skipped for a reason other than a hook or
	// download failure (fetch failure, no files available, file-selection
	// failure, or an outright deploy/install failure). Detail is the reason.
	DeploySkipped
	// DeployDeployed: ModName was (re)deployed successfully.
	DeployDeployed

	// DeployBeforeAllForced fires once, immediately, when install.before_all
	// (a deploy) or uninstall.before_all (a --purge pass) fails and Force is
	// set: the pre-extraction CLI printed this warning as the very first
	// line of output, before anything else (the "Purging..."/"Deploying..."
	// header included) - so this event always precedes DeployPurging and
	// any other event. No mod is in scope (Index/Total/ModName/ModID are
	// zero); Detail matches the DeployResult.Warnings entry verbatim.
	DeployBeforeAllForced
	// DeployNote fires wherever DeployProfile appends an entry to
	// DeployResult.Notes for a specific mod during the main deploy loop
	// (a failed undeploy-before-redeploy, a failed SetModLinkMethod, or a
	// failed SetModDeployed), at the exact point it happens - always
	// before that same mod's own DeployDeployed event, matching the
	// pre-extraction CLI's inline ordering. ModName/ModID identify the
	// mod; for the latter two diagnostics, whose historical text carries
	// no mod identity at all, the event's ModName/ModID are the ONLY way
	// to attribute the diagnostic to a mod.
	DeployNote
	// DeployWarning fires wherever DeployProfile appends an entry to
	// DeployResult.Warnings other than a DeployBeforeAllForced one: a
	// failed install.after_each hook (ModName/ModID set), a failed
	// install.after_all hook, or a failed ApplyProfileOverrides (neither
	// has a mod in scope). The pre-extraction CLI printed the overrides
	// warning immediately once computed, then its batched hook warnings
	// (after_each in mod order, then after_all) right after - so
	// DeployProfile emits the overrides DeployWarning (if any) first, then
	// the after_each/after_all ones, reproducing that print order without
	// changing when each check actually runs (see DeployProfile's body).
	DeployWarning
	// PurgeWarning fires wherever a --purge pass appends an entry to
	// DeployResult.Warnings: a skipped uninstall.before_each mod (fires
	// inline, per mod, as it happens), or a failed uninstall.after_each/
	// after_all hook (fires after the whole purge loop has finished, in
	// mod order then after_all - mirroring the pre-extraction
	// purgeDeployedMods, which accumulated these and printed them
	// together, after every per-mod line, via printHookWarnings).
	PurgeWarning
	// PurgeNote fires wherever a --purge pass appends an entry to
	// DeployResult.Notes for a specific mod (a failed undeploy, or a
	// failed SetModDeployed(false)), inline, immediately after that
	// operation - mirroring the pre-extraction purgeDeployedMods's
	// --verbose-gated "⚠ " lines.
	PurgeNote
	// PurgeComplete fires once, after a non-empty --purge pass has
	// finished everything (including its own hook warnings) but before
	// DeployProfile moves on to gathering mods to deploy. It carries no
	// data; a caller wanting byte-identical pre-extraction output prints
	// exactly one blank line here - purgeDeployedMods's own final
	// `fmt.Println()`, which the initial extraction had misplaced
	// immediately after the purge header instead of at the end of the
	// purge phase.
	PurgeComplete

	// SwitchDisableNote fires for each of the disable loop's two possible
	// per-mod diagnostics (a failed Uninstall, then a failed SetModEnabled),
	// mirroring doProfileSwitch's "  Warning: failed to undeploy %s: %v" /
	// "  Warning: failed to update %s: %v" - both --verbose-gated stdout
	// prints. Detail carries the historical "Warning: " prefix baked in; a
	// caller wanting byte-identical output prints
	// `if verbose { fmt.Printf("  %s\n", p.Detail) }`.
	SwitchDisableNote
	// SwitchDisabled fires once a mod's disable step has finished
	// (regardless of whether SwitchDisableNote fired for it) -
	// doProfileSwitch always disables the DB row and always prints
	// "  ✓ Disabled: %s" even when the undeploy/DB update above it failed.
	// ModName is set.
	SwitchDisabled
	// SwitchEnableNote mirrors SwitchDisableNote for the enable loop's two
	// diagnostics (a failed Install, then a failed SetModEnabled). Unlike
	// the disable loop, a failed Install is fatal FOR THAT MOD ONLY: the mod
	// is skipped (no SwitchEnabled event follows) - see doProfileSwitch's
	// `continue` after the Install failure branch.
	SwitchEnableNote
	// SwitchEnabled fires once a mod has been successfully deployed (and
	// enabled, or deployed but its SetModEnabled bookkeeping failed - see
	// SwitchEnableNote), mirroring "  ✓ Enabled: %s".
	SwitchEnabled
	// SwitchInstalling fires once, before the install loop, only when there
	// is at least one mod to install (Total = len(SwitchPlan.ToInstall)),
	// mirroring doProfileSwitch's "\nInstalling missing mods...".
	SwitchInstalling
	// SwitchInstallingMod fires once per mod to install, before it is even
	// fetched - SourceID/ModID are the only identity available at this
	// point, mirroring "  Installing %s:%s...".
	SwitchInstallingMod
	// SwitchInstallError fires for any of the install loop's mod-fatal-only
	// failure reasons (fetch, get-files, no-files, file-selection, deploy,
	// or save), each already worded to match its historical text exactly
	// (Detail is printed verbatim as "    Error: %s"). Unlike
	// DeployProfile's DeploySkipped, these are NOT accumulated into any
	// SwitchResult slice - doProfileSwitch never printed a final
	// skipped-count summary for profile switch, so there is nothing to
	// accumulate beyond the live event.
	SwitchInstallError
	// SwitchFallbackUsed fires when a to-be-installed mod's stored file IDs
	// were not found on the source and the primary file was used instead,
	// mirroring doProfileSwitch's unconditional (NOT --verbose-gated)
	// "    Warning: stored file IDs not found, using primary".
	SwitchFallbackUsed
	// SwitchDownloading mirrors DeployDownloading for the install loop's
	// download progress (Percent set, gated the same way: only once the
	// source declares a total size).
	SwitchDownloading
	// SwitchDownloadFailed fires when a file download fails; Detail is
	// "download failed: %v". A caller wanting byte-identical output prints a
	// blank line then "    Error: %s" with Detail - see SwitchDownloadDone's
	// doc comment for why the blank line isn't included here.
	SwitchDownloadFailed
	// SwitchDownloadDone fires once per install-loop mod after its download
	// loop finishes, on both success and failure - doProfileSwitch's
	// `fmt.Println()` after the loop runs unconditionally either way. A
	// caller wanting byte-identical output prints a bare blank line here;
	// combined with SwitchDownloadFailed's own leading blank line, a failed
	// download reproduces the original's blank/error/blank sequence, and a
	// successful one reproduces its single trailing blank line.
	SwitchDownloadDone
	// SwitchInstalled fires once a to-be-installed mod has been fetched,
	// downloaded, deployed, and saved to the DB, mirroring "    ✓ Installed:
	// %s". ModName is set (mod.Name, now known).
	SwitchInstalled
	// SwitchInstallNote fires when UpsertMod (recording the profile's
	// FileIDs) fails after a successful install - the sole --verbose-gated
	// diagnostic in the install loop, mirroring "    Warning: could not
	// update profile: %v" (4-space indent, one level deeper than
	// SwitchDisableNote/SwitchEnableNote's 2-space Notes).
	SwitchInstallNote
)

type DeployProgress added in v1.11.0

type DeployProgress struct {
	Index, Total int
	ModName      string
	ModID        string
	// SourceID is populated by ApplyProfileSwitch's install-loop events
	// (SwitchInstallingMod onward), where a mod's SourceID:ModID pair is the
	// only identity known before it has even been fetched (no ModName yet).
	// DeployProfile's own events never set it (zero value, ignored).
	SourceID string
	Phase    DeployPhase
	Detail   string
	Percent  float64
}

DeployProgress reports incremental status during DeployProfile. Index and Total describe ModName's position among the mods being deployed (both zero for phases with no mod/count in scope - see each DeployPhase constant's doc comment). ModID accompanies ModName wherever a specific mod is in scope; for phases whose historical text carries no mod name at all (DeployNote's link-method/mark-deployed cases), it is the only attribution available. Detail and Percent are populated only for the phases documented on DeployPhase's constants; both are zero otherwise.

type DeployResult added in v1.11.0

type DeployResult struct {
	Deployed int
	Skipped  []string
	Warnings []string
	Notes    []string
}

DeployResult reports the outcome of DeployProfile. As with UninstallResult (see its doc comment), every entry below is always recorded - there is no verbosity concept in core - but Warnings and Notes carry the same two display contracts Task 2 established:

  • Warnings holds diagnostics the pre-extraction CLI printed unconditionally to stderr: install.before_all/uninstall.before_all (when forced), a skipped uninstall.before_each during purge, install/uninstall after_each/after_all hook failures, and a profile-overrides application failure. Callers should print each entry to stderr, unconditionally, e.g. `fmt.Fprintf(os.Stderr, "Warning: %v\n", w)`.
  • Notes holds operational diagnostics the pre-extraction CLI only printed under --verbose: a failed undeploy-before-redeploy, a failed SetModLinkMethod, and a failed SetModDeployed, all per mod, plus (for a --purge pass) the equivalent per-mod undeploy/SetModDeployed failures from purging. Each entry already carries its historical prefix ("Warning: " for the deploy-loop trio, "⚠ " for the purge trio) baked into the text, matching each one's pre-extraction wording; a caller wanting byte-identical pre-extraction output should print each entry to stdout ONLY under --verbose, verbatim, e.g. `fmt.Printf(" %s\n", n)`.

Every entry in both slices is ALSO reported via the progress callback at the exact point it is appended (DeployBeforeAllForced/DeployNote/ DeployWarning/PurgeWarning/PurgeNote - see each DeployPhase constant's doc comment for which), with Detail equal to the slice entry verbatim and the phase itself indicating which display contract above applies. A caller driving its console output entirely from progress events (as cmd/lmm's doDeploy does) gets pre-extraction-accurate positioning; the slices remain here, unconditionally, for callers that only want the final, order-independent summary.

Skipped carries one "<mod name>: <reason>" entry per mod that did not deploy, for any reason (hook failure, download failure, install failure); the pre-extraction CLI printed each of these unconditionally as it happened; DeployProgress's DeployBeforeEachSkipped/ DeployDownloadFailed/DeploySkipped events carry the same reason text in real time for callers that want to print them as they occur instead of (or in addition to) at the end.

On error, the returned result carries any diagnostics accumulated before the failure; callers should surface them alongside the error.

type DeployedFile added in v1.3.7

type DeployedFile struct {
	SourceID string
	ModID    string
	FileID   string
	Checksum string
}

DeployedFile is a service-boundary view of a tracked mod file with its checksum.

type DownloadModResult added in v0.8.0

type DownloadModResult struct {
	FilesExtracted int    // Number of files extracted
	Checksum       string // MD5 hash of downloaded archive
}

DownloadModResult contains the outcome of downloading a mod file

type DownloadProgress

type DownloadProgress struct {
	TotalBytes int64   // Total size in bytes (0 if unknown)
	Downloaded int64   // Bytes downloaded so far
	Percentage float64 // Completion percentage (0-100)
}

DownloadProgress represents the current state of a download

type DownloadResult added in v0.8.0

type DownloadResult struct {
	Path     string // Final file path
	Size     int64  // Bytes downloaded
	Checksum string // MD5 hash of downloaded file (recorded in the DB)
	SHA256   string // SHA-256 of downloaded file (compared against source-declared checksums)
}

DownloadResult contains the outcome of a download

type Downloader

type Downloader struct {
	// contains filtered or unexported fields
}

Downloader handles HTTP file downloads with progress tracking

func NewDownloader

func NewDownloader(httpClient *http.Client) *Downloader

NewDownloader creates a new Downloader with the given HTTP client If httpClient is nil, http.DefaultClient is used

func (*Downloader) Download

func (d *Downloader) Download(ctx context.Context, url, destPath string, progressFn ProgressFunc) (*DownloadResult, error)

Download fetches a file from the URL and saves it to destPath, with retries on transient failures (exponential backoff). Progress updates are sent to the optional progressFn callback.

func (*Downloader) DownloadWithHeaders added in v1.7.0

func (d *Downloader) DownloadWithHeaders(ctx context.Context, url, destPath string, headers map[string]string, progressFn ProgressFunc) (*DownloadResult, error)

DownloadWithHeaders is Download with extra request headers applied to every attempt — used for authenticated file downloads from custom sources.

type Extractor

type Extractor struct{}

Extractor handles archive extraction for mod files

func NewExtractor

func NewExtractor() *Extractor

NewExtractor creates a new Extractor

func (*Extractor) CanExtract

func (e *Extractor) CanExtract(filename string) bool

CanExtract returns true if the extractor can handle the given filename

func (*Extractor) DetectFormat

func (e *Extractor) DetectFormat(filename string) string

DetectFormat returns the archive format based on filename extension

func (*Extractor) Extract

func (e *Extractor) Extract(archivePath, destDir string) error

Extract extracts an archive to the destination directory Supports .zip (native), .7z and .rar (via system 7z command)

type HookContext added in v0.12.0

type HookContext struct {
	GameID     string
	GamePath   string
	ModPath    string
	ModID      string // Empty for *_all hooks
	ModName    string // Empty for *_all hooks
	ModVersion string // Empty for *_all hooks
	HookName   string // e.g., "install.before_all"
}

HookContext provides environment information for hook scripts

type HookResult added in v0.12.0

type HookResult struct {
	Stdout   string
	Stderr   string
	ExitCode int
}

HookResult contains the output from running a hook

type HookRunner added in v0.12.0

type HookRunner struct {
	// contains filtered or unexported fields
}

HookRunner executes hook scripts with timeout and environment

func NewHookRunner added in v0.12.0

func NewHookRunner(timeout time.Duration) *HookRunner

NewHookRunner creates a new hook runner with the given timeout

func (*HookRunner) Run added in v0.12.0

func (r *HookRunner) Run(ctx context.Context, scriptPath string, hc HookContext) (*HookResult, error)

Run executes a hook script and returns its output

type ImportOptions added in v0.10.0

type ImportOptions struct {
	SourceID    string // Explicit source (empty = auto-detect or "local")
	ModID       string // Explicit mod ID (empty = auto-detect or generate)
	ProfileName string // Target profile
}

ImportOptions configures the import operation

type ImportResult added in v0.10.0

type ImportResult struct {
	Mod            *domain.Mod
	FilesExtracted int
	LinkedSource   string // "nexusmods", "local", etc.
	AutoDetected   bool   // true if source/ID was parsed from filename
}

ImportResult contains the outcome of importing a local mod

type Importer added in v0.10.0

type Importer struct {
	// contains filtered or unexported fields
}

Importer handles importing mods from local archive files

func NewImporter added in v0.10.0

func NewImporter(cache *cache.Cache) *Importer

NewImporter creates a new Importer

func (*Importer) FindDuplicateMod added in v1.1.0

func (i *Importer) FindDuplicateMod(modName string, installedMods []domain.InstalledMod) *domain.InstalledMod

findDuplicateMod checks if a mod with similar name already exists (for duplicate prevention)

func (*Importer) Import added in v0.10.0

func (i *Importer) Import(ctx context.Context, archivePath string, game *domain.Game, opts ImportOptions) (result *ImportResult, err error)

Import imports a mod from a local archive file

func (*Importer) ScanModPath added in v1.1.0

func (i *Importer) ScanModPath(ctx context.Context, game *domain.Game, installedMods []domain.InstalledMod, opts ScanOptions) ([]ScanResult, error)

ScanModPath scans the game's mod_path for untracked mods

type InstalledModResult added in v0.12.0

type InstalledModResult struct {
	domain.Mod
}

InstalledModResult is a successfully installed mod (wraps domain.Mod for batch result)

type Installer

type Installer struct {
	// contains filtered or unexported fields
}

Installer handles mod installation and uninstallation

func NewInstaller

func NewInstaller(cache *cache.Cache, linker linker.Linker, database *db.DB) *Installer

NewInstaller creates a new installer The db parameter is optional - if nil, file tracking is disabled

func (*Installer) GetConflicts added in v0.9.0

func (i *Installer) GetConflicts(ctx context.Context, game *domain.Game, mod *domain.Mod, profileName string) ([]Conflict, error)

GetConflicts checks if installing a mod would overwrite files from other mods. Returns conflicts for files owned by OTHER mods (not the mod being installed).

func (*Installer) GetDeployedFiles

func (i *Installer) GetDeployedFiles(game *domain.Game, mod *domain.Mod) ([]string, error)

GetDeployedFiles returns the list of files deployed for a mod

func (*Installer) Install

func (i *Installer) Install(ctx context.Context, game *domain.Game, mod *domain.Mod, profileName string) error

Install deploys a mod to the game directory. If DB tracking is enabled and a SaveDeployedFile fails, only the file that failed to track is rolled back so the filesystem stays consistent with the database (previously deployed+tracked files are left in place).

func (*Installer) InstallBatch added in v0.12.0

func (i *Installer) InstallBatch(ctx context.Context, game *domain.Game, mods []*domain.Mod, versions []string, profileName string, opts BatchOptions) (*BatchResult, error)

InstallBatch installs multiple mods with hook support Hook behavior: - install.before_all: If fails, return error immediately (unless Force) - install.before_each: If fails, skip that mod, continue others - install.after_each: If fails, warn (add to Errors), continue - install.after_all: If fails, warn (add to Errors)

func (*Installer) IsInstalled

func (i *Installer) IsInstalled(game *domain.Game, mod *domain.Mod) (bool, error)

IsInstalled checks if a mod is currently deployed. Returns true only if every cached file is deployed (partial installs report as not installed).

func (*Installer) Replace added in v1.3.3

func (i *Installer) Replace(ctx context.Context, game *domain.Game, oldMod, newMod *domain.Mod, profileName string) error

Replace swaps an existing deployment with a new cached version and restores the old files if the replacement fails.

func (*Installer) ReplaceWithCaches added in v1.3.3

func (i *Installer) ReplaceWithCaches(ctx context.Context, game *domain.Game, oldCache, newCache *cache.Cache, oldMod, newMod *domain.Mod, profileName string) error

ReplaceWithCaches swaps an existing deployment using explicit old and new caches.

func (*Installer) ReplaceWithOldCache added in v1.3.3

func (i *Installer) ReplaceWithOldCache(ctx context.Context, game *domain.Game, oldCache *cache.Cache, oldMod, newMod *domain.Mod, profileName string) error

ReplaceWithOldCache swaps an existing deployment using an alternate cache snapshot for the old version.

func (*Installer) Uninstall

func (i *Installer) Uninstall(ctx context.Context, game *domain.Game, mod *domain.Mod, profileName string) error

Uninstall removes a mod from the game directory

func (*Installer) UninstallBatch added in v0.12.0

func (i *Installer) UninstallBatch(ctx context.Context, game *domain.Game, mods []*domain.InstalledMod, profileName string, opts BatchOptions) (*BatchResult, error)

UninstallBatch uninstalls multiple mods with hook support Hook behavior: - uninstall.before_all: If fails, return error immediately (unless Force) - uninstall.before_each: If fails, skip that mod, continue others - uninstall.after_each: If fails, warn (add to Errors), continue - uninstall.after_all: If fails, warn (add to Errors)

type ParsedFilename added in v0.10.0

type ParsedFilename struct {
	ModID    string // NexusMods mod ID
	Version  string // Mod version (normalized)
	BaseName string // Mod name portion before the ID
}

ParsedFilename contains extracted info from a NexusMods-style filename

func ParseNexusModsFilename added in v0.10.0

func ParseNexusModsFilename(filename string) *ParsedFilename

ParseNexusModsFilename attempts to extract mod ID and version from a NexusMods-style filename like "SkyUI-12604-5-2SE.zip". Returns nil if the filename doesn't match the expected pattern.

type ProfileManager

type ProfileManager struct {
	// contains filtered or unexported fields
}

ProfileManager handles profile CRUD operations and switching

func NewProfileManager

func NewProfileManager(configDir string, database *db.DB, cache *cache.Cache, lnk linker.Linker) *ProfileManager

NewProfileManager creates a new profile manager

func (*ProfileManager) AddMod

func (pm *ProfileManager) AddMod(gameID, profileName string, mod domain.ModReference) error

AddMod adds a mod reference to a profile

func (*ProfileManager) Create

func (pm *ProfileManager) Create(gameID, name string) (*domain.Profile, error)

Create creates a new profile for a game

func (*ProfileManager) Delete

func (pm *ProfileManager) Delete(gameID, name string) error

Delete removes a profile

func (*ProfileManager) Export

func (pm *ProfileManager) Export(gameID, profileName string) ([]byte, error)

Export exports a profile to a portable format

func (*ProfileManager) Get

func (pm *ProfileManager) Get(gameID, name string) (*domain.Profile, error)

Get retrieves a specific profile

func (*ProfileManager) GetDefault

func (pm *ProfileManager) GetDefault(gameID string) (*domain.Profile, error)

GetDefault returns the default profile for a game

func (*ProfileManager) Import

func (pm *ProfileManager) Import(data []byte) (*domain.Profile, error)

Import imports a profile from portable format

func (*ProfileManager) ImportWithOptions

func (pm *ProfileManager) ImportWithOptions(data []byte, force bool) (*domain.Profile, error)

ImportWithOptions imports a profile with optional force overwrite

func (*ProfileManager) List

func (pm *ProfileManager) List(gameID string) ([]*domain.Profile, error)

List returns all profiles for a game

func (*ProfileManager) ParseProfile

func (pm *ProfileManager) ParseProfile(data []byte) (*domain.Profile, error)

ParseProfile parses profile data without saving (for preview)

func (*ProfileManager) RemoveMod

func (pm *ProfileManager) RemoveMod(gameID, profileName, sourceID, modID string) error

RemoveMod removes a mod reference from a profile

func (*ProfileManager) ReorderMods

func (pm *ProfileManager) ReorderMods(gameID, profileName string, mods []domain.ModReference) error

ReorderMods updates the load order of mods in a profile

func (*ProfileManager) SetDefault

func (pm *ProfileManager) SetDefault(gameID, name string) error

SetDefault sets a profile as the default for a game

func (*ProfileManager) Switch

func (pm *ProfileManager) Switch(ctx context.Context, game *domain.Game, newProfileName string) error

Switch switches to a different profile, undeploying the current profile's mods and deploying the new profile's mods. It fails fast on any error and rolls back to the previous state (game dir and default profile) so the system is never left in a mixed old/new state.

func (*ProfileManager) UpsertMod added in v0.7.5

func (pm *ProfileManager) UpsertMod(gameID, profileName string, mod domain.ModReference) error

UpsertMod adds or updates a mod reference in a profile. If the mod exists, it updates Version and FileIDs while preserving position. If the mod doesn't exist, it appends to the end. This is the preferred method for install/update operations.

type ProgressFunc

type ProgressFunc func(DownloadProgress)

ProgressFunc is called periodically during download with progress updates

type ResolvedHooks added in v0.12.0

type ResolvedHooks struct {
	Install   domain.HookConfig
	Uninstall domain.HookConfig
}

ResolvedHooks contains the final merged hooks for an operation

func ResolveHooks added in v0.12.0

func ResolveHooks(game *domain.Game, profile *domain.Profile) *ResolvedHooks

ResolveHooks merges game-level hooks with profile-level overrides

func (*ResolvedHooks) GetInstallAfterAll added in v1.3.1

func (h *ResolvedHooks) GetInstallAfterAll() string

GetInstallAfterAll returns the install.after_all command, or "" if not set.

func (*ResolvedHooks) GetInstallAfterEach added in v1.3.1

func (h *ResolvedHooks) GetInstallAfterEach() string

GetInstallAfterEach returns the install.after_each command, or "" if not set.

func (*ResolvedHooks) GetInstallBeforeAll added in v1.3.1

func (h *ResolvedHooks) GetInstallBeforeAll() string

GetInstallBeforeAll returns the install.before_all command, or "" if not set. Nil-safe: returns "" when the receiver is nil.

func (*ResolvedHooks) GetInstallBeforeEach added in v1.3.1

func (h *ResolvedHooks) GetInstallBeforeEach() string

GetInstallBeforeEach returns the install.before_each command, or "" if not set.

func (*ResolvedHooks) GetUninstallAfterAll added in v1.3.1

func (h *ResolvedHooks) GetUninstallAfterAll() string

GetUninstallAfterAll returns the uninstall.after_all command, or "" if not set.

func (*ResolvedHooks) GetUninstallAfterEach added in v1.3.1

func (h *ResolvedHooks) GetUninstallAfterEach() string

GetUninstallAfterEach returns the uninstall.after_each command, or "" if not set.

func (*ResolvedHooks) GetUninstallBeforeAll added in v1.3.1

func (h *ResolvedHooks) GetUninstallBeforeAll() string

GetUninstallBeforeAll returns the uninstall.before_all command, or "" if not set.

func (*ResolvedHooks) GetUninstallBeforeEach added in v1.3.1

func (h *ResolvedHooks) GetUninstallBeforeEach() string

GetUninstallBeforeEach returns the uninstall.before_each command, or "" if not set.

type ScanOptions added in v1.1.0

type ScanOptions struct {
	ProfileName string
	DryRun      bool // If true, don't actually import, just report what would be done
}

ScanOptions configures the scan operation

type ScanResult added in v1.1.0

type ScanResult struct {
	FilePath       string      // Original path in mod_path
	FileName       string      // Base filename
	Mod            *domain.Mod // Detected/created mod info
	MatchedSource  string      // "curseforge", "nexusmods", or "local"
	AlreadyTracked bool        // True if already in lmm database
	Error          error       // Any error during processing
}

ScanResult contains the outcome of scanning a single mod file

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service is the main orchestrator for mod management operations

func NewService

func NewService(cfg ServiceConfig) (*Service, error)

NewService creates a new core service instance

func (*Service) AddGame

func (s *Service) AddGame(game *domain.Game) error

AddGame adds a new game configuration

func (*Service) ApplyModUpdate added in v1.3.3

func (s *Service) ApplyModUpdate(sourceID, modID, gameID, profileName, newVersion string, fileIDs []string) error

ApplyModUpdate updates version and file IDs atomically, preserving rollback state.

func (*Service) ApplyProfileSwitch added in v1.11.0

func (s *Service) ApplyProfileSwitch(ctx context.Context, game *domain.Game, plan *SwitchPlan, progress func(DeployProgress)) (*SwitchResult, error)

ApplyProfileSwitch executes a plan produced by PlanProfileSwitch: disables every ToDisable mod, then enables every ToEnable mod, then downloads and installs every ToInstall mod, and finally calls ProfileManager.SetDefault to make plan.To the active profile - in that order, matching doProfileSwitch exactly. progress may be nil.

doProfileSwitch runs no install/uninstall hooks at all (unlike DeployProfile/UninstallMod), so ApplyProfileSwitch doesn't either - there is deliberately no hook plumbing in its signature or DeployOptions-style options struct, since profile switch takes no CLI flags beyond the target profile name.

func (*Service) Cache

func (s *Service) Cache() *cache.Cache

Cache returns the default cache manager

func (*Service) Close

func (s *Service) Close() error

Close releases resources held by the service

func (*Service) ConfigDir

func (s *Service) ConfigDir() string

ConfigDir returns the configuration directory

func (*Service) DeleteInstalledMod added in v1.3.7

func (s *Service) DeleteInstalledMod(sourceID, modID, gameID, profileName string) error

DeleteInstalledMod removes the installed-mod record from the active profile.

func (*Service) DeleteSourceToken

func (s *Service) DeleteSourceToken(sourceID string) error

DeleteSourceToken removes an API token for a source

func (*Service) DeployProfile added in v1.11.0

func (s *Service) DeployProfile(ctx context.Context, game *domain.Game, profileName string, opts DeployOptions, progress func(DeployProgress)) (*DeployResult, error)

DeployProfile redeploys the mods of a profile in profile order: an optional --purge pass first (undeploying every installed mod), then for each mod to deploy - re-downloading from source if its cache entry is missing - an undeploy-then-install cycle recording the effective link method and deployed state, and finally applying any profile overrides. This is a behavior-preserving extraction of the pre-extraction CLI's doDeploy (cmd/lmm/deploy.go) and purgeDeployedMods (cmd/lmm/purge.go, the --purge-before-deploy call only - the standalone `lmm purge` command is untouched by this extraction); see the task report for the exact mapping.

progress may be nil. When non-nil, it is called synchronously from this function for every notable event - see DeployPhase's constants for what each one means and what Detail/Percent carry.

func (*Service) DisableMod added in v1.11.0

func (s *Service) DisableMod(ctx context.Context, game *domain.Game, profileName, sourceID, modID string) (bool, error)

DisableMod undeploys the mod's files from the game directory — the cache entry is kept so the mod can be re-enabled later without downloading again — and marks it disabled in the database. Returns (false, nil) — not an error — if the mod was already disabled.

Undeploy failures are treated as non-fatal: the game files may already have been removed manually, and refusing to record the user's intent to disable the mod would leave it stuck. This mirrors the pre-extraction CLI, which warned (under --verbose) but always continued to flip the DB state.

func (*Service) DownloadMod

func (s *Service) DownloadMod(ctx context.Context, sourceID string, game *domain.Game, mod *domain.Mod, file *domain.DownloadableFile, progressFn ProgressFunc) (result *DownloadModResult, err error)

DownloadMod downloads a mod file, extracts it, and stores it in the cache Returns the download result including files extracted and checksum. Multiple files from the same mod can be downloaded to the same cache location.

func (*Service) DownloadModToCache added in v1.3.3

func (s *Service) DownloadModToCache(ctx context.Context, gameCache *cache.Cache, sourceID string, game *domain.Game, mod *domain.Mod, file *domain.DownloadableFile, progressFn ProgressFunc) (result *DownloadModResult, err error)

DownloadModToCache downloads a mod file, extracts it, and stores it in the provided cache.

func (*Service) EnableMod added in v1.11.0

func (s *Service) EnableMod(ctx context.Context, game *domain.Game, profileName, sourceID, modID string) (bool, error)

EnableMod deploys an installed-but-disabled mod's files from the cache to the game directory and marks it enabled in the database. Returns (false, nil) — not an error — if the mod was already enabled.

func (*Service) GetDefaultLinkMethod

func (s *Service) GetDefaultLinkMethod() domain.LinkMethod

GetDefaultLinkMethod returns the default link method from config

func (*Service) GetDependencies added in v0.11.0

func (s *Service) GetDependencies(ctx context.Context, sourceID string, mod *domain.Mod) ([]domain.ModReference, error)

GetDependencies returns dependencies for a mod from the specified source

func (*Service) GetDeployedFilesForMod added in v1.3.7

func (s *Service) GetDeployedFilesForMod(gameID, profileName, sourceID, modID string) ([]string, error)

GetDeployedFilesForMod returns the relative paths the given mod has deployed in the named profile.

func (*Service) GetDownloadURL

func (s *Service) GetDownloadURL(ctx context.Context, sourceID string, mod *domain.Mod, fileID string) (string, error)

GetDownloadURL gets the download URL for a specific mod file

func (*Service) GetFileOwner added in v1.3.7

func (s *Service) GetFileOwner(gameID, profileName, relativePath string) (sourceID, modID string, found bool, err error)

GetFileOwner reports which mod currently owns a deployed file. The bool is false when no record exists; err is non-nil only on storage errors.

func (*Service) GetFilesWithChecksums added in v1.3.7

func (s *Service) GetFilesWithChecksums(gameID, profileName string) ([]DeployedFile, error)

GetFilesWithChecksums returns every tracked file in the profile with its recorded checksum (empty when none has been computed yet).

func (*Service) GetGame

func (s *Service) GetGame(gameID string) (*domain.Game, error)

GetGame retrieves a game by ID

func (*Service) GetGameCache

func (s *Service) GetGameCache(game *domain.Game) *cache.Cache

GetGameCache returns a cache manager for the specified game. Uses the game's cache_path if configured (game-scoped: paths omit gameID), otherwise the global cache.

func (*Service) GetGameCachePath

func (s *Service) GetGameCachePath(game *domain.Game) string

GetGameCachePath returns the effective cache path for a game. Uses the game's cache_path if configured, otherwise falls back to global cache.

func (*Service) GetGameLinkMethod

func (s *Service) GetGameLinkMethod(game *domain.Game) domain.LinkMethod

GetGameLinkMethod returns the effective link method for a game. Uses the game's explicit setting if configured, otherwise falls back to global default.

func (*Service) GetInstalledMod

func (s *Service) GetInstalledMod(sourceID, modID, gameID, profileName string) (*domain.InstalledMod, error)

GetInstalledMod retrieves a single installed mod

func (*Service) GetInstalledMods

func (s *Service) GetInstalledMods(gameID, profileName string) ([]domain.InstalledMod, error)

GetInstalledMods returns all installed mods for a game/profile (DB order: installed_at).

func (*Service) GetInstalledModsInProfileOrder added in v1.1.0

func (s *Service) GetInstalledModsInProfileOrder(gameID, profileName string) ([]domain.InstalledMod, error)

GetInstalledModsInProfileOrder returns installed mods in profile load order (first = lowest priority). Mods not present in the profile are omitted. Use this for deploy/switch so deployment order matches load order.

func (*Service) GetInstaller added in v0.7.8

func (s *Service) GetInstaller(game *domain.Game) *Installer

GetInstaller returns an Installer configured for the given game

func (*Service) GetLinker

func (s *Service) GetLinker(method domain.LinkMethod) linker.Linker

GetLinker returns a linker for the given method

func (*Service) GetMod

func (s *Service) GetMod(ctx context.Context, sourceID, gameID, modID string) (*domain.Mod, error)

GetMod retrieves a specific mod from a source

func (*Service) GetModFiles

func (s *Service) GetModFiles(ctx context.Context, sourceID string, mod *domain.Mod) ([]domain.DownloadableFile, error)

GetModFiles retrieves available download files for a mod

func (*Service) GetSource

func (s *Service) GetSource(id string) (source.ModSource, error)

GetSource retrieves a source by ID

func (*Service) GetSourceToken

func (s *Service) GetSourceToken(sourceID string) (*db.StoredToken, error)

GetSourceToken retrieves an API token for a source

func (*Service) IsSourceAuthenticated

func (s *Service) IsSourceAuthenticated(sourceID string) bool

IsSourceAuthenticated checks if a source has a stored API token

func (*Service) ListGames

func (s *Service) ListGames() []*domain.Game

ListGames returns all configured games

func (*Service) ListSourceTokens added in v1.8.0

func (s *Service) ListSourceTokens() ([]db.StoredToken, error)

ListSourceTokens returns every stored API token, including ones whose source is no longer registered (e.g. the custom-source definition file was removed) — used by `lmm auth status` to surface orphaned credentials.

func (*Service) ListSources

func (s *Service) ListSources() []source.ModSource

ListSources returns all registered sources

func (*Service) NewInstallerWithLinker added in v1.3.7

func (s *Service) NewInstallerWithLinker(game *domain.Game, lnk linker.Linker) *Installer

NewInstallerWithLinker returns an Installer for the given game using a caller-supplied linker — used when the CLI overrides the game's default link method (e.g. `lmm deploy --method`).

func (*Service) NewProfileManager added in v1.3.7

func (s *Service) NewProfileManager() *ProfileManager

NewProfileManager returns a ProfileManager wired to this service's storage, so callers do not need direct access to the database or registry.

func (*Service) NewUpdater added in v1.3.7

func (s *Service) NewUpdater() *Updater

NewUpdater returns an Updater wired to this service's source registry.

func (*Service) PlanProfileSwitch added in v1.11.0

func (s *Service) PlanProfileSwitch(ctx context.Context, game *domain.Game, target string) (*SwitchPlan, error)

PlanProfileSwitch computes the diff between game's currently-active default profile and target, without mutating anything (no DB writes, no filesystem changes, no deploys) - callers may call this speculatively (to render a confirmation modal) and discard the result without consequence. See SwitchPlan's doc comment; ctx is accepted for API consistency with the rest of Service's methods and future-proofing, even though today's algorithm performs no I/O that needs it.

func (*Service) RegisterSource

func (s *Service) RegisterSource(src source.ModSource)

RegisterSource adds a mod source to the registry

func (*Service) RollbackModVersion

func (s *Service) RollbackModVersion(sourceID, modID, gameID, profileName string) error

RollbackModVersion reverts a mod to its previous version

func (*Service) SaveFileChecksum added in v1.3.7

func (s *Service) SaveFileChecksum(sourceID, modID, gameID, profileName, fileID, checksum string) error

SaveFileChecksum records the verified checksum for a downloaded mod file.

func (*Service) SaveInstalledMod added in v1.3.7

func (s *Service) SaveInstalledMod(mod *domain.InstalledMod) error

SaveInstalledMod persists an installed-mod record (insert or update).

func (*Service) SaveSourceToken

func (s *Service) SaveSourceToken(sourceID, apiKey string) error

SaveSourceToken saves an API token for a source

func (*Service) SearchAllSources added in v1.10.0

func (s *Service) SearchAllSources(ctx context.Context, gameID, query, category string, tags []string, page, pageSize int) (AggregateSearchResult, error)

SearchAllSources searches every source configured for a game concurrently and merges the results (design §5). Per-source failures become Warnings — one flaky API must not hide local modlets; only all-sources-failed is an error. Sources without search capability are skipped silently. Pagination is per-source: page N requests page N from each source and merges.

func (*Service) SearchMods

func (s *Service) SearchMods(ctx context.Context, sourceID, gameID, query string, category string, tags []string, page, pageSize int) (source.SearchResult, error)

SearchMods searches for mods in a source

func (*Service) SetModDeployed added in v1.3.7

func (s *Service) SetModDeployed(sourceID, modID, gameID, profileName string, deployed bool) error

SetModDeployed records whether a mod's files are currently deployed.

func (*Service) SetModEnabled added in v1.3.7

func (s *Service) SetModEnabled(sourceID, modID, gameID, profileName string, enabled bool) error

SetModEnabled toggles the enabled flag for an installed mod.

func (*Service) SetModFileIDs added in v0.7.3

func (s *Service) SetModFileIDs(sourceID, modID, gameID, profileName string, fileIDs []string) error

SetModFileIDs updates the file IDs for an installed mod

func (*Service) SetModLinkMethod

func (s *Service) SetModLinkMethod(sourceID, modID, gameID, profileName string, linkMethod domain.LinkMethod) error

SetModLinkMethod sets the deployment method for an installed mod

func (*Service) SetModUpdatePolicy

func (s *Service) SetModUpdatePolicy(sourceID, modID, gameID, profileName string, policy domain.UpdatePolicy) error

SetModUpdatePolicy sets the update policy for an installed mod

func (*Service) UninstallMod added in v1.11.0

func (s *Service) UninstallMod(ctx context.Context, game *domain.Game, profileName, sourceID, modID string, opts UninstallOptions) (*UninstallResult, error)

UninstallMod removes a mod from the profile: runs uninstall hooks, undeploys files, deletes the cache entry (unless KeepCache), removes the DB row, and removes the mod from the profile YAML.

Hook failure semantics (matching the pre-extraction CLI's doUninstall):

  • uninstall.before_all / uninstall.before_each: a failure aborts the operation with an error, unless Force is set, in which case it is recorded in Warnings and the uninstall proceeds.
  • uninstall.after_each / uninstall.after_all: always non-fatal; a failure is recorded in Warnings after every other step has already committed.

Undeploy failures, cache-delete failures, and a failure to remove the mod from the profile (e.g. the DB and profile have drifted out of sync) are all non-fatal and always recorded in Notes; the operation still completes. See UninstallResult's doc comment for the Warnings/Notes display contract.

func (*Service) UpdateModVersion

func (s *Service) UpdateModVersion(sourceID, modID, gameID, profileName, newVersion string) error

UpdateModVersion updates the version of an installed mod, preserving the previous version for rollback

type ServiceConfig

type ServiceConfig struct {
	ConfigDir string // Directory for configuration files
	DataDir   string // Directory for database and persistent data
	CacheDir  string // Directory for mod file cache
}

ServiceConfig holds configuration for the core service

type SkippedMod added in v0.12.0

type SkippedMod struct {
	Mod    *domain.Mod
	Reason string
}

SkippedMod represents a mod that was skipped during batch operation

type SourceWarning added in v1.10.0

type SourceWarning struct {
	SourceID string
	Err      error
}

SourceWarning reports a per-source failure during an aggregate operation.

type SwitchPlan added in v1.11.0

type SwitchPlan struct {
	GameID, From, To string

	ToEnable  []domain.InstalledMod // installed+disabled (or installed under a different profile) -> enable, deployed under To
	ToDisable []domain.InstalledMod // enabled under From but absent from To -> disable, undeployed under From
	ToInstall []domain.ModReference // in To but not installed anywhere -> download+install (FileIDs preserved from the installed mod's own record when this is really a cache-miss redeploy - see PlanProfileSwitch)

	NoChanges     bool // To's mod set matches From's content-wise; only SetDefault is needed
	AlreadyActive bool // To is already the active default profile; nothing to plan
}

SwitchPlan is the pure, displayable diff between the currently-active default profile and a target profile - computed by PlanProfileSwitch with zero side effects, so a caller (the CLI, or eventually the TUI) can render it (in a print block or a confirmation modal) before deciding whether to call ApplyProfileSwitch. This is a behavior-preserving extraction of cmd/lmm/profile.go's doProfileSwitch's diff computation (through its "Show changes" print block) - see the task report for the exact mapping.

CRITICAL: this mirrors the CLI's OWN diff algorithm, which is distinct from (and does not call) ProfileManager.Switch - see the task report for why both exist.

type SwitchResult added in v1.11.0

type SwitchResult struct {
	Disabled, Enabled, Installed int
	Notes                        []string
}

SwitchResult reports the outcome of ApplyProfileSwitch. As with DeployResult/UninstallResult, every Notes entry is always recorded - there is no verbosity concept in core.

  • Notes holds every diagnostic doProfileSwitch only printed under --verbose: failed Uninstall/SetModEnabled during the disable loop, failed Install/SetModEnabled during the enable loop, and a failed UpsertMod during the install loop. Each entry already carries its historical "Warning: " prefix, matching doProfileSwitch's exact wording; a caller wanting byte-identical output should print each entry to stdout ONLY under --verbose, e.g. `fmt.Printf(" %s\n", n)` (disable/enable loop notes) or `fmt.Printf(" %s\n", n)` (the install loop's profile-update note, one indent level deeper).

Every Notes entry is ALSO reported via the progress callback at the exact point it is appended (SwitchDisableNote/SwitchEnableNote/SwitchInstallNote - see each DeployPhase constant's doc comment), with Detail equal to the slice entry verbatim.

On error, the returned result carries any diagnostics/counts accumulated before the failure; callers should surface them alongside the error.

type UninstallOptions added in v1.11.0

type UninstallOptions struct {
	KeepCache bool // --keep-cache: skip deleting the mod's cache entry

	// Hook plumbing, mirroring BatchOptions. Hooks and/or HookRunner may be
	// nil to skip hook execution entirely (e.g. --no-hooks).
	Hooks       *ResolvedHooks
	HookRunner  *HookRunner
	HookContext HookContext
	Force       bool // continue past a failing uninstall.before_* hook (warn instead of fail)

}

UninstallOptions configures UninstallMod.

type UninstallResult added in v1.11.0

type UninstallResult struct {
	Warnings []string // unconditional, stderr, audience: operator/always-visible
	Notes    []string // --verbose-gated, stdout, audience: diagnostic detail
}

UninstallResult reports the outcome of UninstallMod. Every entry in both slices below is always recorded — UninstallMod has no verbosity concept — but the two slices carry different display contracts for callers to honor (this is the convention Tasks 3-4 should follow too):

  • Warnings holds diagnostics the pre-extraction CLI printed unconditionally to stderr regardless of --verbose (hook failures: uninstall.before_* when Force is set, and uninstall.after_*, which is always non-fatal). Callers should print each entry to stderr, unconditionally, e.g. `fmt.Fprintf(os.Stderr, "Warning: %v\n", w)`.
  • Notes holds operational diagnostics the pre-extraction CLI only printed under --verbose (undeploy failure, cache-delete failure, and a failure to remove the mod from the profile). Each entry already carries its historical prefix word baked into the text ("Warning: " for undeploy/cache-delete, "Note: " for the profile-removal message, matching the pre-extraction CLI's exact wording for each), so a caller that wants byte-identical pre-extraction output should print each entry to stdout ONLY under --verbose, verbatim, e.g. `fmt.Printf(" %s\n", n)`.

On error, the returned result carries any diagnostics accumulated before the failure; callers should surface them alongside the error.

type UninstalledModResult added in v0.12.0

type UninstalledModResult struct {
	domain.Mod
}

UninstalledModResult is a successfully uninstalled mod (wraps domain.Mod for batch result)

type Updater

type Updater struct {
	// contains filtered or unexported fields
}

Updater checks for and applies mod updates

func NewUpdater

func NewUpdater(registry *source.Registry) *Updater

NewUpdater creates a new updater

func (*Updater) CheckUpdates

func (u *Updater) CheckUpdates(ctx context.Context, game *domain.Game, installed []domain.InstalledMod) ([]domain.Update, error)

CheckUpdates checks for available updates for installed mods. game supplies the source-ID mapping: installed rows persist the lmm game ID, but sources like NexusMods address games by their own domain, so each source's batch is translated via game.SourceIDs before the call (empty mapping = keep the lmm id, matching the search-side semantics in Service.SearchMods/GetMod).

func (*Updater) GetAutoUpdateMods

func (u *Updater) GetAutoUpdateMods(installed []domain.InstalledMod) []domain.InstalledMod

GetAutoUpdateMods filters installed mods to those with auto-update enabled

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL