Prune stale plugin metadata

This commit is contained in:
npc0-hue
2026-09-14 17:32:51 +08:00
parent ecdca8e28b
commit 63e2340db7
10 changed files with 659 additions and 13 deletions
+178
View File
@@ -0,0 +1,178 @@
package repo
import (
"crypto/sha256"
"encoding/hex"
"sort"
"strings"
"browser.local/platform/domain"
)
func normalizeStoreSnapshot(snapshot StoreSnapshot) StoreSnapshot {
plugins, replacements := latestGamePlugins(snapshot.GamePlugins)
snapshot.GamePlugins = plugins
snapshot.ServerInstances = rewriteSnapshotServerInstances(snapshot.ServerInstances, replacements)
snapshot.RuntimeBindings = rewriteSnapshotRuntimeBindings(snapshot.RuntimeBindings, replacements)
snapshot.PluginLifecycles = rewriteSnapshotPluginLifecycles(snapshot.PluginLifecycles, replacements)
snapshot.AIConfigDiffs = rewriteSnapshotAIConfigDiffs(snapshot.AIConfigDiffs, replacements)
snapshot.PluginDataRecords = retainedSnapshotPluginData(snapshot.PluginDataRecords, replacements)
snapshot.Jobs = retainedSnapshotJobs(snapshot.Jobs, snapshot.ServerInstances, plugins)
return snapshot
}
func latestGamePlugins(values []domain.GamePlugin) ([]domain.GamePlugin, map[string]domain.GamePlugin) {
byIdentity := map[string]domain.GamePlugin{}
for _, plugin := range values {
key := domain.GamePluginIdentityKey(plugin)
if current, exists := byIdentity[key]; !exists || domain.GamePluginIsNewer(plugin, current) {
byIdentity[key] = plugin
}
}
replacements := map[string]domain.GamePlugin{}
out := make([]domain.GamePlugin, 0, len(byIdentity))
for _, plugin := range byIdentity {
out = append(out, domain.CopyGamePlugin(plugin))
}
sort.SliceStable(out, func(left, right int) bool { return out[left].ID < out[right].ID })
for _, plugin := range values {
kept, ok := byIdentity[domain.GamePluginIdentityKey(plugin)]
if ok && plugin.ID != kept.ID {
replacements[plugin.ID] = kept
}
}
return out, replacements
}
func rewriteSnapshotServerInstances(values []domain.ServerInstance, replacements map[string]domain.GamePlugin) []domain.ServerInstance {
out := make([]domain.ServerInstance, len(values))
for index, value := range values {
if kept, ok := replacements[value.PluginID]; ok {
value.PluginID = kept.ID
value.PluginVersion = kept.Version
}
out[index] = domain.CopyServerInstance(value)
}
return out
}
func rewriteSnapshotRuntimeBindings(values []domain.RuntimeBinding, replacements map[string]domain.GamePlugin) []domain.RuntimeBinding {
out := make([]domain.RuntimeBinding, len(values))
for index, value := range values {
if kept, ok := replacements[value.PluginID]; ok {
value.PluginID = kept.ID
value.PluginVersion = kept.Version
}
out[index] = domain.CopyRuntimeBinding(value)
}
return out
}
func rewriteSnapshotPluginLifecycles(values []domain.PluginLifecycleInstallation, replacements map[string]domain.GamePlugin) []domain.PluginLifecycleInstallation {
out := make([]domain.PluginLifecycleInstallation, len(values))
for index, value := range values {
if kept, ok := replacements[value.PluginID]; ok {
value.PluginID = kept.ID
if value.TargetVersion != "" {
value.TargetVersion = kept.Version
}
}
out[index] = domain.CopyPluginLifecycleInstallation(value)
}
return out
}
func rewriteSnapshotAIConfigDiffs(values []domain.AIConfigDiffPreview, replacements map[string]domain.GamePlugin) []domain.AIConfigDiffPreview {
out := make([]domain.AIConfigDiffPreview, len(values))
for index, value := range values {
if kept, ok := replacements[value.PluginID]; ok {
value.PluginID = kept.ID
}
out[index] = domain.CopyAIConfigDiffPreview(value)
}
return out
}
func retainedSnapshotPluginData(values []domain.PluginDataRecord, replacements map[string]domain.GamePlugin) []domain.PluginDataRecord {
byID := map[string]domain.PluginDataRecord{}
for _, value := range values {
if isLegacySCUMPluginData(value.PluginID, value.Collection) {
continue
}
if kept, ok := replacements[value.PluginID]; ok {
value.PluginID = kept.ID
value.ID = snapshotPluginDataID(value.ServerInstanceID, kept.ID, value.Collection, value.Key)
}
if current, exists := byID[value.ID]; !exists || value.UpdatedAt.After(current.UpdatedAt) || (value.UpdatedAt.Equal(current.UpdatedAt) && value.CreatedAt.After(current.CreatedAt)) {
byID[value.ID] = domain.CopyPluginDataRecord(value)
}
}
ids := make([]string, 0, len(byID))
for id := range byID {
ids = append(ids, id)
}
sort.Strings(ids)
out := make([]domain.PluginDataRecord, 0, len(ids))
for _, id := range ids {
out = append(out, domain.CopyPluginDataRecord(byID[id]))
}
return out
}
func retainedSnapshotJobs(values []domain.Job, instances []domain.ServerInstance, plugins []domain.GamePlugin) []domain.Job {
pluginByID := map[string]domain.GamePlugin{}
for _, plugin := range plugins {
pluginByID[plugin.ID] = plugin
}
serverPlugin := map[string]domain.GamePlugin{}
for _, instance := range instances {
if plugin, ok := pluginByID[instance.PluginID]; ok {
serverPlugin[instance.ID] = plugin
}
}
out := make([]domain.Job, 0, len(values))
for _, job := range values {
if isLegacySCUMSQLiteQueryJob(job, serverPlugin) {
continue
}
out = append(out, domain.CopyJob(job))
}
return out
}
func isLegacySCUMSQLiteQueryJob(job domain.Job, serverPlugin map[string]domain.GamePlugin) bool {
if job.Capability != domain.JobCapabilityRemoteRunDBSQLiteQuery {
return false
}
plugin, ok := serverPlugin[job.ServerInstanceID]
if !ok {
return false
}
return isSCUMGamePlugin(plugin)
}
func isLegacySCUMPluginData(pluginID string, collection string) bool {
if !isSCUMPluginID(pluginID) {
return false
}
switch strings.ToLower(strings.TrimSpace(collection)) {
case "scum_users", "scum_players", "scum_trajectories", "scum_user_trajectories", "scum_vehicles", "scum_vehicle_trajectories", "scum_vehicle_locks":
return true
default:
return false
}
}
func isSCUMGamePlugin(plugin domain.GamePlugin) bool {
return strings.EqualFold(strings.TrimSpace(plugin.ServerType), "scum") || isSCUMPluginID(plugin.ID)
}
func isSCUMPluginID(pluginID string) bool {
canonical := domain.CanonicalGamePluginID(pluginID)
return canonical == "game.scum" || canonical == "server.scum"
}
func snapshotPluginDataID(serverID, pluginID, collection, key string) string {
sum := sha256.Sum256([]byte(serverID + "\x00" + pluginID + "\x00" + collection + "\x00" + key))
return "plugin-data-" + hex.EncodeToString(sum[:])[:24]
}