wfc-server/database/hash.go

204 lines
4.7 KiB
Go

package database
import (
"context"
"errors"
"regexp"
"wwfc/logging"
"github.com/jackc/pgx/v4/pgxpool"
"github.com/logrusorgru/aurora/v3"
"github.com/sasha-s/go-deadlock"
)
const (
QueryHashes = `SELECT * FROM hashes`
InsertHash = `INSERT
INTO hashes (pack_id, version, hash_ntscu, hash_ntscj, hash_ntsck, hash_pal)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (pack_id, version)
DO UPDATE SET hash_ntscu = $3, hash_ntscj = $4, hash_ntsck = $5, hash_pal = $6`
DeleteHash = `DELETE FROM hashes WHERE pack_id = $1 AND version = $2`
)
type Region byte
const (
R_NTSCU Region = iota
R_NTSCJ
R_NTSCK
R_PAL
)
func IndexByRegionByte(hbr HashesByRegion, b Region) string {
switch b {
case R_NTSCU:
return hbr.NTSCU
case R_NTSCJ:
return hbr.NTSCJ
case R_NTSCK:
return hbr.NTSCK
case R_PAL:
return hbr.PAL
default:
return ""
}
}
type HashesByRegion struct {
NTSCU string
NTSCJ string
NTSCK string
PAL string
}
type HashStore map[uint32]map[uint32]HashesByRegion
var (
mutex = deadlock.Mutex{}
hashes = HashStore{}
emptyRegexp *regexp.Regexp
)
// Used to flatten the 40-wide empty strings returned by the db, and to filter
// whitespace potentially submitted over the api
func flattenBlank(str string) string {
if emptyRegexp.MatchString(str) {
return ""
}
return str
}
func HashInit(pool *pgxpool.Pool, ctx context.Context) error {
mutex.Lock()
defer mutex.Unlock()
// Populate regexp once
emptyRegexp = regexp.MustCompile("^\\s+$")
logging.Info("DATABASE", "Populating hashes from the database")
rows, err := pool.Query(ctx, QueryHashes)
defer rows.Close()
if err != nil {
return err
}
for rows.Next() {
var packID uint32
var version uint32
var hashNTSCU string
var hashNTSCJ string
var hashNTSCK string
var hashPAL string
err = rows.Scan(&packID, &version, &hashNTSCU, &hashNTSCJ, &hashNTSCK, &hashPAL)
if err != nil {
return err
}
versions, exists := hashes[packID]
if !exists {
temp := map[uint32]HashesByRegion{}
hashes[packID] = temp
versions = temp
}
versions[version] = HashesByRegion{
NTSCU: flattenBlank(hashNTSCU),
NTSCJ: flattenBlank(hashNTSCJ),
NTSCK: flattenBlank(hashNTSCK),
PAL: flattenBlank(hashPAL),
}
logging.Info("DATABASE", "Populated hashes for PackID:", aurora.Cyan(packID), "Version:", aurora.Cyan(version), "\nNTSCU:", aurora.Cyan(hashNTSCU), "\nNTSCJ:", aurora.Cyan(hashNTSCJ), "\nNTSCK:", aurora.Cyan(hashNTSCK), "\nPAL:", aurora.Cyan(hashPAL))
}
return nil
}
func GetHashes() HashStore {
mutex.Lock()
defer mutex.Unlock()
// Create a copy while the mutex is locked. Defer runs before return
ret := hashes
return ret
}
var (
ErrPackIDMissing = errors.New("The specified PackID does not exist")
ErrVersionMissing = errors.New("The specified version does not exist")
)
func RemoveHash(pool *pgxpool.Pool, ctx context.Context, packID uint32, version uint32) error {
mutex.Lock()
defer mutex.Unlock()
if versions, exists := hashes[packID]; exists {
if _, exists := versions[version]; exists {
delete(versions, version)
} else {
return ErrVersionMissing
}
} else {
return ErrPackIDMissing
}
_, err := pool.Exec(ctx, DeleteHash, packID, version)
if err != nil {
logging.Error("DATABASE", "Failure to remove hash for PackID:", aurora.Cyan(packID), "Version:", aurora.Cyan(version), "Error:", err.Error())
} else {
logging.Warn("DATABASE", "Removed hashes for PackID:", aurora.Cyan(packID), "Version:", aurora.Cyan(version))
}
return err
}
func UpdateHash(pool *pgxpool.Pool, ctx context.Context, packID uint32, version uint32, hashNTSCU string, hashNTSCJ string, hashNTSCK string, hashPAL string) error {
mutex.Lock()
defer mutex.Unlock()
versions, exists := hashes[packID]
if !exists {
temp := map[uint32]HashesByRegion{}
hashes[packID] = temp
versions = temp
}
versions[version] = HashesByRegion{
NTSCU: flattenBlank(hashNTSCU),
NTSCJ: flattenBlank(hashNTSCJ),
NTSCK: flattenBlank(hashNTSCK),
PAL: flattenBlank(hashPAL),
}
_, err := pool.Exec(ctx, InsertHash, packID, version, hashNTSCU, hashNTSCJ, hashNTSCK, hashPAL)
if err != nil {
logging.Error("DATABASE", "Failed to update hashes for PackID:", aurora.Cyan(packID), "Version:", aurora.Cyan(version), "error:", err.Error())
} else {
logging.Info("DATABASE", "Successfully updated hashes for PackID:", aurora.Cyan(packID), "Version:", aurora.Cyan(version))
}
return err
}
func ValidateHash(packID uint32, version uint32, region Region, hash string) bool {
mutex.Lock()
defer mutex.Unlock()
if versions, exists := hashes[packID]; exists {
if regions, exists := versions[version]; exists {
hash_real := IndexByRegionByte(regions, region)
return hash_real != "" && hash_real == hash
}
}
return false
}