panel v0.9.2 — public release
Self-hostable game server control panel: Go controller + agent, 26 game modules, embedded web UI. One-line install via install.sh / install.ps1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
package regionmedic
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// ChunkCoord is a global chunk coordinate.
|
||||
type ChunkCoord struct {
|
||||
X int `json:"x"`
|
||||
Z int `json:"z"`
|
||||
}
|
||||
|
||||
// AffectedRegion is one region with corruption evidence (error_backup salvage
|
||||
// files whose chunks fall inside it), plus whether its live region file exists.
|
||||
type AffectedRegion struct {
|
||||
Region RegionCoord `json:"region"`
|
||||
ErrorBackups int `json:"error_backups"`
|
||||
Chunks []ChunkCoord `json:"chunks,omitempty"`
|
||||
RegionFile string `json:"region_file,omitempty"` // absolute path if present
|
||||
FilePresent bool `json:"file_present"`
|
||||
}
|
||||
|
||||
// RegionDirScan is the result of scanning a 7DTD save Region/ directory.
|
||||
type RegionDirScan struct {
|
||||
Dir string `json:"dir"`
|
||||
ErrorBackups int `json:"error_backups"` // total salvage files
|
||||
ErrorBackupDirs int `json:"error_backup_chunks"` // distinct chunks
|
||||
Affected []AffectedRegion `json:"affected"` // sorted by count desc
|
||||
}
|
||||
|
||||
// ScanRegionDir lists error_backup_* salvage files in dir, maps each to its
|
||||
// region, and clusters them. The returned Affected slice is sorted by
|
||||
// ErrorBackups descending (most-damaged region first). A region appears only if
|
||||
// it has at least one error_backup chunk.
|
||||
func ScanRegionDir(dir string) (RegionDirScan, error) {
|
||||
out := RegionDirScan{Dir: dir}
|
||||
ents, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
// region -> set of distinct chunk coords (dedup .comp/.uncomp pairs)
|
||||
type acc struct {
|
||||
files int
|
||||
chunks map[ChunkCoord]struct{}
|
||||
}
|
||||
clusters := map[RegionCoord]*acc{}
|
||||
|
||||
for _, e := range ents {
|
||||
if e.IsDir() {
|
||||
continue
|
||||
}
|
||||
cx, cz, ok := ParseErrorBackup(e.Name())
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
out.ErrorBackups++
|
||||
rc := RegionForChunk(cx, cz)
|
||||
a := clusters[rc]
|
||||
if a == nil {
|
||||
a = &acc{chunks: map[ChunkCoord]struct{}{}}
|
||||
clusters[rc] = a
|
||||
}
|
||||
a.files++
|
||||
a.chunks[ChunkCoord{X: cx, Z: cz}] = struct{}{}
|
||||
}
|
||||
|
||||
for rc, a := range clusters {
|
||||
ar := AffectedRegion{Region: rc, ErrorBackups: a.files}
|
||||
for c := range a.chunks {
|
||||
ar.Chunks = append(ar.Chunks, c)
|
||||
}
|
||||
sort.Slice(ar.Chunks, func(i, j int) bool {
|
||||
if ar.Chunks[i].X != ar.Chunks[j].X {
|
||||
return ar.Chunks[i].X < ar.Chunks[j].X
|
||||
}
|
||||
return ar.Chunks[i].Z < ar.Chunks[j].Z
|
||||
})
|
||||
out.ErrorBackupDirs += len(ar.Chunks)
|
||||
rf := filepath.Join(dir, rc.FileName())
|
||||
if st, err := os.Stat(rf); err == nil && !st.IsDir() {
|
||||
ar.RegionFile = rf
|
||||
ar.FilePresent = true
|
||||
}
|
||||
out.Affected = append(out.Affected, ar)
|
||||
}
|
||||
|
||||
sort.Slice(out.Affected, func(i, j int) bool {
|
||||
if out.Affected[i].ErrorBackups != out.Affected[j].ErrorBackups {
|
||||
return out.Affected[i].ErrorBackups > out.Affected[j].ErrorBackups
|
||||
}
|
||||
// stable tiebreak by region id
|
||||
if out.Affected[i].Region.X != out.Affected[j].Region.X {
|
||||
return out.Affected[i].Region.X < out.Affected[j].Region.X
|
||||
}
|
||||
return out.Affected[i].Region.Z < out.Affected[j].Region.Z
|
||||
})
|
||||
return out, nil
|
||||
}
|
||||
Reference in New Issue
Block a user