Prune stale plugin metadata
This commit is contained in:
@@ -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]
|
||||
}
|
||||
Reference in New Issue
Block a user