refactor foundry lib structure; add transport classes for db,http,websocket; add action interface instead of multiple function that accepts action classes; add changing foundry status on websocket responses

This commit is contained in:
lbenedar
2026-04-09 18:29:41 +03:00
parent 468e027418
commit fb4e371348
28 changed files with 900 additions and 791 deletions

View File

@@ -0,0 +1,9 @@
package actions
import "errors"
var (
ErrActionNotFound = errors.New("Action doesn't found")
ErrObjTypeNotMatch = errors.New("Provided value type didn't match obj field type")
ErrObjNotPointer = errors.New("Passed obj is not pointer")
)

View File

@@ -0,0 +1,69 @@
package actions
import (
"encoding/json"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/transport"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/types"
)
const (
WsSessionType = "session"
WsProgressType = "progress"
WsUserActivityType = "userActivity"
WsShutdownType = "shutdown"
)
type WsActions interface {
Action(tr *transport.FoundryTransport, status *types.FoundryStatus) error
}
var WsMsgActions = map[string]func() WsActions{
"session": func() WsActions { return &WsSessionMsg{} },
"progress": func() WsActions { return &WsProgressMsg{} },
// "userActivity": WsUserActivityCursor{},
"shutdown": func() WsActions { return &WsShutdownMsg{} },
}
func GetWsMsgAction(dataType string) WsActions {
if factory, ok := WsMsgActions[dataType]; ok {
return factory()
}
return nil
}
func SplitToTypeAndData(dataJson []byte) (WsActions, error) {
var rawItems []json.RawMessage
err := json.Unmarshal(dataJson, &rawItems)
if err != nil {
return nil, err
}
var dataType string
err = json.Unmarshal(rawItems[0], &dataType)
if err != nil {
return nil, err
}
if dataType == WsUserActivityType {
return nil, nil
}
var data map[string]any
err = json.Unmarshal(rawItems[1], &data)
if err != nil {
return nil, err
}
actionDataPtr := GetWsMsgAction(dataType)
if actionDataPtr == nil {
return nil, ErrActionNotFound
}
err = FillStruct(data, actionDataPtr)
if err != nil {
return nil, err
}
return actionDataPtr, nil
}

View File

@@ -0,0 +1,37 @@
package actions
import (
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/transport"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/types"
)
type WsProgressMsg struct {
Id string `json:"id"`
Message string `json:"message"`
Pct float64 `json:"pct"`
HasChanged bool `json:"hasChanged,omitempty"`
Act string `json:"action"`
Step string `json:"step"`
}
func (msg WsProgressMsg) Action(tr *transport.FoundryTransport, status *types.FoundryStatus) error {
if !msg.HasChanged {
tr.Logger.Debug("World has been launched", "world", msg.Id)
select {
case <-tr.ExchangeChan.ProgressMsg[msg.Id]:
tr.Logger.Warn("Channel for the world is closed", "world", msg.Id)
default:
close(tr.ExchangeChan.ProgressMsg[msg.Id])
}
if status.IsActive != true {
newStatus, err := tr.Http.GetStatus()
if err != nil {
return err
}
status.Update(newStatus)
tr.Logger.Info("Status has been changed", "status", status)
}
}
return nil
}

View File

@@ -0,0 +1,26 @@
package actions
import (
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/transport"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/types"
)
type WsSessionMsg struct {
SessionId string `json:"sessionId"`
UserId *string `json:"userId,omitempty"`
}
func (msg WsSessionMsg) Action(tr *transport.FoundryTransport, status *types.FoundryStatus) error {
if status.IsActive {
tr.Logger.Warn("World is started. Please, return to setup page to fully initialize database")
return nil
}
if tr.IsDbInit {
return nil
}
tr.IsDbInit = true
go tr.FillDBWithFoundryData()
return nil
}

View File

@@ -0,0 +1,30 @@
package actions
import (
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/transport"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/types"
)
type WsShutdownMsg struct {
World string `json:"world"`
UserId *string `json:"userId,omitempty"`
}
func (msg WsShutdownMsg) Action(tr *transport.FoundryTransport, status *types.FoundryStatus) error {
if status.IsActive != false {
newStatus, err := tr.Http.GetStatus()
if err != nil {
return err
}
status.Update(newStatus)
tr.Logger.Info("Status has been changed", "status", status)
}
if tr.IsDbInit {
return nil
}
tr.IsDbInit = true
go tr.FillDBWithFoundryData()
return nil
}

View File

@@ -0,0 +1,20 @@
package actions
import (
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/transport"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/types"
)
type WsUserActivityMsg struct {
Cursor WsUserActivityCursor `json:"cursor"`
}
type WsUserActivityCursor struct {
X int `json:"x"`
Y int `json:"y"`
}
func (msg WsUserActivityMsg) Action(tr *transport.FoundryTransport, status *types.FoundryStatus) error {
tr.Logger.Debug("WsUserActivityMsg")
return nil
}

View File

@@ -0,0 +1,87 @@
package actions
import (
"fmt"
"reflect"
"strings"
)
func findByTag(obj any, tag string) (int, error) {
structType := reflect.TypeOf(obj).Elem()
for i := 0; i < structType.NumField(); i++ {
fieldTags := structType.Field(i).Tag.Get("json")
if tag == strings.Split(fieldTags, ",")[0] {
return i, nil
}
}
return -1, fmt.Errorf("No such field: %s in obj", tag)
}
func setField(obj any, name string, value any) error {
structValue := reflect.ValueOf(obj).Elem()
idx, err := findByTag(obj, name)
if err != nil {
return err
}
structFieldValue := structValue.Field(idx)
if !structFieldValue.IsValid() {
return fmt.Errorf("No such field: %s in obj", name)
}
if !structFieldValue.CanSet() {
return fmt.Errorf("Cannot set %s field value", name)
}
structFieldType := structFieldValue.Type()
val := reflect.ValueOf(value)
if structFieldType != val.Type() {
return ErrObjTypeNotMatch
}
if value == nil {
structFieldValue.Set(reflect.Zero(structFieldType))
} else {
structFieldValue.Set(val)
}
return nil
}
func isPointer(obj any) bool {
iv := reflect.ValueOf(obj)
if !iv.IsValid() {
return false
}
return iv.Kind() == reflect.Pointer
}
func FillStruct(m map[string]any, obj any) error {
if !isPointer(obj) {
return ErrObjNotPointer
}
for k, v := range m {
if v == nil {
continue
}
err := setField(obj, k, v)
if err != nil {
return err
}
}
return nil
}
func Dereference(input any) any {
v := reflect.ValueOf(input)
// Check if the value is a pointer
if v.Kind() == reflect.Pointer {
// v.Elem() returns the value the pointer points to
return v.Elem().Interface()
}
return input
}