add mission md5 hash as unique id, break out db connect ask, add world

This commit is contained in:
2023-05-08 00:15:27 -07:00
parent f8714b7012
commit 43aa384949
9 changed files with 194 additions and 24 deletions

View File

@@ -14,6 +14,7 @@ class CfgFunctions {
class functions { class functions {
file = "\AttendanceTracker\functions"; file = "\AttendanceTracker\functions";
class postInit {postInit = 1;}; class postInit {postInit = 1;};
class connectDB {};
class eventHandlers {}; class eventHandlers {};
class callbackHandler {postInit = 1;}; class callbackHandler {postInit = 1;};
class log {}; class log {};

View File

@@ -39,6 +39,21 @@ addMissionEventHandler ["ExtensionCallback", {
case "connectDB": { case "connectDB": {
systemChat format ["AttendanceTracker: %1", _response#0]; systemChat format ["AttendanceTracker: %1", _response#0];
[_response#0, _response#1, _function] call attendanceTracker_fnc_log; [_response#0, _response#1, _function] call attendanceTracker_fnc_log;
if (_response#0 == "SUCCESS") then {
missionNamespace setVariable ["AttendanceTracker_DBConnected", true];
// log mission info and get back the row Id to send with future messages
private _response = "AttendanceTracker" callExtension ["logMission", [
[AttendanceTracker getVariable ["missionContext", createHashMap]] call CBA_fnc_encodeJSON
]];
AttendanceTracker_missionId = parseNumber _response;
// log world info
private _response = "AttendanceTracker" callExtension ["logWorld", [
[call attendanceTracker_fnc_getWorldInfo] call CBA_fnc_encodeJSON
]];
};
}; };
default { default {
_response call attendanceTracker_fnc_log; _response call attendanceTracker_fnc_log;

View File

@@ -0,0 +1,3 @@
private _database = "AttendanceTracker" callExtension "connectDB";
// systemChat "AttendanceTracker: Connecting to database...";
["Connecting to database...", "INFO"] call attendanceTracker_fnc_log;

View File

@@ -0,0 +1,23 @@
_world = ( configfile >> "CfgWorlds" >> worldName );
_author = getText( _world >> "author" );
_name = getText ( _world >> "description" );
_source = configSourceMod ( _world );
_workshopID = '';
{
if ( ( _x#1 ) == _source ) then {
_workshopID = _x#7;
break;
};
} foreach getLoadedModsInfo;
// [_name, _author, _workshopID];
[
["worldName", _name],
["author", _author],
["worldSize", worldSize],
["workshopID", _workshopID]
];

View File

@@ -16,6 +16,7 @@ _hash set ["profileName", _profileName];
_hash set ["steamName", _steamName]; _hash set ["steamName", _steamName];
_hash set ["isJIP", _isJIP]; _hash set ["isJIP", _isJIP];
_hash set ["roleDescription", _roleDescription]; _hash set ["roleDescription", _roleDescription];
_hash set ["missionHash", missionNamespace getVariable ["AttendanceTracker_missionHash", ""]];
"AttendanceTracker" callExtension ["logAttendance", [[_hash] call CBA_fnc_encodeJSON]]; "AttendanceTracker" callExtension ["logAttendance", [[_hash] call CBA_fnc_encodeJSON]];

View File

@@ -15,6 +15,7 @@ _hash set ["profileName", _profileName];
_hash set ["steamName", _steamName]; _hash set ["steamName", _steamName];
_hash set ["isJIP", false]; _hash set ["isJIP", false];
_hash set ["roleDescription", ""]; _hash set ["roleDescription", ""];
_hash set ["missionHash", missionNamespace getVariable ["AttendanceTracker_missionHash", ""]];
"AttendanceTracker" callExtension ["logAttendance", [[_hash] call CBA_fnc_encodeJSON]]; "AttendanceTracker" callExtension ["logAttendance", [[_hash] call CBA_fnc_encodeJSON]];

View File

@@ -1,6 +1,9 @@
AttendanceTracker = false call CBA_fnc_createNamespace; AttendanceTracker = false call CBA_fnc_createNamespace;
AttendanceTracker_missionStartTimestamp = call attendanceTracker_fnc_timestamp;
AttendanceTracker_missionHash = "AttendanceTracker" callExtension ["getMissionHash", AttendanceTracker_missionStartTimestamp];
AttendanceTracker setVariable ["missionContext", createHashMapFromArray [ AttendanceTracker setVariable ["missionContext", createHashMapFromArray [
["missionName", missionName], ["missionName", missionName],
["briefingName", briefingName], ["briefingName", briefingName],
@@ -9,16 +12,17 @@ AttendanceTracker setVariable ["missionContext", createHashMapFromArray [
["author", getMissionConfigValue ["author", ""]], ["author", getMissionConfigValue ["author", ""]],
["serverName", serverName], ["serverName", serverName],
["serverProfile", profileName], ["serverProfile", profileName],
["missionStart", call attendanceTracker_fnc_timestamp] ["missionStart", AttendanceTracker_missionStartTimestamp],
["missionHash", AttendanceTracker_missionHash]
]]; ]];
// store all user details in a hash when they connect so we can reference it in disconnect events // store all user details in a hash when they connect so we can reference it in disconnect events
AttendanceTracker setVariable ["allUsers", createHashMap]; AttendanceTracker setVariable ["allUsers", createHashMap];
missionNamespace getVariable ["AttendanceTracker_debug", false]; missionNamespace setVariable ["AttendanceTracker_debug", false];
private _database = "AttendanceTracker" callExtension "connectDB"; call attendanceTracker_fnc_connectDB;
// systemChat "AttendanceTracker: Connecting to database...";
["Connecting to database...", "INFO"] call attendanceTracker_fnc_log;
{ {
if (!isServer) exitWith {}; if (!isServer) exitWith {};

Binary file not shown.

View File

@@ -10,6 +10,7 @@ import "C" // This is required to import the C code
import ( import (
"context" "context"
"crypto/md5"
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"fmt" "fmt"
@@ -103,6 +104,18 @@ func loadConfig() {
writeLog(functionName, `["Config loaded", "INFO"]`) writeLog(functionName, `["Config loaded", "INFO"]`)
} }
func getMissionHash(time string) string {
functionName := "getMissionHash"
// get md5 hash of string
// https://stackoverflow.com/questions/2377881/how-to-get-a-md5-hash-from-a-string-in-golang
hash := md5.Sum([]byte(time))
// convert to string
hashString := fmt.Sprintf("%x", hash)
writeLog(functionName, fmt.Sprintf(`["Mission hash: %s", "INFO"]`, hashString))
return hashString
}
func connectDB() string { func connectDB() string {
functionName := "connectDB" functionName := "connectDB"
var err error var err error
@@ -142,10 +155,80 @@ func connectDB() string {
return "ERROR" return "ERROR"
} }
writeLog(functionName, fmt.Sprintf(`["Connected to MySQL/MariaDB version %s", "INFO"]`, version)) writeLog(functionName, fmt.Sprintf(`["Connected to MySQL/MariaDB version %s", "INFO"]`, version))
writeLog(functionName, `["SUCCESS", "INFO"]`)
return version return version
} }
type AttendanceLogItem struct { type WorldInfo struct {
WorldName string `json:"worldName"`
Author string `json:"author"`
WorldSize int `json:"worldSize"`
WorkshopID string `json:"workshopID"`
}
func writeWorldInfo(worldInfo string) {
functionName := "writeWorldInfo"
// worldInfo is json, parse it
var wi WorldInfo
err := json.Unmarshal([]byte(worldInfo), &wi)
if err != nil {
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
return
}
// write to log
writeLog(functionName, fmt.Sprintf(`["WorldName:%s Author:%s WorldSize:%d WorkshopID:%s", "INFO"]`, wi.WorldName, wi.Author, wi.WorldSize, wi.WorkshopID))
// write to database
// check if world exists
var worldID int
err = db.QueryRow("SELECT id FROM worlds WHERE workshop_id = ?", wi.WorkshopID).Scan(&worldID)
if err != nil {
if err == sql.ErrNoRows {
// world does not exist, insert it
stmt, err := db.Prepare("INSERT INTO worlds (world_name, author, world_size, workshop_id) VALUES (?, ?, ?, ?)")
if err != nil {
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
return
}
defer stmt.Close()
res, err := stmt.Exec(wi.WorldName, wi.Author, wi.WorldSize, wi.WorkshopID)
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 {
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
return
}
} else {
// world exists, update it
stmt, err := db.Prepare("UPDATE worlds SET world_name = ?, author = ?, world_size = ? WHERE id = ?")
if err != nil {
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
return
}
defer stmt.Close()
res, err := stmt.Exec(wi.WorldName, wi.Author, wi.WorldSize, 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))
}
}
type MissionInfo struct {
MissionName string `json:"missionName"` MissionName string `json:"missionName"`
BriefingName string `json:"briefingName"` BriefingName string `json:"briefingName"`
MissionNameSource string `json:"missionNameSource"` MissionNameSource string `json:"missionNameSource"`
@@ -154,7 +237,52 @@ type AttendanceLogItem struct {
ServerName string `json:"serverName"` ServerName string `json:"serverName"`
ServerProfile string `json:"serverProfile"` ServerProfile string `json:"serverProfile"`
MissionStart string `json:"missionStart"` MissionStart string `json:"missionStart"`
// situational MissionHash string `json:"missionHash"`
}
func writeMissionInfo(missionInfo string) {
functionName := "writeMissionInfo"
// missionInfo is json, parse it
var mi MissionInfo
err := json.Unmarshal([]byte(missionInfo), &mi)
if err != nil {
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
return
}
// get MySQL friendly datetime
// first, convert string to int
missionStartTime, err := strconv.ParseInt(mi.MissionStart, 10, 64)
if err != nil {
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
return
}
t := time.Unix(0, missionStartTime).Format("2006-01-02 15:04:05")
// write to log
writeLog(functionName, fmt.Sprintf(`["MissionName:%s BriefingName:%s MissionNameSource:%s OnLoadName:%s Author:%s ServerName:%s ServerProfile:%s MissionStart:%s MissionHash:%s", "INFO"]`, mi.MissionName, mi.BriefingName, mi.MissionNameSource, mi.OnLoadName, mi.Author, mi.ServerName, mi.ServerProfile, t, mi.MissionHash))
// write to database
// every mission is unique, so insert it
stmt, err := db.Prepare("INSERT INTO missions (mission_name, briefing_name, mission_name_source, on_load_name, author, server_name, server_profile, mission_start, mission_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")
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, t, mi.MissionHash)
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(`["Mission inserted with ID %d", "INFO"]`, lastID))
}
type AttendanceLogItem struct {
EventType string `json:"eventType"` EventType string `json:"eventType"`
PlayerId string `json:"playerId"` PlayerId string `json:"playerId"`
PlayerUID string `json:"playerUID"` PlayerUID string `json:"playerUID"`
@@ -162,6 +290,7 @@ type AttendanceLogItem struct {
SteamName string `json:"steamName"` SteamName string `json:"steamName"`
IsJIP bool `json:"isJIP"` IsJIP bool `json:"isJIP"`
RoleDescription string `json:"roleDescription"` RoleDescription string `json:"roleDescription"`
MissionHash string `json:"missionHash"`
} }
func writeAttendance(data string) { func writeAttendance(data string) {
@@ -175,14 +304,6 @@ func writeAttendance(data string) {
return return
} }
// get MySQL friendly datetime
// first, convert string to int
missionStartTime, err := strconv.ParseInt(event.MissionStart, 10, 64)
if err != nil {
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
return
}
t := time.Unix(0, missionStartTime).Format("2006-01-02 15:04:05")
// get MySQL friendly NOW // get MySQL friendly NOW
now := time.Now().Format("2006-01-02 15:04:05") now := time.Now().Format("2006-01-02 15:04:05")
@@ -194,16 +315,8 @@ func writeAttendance(data string) {
// send to DB // send to DB
result, err := db.ExecContext(context.Background(), `INSERT INTO AttendanceLog (timestamp, mission_name, briefing_name, mission_name_source, on_load_name, author, server_name, server_profile, mission_start, event_type, player_id, player_uid, profile_name, steam_name, is_jip, role_description) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, result, err := db.ExecContext(context.Background(), `INSERT INTO AttendanceLog (timestamp, event_type, player_id, player_uid, profile_name, steam_name, is_jip, role_description, mission_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
now, now,
event.MissionName,
event.BriefingName,
event.MissionNameSource,
event.OnLoadName,
event.Author,
event.ServerName,
event.ServerProfile,
t,
event.EventType, event.EventType,
event.PlayerId, event.PlayerId,
event.PlayerUID, event.PlayerUID,
@@ -211,6 +324,7 @@ func writeAttendance(data string) {
event.SteamName, event.SteamName,
event.IsJIP, event.IsJIP,
event.RoleDescription, event.RoleDescription,
event.MissionHash,
) )
if err != nil { if err != nil {
@@ -262,6 +376,14 @@ func goRVExtensionArgs(output *C.char, outputsize C.size_t, input *C.char, argv
go writeAttendance(out[0]) go writeAttendance(out[0])
} }
} }
case "logMission":
if argc == 1 {
go writeMissionInfo(out[0])
}
case "logWorld":
if argc == 1 {
go writeWorldInfo(out[0])
}
} }
// Return a result to Arma // Return a result to Arma