fix some bugs

- use writeConnect all the time unless filling old entries
- update column names to clarify UTC datetime
- GORM omit Mission association for server events that won't have one
- add disconnectDB function to reinit while active
This commit is contained in:
2023-07-09 13:21:36 -07:00
parent b28932649f
commit 67624bc074
7 changed files with 88 additions and 54 deletions

2
.gitignore vendored
View File

@@ -6,3 +6,5 @@ config.json
**/AttendanceTracker_x64.h **/AttendanceTracker_x64.h
**/AttendanceTracker.h **/AttendanceTracker.h
/extension/@AttendanceTracker/ /extension/@AttendanceTracker/
\@AttendanceTracker.7z

View File

@@ -45,7 +45,7 @@
private _playerUID = _args select 2; private _playerUID = _args select 2;
if (allUsers find _playerID == -1) exitWith { if (allUsers find _playerID == -1) exitWith {
[format ["(EventHandler) OnUserConnected: %1 (UID %2) is no longer connected, exiting CBA PFH", _playerUID], "DEBUG"] call attendanceTracker_fnc_log; [format ["(EventHandler) OnUserConnected: %1 (UID %2) is no longer connected, exiting CBA PFH", _playerUID], "DEBUG"] call attendanceTracker_fnc_log;
_args call attendanceTracker_fnc_writeDisconnect; _args call attendanceTracker_fnc_writeConnect;
[_handle] call CBA_fnc_removePerFrameHandler; [_handle] call CBA_fnc_removePerFrameHandler;
}; };
@@ -88,7 +88,7 @@
_playerUID, _playerUID,
_profileName, _profileName,
_steamName _steamName
] call attendanceTracker_fnc_writeDisconnect; ] call attendanceTracker_fnc_writeConnect;
}], }],
["PlayerConnected", { ["PlayerConnected", {
params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"]; params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"];
@@ -144,7 +144,7 @@
private _clientStateNumber = _userInfo select 6; private _clientStateNumber = _userInfo select 6;
if (_clientStateNumber < 6) exitWith { if (_clientStateNumber < 6) exitWith {
[format ["(EventHandler) PlayerConnected: %1 (UID) is no longer connected to the mission, exiting CBA PFH", _playerID], "DEBUG"] call attendanceTracker_fnc_log; [format ["(EventHandler) PlayerConnected: %1 (UID) is no longer connected to the mission, exiting CBA PFH", _playerID], "DEBUG"] call attendanceTracker_fnc_log;
_args call attendanceTracker_fnc_writeDisconnect; _args call attendanceTracker_fnc_writeConnect;
[_handle] call CBA_fnc_removePerFrameHandler; [_handle] call CBA_fnc_removePerFrameHandler;
}; };
@@ -190,7 +190,7 @@
_steamName, _steamName,
_jip, _jip,
nil nil
] call attendanceTracker_fnc_writeDisconnect; ] call attendanceTracker_fnc_writeConnect;
false; false;
}], }],
@@ -222,7 +222,7 @@
_steamName, _steamName,
nil, nil,
nil nil
] call attendanceTracker_fnc_writeDisconnect; ] call attendanceTracker_fnc_writeConnect;
[ [
"Mission", "Mission",
@@ -232,6 +232,6 @@
_steamName, _steamName,
nil, nil,
nil nil
] call attendanceTracker_fnc_writeDisconnect; ] call attendanceTracker_fnc_writeConnect;
}] }]
]; ];

View File

@@ -1,3 +1,6 @@
$ENV:GOARCH = "amd64" $ENV:GOARCH = "amd64"
$ENV:CGO_ENABLED = 1 $ENV:CGO_ENABLED = 1
go1.16.4 build -o ../@AttendanceTracker/AttendanceTracker_x64.dll -buildmode=c-shared . go1.16.4 build -o ../@AttendanceTracker/AttendanceTracker_x64.dll -buildmode=c-shared .
go1.16.4 build -o buildDb.exe .

