mirror of
https://github.com/indig0fox/Arma3-AttendanceTracker.git/
synced 2025-12-09 10:11:48 -06:00
Compare commits
2 Commits
v1.0
...
ee0c28d2c8
| Author | SHA1 | Date | |
|---|---|---|---|
|
ee0c28d2c8
|
|||
|
9a859b403a
|
BIN
@AttendanceTracker/AttendanceTracker_x64.dll
Normal file
BIN
@AttendanceTracker/AttendanceTracker_x64.dll
Normal file
Binary file not shown.
86
@AttendanceTracker/AttendanceTracker_x64.h
Normal file
86
@AttendanceTracker/AttendanceTracker_x64.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/* Code generated by cmd/cgo; DO NOT EDIT. */
|
||||
|
||||
/* package main.go */
|
||||
|
||||
|
||||
#line 1 "cgo-builtin-export-prolog"
|
||||
|
||||
#include <stddef.h> /* for ptrdiff_t below */
|
||||
|
||||
#ifndef GO_CGO_EXPORT_PROLOGUE_H
|
||||
#define GO_CGO_EXPORT_PROLOGUE_H
|
||||
|
||||
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
||||
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
/* Start of preamble from import "C" comments. */
|
||||
|
||||
|
||||
#line 3 "main.go"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "extensionCallback.h"
|
||||
|
||||
#line 1 "cgo-generated-wrapper"
|
||||
|
||||
|
||||
/* End of preamble from import "C" comments. */
|
||||
|
||||
|
||||
/* Start of boilerplate cgo prologue. */
|
||||
#line 1 "cgo-gcc-export-header-prolog"
|
||||
|
||||
#ifndef GO_CGO_PROLOGUE_H
|
||||
#define GO_CGO_PROLOGUE_H
|
||||
|
||||
typedef signed char GoInt8;
|
||||
typedef unsigned char GoUint8;
|
||||
typedef short GoInt16;
|
||||
typedef unsigned short GoUint16;
|
||||
typedef int GoInt32;
|
||||
typedef unsigned int GoUint32;
|
||||
typedef long long GoInt64;
|
||||
typedef unsigned long long GoUint64;
|
||||
typedef GoInt64 GoInt;
|
||||
typedef GoUint64 GoUint;
|
||||
typedef __SIZE_TYPE__ GoUintptr;
|
||||
typedef float GoFloat32;
|
||||
typedef double GoFloat64;
|
||||
typedef float _Complex GoComplex64;
|
||||
typedef double _Complex GoComplex128;
|
||||
|
||||
/*
|
||||
static assertion to make sure the file is being used on architecture
|
||||
at least with matching size of GoInt.
|
||||
*/
|
||||
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
|
||||
|
||||
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
||||
typedef _GoString_ GoString;
|
||||
#endif
|
||||
typedef void *GoMap;
|
||||
typedef void *GoChan;
|
||||
typedef struct { void *t; void *v; } GoInterface;
|
||||
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
|
||||
|
||||
#endif
|
||||
|
||||
/* End of boilerplate cgo prologue. */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern __declspec(dllexport) void goRVExtensionVersion(char* output, size_t outputsize);
|
||||
extern __declspec(dllexport) void goRVExtensionArgs(char* output, size_t outputsize, char* input, char** argv, int argc);
|
||||
extern __declspec(dllexport) void goRVExtension(char* output, size_t outputsize, char* input);
|
||||
extern __declspec(dllexport) void goRVExtensionRegisterCallback(extensionCallback fnc);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Binary file not shown.
@@ -21,6 +21,7 @@ class CfgFunctions {
|
||||
class writeConnect {};
|
||||
class writeDisconnect {};
|
||||
class timestamp {};
|
||||
class getSettings {};
|
||||
class getMissionHash {};
|
||||
class getWorldInfo {};
|
||||
class missionLoaded {};
|
||||
|
||||
@@ -21,7 +21,9 @@ addMissionEventHandler ["ExtensionCallback", {
|
||||
false;
|
||||
};
|
||||
|
||||
diag_log format ["Raw callback: %1: %2", _function, _data];
|
||||
if (missionNamespace getVariable ["AttendanceTracker_" + "debug", true]) then {
|
||||
diag_log format ["Raw callback: %1: %2", _function, _data];
|
||||
};
|
||||
|
||||
// Parse response from string array
|
||||
private "_response";
|
||||
@@ -44,12 +46,7 @@ addMissionEventHandler ["ExtensionCallback", {
|
||||
case "connectDB": {
|
||||
systemChat format ["AttendanceTracker: %1", _response#0];
|
||||
[_response#0, _response#1, _function] call attendanceTracker_fnc_log;
|
||||
if (_response#0 == "SUCCESS") then {
|
||||
missionNamespace setVariable ["AttendanceTracker_DBConnected", true];
|
||||
|
||||
// close any null disconnect values from previous mission
|
||||
"AttendanceTracker" callExtension ["fillLastMissionNull", []];
|
||||
|
||||
if (_response#0 == "SUCCESS") then {
|
||||
// log world info
|
||||
private _response = "AttendanceTracker" callExtension [
|
||||
"logWorld",
|
||||
@@ -65,21 +62,13 @@ addMissionEventHandler ["ExtensionCallback", {
|
||||
[AttendanceTracker getVariable ["missionContext", createHashMap]] call CBA_fnc_encodeJSON
|
||||
]
|
||||
];
|
||||
|
||||
missionNamespace setVariable ["AttendanceTracker_DBConnected", true];
|
||||
};
|
||||
};
|
||||
case "writeMissionInfo": {
|
||||
case "writeMission": {
|
||||
if (_response#0 == "MISSION_ID") then {
|
||||
AttendanceTracker_missionId = parseNumber (_response#1);
|
||||
};
|
||||
};
|
||||
case "writeAttendance": {
|
||||
if (_response#0 == "ATT_LOG") then {
|
||||
(_response#1) params ["_eventType", "_netId", "_rowId"];
|
||||
private _storeIndex = ["SERVER", "MISSION"] find _eventType;
|
||||
((AttendanceTracker getVariable ["rowIds", createHashMap]) getOrDefault [
|
||||
_netId,
|
||||
[nil, nil]
|
||||
]) set [_storeIndex, _rowId];
|
||||
AttendanceTracker_missionId = _response#1;
|
||||
};
|
||||
};
|
||||
default {
|
||||
|
||||
@@ -16,16 +16,52 @@
|
||||
|
||||
(AttendanceTracker getVariable ["allUsers", createHashMap]) set [_networkId, _userInfo];
|
||||
|
||||
[
|
||||
|
||||
[ // write d/c for past events
|
||||
"Server",
|
||||
_playerID,
|
||||
_playerUID,
|
||||
_profileName,
|
||||
_steamName,
|
||||
nil,
|
||||
nil
|
||||
] call attendanceTracker_fnc_writeConnect;
|
||||
_steamName
|
||||
] call attendanceTracker_fnc_writeDisconnect;
|
||||
|
||||
// [
|
||||
// "Server",
|
||||
// _playerID,
|
||||
// _playerUID,
|
||||
// _profileName,
|
||||
// _steamName,
|
||||
// nil,
|
||||
// nil
|
||||
// ] call attendanceTracker_fnc_writeConnect;
|
||||
|
||||
// start CBA PFH
|
||||
[format ["(EventHandler) OnUserConnected: Starting CBA PFH for %1", _playerID], "DEBUG"] call attendanceTracker_fnc_log;
|
||||
[
|
||||
{
|
||||
params ["_args", "_handle"];
|
||||
// check if player is still connected
|
||||
private _playerID = _args select 1;
|
||||
private _playerUID = _args select 2;
|
||||
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;
|
||||
_args call attendanceTracker_fnc_writeDisconnect;
|
||||
[_handle] call CBA_fnc_removePerFrameHandler;
|
||||
};
|
||||
|
||||
_args call attendanceTracker_fnc_writeConnect;
|
||||
},
|
||||
missionNamespace getVariable ["AttendanceTracker_" + "dbupdateintervalseconds", 300],
|
||||
[
|
||||
"Server",
|
||||
_playerID,
|
||||
_playerUID,
|
||||
_profileName,
|
||||
_steamName,
|
||||
nil,
|
||||
nil
|
||||
]
|
||||
] call CBA_fnc_addPerFrameHandler;
|
||||
}],
|
||||
["OnUserDisconnected", {
|
||||
params ["_networkId", "_clientStateNumber", "_clientState"];
|
||||
@@ -75,15 +111,56 @@
|
||||
|
||||
(AttendanceTracker getVariable ["allUsers", createHashMap]) set [_playerID, _userInfo];
|
||||
|
||||
[
|
||||
|
||||
[ // write d/c for past events
|
||||
"Mission",
|
||||
_playerID,
|
||||
_playerUID,
|
||||
_profileName,
|
||||
_steamName,
|
||||
_jip,
|
||||
roleDescription _unit
|
||||
] call attendanceTracker_fnc_writeConnect;
|
||||
nil
|
||||
] call attendanceTracker_fnc_writeDisconnect;
|
||||
|
||||
// [
|
||||
// "Mission",
|
||||
// _playerID,
|
||||
// _playerUID,
|
||||
// _profileName,
|
||||
// _steamName,
|
||||
// _jip,
|
||||
// roleDescription _unit
|
||||
// ] call attendanceTracker_fnc_writeConnect;
|
||||
|
||||
// start CBA PFH
|
||||
[format ["(EventHandler) PlayerConnected: Starting CBA PFH for %1", _playerID], "DEBUG"] call attendanceTracker_fnc_log;
|
||||
[
|
||||
{
|
||||
params ["_args", "_handle"];
|
||||
// check if player is still connected
|
||||
private _playerID = _args select 1;
|
||||
private _playerUID = _args select 2;
|
||||
private _userInfo = getUserInfo _playerID;
|
||||
private _clientStateNumber = _userInfo select 6;
|
||||
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;
|
||||
_args call attendanceTracker_fnc_writeDisconnect;
|
||||
[_handle] call CBA_fnc_removePerFrameHandler;
|
||||
};
|
||||
|
||||
_args call attendanceTracker_fnc_writeConnect;
|
||||
},
|
||||
missionNamespace getVariable ["AttendanceTracker_" + "dbupdateintervalseconds", 300],
|
||||
[
|
||||
"Mission",
|
||||
_playerID,
|
||||
_playerUID,
|
||||
_profileName,
|
||||
_steamName,
|
||||
_jip,
|
||||
roleDescription _unit
|
||||
]
|
||||
] call CBA_fnc_addPerFrameHandler;
|
||||
}],
|
||||
["PlayerDisconnected", {
|
||||
// NOTE: HandleDisconnect returns a DIFFERENT _id than PlayerDisconnected and above handlers, so we can't use it here
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
parseSimpleArray ('AttendanceTracker' callExtension "getSettings");
|
||||
@@ -7,7 +7,7 @@ params [
|
||||
if (isNil "_message") exitWith {false};
|
||||
if (
|
||||
missionNamespace getVariable ["AttendanceTracker_debug", false] &&
|
||||
_level == "DEBUG"
|
||||
_level != "WARN" && _level != "ERROR"
|
||||
) exitWith {};
|
||||
|
||||
"AttendanceTracker" callExtension ["log", [_level, _message]];
|
||||
|
||||
@@ -6,7 +6,22 @@ diag_log format ["AttendanceTracker: Mission started at %1", AttendanceTracker_m
|
||||
AttendanceTracker_missionHash = call attendanceTracker_fnc_getMissionHash;
|
||||
diag_log format ["AttendanceTracker: Mission hash is %1", AttendanceTracker_missionHash];
|
||||
|
||||
_settings = call attendanceTracker_fnc_getSettings;
|
||||
if (count _settings > 0) then {
|
||||
for "_i" from 0 to (count _settings) - 1 do {
|
||||
_setting = _settings select _i;
|
||||
_key = _setting select 0;
|
||||
_value = _setting select 1;
|
||||
missionNamespace setVariable ["AttendanceTracker_" + _key, _value];
|
||||
};
|
||||
} else {
|
||||
[format["Failed to parse settings: %1", _settings], "ERROR"] call attendanceTracker_fnc_log;
|
||||
};
|
||||
call attendanceTracker_fnc_connectDB;
|
||||
|
||||
AttendanceTracker setVariable ["missionContext", createHashMapFromArray [
|
||||
["missionHash", AttendanceTracker_missionHash],
|
||||
["missionStart", AttendanceTracker_missionStartTimestamp],
|
||||
["missionName", missionName],
|
||||
["briefingName", briefingName],
|
||||
["missionNameSource", missionNameSource],
|
||||
@@ -24,9 +39,6 @@ AttendanceTracker setVariable ["missionContext", createHashMapFromArray [
|
||||
// store all user details in a hash when they connect so we can reference it in disconnect events
|
||||
AttendanceTracker setVariable ["allUsers", createHashMap];
|
||||
AttendanceTracker setVariable ["rowIds", createHashMap];
|
||||
missionNamespace setVariable ["AttendanceTracker_debug", false];
|
||||
|
||||
call attendanceTracker_fnc_connectDB;
|
||||
|
||||
{
|
||||
if (!isServer) exitWith {};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
// need date for MySQL in format 2006-01-02 15:04:05
|
||||
|
||||
systemTimeUTC params [
|
||||
systemTimeUTC apply {if (_x < 10) then {"0" + str _x} else {str _x}} params [
|
||||
"_year",
|
||||
"_month",
|
||||
"_day",
|
||||
|
||||
@@ -17,8 +17,12 @@ _hash set ["profileName", _profileName];
|
||||
_hash set ["steamName", _steamName];
|
||||
_hash set ["isJIP", _isJIP];
|
||||
_hash set ["roleDescription", _roleDescription];
|
||||
_hash set ["missionHash", missionNamespace getVariable ["AttendanceTracker_missionHash", ""]];
|
||||
|
||||
"AttendanceTracker" callExtension ["writeAttendance", [[_hash] call CBA_fnc_encodeJSON]];
|
||||
[
|
||||
{missionNamespace getVariable ["AttendanceTracker_DBConnected", false]},
|
||||
{"AttendanceTracker" callExtension ["writeAttendance", [[_this] call CBA_fnc_encodeJSON]]},
|
||||
_hash, // args
|
||||
30 // timeout in seconds. if DB never connects, we don't want these building up
|
||||
] call CBA_fnc_waitUntilAndExecute;
|
||||
|
||||
true;
|
||||
@@ -17,8 +17,12 @@ _hash set ["profileName", _profileName];
|
||||
_hash set ["steamName", _steamName];
|
||||
_hash set ["isJIP", _isJIP];
|
||||
_hash set ["roleDescription", _roleDescription];
|
||||
_hash set ["missionHash", missionNamespace getVariable ["AttendanceTracker_missionHash", ""]];
|
||||
|
||||
"AttendanceTracker" callExtension ["writeDisconnectEvent", [[_hash] call CBA_fnc_encodeJSON]];
|
||||
[
|
||||
{missionNamespace getVariable ["AttendanceTracker_DBConnected", false]},
|
||||
{"AttendanceTracker" callExtension ["writeDisconnectEvent", [[_this] call CBA_fnc_encodeJSON]]},
|
||||
_hash, // args
|
||||
30 // timeout in seconds. if DB never connects, we don't want these building up
|
||||
] call CBA_fnc_waitUntilAndExecute;
|
||||
|
||||
true;
|
||||
@@ -1,7 +1,13 @@
|
||||
{
|
||||
"mysqlHost": "127.0.0.1",
|
||||
"mysqlPort": 3306,
|
||||
"mysqlUser": "root",
|
||||
"mysqlPassword": "root",
|
||||
"mysqlDatabase": "arma3_attendance"
|
||||
"sqlConfig": {
|
||||
"mysqlHost": "127.0.0.1",
|
||||
"mysqlPort": 3306,
|
||||
"mysqlUser": "root",
|
||||
"mysqlPassword": "root",
|
||||
"mysqlDatabase": "arma3_attendance"
|
||||
},
|
||||
"armaConfig": {
|
||||
"dbUpdateIntervalSeconds": 90,
|
||||
"debug": false
|
||||
}
|
||||
}
|
||||
7
extension/@AttendanceTracker/config.json
Normal file
7
extension/@AttendanceTracker/config.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"mysqlHost": "127.0.0.1",
|
||||
"mysqlPort": 3306,
|
||||
"mysqlUser": "root",
|
||||
"mysqlPassword": "root",
|
||||
"mysqlDatabase": "testgorm"
|
||||
}
|
||||
@@ -2,4 +2,8 @@ module main.go
|
||||
|
||||
go 1.16
|
||||
|
||||
require github.com/go-sql-driver/mysql v1.6.0
|
||||
require (
|
||||
github.com/go-sql-driver/mysql v1.7.0
|
||||
gorm.io/driver/mysql v1.5.1 // indirect
|
||||
gorm.io/gorm v1.25.2 // indirect
|
||||
)
|
||||
|
||||
@@ -1,2 +1,13 @@
|
||||
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/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw=
|
||||
gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o=
|
||||
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho=
|
||||
gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
|
||||
@@ -9,20 +9,21 @@ package main
|
||||
import "C" // This is required to import the C code
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var EXTENSION_VERSION string = "0.0.1"
|
||||
@@ -37,11 +38,20 @@ var ATTENDANCE_TABLE string = "attendance"
|
||||
var MISSIONS_TABLE string = "missions"
|
||||
var WORLDS_TABLE string = "worlds"
|
||||
|
||||
var LAST_SERVER_TIME uint64 = 0
|
||||
|
||||
// ! TODO make a hash to save key:netId from A3 value:rowId from join event
|
||||
|
||||
var ATConfig AttendanceTrackerConfig
|
||||
var Config AttendanceTrackerConfig
|
||||
var ATConfig ATSQLConfig
|
||||
var A3Config ArmaConfig
|
||||
|
||||
type AttendanceTrackerConfig struct {
|
||||
type ArmaConfig struct {
|
||||
DBUpdateIntervalSeconds int `json:"dbUpdateIntervalSeconds"`
|
||||
Debug bool `json:"debug"`
|
||||
}
|
||||
|
||||
type ATSQLConfig struct {
|
||||
MySQLHost string `json:"mysqlHost"`
|
||||
MySQLPort int `json:"mysqlPort"`
|
||||
MySQLUser string `json:"mysqlUser"`
|
||||
@@ -49,8 +59,13 @@ type AttendanceTrackerConfig struct {
|
||||
MySQLDatabase string `json:"mysqlDatabase"`
|
||||
}
|
||||
|
||||
type AttendanceTrackerConfig struct {
|
||||
ArmaConfig ArmaConfig `json:"armaConfig"`
|
||||
SQLConfig ATSQLConfig `json:"sqlConfig"`
|
||||
}
|
||||
|
||||
// database connection
|
||||
var db *sql.DB
|
||||
var db *gorm.DB
|
||||
|
||||
// configure log output
|
||||
func init() {
|
||||
@@ -74,7 +89,8 @@ func version() {
|
||||
func getDir() string {
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
writeLog("getDir", fmt.Sprintf(`["Error getting working directory: %v", "ERROR"]`, err))
|
||||
return ""
|
||||
}
|
||||
return dir
|
||||
}
|
||||
@@ -95,7 +111,7 @@ func loadConfig() {
|
||||
// LOG_FILE = ADDON_FOLDER + "\\attendanceTracker.log"
|
||||
// CONFIG_FILE = ADDON_FOLDER + "\\config.json"
|
||||
|
||||
file, err := os.Open(CONFIG_FILE)
|
||||
file, err := os.OpenFile(CONFIG_FILE, os.O_RDONLY|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
@@ -103,14 +119,42 @@ func loadConfig() {
|
||||
defer file.Close()
|
||||
|
||||
decoder := json.NewDecoder(file)
|
||||
err = decoder.Decode(&ATConfig)
|
||||
err = decoder.Decode(&Config)
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
A3Config = Config.ArmaConfig
|
||||
ATConfig = Config.SQLConfig
|
||||
writeLog(functionName, `["Config loaded", "INFO"]`)
|
||||
}
|
||||
|
||||
func getSettings() string {
|
||||
// get settings from A3Config and send to Arma
|
||||
var settings string = `[`
|
||||
// iterate through keys in A3Config struct
|
||||
v := reflect.ValueOf(A3Config)
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
// get field name
|
||||
fieldName := v.Type().Field(i).Name
|
||||
// get field value
|
||||
fieldValue := v.Field(i).Interface()
|
||||
// if field value is a string, add quotes
|
||||
fieldValueString := fmt.Sprintf("%v", fieldValue)
|
||||
if reflect.TypeOf(fieldValue).Kind() == reflect.String {
|
||||
fieldValueString = fmt.Sprintf(`"%v"`, fieldValue)
|
||||
}
|
||||
// add to settings, key should be lowercase
|
||||
settings += fmt.Sprintf(`["%s", %s],`, strings.ToLower(fieldName), fieldValueString)
|
||||
}
|
||||
|
||||
// remove last comma
|
||||
settings = strings.TrimSuffix(settings, ",")
|
||||
settings += `]`
|
||||
|
||||
return settings
|
||||
}
|
||||
|
||||
func getMissionHash() string {
|
||||
functionName := "getMissionHash"
|
||||
// get md5 hash of string
|
||||
@@ -123,50 +167,76 @@ func getMissionHash() string {
|
||||
return hashString
|
||||
}
|
||||
|
||||
func connectDB() string {
|
||||
functionName := "connectDB"
|
||||
var err error
|
||||
func updateServerTime(serverTime uint64) {
|
||||
functionName := "updateServerTime"
|
||||
// if serverTime is less than last server time, close server events
|
||||
if serverTime < LAST_SERVER_TIME {
|
||||
writeLog(functionName, `["Server has restarted, closing pending server sessions in attendance", "INFO"]`)
|
||||
closeServerEvents()
|
||||
}
|
||||
LAST_SERVER_TIME = serverTime
|
||||
|
||||
}
|
||||
|
||||
func closeServerEvents() {
|
||||
functionName := "closeServerEvents"
|
||||
writeLog(functionName, `["Closing server events", "INFO"]`)
|
||||
// get all server events with null DisconnectTime & set DisconnectTime to current time
|
||||
op := db.Model(&AttendanceItem{}).Where(`event_type = ? AND disconnect_time IS NULL`).Update("disconnect_time", time.Now().UTC())
|
||||
if op.Error != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, op.Error))
|
||||
return
|
||||
}
|
||||
// log how many
|
||||
writeLog(functionName, fmt.Sprintf(`["%d server events closed", "INFO"]`, op.RowsAffected))
|
||||
}
|
||||
|
||||
func connectDB() error {
|
||||
|
||||
// load config
|
||||
loadConfig()
|
||||
|
||||
// connect to database
|
||||
connectionString := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", ATConfig.MySQLUser, ATConfig.MySQLPassword, ATConfig.MySQLHost, ATConfig.MySQLPort, ATConfig.MySQLDatabase)
|
||||
var err error
|
||||
dsn := fmt.Sprintf(
|
||||
"%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
||||
ATConfig.MySQLUser,
|
||||
ATConfig.MySQLPassword,
|
||||
ATConfig.MySQLHost,
|
||||
ATConfig.MySQLPort,
|
||||
ATConfig.MySQLDatabase,
|
||||
)
|
||||
|
||||
db, err = sql.Open("mysql", connectionString)
|
||||
// log dsn and pause
|
||||
// writeLog("connectDB", fmt.Sprintf(`["DSN: %s", "INFO"]`, dsn))
|
||||
|
||||
if db != nil {
|
||||
// log success and return
|
||||
writeLog("connectDB", `["Database already connected", "INFO"]`)
|
||||
writeLog("connectDB", `["SUCCESS", "INFO"]`)
|
||||
return nil
|
||||
}
|
||||
|
||||
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return "ERROR"
|
||||
}
|
||||
if db == nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, "db is nil"))
|
||||
return "ERROR"
|
||||
}
|
||||
// defer db.Close()
|
||||
|
||||
db.SetConnMaxLifetime(time.Minute * 3)
|
||||
db.SetMaxOpenConns(10)
|
||||
db.SetMaxIdleConns(10)
|
||||
|
||||
pingErr := db.Ping()
|
||||
if pingErr != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, pingErr))
|
||||
return "ERROR"
|
||||
writeLog("connectDB", fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return err
|
||||
}
|
||||
|
||||
// Check the server version
|
||||
var version string
|
||||
err = db.QueryRow("SELECT VERSION()").Scan(&version)
|
||||
// Migrate the schema
|
||||
err = db.AutoMigrate(&World{}, &Mission{}, &AttendanceItem{})
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return "ERROR"
|
||||
writeLog("connectDB", fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return err
|
||||
}
|
||||
writeLog(functionName, fmt.Sprintf(`["Connected to MySQL/MariaDB version %s", "INFO"]`, version))
|
||||
writeLog(functionName, `["SUCCESS", "INFO"]`)
|
||||
return version
|
||||
|
||||
writeLog("connectDB", `["Database connected", "INFO"]`)
|
||||
writeLog("connectDB", `["SUCCESS", "INFO"]`)
|
||||
return nil
|
||||
}
|
||||
|
||||
type WorldInfo struct {
|
||||
type World struct {
|
||||
gorm.Model
|
||||
Author string `json:"author"`
|
||||
WorkshopID string `json:"workshopID"`
|
||||
DisplayName string `json:"displayName"`
|
||||
@@ -175,79 +245,49 @@ type WorldInfo struct {
|
||||
WorldSize int `json:"worldSize"`
|
||||
Latitude float32 `json:"latitude"`
|
||||
Longitude float32 `json:"longitude"`
|
||||
Missions []Mission
|
||||
}
|
||||
|
||||
func writeWorldInfo(worldInfo string) {
|
||||
functionName := "writeWorldInfo"
|
||||
// writeLog(functionName, fmt.Sprintf(`["%s", "DEBUG"]`, worldInfo))
|
||||
// worldInfo is json, parse it
|
||||
var wi WorldInfo
|
||||
var wi World
|
||||
fixedString := fixEscapeQuotes(trimQuotes(worldInfo))
|
||||
err := json.Unmarshal([]byte(fixedString), &wi)
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
// write to log as json
|
||||
// writeLog(functionName, fmt.Sprintf(`["%s", "DEBUG"]`, json.Marshal(wi)))
|
||||
|
||||
// write to database
|
||||
// check if world exists
|
||||
var worldID int
|
||||
err = db.QueryRow("SELECT id FROM worlds WHERE world_name = ?", wi.WorldName).Scan(&worldID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
// world does not exist, insert it
|
||||
stmt, err := db.Prepare(fmt.Sprintf(
|
||||
"INSERT INTO %s (author, workshop_id, display_name, world_name, world_name_original, world_size, latitude, longitude) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
WORLDS_TABLE,
|
||||
))
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
defer stmt.Close()
|
||||
res, err := stmt.Exec(wi.Author, wi.WorkshopID, wi.DisplayName, wi.WorldName, wi.WorldNameOriginal, wi.WorldSize, wi.Latitude, wi.Longitude)
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
lastID, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
writeLog(functionName, fmt.Sprintf(`["World inserted with ID %d", "INFO"]`, lastID))
|
||||
} else {
|
||||
// prevent crash
|
||||
if db == nil {
|
||||
err := connectDB()
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// write world if not exist
|
||||
var world World
|
||||
db.Where("world_name = ?", wi.WorldName).First(&world)
|
||||
if world.ID == 0 {
|
||||
writeLog(functionName, `["World not found, writing new world", "INFO"]`)
|
||||
result := db.Create(&wi)
|
||||
if result.Error != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, result.Error))
|
||||
return
|
||||
}
|
||||
writeLog(functionName, fmt.Sprintf(`["World written with ID %d", "INFO"]`, wi.ID))
|
||||
} else {
|
||||
// world exists, update it
|
||||
stmt, err := db.Prepare(fmt.Sprintf(
|
||||
"UPDATE %s SET author = ?, workshop_id = ?, display_name = ?, world_name = ?, world_name_original = ?, world_size = ?, latitude = ?, longitude = ? WHERE id = ?",
|
||||
WORLDS_TABLE,
|
||||
))
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
defer stmt.Close()
|
||||
res, err := stmt.Exec(wi.Author, wi.WorkshopID, wi.DisplayName, wi.WorldName, wi.WorldNameOriginal, wi.WorldSize, wi.Latitude, wi.Longitude, worldID)
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
writeLog(functionName, fmt.Sprintf(`["World updated with %d rows affected", "INFO"]`, rowsAffected))
|
||||
// return ID
|
||||
writeLog(functionName, fmt.Sprintf(`["World exists with ID %d", "INFO"]`, world.ID))
|
||||
}
|
||||
}
|
||||
|
||||
type MissionInfo struct {
|
||||
type Mission struct {
|
||||
gorm.Model
|
||||
MissionName string `json:"missionName"`
|
||||
BriefingName string `json:"briefingName"`
|
||||
MissionNameSource string `json:"missionNameSource"`
|
||||
@@ -257,85 +297,114 @@ type MissionInfo struct {
|
||||
ServerProfile string `json:"serverProfile"`
|
||||
MissionStart string `json:"missionStart"`
|
||||
MissionHash string `json:"missionHash"`
|
||||
WorldName string `json:"worldName"`
|
||||
WorldName string `json:"worldName" gorm:"-"`
|
||||
WorldID uint
|
||||
World World `gorm:"foreignkey:WorldID"`
|
||||
Attendees []AttendanceItem
|
||||
}
|
||||
|
||||
func writeMissionInfo(missionInfo string) {
|
||||
functionName := "writeMissionInfo"
|
||||
func writeMission(missionJSON string) {
|
||||
functionName := "writeMission"
|
||||
var err error
|
||||
// writeLog(functionName, fmt.Sprintf(`["%s", "DEBUG"]`, missionInfo))
|
||||
// missionInfo is json, parse it
|
||||
var mi MissionInfo
|
||||
fixedString := fixEscapeQuotes(trimQuotes(missionInfo))
|
||||
// writeLog(functionName, fmt.Sprintf(`["%s", "DEBUG"]`, Mission))
|
||||
// Mission is json, parse it
|
||||
var mi Mission
|
||||
fixedString := fixEscapeQuotes(trimQuotes(missionJSON))
|
||||
err = json.Unmarshal([]byte(fixedString), &mi)
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
|
||||
// check if mission exists based on hash
|
||||
var worldID int
|
||||
err = db.QueryRow("SELECT id FROM worlds WHERE world_name = ?", mi.WorldName).Scan(&worldID)
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
|
||||
var stmt *sql.Stmt
|
||||
var res sql.Result
|
||||
|
||||
if worldID != 0 {
|
||||
sqlWorld := fmt.Sprintf(
|
||||
"INSERT INTO %s (mission_name, briefing_name, mission_name_source, on_load_name, author, server_name, server_profile, mission_start, mission_hash, world_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
MISSIONS_TABLE,
|
||||
)
|
||||
stmt, err = db.Prepare(sqlWorld)
|
||||
// prevent crash
|
||||
if db == nil {
|
||||
err := connectDB()
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
res, err = stmt.Exec(mi.MissionName, mi.BriefingName, mi.MissionNameSource, mi.OnLoadName, mi.Author, mi.ServerName, mi.ServerProfile, mi.MissionStart, mi.MissionHash, worldID)
|
||||
|
||||
} else {
|
||||
// if no world was found, write without it
|
||||
sqlNoWorld := fmt.Sprintf(
|
||||
"INSERT INTO %s (mission_name, briefing_name, mission_name_source, on_load_name, author, server_name, server_profile, mission_start, mission_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
MISSIONS_TABLE,
|
||||
)
|
||||
stmt, err = db.Prepare(sqlNoWorld)
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
defer stmt.Close()
|
||||
res, err = stmt.Exec(mi.MissionName, mi.BriefingName, mi.MissionNameSource, mi.OnLoadName, mi.Author, mi.ServerName, mi.ServerProfile, mi.MissionStart, mi.MissionHash)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
// get world from WorldName
|
||||
var world World
|
||||
db.Where("world_name = ?", mi.WorldName).First(&world)
|
||||
if world.ID == 0 {
|
||||
writeLog(functionName, fmt.Sprintf(`["World not found for %s, cannot write mission!", "ERROR"]`, mi.WorldName))
|
||||
return
|
||||
}
|
||||
mi.WorldID = world.ID
|
||||
|
||||
lastID, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
// write mission to database
|
||||
db.Create(&mi)
|
||||
if db.Error != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, db.Error))
|
||||
return
|
||||
}
|
||||
writeLog(functionName, fmt.Sprintf(`["Mission inserted with ID %d", "INFO"]`, lastID))
|
||||
writeLog(functionName, fmt.Sprintf(`["MISSION_ID", "%d"]`, lastID))
|
||||
writeLog(functionName, fmt.Sprintf(`["Mission written with ID %d", "INFO"]`, mi.ID))
|
||||
writeLog(functionName, fmt.Sprintf(`["MISSION_ID", %d]`, mi.ID))
|
||||
}
|
||||
|
||||
type AttendanceLogItem struct {
|
||||
type AttendanceItem struct {
|
||||
gorm.Model
|
||||
MissionHash string `json:"missionHash"`
|
||||
EventType string `json:"eventType"`
|
||||
PlayerId string `json:"playerId"`
|
||||
PlayerUID string `json:"playerUID"`
|
||||
JoinTime time.Time
|
||||
DisconnectTime time.Time
|
||||
ProfileName string `json:"profileName"`
|
||||
SteamName string `json:"steamName"`
|
||||
IsJIP bool `json:"isJIP"`
|
||||
RoleDescription string `json:"roleDescription"`
|
||||
MissionHash string `json:"missionHash"`
|
||||
MissionID int
|
||||
}
|
||||
|
||||
func writeDisconnectEvent(data string) {
|
||||
functionName := "writeDisconnectEvent"
|
||||
var err error
|
||||
// data is json, parse it
|
||||
stringjson := fixEscapeQuotes(trimQuotes(data))
|
||||
var event AttendanceItem
|
||||
err = json.Unmarshal([]byte(stringjson), &event)
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
|
||||
// prevent crash
|
||||
if db == nil {
|
||||
err := connectDB()
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// get all attendance rows of type without disconnect rows
|
||||
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)
|
||||
for _, row := range attendanceRows {
|
||||
// put to json
|
||||
json, err := json.Marshal(row)
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
writeLog(functionName, fmt.Sprintf(`["Updating disconnect time for %s", "INFO"]`, json))
|
||||
if row.JoinTime.Before(time.Now().UTC().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
|
||||
row.DisconnectTime = time.Now().UTC().Add(-1 * time.Hour)
|
||||
} else if row.JoinTime.Before(time.Now().UTC().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
|
||||
row.DisconnectTime = time.Now().UTC().Add(-6 * time.Hour)
|
||||
} else {
|
||||
// otherwise, update DisconnectTime to now
|
||||
row.DisconnectTime = time.Now().UTC()
|
||||
}
|
||||
db.Save(&row)
|
||||
}
|
||||
|
||||
writeLog(functionName, fmt.Sprintf(`["Disconnect events written for %s", "DEBUG"]`, event.PlayerUID))
|
||||
}
|
||||
|
||||
func writeAttendance(data string) {
|
||||
@@ -343,190 +412,82 @@ func writeAttendance(data string) {
|
||||
var err error
|
||||
// data is json, parse it
|
||||
stringjson := fixEscapeQuotes(trimQuotes(data))
|
||||
var event AttendanceLogItem
|
||||
var event AttendanceItem
|
||||
err = json.Unmarshal([]byte(stringjson), &event)
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
|
||||
// get MySQL friendly NOW
|
||||
now := time.Now().UTC().Format("2006-01-02 15:04:05")
|
||||
|
||||
// prevent crash
|
||||
if db == nil {
|
||||
writeLog(functionName, `["db is nil", "ERROR"]`)
|
||||
return
|
||||
}
|
||||
|
||||
// send to DB
|
||||
var result sql.Result
|
||||
|
||||
if event.EventType == "Server" {
|
||||
sql := fmt.Sprintf(
|
||||
`INSERT INTO %s (join_time, event_type, player_id, player_uid, profile_name, steam_name, is_jip, role_description) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
ATTENDANCE_TABLE,
|
||||
)
|
||||
result, err = db.ExecContext(
|
||||
context.Background(),
|
||||
sql,
|
||||
now,
|
||||
event.EventType,
|
||||
event.PlayerId,
|
||||
event.PlayerUID,
|
||||
event.ProfileName,
|
||||
event.SteamName,
|
||||
event.IsJIP,
|
||||
event.RoleDescription,
|
||||
)
|
||||
} else if event.EventType == "Mission" {
|
||||
sql := fmt.Sprintf(
|
||||
`INSERT INTO %s (join_time, event_type, player_id, player_uid, profile_name, steam_name, is_jip, role_description, mission_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
ATTENDANCE_TABLE,
|
||||
)
|
||||
result, err = db.ExecContext(
|
||||
context.Background(),
|
||||
sql,
|
||||
now,
|
||||
event.EventType,
|
||||
event.PlayerId,
|
||||
event.PlayerUID,
|
||||
event.ProfileName,
|
||||
event.SteamName,
|
||||
event.IsJIP,
|
||||
event.RoleDescription,
|
||||
event.MissionHash,
|
||||
)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
|
||||
writeLog(functionName, fmt.Sprintf(`["Saved attendance for %s to row id %d", "INFO"]`, event.ProfileName, id))
|
||||
if event.EventType == "Server" {
|
||||
writeLog(functionName, fmt.Sprintf(`["ATT_LOG", ["SERVER", "%s", "%d"]]`, event.PlayerId, id))
|
||||
} else if event.EventType == "Mission" {
|
||||
writeLog(functionName, fmt.Sprintf(`["ATT_LOG", ["MISSION", "%s", "%d"]]`, event.PlayerId, id))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type DisconnectItem struct {
|
||||
EventType string `json:"eventType"`
|
||||
PlayerId string `json:"playerId"`
|
||||
MissionHash string `json:"missionHash"`
|
||||
}
|
||||
|
||||
func writeDisconnectEvent(data string) {
|
||||
functionName := "writeDisconnectEvent"
|
||||
// data is json, parse it
|
||||
stringjson := fixEscapeQuotes(trimQuotes(data))
|
||||
var event DisconnectItem
|
||||
err := json.Unmarshal([]byte(stringjson), &event)
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
|
||||
// get MySQL friendly NOW
|
||||
now := time.Now().UTC().Format("2006-01-02 15:04:05")
|
||||
|
||||
// prevent crash
|
||||
if db == nil {
|
||||
writeLog(functionName, `["db is nil", "ERROR"]`)
|
||||
return
|
||||
}
|
||||
|
||||
// first, check if a row exists for this player
|
||||
var sql string
|
||||
if event.EventType == "Mission" {
|
||||
sql = fmt.Sprintf(
|
||||
`
|
||||
SELECT id FROM attendance
|
||||
WHERE player_id = '%s' and event_type = '%s' and mission_hash = '%s' and disconnect_time IS NULL and join_time >= (NOW() - INTERVAL 24 hour)
|
||||
ORDER BY join_time DESC
|
||||
`,
|
||||
event.PlayerId,
|
||||
event.EventType,
|
||||
event.MissionHash,
|
||||
)
|
||||
} else if event.EventType == "Server" {
|
||||
sql = fmt.Sprintf(
|
||||
`
|
||||
SELECT id FROM attendance
|
||||
WHERE player_id = '%s' and event_type = '%s' and disconnect_time IS NULL and join_time >= (NOW() - INTERVAL 24 hour)
|
||||
ORDER BY join_time DESC
|
||||
`,
|
||||
event.PlayerId,
|
||||
event.EventType,
|
||||
)
|
||||
} else {
|
||||
writeLog(functionName, fmt.Sprintf(`["Unknown event type %s", "ERROR"]`, event.EventType))
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := db.QueryContext(context.Background(), sql)
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
// if there is a row, update it
|
||||
if rows.Next() {
|
||||
// create interface to hold values
|
||||
var rowId int64
|
||||
|
||||
err = rows.Scan(&rowId)
|
||||
err := connectDB()
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// update the row
|
||||
sql = fmt.Sprintf(
|
||||
`UPDATE attendance SET disconnect_time = '%s' WHERE id = %d`,
|
||||
now,
|
||||
rowId,
|
||||
)
|
||||
var playerUid string
|
||||
var rowId uint
|
||||
if event.EventType == "Server" {
|
||||
// check for most recent existing attendance row
|
||||
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)
|
||||
if attendance.ID != 0 {
|
||||
// update disconnect time
|
||||
row := db.Model(&attendance).Update("disconnect_time", attendance.DisconnectTime)
|
||||
if row.Error != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, row.Error))
|
||||
return
|
||||
}
|
||||
rowId, playerUid = attendance.ID, attendance.PlayerUID
|
||||
|
||||
_, err := db.ExecContext(context.Background(), sql)
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
} else {
|
||||
// insert new row
|
||||
event.JoinTime = time.Now().UTC()
|
||||
row := db.Create(&event)
|
||||
if row.Error != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, row.Error))
|
||||
return
|
||||
}
|
||||
rowId, playerUid = event.ID, event.PlayerUID
|
||||
}
|
||||
} else if event.EventType == "Mission" {
|
||||
// use gorm to associate this event with the mission sharing a mission hash
|
||||
var mission Mission
|
||||
db.Where("mission_hash = ?", event.MissionHash).First(&mission)
|
||||
if mission.ID != 0 {
|
||||
event.MissionID = int(mission.ID)
|
||||
} else {
|
||||
writeLog(functionName, fmt.Sprintf(`["Mission not found for hash %s", "ERROR"]`, event.MissionHash))
|
||||
return
|
||||
}
|
||||
writeLog(functionName, fmt.Sprintf(`["Saved disconnect event for %s to row id %d", "INFO"]`, event.PlayerId, rowId))
|
||||
|
||||
} else {
|
||||
// otherwise, log an error
|
||||
writeLog(functionName, fmt.Sprintf(`["No row found for %s, %s", "ERROR"]`, event.PlayerId, event.EventType))
|
||||
}
|
||||
}
|
||||
|
||||
func fillLastMissionNull() {
|
||||
functionName := "fillLastMissionNull"
|
||||
// prevent crash
|
||||
if db == nil {
|
||||
writeLog(functionName, `["db is nil", "ERROR"]`)
|
||||
return
|
||||
// check for most recent JoinTime for this player and event type
|
||||
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)
|
||||
if attendance.ID != 0 {
|
||||
// update disconnect time
|
||||
row := db.Model(&attendance).Update("disconnect_time", time.Now().UTC())
|
||||
if row.Error != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, row.Error))
|
||||
return
|
||||
}
|
||||
rowId, playerUid = attendance.ID, attendance.PlayerUID
|
||||
} else {
|
||||
event.JoinTime = time.Now().UTC()
|
||||
// insert new row
|
||||
row := db.Create(&event)
|
||||
if row.Error != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, row.Error))
|
||||
return
|
||||
}
|
||||
rowId, playerUid = event.ID, event.PlayerUID
|
||||
}
|
||||
}
|
||||
|
||||
sql := `call proc_filllastmissionnull`
|
||||
|
||||
_, err := db.ExecContext(context.Background(), sql)
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
writeLog(functionName, `["Filled mission event NULLs", "INFO"]`)
|
||||
writeLog(functionName, fmt.Sprintf(`["Saved attendance for %s to row id %d", "DEBUG"]`, playerUid, rowId))
|
||||
}
|
||||
|
||||
func runExtensionCallback(name *C.char, function *C.char, data *C.char) C.int {
|
||||
@@ -557,10 +518,6 @@ func goRVExtensionArgs(output *C.char, outputsize C.size_t, input *C.char, argv
|
||||
temp := fmt.Sprintf("Function: %s nb params: %d", C.GoString(input), argc)
|
||||
|
||||
switch C.GoString(input) {
|
||||
case "fillLastMissionNull":
|
||||
{
|
||||
go fillLastMissionNull()
|
||||
}
|
||||
case "writeAttendance":
|
||||
{ // callExtension ["logAttendance", [_hash] call CBA_fnc_encodeJSON]];
|
||||
if argc == 1 {
|
||||
@@ -568,19 +525,18 @@ func goRVExtensionArgs(output *C.char, outputsize C.size_t, input *C.char, argv
|
||||
}
|
||||
}
|
||||
case "writeDisconnectEvent":
|
||||
{ // callExtension ["writeDisconnectEvent", [[_hash] call CBA_fnc_encodeJSON]];
|
||||
|
||||
{ // callExtension ["writeDisconnectEvent", [_hash] call CBA_fnc_encodeJSON]];
|
||||
if argc == 1 {
|
||||
go writeDisconnectEvent(out[0])
|
||||
}
|
||||
}
|
||||
case "logMission":
|
||||
if argc == 1 {
|
||||
go writeMissionInfo(out[0])
|
||||
writeMission(out[0])
|
||||
}
|
||||
case "logWorld":
|
||||
if argc == 1 {
|
||||
go writeWorldInfo(out[0])
|
||||
writeWorldInfo(out[0])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -636,8 +592,7 @@ func writeLog(functionName string, data string) {
|
||||
|
||||
// get calling function & line
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
log.Printf(`%s:%d: %s`, path.Base(file), line, data)
|
||||
log.Printf(`%s: %s`, functionName, data)
|
||||
log.Printf(`%s:%d:%s %s`, path.Base(file), line, functionName, data)
|
||||
}
|
||||
|
||||
//export goRVExtension
|
||||
@@ -652,10 +607,13 @@ func goRVExtension(output *C.char, outputsize C.size_t, input *C.char) {
|
||||
temp = EXTENSION_VERSION
|
||||
case "getDir":
|
||||
temp = getDir()
|
||||
case "getSettings":
|
||||
loadConfig()
|
||||
temp = getSettings()
|
||||
case "getTimestamp":
|
||||
temp = fmt.Sprintf(`["%s"]`, getTimestamp())
|
||||
case "connectDB":
|
||||
go connectDB()
|
||||
connectDB()
|
||||
temp = fmt.Sprintf(`["%s"]`, "Connecting to DB")
|
||||
case "getMissionHash":
|
||||
temp = fmt.Sprintf(`["%s"]`, getMissionHash())
|
||||
|
||||
Reference in New Issue
Block a user