mirror of
https://github.com/indig0fox/Arma3-AttendanceTracker.git/
synced 2025-12-08 01:41:49 -06:00
initial version
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
*.log
|
||||
|
||||
\@17thAttendanceTracker/config.json
|
||||
|
||||
*.bak
|
||||
BIN
@17thAttendanceTracker/addons/AttendanceTracker.pbo
Normal file
BIN
@17thAttendanceTracker/addons/AttendanceTracker.pbo
Normal file
Binary file not shown.
25
@17thAttendanceTracker/addons/AttendanceTracker/config.cpp
Normal file
25
@17thAttendanceTracker/addons/AttendanceTracker/config.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
class CfgPatches {
|
||||
class AttendanceTracker {
|
||||
units[] = {};
|
||||
weapons[] = {};
|
||||
requiredVersion = 2.10;
|
||||
requiredAddons[] = {};
|
||||
author[] = {"IndigoFox"};
|
||||
authorUrl = "http://example.com";
|
||||
};
|
||||
};
|
||||
|
||||
class CfgFunctions {
|
||||
class attendanceTracker {
|
||||
class functions {
|
||||
file = "\AttendanceTracker\functions";
|
||||
class postInit {postInit = 1;};
|
||||
class eventHandlers {};
|
||||
class callbackHandler {postInit = 1;};
|
||||
class log {};
|
||||
class logMissionEvent {};
|
||||
class logServerEvent {};
|
||||
class timestamp {};
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
addMissionEventHandler ["ExtensionCallback", {
|
||||
params ["_name", "_function", "_data"];
|
||||
if !(_name == "AttendanceTracker") exitWith {};
|
||||
|
||||
// Validate data param
|
||||
if (isNil "_data") then {_data = ""};
|
||||
|
||||
if (_data isEqualTo "") exitWith {
|
||||
[
|
||||
format ["Callback empty data: %1", _function],
|
||||
"WARN"
|
||||
] call attendanceTracker_fnc_log;
|
||||
false;
|
||||
};
|
||||
|
||||
if (typeName _data != "STRING") exitWith {
|
||||
[
|
||||
format ["Callback invalid data: %1: %2", _function, _data],
|
||||
"WARN"
|
||||
] call attendanceTracker_fnc_log;
|
||||
false;
|
||||
};
|
||||
|
||||
// Parse response from string array
|
||||
private "_response";
|
||||
try {
|
||||
// diag_log format ["Raw callback: %1: %2", _function, _data];
|
||||
_response = parseSimpleArray _data;
|
||||
} catch {
|
||||
[
|
||||
format ["Callback invalid data: %1: %2: %3", _function, _data, _exception],
|
||||
"WARN"
|
||||
] call attendanceTracker_fnc_log;
|
||||
};
|
||||
|
||||
if (isNil "_response") exitWith {false};
|
||||
|
||||
switch (_function) do {
|
||||
case "connectDB": {
|
||||
systemChat format ["AttendanceTracker: %1", _response#0];
|
||||
[_response#0, _response#1, _function] call attendanceTracker_fnc_log;
|
||||
};
|
||||
default {
|
||||
_response call attendanceTracker_fnc_log;
|
||||
};
|
||||
};
|
||||
true;
|
||||
}];
|
||||
@@ -0,0 +1,78 @@
|
||||
[
|
||||
["OnUserConnected", {
|
||||
params ["_networkId", "_clientStateNumber", "_clientState"];
|
||||
private _userInfo = (getUserInfo _networkId);
|
||||
_userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"];
|
||||
if (_isHC) exitWith {};
|
||||
|
||||
[
|
||||
"ConnectedServer",
|
||||
_playerUID,
|
||||
_profileName,
|
||||
_steamName
|
||||
] call attendanceTracker_fnc_logServerEvent;
|
||||
|
||||
(AttendanceTracker getVariable ["allUsers", createHashMap]) set [_networkId, _userInfo];
|
||||
|
||||
[format ["(EventHandler) OnUserConnected fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log;
|
||||
}],
|
||||
["OnUserDisconnected", {
|
||||
params ["_networkId", "_clientStateNumber", "_clientState"];
|
||||
private _userInfo = (AttendanceTracker getVariable ["allUsers", createHashMap]) get [_networkId, nil];
|
||||
if (isNil "_userInfo") exitWith {};
|
||||
|
||||
_userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"];
|
||||
if (_isHC) exitWith {};
|
||||
|
||||
[
|
||||
"DisconnectedServer",
|
||||
_playerUID,
|
||||
_profileName,
|
||||
_steamName
|
||||
] call attendanceTracker_fnc_logServerEvent;
|
||||
|
||||
[format ["(EventHandler) OnUserDisconnected fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log;
|
||||
}],
|
||||
["PlayerConnected", {
|
||||
params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"];
|
||||
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",
|
||||
_playerUID,
|
||||
_profileName,
|
||||
_steamName,
|
||||
_jip,
|
||||
roleDescription _unit
|
||||
] call attendanceTracker_fnc_logMissionEvent;
|
||||
|
||||
[format ["(EventHandler) PlayerConnected fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log;
|
||||
}],
|
||||
["HandleDisconnect", {
|
||||
params ["_unit", "_id", "_uid", "_name"];
|
||||
private _userInfo = (AttendanceTracker getVariable ["allUsers", createHashMap]) get [_id toFixed 0, nil];
|
||||
if (isNil "_userInfo") exitWith {};
|
||||
|
||||
_userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"];
|
||||
|
||||
if (_isHC) exitWith {};
|
||||
|
||||
[
|
||||
"DisconnectedMission",
|
||||
_playerUID,
|
||||
_profileName,
|
||||
_steamName,
|
||||
_jip
|
||||
] call attendanceTracker_fnc_logMissionEvent;
|
||||
|
||||
[format ["(EventHandler) HandleDisconnect fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log;
|
||||
false;
|
||||
}]
|
||||
];
|
||||
@@ -0,0 +1,17 @@
|
||||
params [
|
||||
["_message", "", [""]],
|
||||
["_level", "INFO", [""]],
|
||||
"_function"
|
||||
];
|
||||
|
||||
if (isNil "_message") exitWith {false};
|
||||
|
||||
"AttendanceTracker" callExtension ["log", [_level, _message]];
|
||||
|
||||
if (!isNil "_function") then {
|
||||
diag_log formatText["[AttendanceTracker] (%1): <%2> %3", _level, _function, _message];
|
||||
} else {
|
||||
diag_log formatText["[AttendanceTracker] (%1): %2", _level, _message];
|
||||
};
|
||||
|
||||
true;
|
||||
@@ -0,0 +1,20 @@
|
||||
params [
|
||||
["_eventType", ""],
|
||||
["_playerUID", ""],
|
||||
["_profileName", ""],
|
||||
["_steamName", ""],
|
||||
["_isJIP", false, [true, false]],
|
||||
["_roleDescription", ""]
|
||||
];
|
||||
|
||||
private _hash = + (AttendanceTracker getVariable ["missionContext", createHashMap]);
|
||||
_hash set ["eventType", _eventType];
|
||||
_hash set ["playerUID", _playerUID];
|
||||
_hash set ["profileName", _profileName];
|
||||
_hash set ["steamName", _steamName];
|
||||
_hash set ["isJIP", _isJIP];
|
||||
_hash set ["roleDescription", _roleDescription];
|
||||
|
||||
"AttendanceTracker" callExtension ["logAttendance", [[_hash] call CBA_fnc_encodeJSON]];
|
||||
|
||||
true;
|
||||
@@ -0,0 +1,19 @@
|
||||
params [
|
||||
["_eventType", ""],
|
||||
["_playerUID", ""],
|
||||
["_profileName", ""],
|
||||
["_steamName", ""]
|
||||
];
|
||||
|
||||
|
||||
private _hash = + (AttendanceTracker getVariable ["missionContext", createHashMap]);
|
||||
_hash set ["eventType", _eventType];
|
||||
_hash set ["playerUID", _playerUID];
|
||||
_hash set ["profileName", _profileName];
|
||||
_hash set ["steamName", _steamName];
|
||||
_hash set ["isJIP", ""];
|
||||
_hash set ["roleDescription", ""];
|
||||
|
||||
"AttendanceTracker" callExtension ["logAttendance", [[_hash] call CBA_fnc_encodeJSON]];
|
||||
|
||||
true;
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
AttendanceTracker = false call CBA_fnc_createNamespace;
|
||||
|
||||
AttendanceTracker setVariable ["missionContext", createHashMapFromArray [
|
||||
["missionName", missionName],
|
||||
["briefingName", briefingName],
|
||||
["missionNameSource", missionNameSource],
|
||||
["onLoadName", getMissionConfigValue ["onLoadName", ""]],
|
||||
["author", getMissionConfigValue ["author", ""]],
|
||||
["serverName", serverName],
|
||||
["serverProfile", profileName],
|
||||
["missionStart", call attendanceTracker_fnc_timestamp]
|
||||
]];
|
||||
|
||||
// store all user details in a hash when they connect so we can reference it in disconnect events
|
||||
AttendanceTracker setVariable ["allUsers", createHashMap];
|
||||
|
||||
private _database = "AttendanceTracker" callExtension "connectDB";
|
||||
systemChat "AttendanceTracker: Connecting to database...";
|
||||
["Connecting to database...", "INFO"] call attendanceTracker_fnc_log;
|
||||
|
||||
{
|
||||
if (!isServer) exitWith {};
|
||||
_x params ["_ehName", "_code"];
|
||||
|
||||
_handle = (addMissionEventHandler [_ehName, _code]);
|
||||
if (isNil "_handle") then {
|
||||
[format["Failed to add Mission event handler: %1", _x], "ERROR"] call attendanceTracker_fnc_log;
|
||||
false;
|
||||
} else {
|
||||
missionNamespace setVariable [
|
||||
("AttendanceTracker" + "_MEH_" + _ehName),
|
||||
_handle
|
||||
];
|
||||
true;
|
||||
};
|
||||
} forEach (call attendanceTracker_fnc_eventHandlers);
|
||||
@@ -0,0 +1 @@
|
||||
(parseSimpleArray ("AttendanceTracker" callExtension "getTimestamp")) select 0;
|
||||
7
@17thAttendanceTracker/config.example.json
Normal file
7
@17thAttendanceTracker/config.example.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"mysqlHost": "127.0.0.1",
|
||||
"mysqlPort": 3306,
|
||||
"mysqlUser": "root",
|
||||
"mysqlPassword": "root",
|
||||
"mysqlDatabase": "db"
|
||||
}
|
||||
0
@17thAttendanceTracker/mod.cpp
Normal file
0
@17thAttendanceTracker/mod.cpp
Normal file
7
extension/@17thAttendanceTracker/config.example.json
Normal file
7
extension/@17thAttendanceTracker/config.example.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"mysqlHost": "127.0.0.1",
|
||||
"mysqlPort": 3306,
|
||||
"mysqlUser": "root",
|
||||
"mysqlPassword": "root",
|
||||
"mysqlDatabase": "db"
|
||||
}
|
||||
BIN
extension/AttendanceTracker_x64.dll
Normal file
BIN
extension/AttendanceTracker_x64.dll
Normal file
Binary file not shown.
86
extension/AttendanceTracker_x64.h
Normal file
86
extension/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
|
||||
61
extension/RVExtension.c
Normal file
61
extension/RVExtension.c
Normal file
@@ -0,0 +1,61 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "extensionCallback.h"
|
||||
|
||||
extern void goRVExtension(char *output, size_t outputSize, char *input);
|
||||
extern void goRVExtensionVersion(char *output, size_t outputSize);
|
||||
extern void goRVExtensionArgs(char *output, size_t outputSize, char *input, char **argv, int argc);
|
||||
extern void goRVExtensionRegisterCallback(extensionCallback fnc);
|
||||
|
||||
#ifdef WIN64
|
||||
__declspec(dllexport) void RVExtension(char *output, size_t outputSize, char *input)
|
||||
{
|
||||
goRVExtension(output, outputSize, input);
|
||||
}
|
||||
|
||||
__declspec(dllexport) void RVExtensionVersion(char *output, size_t outputSize)
|
||||
{
|
||||
goRVExtensionVersion(output, outputSize);
|
||||
}
|
||||
|
||||
__declspec(dllexport) void RVExtensionArgs(char *output, size_t outputSize, char *input, char **argv, int argc)
|
||||
{
|
||||
goRVExtensionArgs(output, outputSize, input, argv, argc);
|
||||
}
|
||||
|
||||
__declspec(dllexport) void RVExtensionRegisterCallback(extensionCallback fnc)
|
||||
{
|
||||
goRVExtensionRegisterCallback(fnc);
|
||||
}
|
||||
#else
|
||||
__declspec(dllexport) void __stdcall _RVExtension(char *output, size_t outputSize, char *input)
|
||||
{
|
||||
goRVExtension(output, outputSize, input);
|
||||
}
|
||||
|
||||
__declspec(dllexport) void __stdcall _RVExtensionVersion(char *output, size_t outputSize)
|
||||
{
|
||||
goRVExtensionVersion(output, outputSize);
|
||||
}
|
||||
|
||||
__declspec(dllexport) void __stdcall _RVExtensionArgs(char *output, size_t outputSize, char *input, char **argv, int argc)
|
||||
{
|
||||
goRVExtensionArgs(output, outputSize, input, argv, argc);
|
||||
}
|
||||
|
||||
__declspec(dllexport) void __stdcall _RVExtensionRegisterCallback(extensionCallback fnc)
|
||||
{
|
||||
goRVExtensionRegisterCallback(fnc);
|
||||
}
|
||||
#endif
|
||||
// do this for all the other exported functions
|
||||
|
||||
// dll entrypoint
|
||||
// Path: RVExtension.c
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
3
extension/build.txt
Normal file
3
extension/build.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
$ENV:GOARCH = "amd64"
|
||||
$ENV:CGO_ENABLED = 1
|
||||
go1.16.4 build -o AttendanceTracker_x64.dll -buildmode=c-shared .
|
||||
BIN
extension/callExtension.exe
Normal file
BIN
extension/callExtension.exe
Normal file
Binary file not shown.
BIN
extension/callExtension_x64.exe
Normal file
BIN
extension/callExtension_x64.exe
Normal file
Binary file not shown.
11
extension/extensionCallback.h
Normal file
11
extension/extensionCallback.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef int (*extensionCallback)(char const *name, char const *function, char const *data);
|
||||
|
||||
/* https://golang.org/cmd/cgo/#hdr-C_references_to_Go */
|
||||
static inline int runExtensionCallback(extensionCallback fnc, char const *name, char const *function, char const *data)
|
||||
{
|
||||
return fnc(name, function, data);
|
||||
}
|
||||
5
extension/go.mod
Normal file
5
extension/go.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module main.go
|
||||
|
||||
go 1.16
|
||||
|
||||
require github.com/go-sql-driver/mysql v1.6.0
|
||||
2
extension/go.sum
Normal file
2
extension/go.sum
Normal file
@@ -0,0 +1,2 @@
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
341
extension/main.go
Normal file
341
extension/main.go
Normal file
@@ -0,0 +1,341 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "extensionCallback.h"
|
||||
*/
|
||||
import "C" // This is required to import the C code
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
var EXTENSION_VERSION string = "0.0.1"
|
||||
var extensionCallbackFnc C.extensionCallback
|
||||
|
||||
// file paths
|
||||
var ADDON_FOLDER string = getDir() + "\\@17thAttendanceTracker"
|
||||
var LOG_FILE string = ADDON_FOLDER + "\\attendanceTracker.log"
|
||||
var CONFIG_FILE string = ADDON_FOLDER + "\\config.json"
|
||||
|
||||
var ATConfig AttendanceTrackerConfig
|
||||
|
||||
type AttendanceTrackerConfig struct {
|
||||
MySQLHost string `json:"mysqlHost"`
|
||||
MySQLPort int `json:"mysqlPort"`
|
||||
MySQLUser string `json:"mysqlUser"`
|
||||
MySQLPassword string `json:"mysqlPassword"`
|
||||
MySQLDatabase string `json:"mysqlDatabase"`
|
||||
}
|
||||
|
||||
// database connection
|
||||
var db *sql.DB
|
||||
|
||||
// configure log output
|
||||
func init() {
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
// log to file
|
||||
f, err := os.OpenFile(LOG_FILE, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
log.Fatalf("error opening file: %v", err)
|
||||
}
|
||||
// log to console as well
|
||||
// log.SetOutput(io.MultiWriter(f, os.Stdout))
|
||||
// log only to file
|
||||
log.SetOutput(f)
|
||||
}
|
||||
|
||||
func version() {
|
||||
functionName := "version"
|
||||
writeLog(functionName, fmt.Sprintf(`["AttendanceTracker Extension Version:%s", "INFO"]`, EXTENSION_VERSION))
|
||||
}
|
||||
|
||||
func getDir() string {
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
func loadConfig() {
|
||||
// load config from file as JSON
|
||||
functionName := "loadConfig"
|
||||
file, err := os.Open(CONFIG_FILE)
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
decoder := json.NewDecoder(file)
|
||||
err = decoder.Decode(&ATConfig)
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return
|
||||
}
|
||||
writeLog(functionName, `["Config loaded", "INFO"]`)
|
||||
}
|
||||
|
||||
func connectDB() string {
|
||||
functionName := "connectDB"
|
||||
var err 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)
|
||||
|
||||
db, err = sql.Open("mysql", connectionString)
|
||||
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"
|
||||
}
|
||||
|
||||
// Connect and check the server version
|
||||
var version string
|
||||
err = db.QueryRow("SELECT VERSION()").Scan(&version)
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
return "ERROR"
|
||||
}
|
||||
writeLog(functionName, fmt.Sprintf(`["Connected to MySQL/MariaDB version %s", "INFO"]`, version))
|
||||
return version
|
||||
}
|
||||
|
||||
type AttendanceLogItem struct {
|
||||
MissionName string `json:"missionName"`
|
||||
BriefingName string `json:"briefingName"`
|
||||
MissionNameSource string `json:"missionNameSource"`
|
||||
OnLoadName string `json:"onLoadName"`
|
||||
Author string `json:"author"`
|
||||
ServerName string `json:"serverName"`
|
||||
ServerProfile string `json:"serverProfile"`
|
||||
MissionStart string `json:"missionStart"`
|
||||
// situational
|
||||
EventType string `json:"eventType"`
|
||||
PlayerUID string `json:"playerUID"`
|
||||
ProfileName string `json:"profileName"`
|
||||
SteamName string `json:"steamName"`
|
||||
IsJIP bool `json:"isJIP"`
|
||||
RoleDescription string `json:"roleDescription"`
|
||||
}
|
||||
|
||||
func writeAttendance(data string) {
|
||||
functionName := "writeAttendance"
|
||||
// data is json, parse it
|
||||
stringjson := fixEscapeQuotes(trimQuotes(data))
|
||||
var event AttendanceLogItem
|
||||
err := json.Unmarshal([]byte(stringjson), &event)
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
||||
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
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
|
||||
// prevent crash
|
||||
if db == nil {
|
||||
writeLog(functionName, `["db is nil", "ERROR"]`)
|
||||
return
|
||||
}
|
||||
|
||||
// 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_uid, profile_name, steam_name, is_jip, role_description) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
||||
now,
|
||||
event.MissionName,
|
||||
event.BriefingName,
|
||||
event.MissionNameSource,
|
||||
event.OnLoadName,
|
||||
event.Author,
|
||||
event.ServerName,
|
||||
event.ServerProfile,
|
||||
t,
|
||||
event.EventType,
|
||||
event.PlayerUID,
|
||||
event.ProfileName,
|
||||
event.SteamName,
|
||||
event.IsJIP,
|
||||
event.RoleDescription,
|
||||
)
|
||||
|
||||
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))
|
||||
|
||||
}
|
||||
|
||||
func runExtensionCallback(name *C.char, function *C.char, data *C.char) C.int {
|
||||
return C.runExtensionCallback(extensionCallbackFnc, name, function, data)
|
||||
}
|
||||
|
||||
//export goRVExtensionVersion
|
||||
func goRVExtensionVersion(output *C.char, outputsize C.size_t) {
|
||||
result := C.CString(EXTENSION_VERSION)
|
||||
defer C.free(unsafe.Pointer(result))
|
||||
var size = C.strlen(result) + 1
|
||||
if size > outputsize {
|
||||
size = outputsize
|
||||
}
|
||||
C.memmove(unsafe.Pointer(output), unsafe.Pointer(result), size)
|
||||
}
|
||||
|
||||
//export goRVExtensionArgs
|
||||
func goRVExtensionArgs(output *C.char, outputsize C.size_t, input *C.char, argv **C.char, argc C.int) {
|
||||
var offset = unsafe.Sizeof(uintptr(0))
|
||||
var out []string
|
||||
for index := C.int(0); index < argc; index++ {
|
||||
out = append(out, C.GoString(*argv))
|
||||
argv = (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(argv)) + offset))
|
||||
}
|
||||
|
||||
// temp := fmt.Sprintf("Function: %s nb params: %d params: %s!", C.GoString(input), argc, out)
|
||||
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];
|
||||
if argc == 1 {
|
||||
go writeAttendance(out[0])
|
||||
}
|
||||
}
|
||||
|
||||
// Return a result to Arma
|
||||
result := C.CString(temp)
|
||||
defer C.free(unsafe.Pointer(result))
|
||||
var size = C.strlen(result) + 1
|
||||
if size > outputsize {
|
||||
size = outputsize
|
||||
}
|
||||
|
||||
C.memmove(unsafe.Pointer(output), unsafe.Pointer(result), size)
|
||||
}
|
||||
|
||||
func callBackExample() {
|
||||
name := C.CString("arma")
|
||||
defer C.free(unsafe.Pointer(name))
|
||||
function := C.CString("funcToExecute")
|
||||
defer C.free(unsafe.Pointer(function))
|
||||
// Make a callback to Arma
|
||||
for i := 0; i < 3; i++ {
|
||||
time.Sleep(2 * time.Second)
|
||||
param := C.CString(fmt.Sprintf("Loop: %d", i))
|
||||
defer C.free(unsafe.Pointer(param))
|
||||
runExtensionCallback(name, function, param)
|
||||
}
|
||||
}
|
||||
|
||||
func getTimestamp() int64 {
|
||||
// get the current unix timestamp in nanoseconds
|
||||
return time.Now().UnixNano()
|
||||
}
|
||||
|
||||
func trimQuotes(s string) string {
|
||||
// trim the start and end quotes from a string
|
||||
return strings.Trim(s, `"`)
|
||||
}
|
||||
|
||||
func fixEscapeQuotes(s string) string {
|
||||
// fix the escape quotes in a string
|
||||
return strings.Replace(s, `""`, `"`, -1)
|
||||
}
|
||||
|
||||
func writeLog(functionName string, data string) {
|
||||
statusName := C.CString("AttendanceTracker")
|
||||
defer C.free(unsafe.Pointer(statusName))
|
||||
statusFunction := C.CString(functionName)
|
||||
defer C.free(unsafe.Pointer(statusFunction))
|
||||
statusParam := C.CString(data)
|
||||
defer C.free(unsafe.Pointer(statusParam))
|
||||
runExtensionCallback(statusName, statusFunction, statusParam)
|
||||
|
||||
log.Printf(`%s: %s`, functionName, data)
|
||||
}
|
||||
|
||||
//export goRVExtension
|
||||
func goRVExtension(output *C.char, outputsize C.size_t, input *C.char) {
|
||||
|
||||
var temp string
|
||||
|
||||
// logLine("goRVExtension", fmt.Sprintf(`["Input: %s", "DEBUG"]`, C.GoString(input)), true)
|
||||
|
||||
switch C.GoString(input) {
|
||||
case "version":
|
||||
temp = EXTENSION_VERSION
|
||||
case "getDir":
|
||||
temp = getDir()
|
||||
case "getTimestamp":
|
||||
time := getTimestamp()
|
||||
temp = fmt.Sprintf(`["%s"]`, strconv.FormatInt(time, 10))
|
||||
case "connectDB":
|
||||
go connectDB()
|
||||
temp = fmt.Sprintf(`["%s"]`, "Connecting to DB")
|
||||
|
||||
default:
|
||||
temp = fmt.Sprintf(`["%s"]`, "Unknown Function")
|
||||
}
|
||||
|
||||
result := C.CString(temp)
|
||||
defer C.free(unsafe.Pointer(result))
|
||||
var size = C.strlen(result) + 1
|
||||
if size > outputsize {
|
||||
size = outputsize
|
||||
}
|
||||
|
||||
C.memmove(unsafe.Pointer(output), unsafe.Pointer(result), size)
|
||||
// return
|
||||
}
|
||||
|
||||
//export goRVExtensionRegisterCallback
|
||||
func goRVExtensionRegisterCallback(fnc C.extensionCallback) {
|
||||
extensionCallbackFnc = fnc
|
||||
}
|
||||
|
||||
func main() {}
|
||||
7
extension/test.sqf
Normal file
7
extension/test.sqf
Normal file
@@ -0,0 +1,7 @@
|
||||
freeExtension "AttendanceTracker";
|
||||
"AttendanceTracker" callExtension "connectDB";
|
||||
sleep 2;
|
||||
"attendanceTracker" callExtension ["logAttendance", ["{""playerUID"": ""76561197991996737"", ""roleDescription"": ""NULL"", ""missionNameSource"": ""aaaltisaiatk"", ""eventType"": ""ConnectedMission"", ""briefingName"": ""aaaltisaiatk"", ""profileName"": ""IndigoFox"", ""serverName"": ""IndigoFox on DESKTOP-6B2U0AT"", ""steamName"": ""IndigoFox"", ""onLoadName"": ""NULL"", ""missionName"": ""aaaltisaiatk"", ""isJIP"": false, ""author"": ""IndigoFox"", ""missionStart"": ""1682549469590908300""}"]]
|
||||
"attendanceTracker" callExtension ["logAttendance", ["{""playerUID"": ""76561197991996737"", ""roleDescription"": ""NULL"", ""missionNameSource"": ""aaaltisaiatk"", ""eventType"": ""ConnectedServer"", ""briefingName"": ""aaaltisaiatk"", ""profileName"": ""IndigoFox"", ""serverName"": ""IndigoFox on DESKTOP-6B2U0AT"", ""steamName"": ""IndigoFox"", ""onLoadName"": ""NULL"", ""missionName"": ""aaaltisaiatk"", ""isJIP"": false, ""author"": ""IndigoFox"", ""missionStart"": ""1682549469590908300""}"]]
|
||||
sleep 15;
|
||||
exit;
|
||||
Reference in New Issue
Block a user