BIN
extension/buildDb.exe Normal file

Binary file not shown.

View File

@@ -4,6 +4,6 @@ go 1.16
require ( require (
github.com/go-sql-driver/mysql v1.7.0 github.com/go-sql-driver/mysql v1.7.0
gorm.io/driver/mysql v1.5.1 // indirect gorm.io/driver/mysql v1.5.1
gorm.io/gorm v1.25.2 // indirect gorm.io/gorm v1.25.2
) )

View File

@@ -1,5 +1,3 @@
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=

View File

@@ -123,6 +123,8 @@ func loadConfig() {
} }
defer file.Close() defer file.Close()
// log.Println("Loading config from", CONFIG_FILE)
decoder := json.NewDecoder(file) decoder := json.NewDecoder(file)
err = decoder.Decode(&Config) err = decoder.Decode(&Config)
if err != nil { if err != nil {
@@ -131,6 +133,7 @@ func loadConfig() {
} }
A3Config = Config.ArmaConfig A3Config = Config.ArmaConfig
ATConfig = Config.SQLConfig ATConfig = Config.SQLConfig
writeLog(functionName, `["Config loaded", "INFO"]`) writeLog(functionName, `["Config loaded", "INFO"]`)
} }
@@ -222,23 +225,23 @@ func closeServerEvents() {
writeLog(functionName, `["Filling missing disconnect events due to server restart.", "DEBUG"]`) writeLog(functionName, `["Filling missing disconnect events due to server restart.", "DEBUG"]`)
// get all events with null DisconnectTime & set DisconnectTime to current time // get all events with null DisconnectTime & set DisconnectTime to current time
var events []AttendanceItem var events []AttendanceItem
db.Where("disconnect_time = '0000-00-00 00:00:00'").Find(&events) db.Where("disconnect_time_utc = '0000-00-00 00:00:00'").Find(&events)
for _, event := range events { for _, event := range events {
// if difference between JoinTime and current time is greater than threshold, set to threshold // if difference between JoinTime and current time is greater than threshold, set to threshold
if event.EventType == "Server" { if event.EventType == "Server" {
var timeThreshold time.Time = event.JoinTime.Add(-time.Duration(A3Config.ServerEventFillNullMinutes) * time.Minute) var timeThreshold time.Time = event.JoinTimeUTC.Add(-time.Duration(A3Config.ServerEventFillNullMinutes) * time.Minute)
if event.JoinTime.Before(timeThreshold) { if event.JoinTimeUTC.Before(timeThreshold) {
event.DisconnectTime = timeThreshold event.DisconnectTimeUTC = timeThreshold
} else { } else {
event.DisconnectTime = time.Now() event.DisconnectTimeUTC = time.Now()
} }
} else if event.EventType == "Mission" { } else if event.EventType == "Mission" {
var timeThreshold time.Time = event.JoinTime.Add(-time.Duration(A3Config.MissionEventFillNullMinutes) * time.Minute) var timeThreshold time.Time = event.JoinTimeUTC.Add(-time.Duration(A3Config.MissionEventFillNullMinutes) * time.Minute)
if event.JoinTime.Before(timeThreshold) { if event.JoinTimeUTC.Before(timeThreshold) {
event.DisconnectTime = timeThreshold event.DisconnectTimeUTC = timeThreshold
} else { } else {
event.DisconnectTime = time.Now() event.DisconnectTimeUTC = time.Now()
} }
} }
db.Save(&event) db.Save(&event)
@@ -270,6 +273,7 @@ func connectDB() error {
// log dsn and pause // log dsn and pause
// writeLog("connectDB", fmt.Sprintf(`["DSN: %s", "INFO"]`, dsn)) // writeLog("connectDB", fmt.Sprintf(`["DSN: %s", "INFO"]`, dsn))
// fmt.Println(dsn)
if db != nil { if db != nil {
// log success and return // log success and return
@@ -280,13 +284,15 @@ func connectDB() error {
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil { if err != nil {
// log.Println(err)
writeLog("connectDB", fmt.Sprintf(`["%s", "ERROR"]`, err)) writeLog("connectDB", fmt.Sprintf(`["%s", "ERROR"]`, err))
return err return err
} }
// Migrate the schema // Migrate the schema
err = db.AutoMigrate(&World{}, &Mission{}, &AttendanceItem{}) err = db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&World{}, &Mission{}, &AttendanceItem{})
if err != nil { if err != nil {
// log.Println(err)
writeLog("connectDB", fmt.Sprintf(`["%s", "ERROR"]`, err)) writeLog("connectDB", fmt.Sprintf(`["%s", "ERROR"]`, err))
return err return err
} }
@@ -303,7 +309,7 @@ type World struct {
DisplayName string `json:"displayName"` DisplayName string `json:"displayName"`
WorldName string `json:"worldName"` WorldName string `json:"worldName"`
WorldNameOriginal string `json:"worldNameOriginal"` WorldNameOriginal string `json:"worldNameOriginal"`
WorldSize int `json:"worldSize"` WorldSize float32 `json:"worldSize"`
Latitude float32 `json:"latitude"` Latitude float32 `json:"latitude"`
Longitude float32 `json:"longitude"` Longitude float32 `json:"longitude"`
Missions []Mission Missions []Mission
@@ -362,7 +368,7 @@ type Mission struct {
ServerName string `json:"serverName"` ServerName string `json:"serverName"`
ServerProfile string `json:"serverProfile"` ServerProfile string `json:"serverProfile"`
MissionStart time.Time `json:"missionStart" gorm:"type:datetime"` MissionStart time.Time `json:"missionStart" gorm:"type:datetime"`
MissionHash string `json:"missionHash"` MissionHash string `json:"missionHash" gorm:"index"`
WorldName string `json:"worldName" gorm:"-"` WorldName string `json:"worldName" gorm:"-"`
WorldID uint WorldID uint
World World `gorm:"foreignkey:WorldID"` World World `gorm:"foreignkey:WorldID"`
@@ -412,17 +418,18 @@ func writeMission(missionJSON string) {
type AttendanceItem struct { type AttendanceItem struct {
gorm.Model gorm.Model
MissionHash string `json:"missionHash"` MissionHash string `json:"missionHash"`
EventType string `json:"eventType"` EventType string `json:"eventType"`
PlayerId string `json:"playerId"` PlayerId string `json:"playerId"`
PlayerUID string `json:"playerUID"` PlayerUID string `json:"playerUID"`
JoinTime time.Time JoinTimeUTC time.Time
DisconnectTime time.Time DisconnectTimeUTC time.Time
ProfileName string `json:"profileName"` ProfileName string `json:"profileName"`
SteamName string `json:"steamName"` SteamName string `json:"steamName"`
IsJIP bool `json:"isJIP"` IsJIP bool `json:"isJIP" gorm:"column:is_jip"`
RoleDescription string `json:"roleDescription"` RoleDescription string `json:"roleDescription"`
MissionID int MissionID uint
Mission Mission `gorm:"foreignkey:MissionID"`
} }
func writeDisconnectEvent(data string) { func writeDisconnectEvent(data string) {
@@ -448,18 +455,18 @@ func writeDisconnectEvent(data string) {
// get all attendance rows of type without disconnect rows // get all attendance rows of type without disconnect rows
var attendanceRows []AttendanceItem var attendanceRows []AttendanceItem
db.Where("player_uid = ? AND event_type = ? AND disconnect_time = '0000-00-00 00:00:00'", event.PlayerUID, event.EventType).Find(&attendanceRows) db.Where("player_uid = ? AND event_type = ? AND disconnect_time_utc = '0000-00-00 00:00:00'", event.PlayerUID, event.EventType).Find(&attendanceRows)
for _, row := range attendanceRows { for _, row := range attendanceRows {
// update disconnect time // update disconnect time
if row.JoinTime.Before(time.Now().Add(-1*time.Hour)) && row.EventType == "Mission" { if row.JoinTimeUTC.Before(time.Now().Add(-1*time.Hour)) && row.EventType == "Mission" {
// if mission JoinTime is more than 1 hour ago, simplify this to write DisconnectTime as 1 hour from JoinTime. this to account for crashes where people don't immediately rejoin // if mission JoinTime is more than 1 hour ago, simplify this to write DisconnectTime as 1 hour from JoinTime. this to account for crashes where people don't immediately rejoin
row.DisconnectTime = row.JoinTime.Add(-1 * time.Hour) row.DisconnectTimeUTC = row.JoinTimeUTC.Add(-1 * time.Hour)
} else if row.JoinTime.Before(time.Now().Add(-6*time.Hour)) && row.EventType == "Server" { } else if row.JoinTimeUTC.Before(time.Now().Add(-6*time.Hour)) && row.EventType == "Server" {
// if server JoinTime is more than 6 hours ago, simplify this to write DisconnectTime as 6 hours from JoinTime. this to account for server crashes where people don't immediately rejoin without overwriting valid (potentially lengthy) server sessions // if server JoinTime is more than 6 hours ago, simplify this to write DisconnectTime as 6 hours from JoinTime. this to account for server crashes where people don't immediately rejoin without overwriting valid (potentially lengthy) server sessions
row.DisconnectTime = row.JoinTime.Add(-6 * time.Hour) row.DisconnectTimeUTC = row.JoinTimeUTC.Add(-6 * time.Hour)
} else { } else {
// otherwise, update DisconnectTime to now // otherwise, update DisconnectTime to now
row.DisconnectTime = time.Now() row.DisconnectTimeUTC = time.Now()
} }
db.Save(&row) db.Save(&row)
} }
@@ -493,10 +500,10 @@ func writeAttendance(data string) {
if event.EventType == "Server" { if event.EventType == "Server" {
// check for most recent existing attendance row // check for most recent existing attendance row
var attendance AttendanceItem var attendance AttendanceItem
db.Where("player_id = ? AND player_uid = ? AND event_type = ?", event.PlayerId, event.PlayerUID, event.EventType).Order("join_time desc").First(&attendance) db.Where("player_id = ? AND player_uid = ? AND event_type = ?", event.PlayerId, event.PlayerUID, event.EventType).Order("join_time_utc desc").First(&attendance)
if attendance.ID != 0 { if attendance.ID != 0 {
// update disconnect time // update disconnect time
row := db.Model(&attendance).Update("disconnect_time", attendance.DisconnectTime) row := db.Model(&attendance).Update("disconnect_time_utc", attendance.DisconnectTimeUTC)
if row.Error != nil { if row.Error != nil {
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, row.Error)) writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, row.Error))
return return
@@ -505,8 +512,8 @@ func writeAttendance(data string) {
} else { } else {
// insert new row // insert new row
event.JoinTime = time.Now() event.JoinTimeUTC = time.Now()
row := db.Create(&event) row := db.Omit("MissionID").Create(&event)
if row.Error != nil { if row.Error != nil {
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, row.Error)) writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, row.Error))
return return
@@ -518,7 +525,7 @@ func writeAttendance(data string) {
var mission Mission var mission Mission
db.Where("mission_hash = ?", event.MissionHash).First(&mission) db.Where("mission_hash = ?", event.MissionHash).First(&mission)
if mission.ID != 0 { if mission.ID != 0 {
event.MissionID = int(mission.ID) event.MissionID = uint(mission.ID)
} else { } else {
writeLog(functionName, fmt.Sprintf(`["Mission not found for hash %s", "ERROR"]`, event.MissionHash)) writeLog(functionName, fmt.Sprintf(`["Mission not found for hash %s", "ERROR"]`, event.MissionHash))
return return
@@ -526,17 +533,17 @@ func writeAttendance(data string) {
// check for most recent JoinTime for this player and event type // check for most recent JoinTime for this player and event type
var attendance AttendanceItem var attendance AttendanceItem
db.Where("player_id = ? AND player_uid = ? AND event_type = ? AND mission_hash = ?", event.PlayerId, event.PlayerUID, event.EventType, event.MissionHash).Order("join_time desc").First(&attendance) db.Where("player_id = ? AND player_uid = ? AND event_type = ? AND mission_hash = ?", event.PlayerId, event.PlayerUID, event.EventType, event.MissionHash).Order("join_time_utc desc").First(&attendance)
if attendance.ID != 0 { if attendance.ID != 0 {
// update disconnect time // update disconnect time
row := db.Model(&attendance).Update("disconnect_time", time.Now()) row := db.Model(&attendance).Update("disconnect_time_utc", time.Now())
if row.Error != nil { if row.Error != nil {
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, row.Error)) writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, row.Error))
return return
} }
rowId, playerUid = attendance.ID, attendance.PlayerUID rowId, playerUid = attendance.ID, attendance.PlayerUID
} else { } else {
event.JoinTime = time.Now() event.JoinTimeUTC = time.Now()
// insert new row // insert new row
row := db.Create(&event) row := db.Create(&event)
if row.Error != nil { if row.Error != nil {
@@ -653,17 +660,28 @@ func fixEscapeQuotes(s string) string {
} }
func writeLog(functionName string, data string) { func writeLog(functionName string, data string) {
// get calling function & line
_, file, line, _ := runtime.Caller(1)
log.Printf(`%s:%d:%s %s`, path.Base(file), line, functionName, data)
if extensionCallbackFnc == nil {
return
}
statusName := C.CString("AttendanceTracker") statusName := C.CString("AttendanceTracker")
defer C.free(unsafe.Pointer(statusName)) defer C.free(unsafe.Pointer(statusName))
statusFunction := C.CString(functionName) statusFunction := C.CString(functionName)
defer C.free(unsafe.Pointer(statusFunction)) defer C.free(unsafe.Pointer(statusFunction))
statusParam := C.CString(data) statusParam := C.CString(data)
defer C.free(unsafe.Pointer(statusParam)) defer C.free(unsafe.Pointer(statusParam))
runExtensionCallback(statusName, statusFunction, statusParam)
// get calling function & line runExtensionCallback(statusName, statusFunction, statusParam)
_, file, line, _ := runtime.Caller(1) }
log.Printf(`%s:%d:%s %s`, path.Base(file), line, functionName, data)
func disconnectDB() {
if db != nil {
db = nil
}
} }
//export goRVExtension //export goRVExtension
@@ -684,8 +702,11 @@ func goRVExtension(output *C.char, outputsize C.size_t, input *C.char) {
case "getTimestamp": case "getTimestamp":
temp = fmt.Sprintf(`["%s"]`, getTimestamp()) temp = fmt.Sprintf(`["%s"]`, getTimestamp())
case "connectDB": case "connectDB":
connectDB()
temp = fmt.Sprintf(`["%s"]`, "Connecting to DB") temp = fmt.Sprintf(`["%s"]`, "Connecting to DB")
connectDB()
case "disconnectDB":
temp = fmt.Sprintf(`["%s"]`, "Disconnecting from DB")
disconnectDB()
case "getMissionHash": case "getMissionHash":
temp = fmt.Sprintf(`["%s"]`, getMissionHash()) temp = fmt.Sprintf(`["%s"]`, getMissionHash())
default: default:
@@ -708,4 +729,14 @@ func goRVExtensionRegisterCallback(fnc C.extensionCallback) {
extensionCallbackFnc = fnc extensionCallbackFnc = fnc
} }
func main() {} func main() {
loadConfig()
fmt.Println("Running DB connect/migrate to build schema...")
err := connectDB()
if err != nil {
fmt.Println(err)
} else {
fmt.Println("DB connect/migrate complete!")
}
fmt.Scanln()
}