Files
browser/platform/repo/metadata_retention.go

130 lines
4.5 KiB
Go

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.PluginDataRecords = retainedSnapshotPluginData(snapshot.PluginDataRecords, replacements)
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 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 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 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]
}