mirror of
https://github.com/indig0fox/Arma3-AttendanceTracker.git/
synced 2025-12-08 09:51:47 -06:00
- use writeConnect all the time unless filling old entries - update column names to clarify UTC datetime - GORM omit Mission association for server events that won't have one - add disconnectDB function to reinit while active
743 lines
21 KiB
Go
743 lines
21 KiB
Go
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 (
|
|
"crypto/md5"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"path"
|
|
"reflect"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"unsafe"
|
|
|
|
_ "github.com/go-sql-driver/mysql"
|
|
"gorm.io/driver/mysql"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
var EXTENSION_VERSION string = "0.0.1"
|
|
var extensionCallbackFnc C.extensionCallback
|
|
|
|
// file paths
|
|
var ADDON_FOLDER string = getDir() + "\\@AttendanceTracker"
|
|
var LOG_FILE string = ADDON_FOLDER + "\\attendanceTracker.log"
|
|
var CONFIG_FILE string = ADDON_FOLDER + "\\config.json"
|
|
var SERVER_TIME_FILE string = ADDON_FOLDER + "\\lastServerTime.txt"
|
|
|
|
var ATTENDANCE_TABLE string = "attendance"
|
|
var MISSIONS_TABLE string = "missions"
|
|
var WORLDS_TABLE string = "worlds"
|
|
|
|
var LAST_SERVER_TIME uint64 = 0
|
|
|
|
// ! TODO make a hash to save key:netId from A3 value:rowId from join event
|
|
|
|
var Config AttendanceTrackerConfig
|
|
var ATConfig ATSQLConfig
|
|
var A3Config ArmaConfig
|
|
|
|
type ArmaConfig struct {
|
|
DBUpdateIntervalSeconds int `json:"dbUpdateIntervalSeconds"`
|
|
Debug bool `json:"debug"`
|
|
ServerEventFillNullMinutes int `json:"serverEventFillNullMinutes"`
|
|
MissionEventFillNullMinutes int `json:"missionEventFillNullMinutes"`
|
|
}
|
|
|
|
type ATSQLConfig struct {
|
|
MySQLHost string `json:"mysqlHost"`
|
|
MySQLPort int `json:"mysqlPort"`
|
|
MySQLUser string `json:"mysqlUser"`
|
|
MySQLPassword string `json:"mysqlPassword"`
|
|
MySQLDatabase string `json:"mysqlDatabase"`
|
|
}
|
|
|
|
type AttendanceTrackerConfig struct {
|
|
ArmaConfig ArmaConfig `json:"armaConfig"`
|
|
SQLConfig ATSQLConfig `json:"sqlConfig"`
|
|
}
|
|
|
|
// database connection
|
|
var db *gorm.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 {
|
|
writeLog("getDir", fmt.Sprintf(`["Error getting working directory: %v", "ERROR"]`, err))
|
|
return ""
|
|
}
|
|
return dir
|
|
}
|
|
|
|
func loadConfig() {
|
|
// load config from file as JSON
|
|
functionName := "loadConfig"
|
|
|
|
// get location of this dll
|
|
// dllPath, err := filepath.Abs(os.Args[0])
|
|
// if err != nil {
|
|
// writeLog(functionName, fmt.Sprintf(`["Error getting DLL path: %v", "ERROR"]`, err))
|
|
// return
|
|
// }
|
|
|
|
// set the addon directory to the parent directory of the dll
|
|
// ADDON_FOLDER = filepath.Dir(dllPath)
|
|
// LOG_FILE = ADDON_FOLDER + "\\attendanceTracker.log"
|
|
// CONFIG_FILE = ADDON_FOLDER + "\\config.json"
|
|
|
|
file, err := os.OpenFile(CONFIG_FILE, os.O_RDONLY|os.O_CREATE, 0666)
|
|
if err != nil {
|
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
// log.Println("Loading config from", CONFIG_FILE)
|
|
|
|
decoder := json.NewDecoder(file)
|
|
err = decoder.Decode(&Config)
|
|
if err != nil {
|
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
|
return
|
|
}
|
|
A3Config = Config.ArmaConfig
|
|
ATConfig = Config.SQLConfig
|
|
|
|
writeLog(functionName, `["Config loaded", "INFO"]`)
|
|
}
|
|
|
|
func getSettings() string {
|
|
// get settings from A3Config and send to Arma
|
|
var settings string = `[`
|
|
// iterate through keys in A3Config struct
|
|
v := reflect.ValueOf(A3Config)
|
|
for i := 0; i < v.NumField(); i++ {
|
|
// get field name
|
|
fieldName := v.Type().Field(i).Name
|
|
// get field value
|
|
fieldValue := v.Field(i).Interface()
|
|
// if field value is a string, add quotes
|
|
fieldValueString := fmt.Sprintf("%v", fieldValue)
|
|
if reflect.TypeOf(fieldValue).Kind() == reflect.String {
|
|
fieldValueString = fmt.Sprintf(`"%v"`, fieldValue)
|
|
}
|
|
// add to settings, key should be lowercase
|
|
settings += fmt.Sprintf(`["%s", %s],`, strings.ToLower(fieldName), fieldValueString)
|
|
}
|
|
|
|
// remove last comma
|
|
settings = strings.TrimSuffix(settings, ",")
|
|
settings += `]`
|
|
|
|
return settings
|
|
}
|
|
|
|
func getMissionHash() string {
|
|
functionName := "getMissionHash"
|
|
// get md5 hash of string
|
|
// 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")))
|
|
|
|
// convert to string
|
|
hashString := fmt.Sprintf(`%x`, hash)
|
|
writeLog(functionName, fmt.Sprintf(`["Mission hash: %s", "INFO"]`, hashString))
|
|
return hashString
|
|
}
|
|
|
|
func updateServerTime(serverTime uint64) {
|
|
functionName := "updateServerTime"
|
|
|
|
var err error
|
|
|
|
// check .txt file for server time
|
|
// first, check if it exists
|
|
if _, err := os.Stat(SERVER_TIME_FILE); os.IsNotExist(err) {
|
|
// file does not exist, create it and write serverTime to it
|
|
writeLog(functionName, `["Server time file does not exist, creating it", "DEBUG"]`)
|
|
err = ioutil.WriteFile(SERVER_TIME_FILE, []byte(strconv.FormatUint(serverTime, 10)), 0666)
|
|
if err != nil {
|
|
writeLog(functionName, fmt.Sprintf(`["Error writing server time to file: %v", "ERROR"]`, err))
|
|
}
|
|
return
|
|
}
|
|
|
|
// file exists, read it
|
|
line, err := ioutil.ReadFile(SERVER_TIME_FILE)
|
|
if err != nil {
|
|
writeLog(functionName, fmt.Sprintf(`["Error reading server time file: %v", "ERROR"]`, err))
|
|
return
|
|
}
|
|
|
|
// convert to uint64
|
|
LAST_SERVER_TIME, err := strconv.ParseUint(string(line), 10, 64)
|
|
if err != nil {
|
|
writeLog(functionName, fmt.Sprintf(`["Error converting server time to uint64: %v", "ERROR"]`, err))
|
|
return
|
|
}
|
|
|
|
// if serverTime is less than last server time, close server events
|
|
if serverTime < LAST_SERVER_TIME {
|
|
closeServerEvents()
|
|
}
|
|
LAST_SERVER_TIME = serverTime
|
|
|
|
// write server time to file
|
|
err = ioutil.WriteFile(SERVER_TIME_FILE, []byte(strconv.FormatUint(serverTime, 10)), 0666)
|
|
if err != nil {
|
|
writeLog(functionName, fmt.Sprintf(`["Error writing server time to file: %v", "ERROR"]`, err))
|
|
return
|
|
}
|
|
}
|
|
|
|
func closeServerEvents() {
|
|
functionName := "closeServerEvents"
|
|
writeLog(functionName, `["Filling missing disconnect events due to server restart.", "DEBUG"]`)
|
|
// get all events with null DisconnectTime & set DisconnectTime to current time
|
|
var events []AttendanceItem
|
|
db.Where("disconnect_time_utc = '0000-00-00 00:00:00'").Find(&events)
|
|
for _, event := range events {
|
|
|
|
// if difference between JoinTime and current time is greater than threshold, set to threshold
|
|
if event.EventType == "Server" {
|
|
var timeThreshold time.Time = event.JoinTimeUTC.Add(-time.Duration(A3Config.ServerEventFillNullMinutes) * time.Minute)
|
|
if event.JoinTimeUTC.Before(timeThreshold) {
|
|
event.DisconnectTimeUTC = timeThreshold
|
|
} else {
|
|
event.DisconnectTimeUTC = time.Now()
|
|
}
|
|
} else if event.EventType == "Mission" {
|
|
var timeThreshold time.Time = event.JoinTimeUTC.Add(-time.Duration(A3Config.MissionEventFillNullMinutes) * time.Minute)
|
|
if event.JoinTimeUTC.Before(timeThreshold) {
|
|
event.DisconnectTimeUTC = timeThreshold
|
|
} else {
|
|
event.DisconnectTimeUTC = time.Now()
|
|
}
|
|
}
|
|
db.Save(&event)
|
|
if db.Error != nil {
|
|
writeLog(functionName, fmt.Sprintf(`["Error filling missing disconnects: %v", "ERROR"]`, db.Error))
|
|
return
|
|
}
|
|
}
|
|
|
|
// log how many
|
|
writeLog(functionName, fmt.Sprintf(`["%d missing disconnects filled.", "INFO"]`, len(events)))
|
|
}
|
|
|
|
func connectDB() error {
|
|
|
|
// load config
|
|
loadConfig()
|
|
|
|
// connect to database
|
|
var err error
|
|
dsn := fmt.Sprintf(
|
|
"%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True",
|
|
ATConfig.MySQLUser,
|
|
ATConfig.MySQLPassword,
|
|
ATConfig.MySQLHost,
|
|
ATConfig.MySQLPort,
|
|
ATConfig.MySQLDatabase,
|
|
)
|
|
|
|
// log dsn and pause
|
|
// writeLog("connectDB", fmt.Sprintf(`["DSN: %s", "INFO"]`, dsn))
|
|
// fmt.Println(dsn)
|
|
|
|
if db != nil {
|
|
// log success and return
|
|
writeLog("connectDB", `["Database already connected", "INFO"]`)
|
|
writeLog("connectDB", `["SUCCESS", "INFO"]`)
|
|
return nil
|
|
}
|
|
|
|
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
|
if err != nil {
|
|
// log.Println(err)
|
|
writeLog("connectDB", fmt.Sprintf(`["%s", "ERROR"]`, err))
|
|
return err
|
|
}
|
|
|
|
// Migrate the schema
|
|
err = db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&World{}, &Mission{}, &AttendanceItem{})
|
|
if err != nil {
|
|
// log.Println(err)
|
|
writeLog("connectDB", fmt.Sprintf(`["%s", "ERROR"]`, err))
|
|
return err
|
|
}
|
|
|
|
writeLog("connectDB", `["Database connected", "INFO"]`)
|
|
writeLog("connectDB", `["SUCCESS", "INFO"]`)
|
|
return nil
|
|
}
|
|
|
|
type World struct {
|
|
gorm.Model
|
|
Author string `json:"author"`
|
|
WorkshopID string `json:"workshopID"`
|
|
DisplayName string `json:"displayName"`
|
|
WorldName string `json:"worldName"`
|
|
WorldNameOriginal string `json:"worldNameOriginal"`
|
|
WorldSize float32 `json:"worldSize"`
|
|
Latitude float32 `json:"latitude"`
|
|
Longitude float32 `json:"longitude"`
|
|
Missions []Mission
|
|
}
|
|
|
|
func writeWorldInfo(worldInfo string) {
|
|
functionName := "writeWorldInfo"
|
|
// writeLog(functionName, fmt.Sprintf(`["%s", "DEBUG"]`, worldInfo))
|
|
// worldInfo is json, parse it
|
|
var wi World
|
|
fixedString := fixEscapeQuotes(trimQuotes(worldInfo))
|
|
err := json.Unmarshal([]byte(fixedString), &wi)
|
|
if err != nil {
|
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
|
return
|
|
}
|
|
|
|
// prevent crash
|
|
if db == nil {
|
|
err := connectDB()
|
|
if err != nil {
|
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
|
return
|
|
}
|
|
}
|
|
|
|
// write world if not exist
|
|
var world World
|
|
var returnId uint
|
|
db.Where("world_name = ?", wi.WorldName).First(&world)
|
|
if world.ID == 0 {
|
|
writeLog(functionName, `["World not found, writing new world", "INFO"]`)
|
|
result := db.Create(&wi)
|
|
if result.Error != nil {
|
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, result.Error))
|
|
return
|
|
}
|
|
writeLog(functionName, fmt.Sprintf(`["World written with ID %d", "INFO"]`, wi.ID))
|
|
returnId = wi.ID
|
|
} else {
|
|
// return ID
|
|
writeLog(functionName, fmt.Sprintf(`["World exists with ID %d", "INFO"]`, world.ID))
|
|
returnId = world.ID
|
|
}
|
|
|
|
writeLog(functionName, fmt.Sprintf(`["WORLD_ID", %d]`, returnId))
|
|
}
|
|
|
|
type Mission struct {
|
|
gorm.Model
|
|
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 time.Time `json:"missionStart" gorm:"type:datetime"`
|
|
MissionHash string `json:"missionHash" gorm:"index"`
|
|
WorldName string `json:"worldName" gorm:"-"`
|
|
WorldID uint
|
|
World World `gorm:"foreignkey:WorldID"`
|
|
Attendees []AttendanceItem
|
|
}
|
|
|
|
func writeMission(missionJSON string) {
|
|
functionName := "writeMission"
|
|
var err error
|
|
// writeLog(functionName, fmt.Sprintf(`["%s", "DEBUG"]`, Mission))
|
|
// Mission is json, parse it
|
|
var mi Mission
|
|
fixedString := fixEscapeQuotes(trimQuotes(missionJSON))
|
|
err = json.Unmarshal([]byte(fixedString), &mi)
|
|
if err != nil {
|
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
|
return
|
|
}
|
|
|
|
// prevent crash
|
|
if db == nil {
|
|
err := connectDB()
|
|
if err != nil {
|
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
|
return
|
|
}
|
|
}
|
|
|
|
// get world from WorldName
|
|
var world World
|
|
db.Where("world_name = ?", mi.WorldName).First(&world)
|
|
if world.ID == 0 {
|
|
writeLog(functionName, fmt.Sprintf(`["World not found for %s, cannot write mission!", "ERROR"]`, mi.WorldName))
|
|
return
|
|
}
|
|
mi.WorldID = world.ID
|
|
|
|
// write mission to database
|
|
db.Create(&mi)
|
|
if db.Error != nil {
|
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, db.Error))
|
|
return
|
|
}
|
|
writeLog(functionName, fmt.Sprintf(`["Mission written with ID %d", "INFO"]`, mi.ID))
|
|
writeLog(functionName, fmt.Sprintf(`["MISSION_ID", %d]`, mi.ID))
|
|
}
|
|
|
|
type AttendanceItem struct {
|
|
gorm.Model
|
|
MissionHash string `json:"missionHash"`
|
|
EventType string `json:"eventType"`
|
|
PlayerId string `json:"playerId"`
|
|
PlayerUID string `json:"playerUID"`
|
|
JoinTimeUTC time.Time
|
|
DisconnectTimeUTC time.Time
|
|
ProfileName string `json:"profileName"`
|
|
SteamName string `json:"steamName"`
|
|
IsJIP bool `json:"isJIP" gorm:"column:is_jip"`
|
|
RoleDescription string `json:"roleDescription"`
|
|
MissionID uint
|
|
Mission Mission `gorm:"foreignkey:MissionID"`
|
|
}
|
|
|
|
func writeDisconnectEvent(data string) {
|
|
functionName := "writeDisconnectEvent"
|
|
var err error
|
|
// data is json, parse it
|
|
stringjson := fixEscapeQuotes(trimQuotes(data))
|
|
var event AttendanceItem
|
|
err = json.Unmarshal([]byte(stringjson), &event)
|
|
if err != nil {
|
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
|
return
|
|
}
|
|
|
|
// prevent crash
|
|
if db == nil {
|
|
err := connectDB()
|
|
if err != nil {
|
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
|
return
|
|
}
|
|
}
|
|
|
|
// get all attendance rows of type without disconnect rows
|
|
var attendanceRows []AttendanceItem
|
|
db.Where("player_uid = ? AND event_type = ? AND disconnect_time_utc = '0000-00-00 00:00:00'", event.PlayerUID, event.EventType).Find(&attendanceRows)
|
|
for _, row := range attendanceRows {
|
|
// update disconnect time
|
|
if row.JoinTimeUTC.Before(time.Now().Add(-1*time.Hour)) && row.EventType == "Mission" {
|
|
// if mission JoinTime is more than 1 hour ago, simplify this to write DisconnectTime as 1 hour from JoinTime. this to account for crashes where people don't immediately rejoin
|
|
row.DisconnectTimeUTC = row.JoinTimeUTC.Add(-1 * time.Hour)
|
|
} else if row.JoinTimeUTC.Before(time.Now().Add(-6*time.Hour)) && row.EventType == "Server" {
|
|
// if server JoinTime is more than 6 hours ago, simplify this to write DisconnectTime as 6 hours from JoinTime. this to account for server crashes where people don't immediately rejoin without overwriting valid (potentially lengthy) server sessions
|
|
row.DisconnectTimeUTC = row.JoinTimeUTC.Add(-6 * time.Hour)
|
|
} else {
|
|
// otherwise, update DisconnectTime to now
|
|
row.DisconnectTimeUTC = time.Now()
|
|
}
|
|
db.Save(&row)
|
|
}
|
|
|
|
writeLog(functionName, fmt.Sprintf(`["Disconnect events written for %s", "DEBUG"]`, event.PlayerUID))
|
|
}
|
|
|
|
func writeAttendance(data string) {
|
|
functionName := "writeAttendance"
|
|
var err error
|
|
// data is json, parse it
|
|
stringjson := fixEscapeQuotes(trimQuotes(data))
|
|
var event AttendanceItem
|
|
err = json.Unmarshal([]byte(stringjson), &event)
|
|
if err != nil {
|
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
|
return
|
|
}
|
|
|
|
// prevent crash
|
|
if db == nil {
|
|
err := connectDB()
|
|
if err != nil {
|
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, err))
|
|
return
|
|
}
|
|
}
|
|
|
|
var playerUid string
|
|
var rowId uint
|
|
if event.EventType == "Server" {
|
|
// check for most recent existing attendance row
|
|
var attendance AttendanceItem
|
|
db.Where("player_id = ? AND player_uid = ? AND event_type = ?", event.PlayerId, event.PlayerUID, event.EventType).Order("join_time_utc desc").First(&attendance)
|
|
if attendance.ID != 0 {
|
|
// update disconnect time
|
|
row := db.Model(&attendance).Update("disconnect_time_utc", attendance.DisconnectTimeUTC)
|
|
if row.Error != nil {
|
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, row.Error))
|
|
return
|
|
}
|
|
rowId, playerUid = attendance.ID, attendance.PlayerUID
|
|
|
|
} else {
|
|
// insert new row
|
|
event.JoinTimeUTC = time.Now()
|
|
row := db.Omit("MissionID").Create(&event)
|
|
if row.Error != nil {
|
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, row.Error))
|
|
return
|
|
}
|
|
rowId, playerUid = event.ID, event.PlayerUID
|
|
}
|
|
} else if event.EventType == "Mission" {
|
|
// use gorm to associate this event with the mission sharing a mission hash
|
|
var mission Mission
|
|
db.Where("mission_hash = ?", event.MissionHash).First(&mission)
|
|
if mission.ID != 0 {
|
|
event.MissionID = uint(mission.ID)
|
|
} else {
|
|
writeLog(functionName, fmt.Sprintf(`["Mission not found for hash %s", "ERROR"]`, event.MissionHash))
|
|
return
|
|
}
|
|
|
|
// check for most recent JoinTime for this player and event type
|
|
var attendance AttendanceItem
|
|
db.Where("player_id = ? AND player_uid = ? AND event_type = ? AND mission_hash = ?", event.PlayerId, event.PlayerUID, event.EventType, event.MissionHash).Order("join_time_utc desc").First(&attendance)
|
|
if attendance.ID != 0 {
|
|
// update disconnect time
|
|
row := db.Model(&attendance).Update("disconnect_time_utc", time.Now())
|
|
if row.Error != nil {
|
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, row.Error))
|
|
return
|
|
}
|
|
rowId, playerUid = attendance.ID, attendance.PlayerUID
|
|
} else {
|
|
event.JoinTimeUTC = time.Now()
|
|
// insert new row
|
|
row := db.Create(&event)
|
|
if row.Error != nil {
|
|
writeLog(functionName, fmt.Sprintf(`["%s", "ERROR"]`, row.Error))
|
|
return
|
|
}
|
|
rowId, playerUid = event.ID, event.PlayerUID
|
|
}
|
|
}
|
|
|
|
writeLog(functionName, fmt.Sprintf(`["Saved attendance for %s to row id %d", "DEBUG"]`, playerUid, rowId))
|
|
}
|
|
|
|
func runExtensionCallback(name *C.char, function *C.char, data *C.char) C.int {
|
|
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 "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 writeMission(out[0])
|
|
}
|
|
case "logWorld":
|
|
if argc == 1 {
|
|
go writeWorldInfo(out[0])
|
|
}
|
|
case "updateServerTime":
|
|
if argc == 1 {
|
|
// convert to uint64
|
|
serverTime, err := strconv.ParseUint(out[0], 10, 64)
|
|
if err != nil {
|
|
writeLog("updateServerTime", fmt.Sprintf(`["%s", "ERROR"]`, err))
|
|
temp = "ERROR parsing server time"
|
|
} else {
|
|
go updateServerTime(serverTime)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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() string {
|
|
// get the current unix timestamp in nanoseconds
|
|
// return time.Now().Local().Unix()
|
|
return time.Now().Format("2006-01-02 15:04:05")
|
|
}
|
|
|
|
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) {
|
|
// get calling function & line
|
|
_, file, line, _ := runtime.Caller(1)
|
|
log.Printf(`%s:%d:%s %s`, path.Base(file), line, functionName, data)
|
|
|
|
if extensionCallbackFnc == nil {
|
|
return
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
func disconnectDB() {
|
|
if db != nil {
|
|
db = nil
|
|
}
|
|
}
|
|
|
|
//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 "getSettings":
|
|
loadConfig()
|
|
temp = getSettings()
|
|
case "getTimestamp":
|
|
temp = fmt.Sprintf(`["%s"]`, getTimestamp())
|
|
case "connectDB":
|
|
temp = fmt.Sprintf(`["%s"]`, "Connecting to DB")
|
|
connectDB()
|
|
case "disconnectDB":
|
|
temp = fmt.Sprintf(`["%s"]`, "Disconnecting from DB")
|
|
disconnectDB()
|
|
case "getMissionHash":
|
|
temp = fmt.Sprintf(`["%s"]`, getMissionHash())
|
|
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() {
|
|
loadConfig()
|
|
fmt.Println("Running DB connect/migrate to build schema...")
|
|
err := connectDB()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
} else {
|
|
fmt.Println("DB connect/migrate complete!")
|
|
}
|
|
fmt.Scanln()
|
|
}
|