diff --git a/@AttendanceTracker/addons/AttendanceTracker.pbo b/@AttendanceTracker/addons/AttendanceTracker.pbo index 2c04dde..c627f78 100644 Binary files a/@AttendanceTracker/addons/AttendanceTracker.pbo and b/@AttendanceTracker/addons/AttendanceTracker.pbo differ diff --git a/@AttendanceTracker/addons/AttendanceTracker/config.cpp b/@AttendanceTracker/addons/AttendanceTracker/config.cpp index 128ee5d..479b5c4 100644 --- a/@AttendanceTracker/addons/AttendanceTracker/config.cpp +++ b/@AttendanceTracker/addons/AttendanceTracker/config.cpp @@ -18,8 +18,8 @@ class CfgFunctions { class eventHandlers {}; class callbackHandler {postInit = 1;}; class log {}; - class logMissionEvent {}; - class logServerEvent {}; + class writeConnect {}; + class writeDisconnect {}; class timestamp {}; class getMissionHash {}; class getWorldInfo {}; diff --git a/@AttendanceTracker/addons/AttendanceTracker/functions/fn_callbackHandler.sqf b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_callbackHandler.sqf index 3d957d2..4a5d641 100644 --- a/@AttendanceTracker/addons/AttendanceTracker/functions/fn_callbackHandler.sqf +++ b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_callbackHandler.sqf @@ -46,6 +46,9 @@ addMissionEventHandler ["ExtensionCallback", { [_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", []]; // log mission info and get back the row Id to send with future messages private _response = "AttendanceTracker" callExtension [ diff --git a/@AttendanceTracker/addons/AttendanceTracker/functions/fn_eventHandlers.sqf b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_eventHandlers.sqf index 98a3546..a7b9c0a 100644 --- a/@AttendanceTracker/addons/AttendanceTracker/functions/fn_eventHandlers.sqf +++ b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_eventHandlers.sqf @@ -15,15 +15,16 @@ }; (AttendanceTracker getVariable ["allUsers", createHashMap]) set [_networkId, _userInfo]; - (AttendanceTracker getVariable ["rowIds", createHashMap]) set [_networkId, [nil, nil]]; // reset rowId on connect + [ "Server", _playerID, _playerUID, _profileName, _steamName, - nil // send rowId on d/c only - ] call attendanceTracker_fnc_logServerEvent; + nil, + nil + ] call attendanceTracker_fnc_writeConnect; }], ["OnUserDisconnected", { @@ -45,19 +46,13 @@ [format ["(EventHandler) OnUserDisconnected: %1 is HC, skipping", _playerID], "DEBUG"] call attendanceTracker_fnc_log; }; - private _rowId = ((AttendanceTracker getVariable ["rowIds", createHashMap]) getOrDefault [ - _networkId, - [nil, nil] - ]) select 0; - [ "Server", _playerID, _playerUID, _profileName, - _steamName, - (if (!isNil "_rowId") then {_rowId} else {nil}) // send rowId on d/c only - ] call attendanceTracker_fnc_logServerEvent; + _steamName + ] call attendanceTracker_fnc_writeDisconnect; }], ["PlayerConnected", { params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"]; @@ -79,6 +74,7 @@ }; (AttendanceTracker getVariable ["allUsers", createHashMap]) set [_playerID, _userInfo]; + [ "Mission", _playerID, @@ -86,9 +82,8 @@ _profileName, _steamName, _jip, - roleDescription _unit, - nil // send rowId on d/c only - ] call attendanceTracker_fnc_logMissionEvent; + roleDescription _unit + ] call attendanceTracker_fnc_writeConnect; }], ["PlayerDisconnected", { // NOTE: HandleDisconnect returns a DIFFERENT _id than PlayerDisconnected and above handlers, so we can't use it here @@ -109,11 +104,6 @@ if (_isHC) exitWith { [format ["(EventHandler) HandleDisconnect: %1 is HC, skipping", _playerID], "DEBUG"] call attendanceTracker_fnc_log; }; - - private _rowId = ((AttendanceTracker getVariable ["rowIds", createHashMap]) getOrDefault [ - _idstr, - [nil, nil] - ]) select 1; [ "Mission", @@ -122,10 +112,8 @@ _profileName, _steamName, _jip, - nil, - (if (!isNil "_rowId") then {_rowId} else {nil}) // send rowId on d/c only - ] call attendanceTracker_fnc_logMissionEvent; - + nil + ] call attendanceTracker_fnc_writeDisconnect; false; }], @@ -149,32 +137,24 @@ [format ["(EventHandler) OnUserKicked: %1 is HC, skipping", _playerID], "DEBUG"] call attendanceTracker_fnc_log; }; - private _rowId = ((AttendanceTracker getVariable ["rowIds", createHashMap]) getOrDefault [ - _networkId, - [nil, nil] - ]) select 0; - [ "Server", _playerID, _playerUID, _profileName, _steamName, - (if (!isNil "_rowId") then {_rowId} else {nil}) // send rowId on d/c only - ] call attendanceTracker_fnc_logServerEvent; + nil, + nil + ] call attendanceTracker_fnc_writeDisconnect; - - private _rowId = ((AttendanceTracker getVariable ["rowIds", createHashMap]) getOrDefault [ - _networkId, - [nil, nil] - ]) select 1; [ "Mission", _playerID, _playerUID, _profileName, _steamName, - nil // send rowId on d/c only - ] call attendanceTracker_fnc_logMissionEvent; + nil, + nil + ] call attendanceTracker_fnc_writeDisconnect; }] ]; \ No newline at end of file diff --git a/@AttendanceTracker/addons/AttendanceTracker/functions/fn_logMissionEvent.sqf b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_writeConnect.sqf similarity index 66% rename from @AttendanceTracker/addons/AttendanceTracker/functions/fn_logMissionEvent.sqf rename to @AttendanceTracker/addons/AttendanceTracker/functions/fn_writeConnect.sqf index 5684c44..a792d3e 100644 --- a/@AttendanceTracker/addons/AttendanceTracker/functions/fn_logMissionEvent.sqf +++ b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_writeConnect.sqf @@ -5,11 +5,11 @@ params [ ["_profileName", ""], ["_steamName", ""], ["_isJIP", false, [true, false]], - ["_roleDescription", ""], - ["_rowID", nil] + ["_roleDescription", ""] ]; private _hash = + (AttendanceTracker getVariable ["missionContext", createHashMap]); + _hash set ["eventType", _eventType]; _hash set ["playerId", _playerId]; _hash set ["playerUID", _playerUID]; @@ -19,11 +19,6 @@ _hash set ["isJIP", _isJIP]; _hash set ["roleDescription", _roleDescription]; _hash set ["missionHash", missionNamespace getVariable ["AttendanceTracker_missionHash", ""]]; -if (!isNil "_rowID") then { - _hash set ["rowID", _rowID]; - "AttendanceTracker" callExtension ["writeDisconnectEvent", [[_hash] call CBA_fnc_encodeJSON]]; -} else { - "AttendanceTracker" callExtension ["writeAttendance", [[_hash] call CBA_fnc_encodeJSON]]; -}; +"AttendanceTracker" callExtension ["writeAttendance", [[_hash] call CBA_fnc_encodeJSON]]; true; \ No newline at end of file diff --git a/@AttendanceTracker/addons/AttendanceTracker/functions/fn_logServerEvent.sqf b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_writeDisconnect.sqf similarity index 56% rename from @AttendanceTracker/addons/AttendanceTracker/functions/fn_logServerEvent.sqf rename to @AttendanceTracker/addons/AttendanceTracker/functions/fn_writeDisconnect.sqf index 6f7ec6a..c0e832a 100644 --- a/@AttendanceTracker/addons/AttendanceTracker/functions/fn_logServerEvent.sqf +++ b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_writeDisconnect.sqf @@ -4,26 +4,21 @@ params [ ["_playerUID", ""], ["_profileName", ""], ["_steamName", ""], - ["_rowID", nil] + ["_isJIP", false, [true, false]], + ["_roleDescription", ""] ]; - private _hash = + (AttendanceTracker getVariable ["missionContext", createHashMap]); -_hash set ["networkId", netID player]; + _hash set ["eventType", _eventType]; _hash set ["playerId", _playerId]; _hash set ["playerUID", _playerUID]; _hash set ["profileName", _profileName]; _hash set ["steamName", _steamName]; -_hash set ["isJIP", false]; -_hash set ["roleDescription", ""]; +_hash set ["isJIP", _isJIP]; +_hash set ["roleDescription", _roleDescription]; _hash set ["missionHash", missionNamespace getVariable ["AttendanceTracker_missionHash", ""]]; -if (!isNil "_rowID") then { - _hash set ["rowID", _rowID]; - "AttendanceTracker" callExtension ["writeDisconnectEvent", [[_hash] call CBA_fnc_encodeJSON]]; -} else { - "AttendanceTracker" callExtension ["writeAttendance", [[_hash] call CBA_fnc_encodeJSON]]; -}; +"AttendanceTracker" callExtension ["writeDisconnectEvent", [[_hash] call CBA_fnc_encodeJSON]]; true; \ No newline at end of file diff --git a/extension/AttendanceTracker_x64.dll b/extension/AttendanceTracker_x64.dll index e4ec5d4..0790143 100644 Binary files a/extension/AttendanceTracker_x64.dll and b/extension/AttendanceTracker_x64.dll differ diff --git a/extension/main.go b/extension/main.go index e4907f1..ab2d181 100644 --- a/extension/main.go +++ b/extension/main.go @@ -115,7 +115,7 @@ func getMissionHash() 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.Now().Format("2006-01-02 15:04:05"))) + hash := md5.Sum([]byte(time.Now().UTC().Format("2006-01-02 15:04:05"))) // convert to string hashString := fmt.Sprintf(`%x`, hash) @@ -154,7 +154,7 @@ func connectDB() string { return "ERROR" } - // Connect and check the server version + // Check the server version var version string err = db.QueryRow("SELECT VERSION()").Scan(&version) if err != nil { @@ -310,23 +310,22 @@ type AttendanceLogItem struct { IsJIP bool `json:"isJIP"` RoleDescription string `json:"roleDescription"` MissionHash string `json:"missionHash"` - // - RowID int64 `json:"rowID"` // optional } func writeAttendance(data string) { functionName := "writeAttendance" + var err error // data is json, parse it stringjson := fixEscapeQuotes(trimQuotes(data)) var event AttendanceLogItem - err := json.Unmarshal([]byte(stringjson), &event) + err = json.Unmarshal([]byte(stringjson), &event) if err != nil { writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err)) return } // get MySQL friendly NOW - now := time.Now().Format("2006-01-02 15:04:05") + now := time.Now().UTC().Format("2006-01-02 15:04:05") // prevent crash if db == nil { @@ -335,22 +334,44 @@ func writeAttendance(data string) { } // send to DB - result, err := db.ExecContext( - context.Background(), - fmt.Sprintf( + 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, - ), - now, - event.EventType, - event.PlayerId, - event.PlayerUID, - event.ProfileName, - event.SteamName, - event.IsJIP, - event.RoleDescription, - event.MissionHash, - ) + ) + 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)) @@ -373,8 +394,9 @@ func writeAttendance(data string) { } type DisconnectItem struct { - PlayerId string `json:"playerId"` - RowId string `json:"rowId"` + EventType string `json:"eventType"` + PlayerId string `json:"playerId"` + MissionHash string `json:"missionHash"` } func writeDisconnectEvent(data string) { @@ -389,7 +411,7 @@ func writeDisconnectEvent(data string) { } // get MySQL friendly NOW - now := time.Now().Format("2006-01-02 15:04:05") + now := time.Now().UTC().Format("2006-01-02 15:04:05") // prevent crash if db == nil { @@ -397,31 +419,88 @@ func writeDisconnectEvent(data string) { return } - // send to DB - result, err := db.ExecContext( - context.Background(), - fmt.Sprintf( - `UPDATE %s SET disconnect_time = ? WHERE id = ?`, - ATTENDANCE_TABLE, - ), - now, - event.RowId, - ) + // 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() - rowsAffected, err := result.RowsAffected() + // if there is a row, update it + if rows.Next() { + // create interface to hold values + var rowId int64 + + err = rows.Scan(&rowId) + 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, + ) + + _, err := db.ExecContext(context.Background(), sql) + if err != nil { + writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err)) + 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 + } + + sql := `call proc_filllastmissionnull` + + _, err := db.ExecContext(context.Background(), sql) if err != nil { writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err)) return } - - if rowsAffected == 1 { - writeLog(functionName, fmt.Sprintf(`["Saved disconnect event for %s to row id %s", "INFO"]`, event.PlayerId, event.RowId)) - } + writeLog(functionName, `["Filled mission event NULLs", "INFO"]`) } func runExtensionCallback(name *C.char, function *C.char, data *C.char) C.int { @@ -452,6 +531,10 @@ 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 { @@ -503,7 +586,7 @@ func callBackExample() { func getTimestamp() string { // get the current unix timestamp in nanoseconds // return time.Now().Local().Unix() - return time.Now().Format("2006-01-02 15:04:05") + return time.Now().UTC().Format("2006-01-02 15:04:05") } func trimQuotes(s string) string {