diff --git a/@17thAttendanceTracker/addons/AttendanceTracker.pbo b/@17thAttendanceTracker/addons/AttendanceTracker.pbo deleted file mode 100644 index f4781c4..0000000 Binary files a/@17thAttendanceTracker/addons/AttendanceTracker.pbo and /dev/null differ diff --git a/@17thAttendanceTracker/addons/AttendanceTracker/functions/fn_eventHandlers.sqf b/@17thAttendanceTracker/addons/AttendanceTracker/functions/fn_eventHandlers.sqf deleted file mode 100644 index ce1d0c6..0000000 --- a/@17thAttendanceTracker/addons/AttendanceTracker/functions/fn_eventHandlers.sqf +++ /dev/null @@ -1,91 +0,0 @@ -[ - ["OnUserConnected", { - params ["_networkId", "_clientStateNumber", "_clientState"]; - - [format ["(EventHandler) OnUserConnected fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log; - - private _userInfo = (getUserInfo _networkId); - _userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"]; - if (_isHC) exitWith {}; - - [ - "ConnectedServer", - _playerID, - _playerUID, - _profileName, - _steamName - ] call attendanceTracker_fnc_logServerEvent; - - (AttendanceTracker getVariable ["allUsers", createHashMap]) set [_networkId, _userInfo]; - }], - ["OnUserDisconnected", { - params ["_networkId", "_clientStateNumber", "_clientState"]; - - [format ["(EventHandler) OnUserDisconnected fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log; - - private _userInfo = (AttendanceTracker getVariable ["allUsers", createHashMap]) get _networkId; - if (isNil "_userInfo") exitWith {}; - - _userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"]; - if (_isHC) exitWith {}; - - [ - "DisconnectedServer", - _playerID, - _playerUID, - _profileName, - _steamName - ] call attendanceTracker_fnc_logServerEvent; - }], - ["PlayerConnected", { - params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"]; - - [format ["(EventHandler) PlayerConnected fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log; - - private _userInfo = (getUserInfo _idstr); - if (isNil "_userInfo") exitWith {}; - - _userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"]; - - (AttendanceTracker getVariable ["allUsers", createHashMap]) set [_playerID, _userInfo]; - - if (_isHC) exitWith {}; - - [ - "ConnectedMission", - _playerID, - _playerUID, - _profileName, - _steamName, - _jip, - roleDescription _unit - ] call attendanceTracker_fnc_logMissionEvent; - }], - ["PlayerDisconnected", { - // NOTE: HandleDisconnect returns a DIFFERENT _id than PlayerDisconnected and above handlers, so we can't use it here - params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"]; - - [format ["(EventHandler) HandleDisconnect fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log; - - private _userInfo = (AttendanceTracker getVariable ["allUsers", createHashMap]) get _idstr; - if (isNil "_userInfo") exitWith { - [format ["(EventHandler) HandleDisconnect: No user info found for %1", _idstr], "DEBUG"] call attendanceTracker_fnc_log; - }; - - _userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"]; - - if (_isHC) exitWith {}; - - [ - "DisconnectedMission", - _playerID, - _playerUID, - _profileName, - _steamName, - _jip - ] call attendanceTracker_fnc_logMissionEvent; - - - false; - }] -]; \ No newline at end of file diff --git a/@17thAttendanceTracker/addons/AttendanceTracker/functions/fn_timestamp.sqf b/@17thAttendanceTracker/addons/AttendanceTracker/functions/fn_timestamp.sqf deleted file mode 100644 index 19f8fb3..0000000 --- a/@17thAttendanceTracker/addons/AttendanceTracker/functions/fn_timestamp.sqf +++ /dev/null @@ -1 +0,0 @@ -(parseSimpleArray ("AttendanceTracker" callExtension "getTimestamp")) select 0; \ No newline at end of file diff --git a/@AttendanceTracker/addons/AttendanceTracker.pbo b/@AttendanceTracker/addons/AttendanceTracker.pbo new file mode 100644 index 0000000..35652aa Binary files /dev/null and b/@AttendanceTracker/addons/AttendanceTracker.pbo differ diff --git a/@17thAttendanceTracker/addons/AttendanceTracker/config.cpp b/@AttendanceTracker/addons/AttendanceTracker/config.cpp similarity index 77% rename from @17thAttendanceTracker/addons/AttendanceTracker/config.cpp rename to @AttendanceTracker/addons/AttendanceTracker/config.cpp index b191b30..479b5c4 100644 --- a/@17thAttendanceTracker/addons/AttendanceTracker/config.cpp +++ b/@AttendanceTracker/addons/AttendanceTracker/config.cpp @@ -18,9 +18,12 @@ class CfgFunctions { class eventHandlers {}; class callbackHandler {postInit = 1;}; class log {}; - class logMissionEvent {}; - class logServerEvent {}; + class writeConnect {}; + class writeDisconnect {}; class timestamp {}; + class getMissionHash {}; + class getWorldInfo {}; + class missionLoaded {}; }; }; }; \ No newline at end of file diff --git a/@17thAttendanceTracker/addons/AttendanceTracker/functions/fn_callbackHandler.sqf b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_callbackHandler.sqf similarity index 54% rename from @17thAttendanceTracker/addons/AttendanceTracker/functions/fn_callbackHandler.sqf rename to @AttendanceTracker/addons/AttendanceTracker/functions/fn_callbackHandler.sqf index b5a0223..077e583 100644 --- a/@17thAttendanceTracker/addons/AttendanceTracker/functions/fn_callbackHandler.sqf +++ b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_callbackHandler.sqf @@ -21,11 +21,16 @@ addMissionEventHandler ["ExtensionCallback", { false; }; + diag_log format ["Raw callback: %1: %2", _function, _data]; + // Parse response from string array private "_response"; try { // diag_log format ["Raw callback: %1: %2", _function, _data]; _response = parseSimpleArray _data; + if (_response isEqualTo []) then { + throw "Failed to parse response as array"; + }; } catch { [ format ["Callback invalid data: %1: %2: %3", _function, _data, _exception], @@ -41,18 +46,40 @@ 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 world info + private _response = "AttendanceTracker" callExtension [ + "logWorld", + [ + [(call attendanceTracker_fnc_getWorldInfo)] call CBA_fnc_encodeJSON + ] + ]; // 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 - ]]; + private _response = "AttendanceTracker" callExtension [ + "logMission", + [ + [AttendanceTracker getVariable ["missionContext", createHashMap]] call CBA_fnc_encodeJSON + ] + ]; + }; + }; + case "writeMissionInfo": { + 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]; }; }; default { diff --git a/@17thAttendanceTracker/addons/AttendanceTracker/functions/fn_connectDB.sqf b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_connectDB.sqf similarity index 100% rename from @17thAttendanceTracker/addons/AttendanceTracker/functions/fn_connectDB.sqf rename to @AttendanceTracker/addons/AttendanceTracker/functions/fn_connectDB.sqf diff --git a/@AttendanceTracker/addons/AttendanceTracker/functions/fn_eventHandlers.sqf b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_eventHandlers.sqf new file mode 100644 index 0000000..a7b9c0a --- /dev/null +++ b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_eventHandlers.sqf @@ -0,0 +1,160 @@ +[ + ["OnUserConnected", { + params ["_networkId", "_clientStateNumber", "_clientState"]; + + [format ["(EventHandler) OnUserConnected fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log; + + private _userInfo = (getUserInfo _networkId); + if (isNil "_userInfo") exitWith { + [format ["(EventHandler) OnUserConnected: No user info found for %1", _networkId], "DEBUG"] call attendanceTracker_fnc_log; + }; + + _userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"]; + if (_isHC) exitWith { + [format ["(EventHandler) OnUserConnected: %1 is HC, skipping", _playerID], "DEBUG"] call attendanceTracker_fnc_log; + }; + + (AttendanceTracker getVariable ["allUsers", createHashMap]) set [_networkId, _userInfo]; + + [ + "Server", + _playerID, + _playerUID, + _profileName, + _steamName, + nil, + nil + ] call attendanceTracker_fnc_writeConnect; + + }], + ["OnUserDisconnected", { + params ["_networkId", "_clientStateNumber", "_clientState"]; + + [format ["(EventHandler) OnUserDisconnected fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log; + + if !(call attendanceTracker_fnc_missionLoaded) exitWith { + [format ["(EventHandler) OnUserDisconnected: Server is in Mission Asked, likely mission selection state. Skipping.."], "DEBUG"] call attendanceTracker_fnc_log; + }; + + private _userInfo = (AttendanceTracker getVariable ["allUsers", createHashMap]) get _networkId; + if (isNil "_userInfo") exitWith { + [format ["(EventHandler) OnUserDisconnected: No user info found for %1", _networkId], "DEBUG"] call attendanceTracker_fnc_log; + }; + + _userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"]; + if (_isHC) exitWith { + [format ["(EventHandler) OnUserDisconnected: %1 is HC, skipping", _playerID], "DEBUG"] call attendanceTracker_fnc_log; + }; + + [ + "Server", + _playerID, + _playerUID, + _profileName, + _steamName + ] call attendanceTracker_fnc_writeDisconnect; + }], + ["PlayerConnected", { + params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"]; + + [format ["(EventHandler) PlayerConnected fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log; + + if !(call attendanceTracker_fnc_missionLoaded) exitWith { + [format ["(EventHandler) PlayerConnected: Server is in Mission Asked, likely mission selection state. Skipping.."], "DEBUG"] call attendanceTracker_fnc_log; + }; + + private _userInfo = (getUserInfo _idstr); + if (isNil "_userInfo") exitWith { + [format ["(EventHandler) PlayerConnected: No user info found for %1", _idstr], "DEBUG"] call attendanceTracker_fnc_log; + }; + + _userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"]; + if (_isHC) exitWith { + [format ["(EventHandler) PlayerConnected: %1 is HC, skipping", _playerID], "DEBUG"] call attendanceTracker_fnc_log; + }; + + (AttendanceTracker getVariable ["allUsers", createHashMap]) set [_playerID, _userInfo]; + + [ + "Mission", + _playerID, + _playerUID, + _profileName, + _steamName, + _jip, + 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 + params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"]; + + [format ["(EventHandler) HandleDisconnect fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log; + + if !(call attendanceTracker_fnc_missionLoaded) exitWith { + [format ["(EventHandler) HandleDisconnect: Server is in Mission Asked, likely mission selection state. Skipping.."], "DEBUG"] call attendanceTracker_fnc_log; + }; + + private _userInfo = (AttendanceTracker getVariable ["allUsers", createHashMap]) get _idstr; + if (isNil "_userInfo") exitWith { + [format ["(EventHandler) HandleDisconnect: No user info found for %1", _idstr], "DEBUG"] call attendanceTracker_fnc_log; + }; + + _userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit", "_rowId"]; + if (_isHC) exitWith { + [format ["(EventHandler) HandleDisconnect: %1 is HC, skipping", _playerID], "DEBUG"] call attendanceTracker_fnc_log; + }; + + [ + "Mission", + _playerID, + _playerUID, + _profileName, + _steamName, + _jip, + nil + ] call attendanceTracker_fnc_writeDisconnect; + + false; + }], + ["OnUserKicked", { + params ["_networkId", "_kickTypeNumber", "_kickType", "_kickReason", "_kickMessageIncReason"]; + + [format ["(EventHandler) OnUserKicked fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log; + + if !(call attendanceTracker_fnc_missionLoaded) exitWith { + [format ["(EventHandler) OnUserKicked: Server is in Mission Asked, likely mission selection state. Skipping.."], "DEBUG"] call attendanceTracker_fnc_log; + }; + + private _userInfo = (AttendanceTracker getVariable ["allUsers", createHashMap]) get _networkId; + if (isNil "_userInfo") exitWith { + [format ["(EventHandler) OnUserKicked: No user info found for %1", _networkId], "DEBUG"] call attendanceTracker_fnc_log; + }; + + _userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"]; + + if (_isHC) exitWith { + [format ["(EventHandler) OnUserKicked: %1 is HC, skipping", _playerID], "DEBUG"] call attendanceTracker_fnc_log; + }; + + [ + "Server", + _playerID, + _playerUID, + _profileName, + _steamName, + nil, + nil + ] call attendanceTracker_fnc_writeDisconnect; + + [ + "Mission", + _playerID, + _playerUID, + _profileName, + _steamName, + nil, + nil + ] call attendanceTracker_fnc_writeDisconnect; + }] +]; \ No newline at end of file diff --git a/@AttendanceTracker/addons/AttendanceTracker/functions/fn_getMissionHash.sqf b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_getMissionHash.sqf new file mode 100644 index 0000000..b1cf10a --- /dev/null +++ b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_getMissionHash.sqf @@ -0,0 +1 @@ +(parseSimpleArray ("AttendanceTracker" callExtension "getMissionHash")) select 0; \ No newline at end of file diff --git a/@17thAttendanceTracker/addons/AttendanceTracker/functions/fn_getWorldInfo.sqf b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_getWorldInfo.sqf similarity index 53% rename from @17thAttendanceTracker/addons/AttendanceTracker/functions/fn_getWorldInfo.sqf rename to @AttendanceTracker/addons/AttendanceTracker/functions/fn_getWorldInfo.sqf index 75bc7d7..28b0c55 100644 --- a/@17thAttendanceTracker/addons/AttendanceTracker/functions/fn_getWorldInfo.sqf +++ b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_getWorldInfo.sqf @@ -14,10 +14,15 @@ _workshopID = ''; } foreach getLoadedModsInfo; // [_name, _author, _workshopID]; - -[ - ["worldName", _name], +_return = createHashMapFromArray [ ["author", _author], + ["workshopID", _workshopID], + ["displayName", _name], + ["worldName", toLower worldName], + ["worldNameOriginal", _name], ["worldSize", worldSize], - ["workshopID", _workshopID] -]; \ No newline at end of file + ["latitude", getNumber( _world >> "latitude" )], + ["longitude", getNumber( _world >> "longitude" )] +]; +diag_log format ["Attendance Tracker: WorldInfo is: %1", _return]; +_return diff --git a/@17thAttendanceTracker/addons/AttendanceTracker/functions/fn_log.sqf b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_log.sqf similarity index 100% rename from @17thAttendanceTracker/addons/AttendanceTracker/functions/fn_log.sqf rename to @AttendanceTracker/addons/AttendanceTracker/functions/fn_log.sqf diff --git a/@AttendanceTracker/addons/AttendanceTracker/functions/fn_missionLoaded.sqf b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_missionLoaded.sqf new file mode 100644 index 0000000..b526f86 --- /dev/null +++ b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_missionLoaded.sqf @@ -0,0 +1 @@ +!(getClientStateNumber <= 5 || getClientStateNumber isEqualTo 11); \ No newline at end of file diff --git a/@17thAttendanceTracker/addons/AttendanceTracker/functions/fn_postInit.sqf b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_postInit.sqf similarity index 72% rename from @17thAttendanceTracker/addons/AttendanceTracker/functions/fn_postInit.sqf rename to @AttendanceTracker/addons/AttendanceTracker/functions/fn_postInit.sqf index f9fe4bd..9f528dd 100644 --- a/@17thAttendanceTracker/addons/AttendanceTracker/functions/fn_postInit.sqf +++ b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_postInit.sqf @@ -2,7 +2,9 @@ AttendanceTracker = false call CBA_fnc_createNamespace; AttendanceTracker_missionStartTimestamp = call attendanceTracker_fnc_timestamp; -AttendanceTracker_missionHash = "AttendanceTracker" callExtension ["getMissionHash", AttendanceTracker_missionStartTimestamp]; +diag_log format ["AttendanceTracker: Mission started at %1", AttendanceTracker_missionStartTimestamp]; +AttendanceTracker_missionHash = call attendanceTracker_fnc_getMissionHash; +diag_log format ["AttendanceTracker: Mission hash is %1", AttendanceTracker_missionHash]; AttendanceTracker setVariable ["missionContext", createHashMapFromArray [ ["missionName", missionName], @@ -13,13 +15,15 @@ AttendanceTracker setVariable ["missionContext", createHashMapFromArray [ ["serverName", serverName], ["serverProfile", profileName], ["missionStart", AttendanceTracker_missionStartTimestamp], - ["missionHash", AttendanceTracker_missionHash] + ["missionHash", AttendanceTracker_missionHash], + ["worldName", toLower worldName] ]]; // 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; diff --git a/@AttendanceTracker/addons/AttendanceTracker/functions/fn_timestamp.sqf b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_timestamp.sqf new file mode 100644 index 0000000..b578b47 --- /dev/null +++ b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_timestamp.sqf @@ -0,0 +1,24 @@ +// (parseSimpleArray ("AttendanceTracker" callExtension "getTimestamp")) select 0; + +// need date for MySQL in format 2006-01-02 15:04:05 + +systemTimeUTC params [ + "_year", + "_month", + "_day", + "_hour", + "_minute", + "_second", + "_millisecond" +]; + +format[ + "%1-%2-%3 %4:%5:%6", + _year, + _month, + _day, + _hour, + _minute, + _second +]; + diff --git a/@17thAttendanceTracker/addons/AttendanceTracker/functions/fn_logMissionEvent.sqf b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_writeConnect.sqf similarity index 86% rename from @17thAttendanceTracker/addons/AttendanceTracker/functions/fn_logMissionEvent.sqf rename to @AttendanceTracker/addons/AttendanceTracker/functions/fn_writeConnect.sqf index be3c8ce..a792d3e 100644 --- a/@17thAttendanceTracker/addons/AttendanceTracker/functions/fn_logMissionEvent.sqf +++ b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_writeConnect.sqf @@ -9,6 +9,7 @@ params [ ]; private _hash = + (AttendanceTracker getVariable ["missionContext", createHashMap]); + _hash set ["eventType", _eventType]; _hash set ["playerId", _playerId]; _hash set ["playerUID", _playerUID]; @@ -18,6 +19,6 @@ _hash set ["isJIP", _isJIP]; _hash set ["roleDescription", _roleDescription]; _hash set ["missionHash", missionNamespace getVariable ["AttendanceTracker_missionHash", ""]]; -"AttendanceTracker" callExtension ["logAttendance", [[_hash] call CBA_fnc_encodeJSON]]; +"AttendanceTracker" callExtension ["writeAttendance", [[_hash] call CBA_fnc_encodeJSON]]; true; \ No newline at end of file diff --git a/@17thAttendanceTracker/addons/AttendanceTracker/functions/fn_logServerEvent.sqf b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_writeDisconnect.sqf similarity index 64% rename from @17thAttendanceTracker/addons/AttendanceTracker/functions/fn_logServerEvent.sqf rename to @AttendanceTracker/addons/AttendanceTracker/functions/fn_writeDisconnect.sqf index 613ecce..c0e832a 100644 --- a/@17thAttendanceTracker/addons/AttendanceTracker/functions/fn_logServerEvent.sqf +++ b/@AttendanceTracker/addons/AttendanceTracker/functions/fn_writeDisconnect.sqf @@ -3,20 +3,22 @@ params [ ["_playerId", ""], ["_playerUID", ""], ["_profileName", ""], - ["_steamName", ""] + ["_steamName", ""], + ["_isJIP", false, [true, false]], + ["_roleDescription", ""] ]; - private _hash = + (AttendanceTracker getVariable ["missionContext", createHashMap]); + _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", ""]]; -"AttendanceTracker" callExtension ["logAttendance", [[_hash] call CBA_fnc_encodeJSON]]; +"AttendanceTracker" callExtension ["writeDisconnectEvent", [[_hash] call CBA_fnc_encodeJSON]]; true; \ No newline at end of file diff --git a/@17thAttendanceTracker/config.example.json b/@AttendanceTracker/config.example.json similarity index 100% rename from @17thAttendanceTracker/config.example.json rename to @AttendanceTracker/config.example.json diff --git a/@AttendanceTracker/config.json b/@AttendanceTracker/config.json new file mode 100644 index 0000000..2358f51 --- /dev/null +++ b/@AttendanceTracker/config.json @@ -0,0 +1,7 @@ +{ + "mysqlHost": "127.0.0.1", + "mysqlPort": 12730, + "mysqlUser": "root", + "mysqlPassword": "i&Lz8A3RuPcY5b326ALXgjl", + "mysqlDatabase": "testdb" +} \ No newline at end of file diff --git a/@17thAttendanceTracker/mod.cpp b/@AttendanceTracker/mod.cpp similarity index 100% rename from @17thAttendanceTracker/mod.cpp rename to @AttendanceTracker/mod.cpp diff --git a/README.md b/README.md index 0cbbef2..6519283 100644 --- a/README.md +++ b/README.md @@ -7,31 +7,68 @@ Create a database with a name of your choosing. Then, run the following SQL command against it to create a table. ```sql -CREATE TABLE `attendancelog` ( - `id` INT(11) NOT NULL AUTO_INCREMENT, - `timestamp` DATETIME NOT NULL, - `event_hash` VARCHAR(100) NOT NULL DEFAULT md5(concat(`server_name`,`mission_name`,`author`,`mission_start`)) COLLATE 'utf8mb3_general_ci', - `event_type` VARCHAR(100) NOT NULL COLLATE 'utf8mb3_general_ci', - `player_id` VARCHAR(30) NOT NULL COLLATE 'utf8mb3_general_ci', - `player_uid` VARCHAR(100) NOT NULL COLLATE 'utf8mb3_general_ci', - `profile_name` VARCHAR(100) NOT NULL COLLATE 'utf8mb3_general_ci', - `steam_name` VARCHAR(100) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', - `is_jip` TINYINT(4) NULL DEFAULT NULL, - `role_description` VARCHAR(100) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', - `mission_start` DATETIME NOT NULL, - `mission_name` VARCHAR(100) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', - `briefing_name` VARCHAR(100) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', - `mission_name_source` VARCHAR(100) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', - `on_load_name` VARCHAR(100) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', - `author` VARCHAR(100) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', - `server_name` VARCHAR(100) NOT NULL COLLATE 'utf8mb3_general_ci', - `server_profile` VARCHAR(100) NOT NULL COLLATE 'utf8mb3_general_ci', - PRIMARY KEY (`id`) USING BTREE +-- a3server.attendancelog definition + +CREATE TABLE `attendance` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `join_time` DATETIME NULL DEFAULT NULL, + `disconnect_time` DATETIME NULL DEFAULT NULL, + `mission_hash` VARCHAR(100) NULL DEFAULT '' COLLATE 'utf8mb3_general_ci', + `event_type` VARCHAR(100) NOT NULL COLLATE 'utf8mb3_general_ci', + `player_id` VARCHAR(30) NOT NULL COLLATE 'utf8mb3_general_ci', + `player_uid` VARCHAR(100) NOT NULL COLLATE 'utf8mb3_general_ci', + `profile_name` VARCHAR(100) NOT NULL COLLATE 'utf8mb3_general_ci', + `steam_name` VARCHAR(100) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', + `is_jip` TINYINT(4) NULL DEFAULT NULL, + `role_description` VARCHAR(100) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', + PRIMARY KEY (`id`) USING BTREE ) COLLATE='utf8mb3_general_ci' ENGINE=InnoDB -AUTO_INCREMENT=383 +AUTO_INCREMENT=5868 ; + + +-- a3server.`missions` definition + +CREATE TABLE `missions` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `mission_name` VARCHAR(100) NOT NULL COLLATE 'utf8mb3_general_ci', + `mission_name_source` VARCHAR(100) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', + `briefing_name` VARCHAR(100) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', + `on_load_name` VARCHAR(100) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', + `author` VARCHAR(100) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', + `server_name` VARCHAR(100) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', + `server_profile` VARCHAR(100) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', + `mission_start` DATETIME NULL DEFAULT NULL, + `mission_hash` VARCHAR(100) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', + PRIMARY KEY (`id`) USING BTREE +) +COLLATE='utf8mb3_general_ci' +ENGINE=InnoDB +; + + + +-- a3server.`worlds` definition + +CREATE TABLE `worlds` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `author` VARCHAR(100) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', + `display_name` VARCHAR(100) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', + `world_name` VARCHAR(100) NOT NULL COLLATE 'utf8mb3_general_ci', + `world_name_original` VARCHAR(100) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', + `world_size` INT(11) NULL DEFAULT NULL, + `latitude` FLOAT NULL DEFAULT NULL, + `longitude` FLOAT NULL DEFAULT NULL, + `workshop_id` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', + PRIMARY KEY (`id`) USING BTREE +) +COLLATE='utf8mb3_general_ci' +ENGINE=InnoDB +AUTO_INCREMENT=2 +; + ``` -Finally, copy `config.example.json` to `config.json` and update it with your database credentials. \ No newline at end of file +Finally, copy `config.example.json` to `config.json` and update it with your database credentials. diff --git a/extension/@17thAttendanceTracker/config.example.json b/extension/@AttendanceTracker/config.example.json similarity index 100% rename from extension/@17thAttendanceTracker/config.example.json rename to extension/@AttendanceTracker/config.example.json diff --git a/extension/AttendanceTracker_x64.dll b/extension/AttendanceTracker_x64.dll index f84d35e..cd29838 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 f31640e..d8f11f9 100644 --- a/extension/main.go +++ b/extension/main.go @@ -16,7 +16,8 @@ import ( "fmt" "log" "os" - "strconv" + "path" + "runtime" "strings" "time" "unsafe" @@ -32,6 +33,12 @@ var ADDON_FOLDER string = getDir() + "\\@AttendanceTracker" var LOG_FILE string = ADDON_FOLDER + "\\attendanceTracker.log" var CONFIG_FILE string = ADDON_FOLDER + "\\config.json" +var ATTENDANCE_TABLE string = "attendance" +var MISSIONS_TABLE string = "missions" +var WORLDS_TABLE string = "worlds" + +// ! TODO make a hash to save key:netId from A3 value:rowId from join event + var ATConfig AttendanceTrackerConfig type AttendanceTrackerConfig struct { @@ -104,14 +111,14 @@ func loadConfig() { writeLog(functionName, `["Config loaded", "INFO"]`) } -func getMissionHash(time string) string { +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)) + hash := md5.Sum([]byte(time.Now().UTC().Format("2006-01-02 15:04:05"))) // convert to string - hashString := fmt.Sprintf("%x", hash) + hashString := fmt.Sprintf(`%x`, hash) writeLog(functionName, fmt.Sprintf(`["Mission hash: %s", "INFO"]`, hashString)) return hashString } @@ -147,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 { @@ -160,38 +167,47 @@ func connectDB() string { } type WorldInfo struct { - WorldName string `json:"worldName"` - Author string `json:"author"` - WorldSize int `json:"worldSize"` - WorkshopID string `json:"workshopID"` + Author string `json:"author"` + WorkshopID string `json:"workshopID"` + DisplayName string `json:"displayName"` + WorldName string `json:"worldName"` + WorldNameOriginal string `json:"worldNameOriginal"` + WorldSize int `json:"worldSize"` + Latitude float32 `json:"latitude"` + Longitude float32 `json:"longitude"` } func writeWorldInfo(worldInfo string) { functionName := "writeWorldInfo" + // writeLog(functionName, fmt.Sprintf(`["%s", "DEBUG"]`, worldInfo)) // worldInfo is json, parse it var wi WorldInfo - err := json.Unmarshal([]byte(worldInfo), &wi) + fixedString := fixEscapeQuotes(trimQuotes(worldInfo)) + err := json.Unmarshal([]byte(fixedString), &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 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 workshop_id = ?", wi.WorkshopID).Scan(&worldID) + 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("INSERT INTO worlds (world_name, author, world_size, workshop_id) VALUES (?, ?, ?, ?)") + 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.WorldName, wi.Author, wi.WorldSize, wi.WorkshopID) + 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 @@ -208,13 +224,16 @@ func writeWorldInfo(worldInfo string) { } } else { // world exists, update it - stmt, err := db.Prepare("UPDATE worlds SET world_name = ?, author = ?, world_size = ? WHERE id = ?") + 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.WorldName, wi.Author, wi.WorldSize, worldID) + 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 @@ -238,48 +257,74 @@ type MissionInfo struct { ServerProfile string `json:"serverProfile"` MissionStart string `json:"missionStart"` MissionHash string `json:"missionHash"` + WorldName string `json:"worldName"` } func writeMissionInfo(missionInfo string) { functionName := "writeMissionInfo" + var err error + // writeLog(functionName, fmt.Sprintf(`["%s", "DEBUG"]`, missionInfo)) // missionInfo is json, parse it var mi MissionInfo - err := json.Unmarshal([]byte(missionInfo), &mi) + fixedString := fixEscapeQuotes(trimQuotes(missionInfo)) + err = json.Unmarshal([]byte(fixedString), &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) + // 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 } - 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) + 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) + 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)) 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)) + writeLog(functionName, fmt.Sprintf(`["MISSION_ID", "%d"]`, lastID)) } type AttendanceLogItem struct { @@ -295,17 +340,18 @@ type AttendanceLogItem struct { 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 { @@ -314,18 +360,44 @@ func writeAttendance(data string) { } // send to DB + var result sql.Result - 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, - event.EventType, - event.PlayerId, - event.PlayerUID, - event.ProfileName, - event.SteamName, - event.IsJIP, - event.RoleDescription, - event.MissionHash, - ) + 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)) @@ -339,9 +411,124 @@ func writeAttendance(data string) { } 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) + 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 + } + writeLog(functionName, `["Filled mission event NULLs", "INFO"]`) +} + func runExtensionCallback(name *C.char, function *C.char, data *C.char) C.int { return C.runExtensionCallback(extensionCallbackFnc, name, function, data) } @@ -370,12 +557,23 @@ 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 "logAttendance": - { // callExtension ["serverEvent", [_hash] call CBA_fnc_encodeJSON]; + case "fillLastMissionNull": + { + go fillLastMissionNull() + } + case "writeAttendance": + { // callExtension ["logAttendance", [_hash] call CBA_fnc_encodeJSON]]; if argc == 1 { go writeAttendance(out[0]) } } + case "writeDisconnectEvent": + { // callExtension ["writeDisconnectEvent", [[_hash] call CBA_fnc_encodeJSON]]; + + if argc == 1 { + go writeDisconnectEvent(out[0]) + } + } case "logMission": if argc == 1 { go writeMissionInfo(out[0]) @@ -411,9 +609,10 @@ func callBackExample() { } } -func getTimestamp() int64 { +func getTimestamp() string { // get the current unix timestamp in nanoseconds - return time.Now().UnixNano() + // return time.Now().Local().Unix() + return time.Now().UTC().Format("2006-01-02 15:04:05") } func trimQuotes(s string) string { @@ -435,6 +634,9 @@ func writeLog(functionName string, data string) { defer C.free(unsafe.Pointer(statusParam)) runExtensionCallback(statusName, statusFunction, statusParam) + // 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) } @@ -451,12 +653,12 @@ func goRVExtension(output *C.char, outputsize C.size_t, input *C.char) { case "getDir": temp = getDir() case "getTimestamp": - time := getTimestamp() - temp = fmt.Sprintf(`["%s"]`, strconv.FormatInt(time, 10)) + temp = fmt.Sprintf(`["%s"]`, getTimestamp()) case "connectDB": go connectDB() temp = fmt.Sprintf(`["%s"]`, "Connecting to DB") - + case "getMissionHash": + temp = fmt.Sprintf(`["%s"]`, getMissionHash()) default: temp = fmt.Sprintf(`["%s"]`, "Unknown Function") }