mirror of
https://github.com/indig0fox/Arma3-AttendanceTracker.git/
synced 2025-12-08 09:51:47 -06:00
Compare commits
8 Commits
796f2e6818
...
v1.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d50801c29 | |||
|
6cf76d1019
|
|||
|
62fbe8b24c
|
|||
|
71ec70ef6a
|
|||
|
7a8356c92c
|
|||
| 36820c57f3 | |||
|
1892805fe9
|
|||
|
5dc3b5cb03
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -9,3 +9,7 @@ hemtt
|
|||||||
hemtt.exe
|
hemtt.exe
|
||||||
*.biprivatekey
|
*.biprivatekey
|
||||||
*.bk
|
*.bk
|
||||||
|
|
||||||
|
releases/
|
||||||
|
mariadb/db/
|
||||||
|
AttendanceTracker.config.json
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ git_hash = 6 # Default: 8
|
|||||||
|
|
||||||
[files]
|
[files]
|
||||||
include=[
|
include=[
|
||||||
"AttendanceTracker.config.json",
|
"AttendanceTracker.config.example.json",
|
||||||
|
# "AttendanceTracker.config.json", # used for copying active config during debugging
|
||||||
"LICENSE",
|
"LICENSE",
|
||||||
"README",
|
"README",
|
||||||
"mod.cpp",
|
"mod.cpp",
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
"mysqlHost": "localhost",
|
"mysqlHost": "localhost",
|
||||||
"mysqlPort": 3306,
|
"mysqlPort": 3306,
|
||||||
"mysqlUser": "root",
|
"mysqlUser": "root",
|
||||||
"mysqlPassword": "password",
|
"mysqlPassword": "example",
|
||||||
"mysqlDatabase": "a3attendance"
|
"mysqlDatabase": "a3attendance"
|
||||||
},
|
},
|
||||||
"armaConfig": {
|
"armaConfig": {
|
||||||
"dbUpdateInterval": "90s",
|
"dbUpdateInterval": "90s",
|
||||||
"debug": false,
|
"debug": false,
|
||||||
"traceLogToFile": false
|
"trace": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
# Arma 3 Attendance Tracker
|
# Arma 3 Attendance Tracker
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
### Set Up a Database Engine
|
### Set Up a Database Engine
|
||||||
@@ -37,7 +35,7 @@ CREATE DATABASE `arma3_attendance`;
|
|||||||
|
|
||||||
1. Download the latest release from the [releases page](https://github.com/indig0fox/Arma3-AttendanceTracker/releases).
|
1. Download the latest release from the [releases page](https://github.com/indig0fox/Arma3-AttendanceTracker/releases).
|
||||||
1. Extract the .zip and move `@AttendanceTracker` to your Arma 3 server's root directory.
|
1. Extract the .zip and move `@AttendanceTracker` to your Arma 3 server's root directory.
|
||||||
1. Inside of `@AttendanceTracker` you will find a `config.json` file. Open this file and configure it to your circumstances. See the [Configuration](#configuration) section for more information.
|
1. Inside of `@AttendanceTracker` you will find an `AttendanceTracker.config.example.json` file. Copy this as `AttendanceTRacker.config.json`. Open this new file and configure it to your circumstances. See the [Configuration](#configuration) section for more information.
|
||||||
1. Add the mod to your server's startup parameters. For example: `-serverMod="@AttendanceTracker;"`
|
1. Add the mod to your server's startup parameters. For example: `-serverMod="@AttendanceTracker;"`
|
||||||
|
|
||||||
At next run, the Arma 3 server will launch with the mod running.
|
At next run, the Arma 3 server will launch with the mod running.
|
||||||
@@ -55,10 +53,9 @@ The following table describes the configuration options.
|
|||||||
| sqlConfig.mySqlUser | string | The username to use when connecting to your MySQL instance. | root |
|
| sqlConfig.mySqlUser | string | The username to use when connecting to your MySQL instance. | root |
|
||||||
| sqlConfig.mySqlPassword | string | The password to use when connecting to your MySQL instance. | root |
|
| sqlConfig.mySqlPassword | string | The password to use when connecting to your MySQL instance. | root |
|
||||||
| sqlConfig.mySqlDatabase | string | The name of the database to use. | arma3_attendance |
|
| sqlConfig.mySqlDatabase | string | The name of the database to use. | arma3_attendance |
|
||||||
| armaConfig.dbUpdateIntervalSeconds | integer | The number of seconds between disconnect_time updates per user. | 90 |
|
| armaConfig.dbUpdateInterval | string, [`time.Duration` Go type](https://pkg.go.dev/time#ParseDuration) | The number of seconds between disconnect_time updates per user. | "90s" |
|
||||||
| armaConfig.serverEventFillNullMinutes | integer | The max session duration to fill in for missing server disconnect_time values. | 90 |
|
|
||||||
| armaConfig.missionEventFillNullMinutes | integer | The max session duration to fill in for missing mission disconnect_time values. | 15 |
|
|
||||||
| armaConfig.debug | boolean | Whether or not to enable debug logging. | false |
|
| armaConfig.debug | boolean | Whether or not to enable debug logging. | false |
|
||||||
|
| armaConfig.traceLogToFile | boolean | Whether or not to enable trace logging to the addon folder's log file. | false |
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@@ -97,23 +94,16 @@ FROM mysql.time_zone_name
|
|||||||
|
|
||||||
### Performance
|
### Performance
|
||||||
|
|
||||||
The extension will update the disconnect_time field for each player every `dbUpdateIntervalSeconds` seconds. This is to ensure that the disconnect_time field is updated in the event that the server crashes or the mission ends without a disconnect event.
|
The extension will update the disconnect_time field for each player every `dbUpdateInterval` seconds. This is to ensure that the disconnect_time field is updated in the event that the server crashes or the mission ends without a disconnect event.
|
||||||
|
|
||||||
These calls are threaded in the Go runtime and will not block the Arma 3 server while processing. The default value of 90 seconds should be sufficient for most servers. Each period begins when a player connects to the server or connects to a mission, which provides a natural offset.
|
These calls are threaded in the Go runtime and will not block the Arma 3 server while processing. The default value of 90 seconds should be sufficient for most servers. Each period begins when a player connects to the server or connects to a mission, which provides a natural offset.
|
||||||
|
|
||||||
### NULL disconnect_time Values
|
|
||||||
|
|
||||||
In the event that the server crashes or a disconnect event for a mission is not sent, the next join for each will update past rows based on the following:
|
|
||||||
|
|
||||||
If the join time for a row is within [`{event_type}EventFillNullMinutes`](#configuration) minutes of the previous disconnect time, the previous disconnect time will be updated to the new join time. Otherwise, it will be set as [`{event_type}EventFillNullMinutes`](#configuration) from the join time for that row.
|
|
||||||
|
|
||||||
This is an attempt to account for missing events for individual players while not attributing large gap periods to their calculated session times. If the server crashes, the extension will update all rows with a NULL disconnect_time to the current time. See [Server Crash Time Filling](#server-crash-time-filling) for more information.
|
|
||||||
|
|
||||||
#### Server Crash Time Filling
|
#### Server Crash Time Filling
|
||||||
|
|
||||||
The addon will update `@AttendanceTracker/lastServerTime.txt` with Arma 3's `diag_tickTime` every 30 seconds. This is to ensure that the server time is always available to the extension, even if the server crashes. This file is not used for any other purpose.
|
In the event that the server crashes and a user has not been in the mission longer than `dbUpdateInterval` and therefore has a NULL `disconnect_time_utc` value, upon next launch the extension will update the row procedure:
|
||||||
|
|
||||||
On each time update, the extension will check this file and compare the received value to it. If the lastServerTime < lastServerTime.txt, the extension will assume that the server has restarted and will update all event rows with a NULL disconnect_time to the current time OR the threshold specified in the configuration file, whichever produces the smaller session duration.
|
- If more than `dbUpdateInterval` has passed since the row's `join_time_utc` value, the row will be updated with a `disconnect_time_utc` value of `join_time_utc + dbUpdateInterval`.
|
||||||
|
- If less than `dbUpdateInterval` has passed since the row's `join_time_utc` value, the row will be updated with a `disconnect_time_utc` value of the current time.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -123,7 +113,7 @@ On each time update, the extension will check this file and compare the received
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| worlds | Stores world information. |
|
| worlds | Stores world information. |
|
||||||
| missions | Stores mission information. |
|
| missions | Stores mission information. |
|
||||||
| attendance_items | Stores rows that indicate player information and join/disconnect times. |
|
| sessions | Stores rows that indicate player information and join/disconnect times. |
|
||||||
|
|
||||||
### Worlds
|
### Worlds
|
||||||
|
|
||||||
@@ -133,9 +123,9 @@ The worlds table will store basic info about the world. This is used to link mis
|
|||||||
|
|
||||||
The missions table will store basic info about the mission. This is used to link attendance items to missions.
|
The missions table will store basic info about the mission. This is used to link attendance items to missions.
|
||||||
|
|
||||||
### Attendance Items
|
### Sessions
|
||||||
|
|
||||||
The attendance_items table will store rows that indicate player information and join/disconnect times. This can be used to calculate play time per player per mission. Each row is also linked to a mission, so that these records can be grouped.
|
The sessions table will store rows that indicate player information and join/disconnect times. This can be used to calculate play time per player per mission. Each row is also linked to a mission, so that these records can be grouped.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -143,37 +133,9 @@ The attendance_items table will store rows that indicate player information and
|
|||||||
|
|
||||||
### Show missions with attendance
|
### Show missions with attendance
|
||||||
|
|
||||||
This will retrieve a view showing all missions with attendance data, sorted by the most recent mission joins first. Mission events without a mission disconnect_time (due to server crash or in-progress mission) will be ignored.
|
|
||||||
|
|
||||||
See [Timezone](#timezone) for more information on converting times to your local timezone.
|
See [Timezone](#timezone) for more information on converting times to your local timezone.
|
||||||
|
|
||||||
```sql
|
TODO
|
||||||
select
|
|
||||||
a.server_profile as Server,
|
|
||||||
a.briefing_name as "Mission Name",
|
|
||||||
CONVERT_TZ(a.mission_start, 'UTC', 'US/Eastern') as "Start Time",
|
|
||||||
b.display_name as "World",
|
|
||||||
c.profile_name as "Player Name",
|
|
||||||
c.player_uid as "Player UID",
|
|
||||||
TIMESTAMPDIFF(
|
|
||||||
MINUTE,
|
|
||||||
c.join_time,
|
|
||||||
c.disconnect_time
|
|
||||||
) as "Play Time (m)",
|
|
||||||
CONVERT_TZ(c.join_time, 'UTC', 'US/Eastern') as "Join Time",
|
|
||||||
CONVERT_TZ(c.disconnect_time, 'UTC', 'US/Eastern') as "Leave Time"
|
|
||||||
from missions a
|
|
||||||
LEFT JOIN worlds b ON a.world_id = b.id
|
|
||||||
LEFT JOIN attendance_items c ON a.mission_hash = c.mission_hash
|
|
||||||
where
|
|
||||||
c.event_type = 'Mission'
|
|
||||||
AND c.disconnect_time IS NOT NULL
|
|
||||||
AND TIMESTAMPDIFF(
|
|
||||||
MINUTE,
|
|
||||||
c.join_time,
|
|
||||||
c.disconnect_time
|
|
||||||
) > 0
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -187,9 +149,7 @@ Pull requests are welcome. For major changes, please open an issue first to disc
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- [Go 1.16.4](https://golang.org/doc/install)
|
- Docker
|
||||||
- [MinGW-w64](https://sourceforge.net/projects/mingw-w64/) (Windows only)
|
|
||||||
- [GCC](https://gcc.gnu.org/) (Linux only)
|
|
||||||
|
|
||||||
### Building Extension using Docker
|
### Building Extension using Docker
|
||||||
|
|
||||||
@@ -199,40 +159,50 @@ Once it's built, copy the file from ./dist to the project root, then build the a
|
|||||||
|
|
||||||
#### COMPILING FOR WINDOWS
|
#### COMPILING FOR WINDOWS
|
||||||
|
|
||||||
|
These compile commands should be run from the project root.
|
||||||
|
|
||||||
```ps1
|
```ps1
|
||||||
docker pull x1unix/go-mingw:1.20
|
docker pull x1unix/go-mingw:1.20
|
||||||
|
|
||||||
# version is semantic + build date + git hash
|
# version is semantic + build date + git hash
|
||||||
# e.g. 1.0.0-2021-05-30-1a2b3c4d
|
# e.g. 1.0.0-2021-05-30-1a2b3c4d
|
||||||
$versionSem = '0.2.0'
|
$versionSem = '1.1.1'
|
||||||
$dateStr = Get-Date -Format 'yyyyMMdd'
|
$dateStr = Get-Date -Format 'yyyyMMdd'
|
||||||
$version = "$versionSem-$dateStr-$(git rev-parse --short HEAD)"
|
$version = "$versionSem-$dateStr-$(git rev-parse --short HEAD)"
|
||||||
|
|
||||||
# Compile x64 Windows DLL
|
# Compile x64 Windows DLL
|
||||||
docker run --rm -it -v ${PWD}:/go/work -w /go/work x1unix/go-mingw:1.20 go build -o dist/AttendanceTracker_x64.dll -buildmode=c-shared -ldflags "-w -s -X main.EXTENSION_VERSION=$version" ./cmd
|
docker run --rm -it -v ${PWD}\extension\AttendanceTracker:/go/work -w /go/work -e GOARCH=amd64 -e CGO_ENABLED=1 x1unix/go-mingw:1.20 go build -o ./dist/AttendanceTracker_x64.dll -buildmode=c-shared -ldflags "-w -s -X main.EXTENSION_VERSION=$version" ./cmd
|
||||||
|
|
||||||
|
Move-Item -Path ./extension/AttendanceTracker/dist/AttendanceTracker_x64.dll -Destination ./AttendanceTracker_x64.dll -Force
|
||||||
|
|
||||||
# Compile x86 Windows DLL
|
# Compile x86 Windows DLL
|
||||||
docker run --rm -it -v ${PWD}:/go/work -w /go/work -e GOARCH=386 x1unix/go-mingw:1.20 go build -o dist/AttendanceTracker.dll -buildmode=c-shared -ldflags "-w -s -X main.EXTENSION_VERSION=$version" ./cmd
|
docker run --rm -it -v ${PWD}\extension\AttendanceTracker:/go/work -w /go/work -e GOARCH=386 -e CGO_ENABLED=1 x1unix/go-mingw:1.20 go build -o ./dist/AttendanceTracker.dll -buildmode=c-shared -ldflags "-w -s -X main.EXTENSION_VERSION=$version" ./cmd
|
||||||
|
|
||||||
|
Move-Item -Path ./extension/AttendanceTracker/dist/AttendanceTracker.dll -Destination ./AttendanceTracker.dll -Force
|
||||||
|
|
||||||
# Compile x64 Windows EXE
|
# Compile x64 Windows EXE
|
||||||
docker run --rm -it -v ${PWD}:/go/work -w /go/work x1unix/go-mingw:1.20 go build -o dist/AttendanceTracker_x64.exe -ldflags "-w -s -X main.EXTENSION_VERSION=$version" ./cmd
|
docker run --rm -it -v ${PWD}:/go/work -w /go/work -e GOARCH=amd64 -e CGO_ENABLED=1 x1unix/go-mingw:1.20 go build -o ./dist/AttendanceTracker_x64.exe -ldflags "-w -s -X main.EXTENSION_VERSION=$version" ./extension/AttendanceTracker/cmd
|
||||||
```
|
```
|
||||||
|
|
||||||
#### COMPILING FOR LINUX
|
#### COMPILING FOR LINUX
|
||||||
|
|
||||||
```bash
|
```ps1
|
||||||
docker build -t indifox926/build-a3go:linux-so -f ./build/Dockerfile.build ./cmd
|
docker build -t indifox926/build-a3go:linux-so -f ./build/Dockerfile.build .
|
||||||
|
|
||||||
# Compile x64 Linux .so
|
# Compile x64 Linux .so
|
||||||
docker run --rm -it -v ${PWD}:/app -e GOOS=linux -e GOARCH=amd64 -e CGO_ENABLED=1 -e CC=gcc indifox926/build-a3go:linux-so go build -o dist/AttendanceTracker_x64.so -linkshared -ldflags "-w -s -X main.EXTENSION_VERSION=$version" ./cmd
|
docker run --rm -it -v ${PWD}\extension\AttendanceTracker:/app -e GOOS=linux -e GOARCH=amd64 -e CGO_ENABLED=1 indifox926/build-a3go:linux-so go build -o ./dist/AttendanceTracker_x64.so -linkshared -ldflags "-w -s -X main.EXTENSION_VERSION=$version" ./cmd
|
||||||
|
|
||||||
|
Move-Item -Path ./extension/AttendanceTracker/dist/AttendanceTracker_x64.so -Destination ./AttendanceTracker_x64.so -Force
|
||||||
|
|
||||||
# Compile x86 Linux .so
|
# Compile x86 Linux .so
|
||||||
docker run --rm -it -v ${PWD}:/app -e GOOS=linux -e GOARCH=386 -e CGO_ENABLED=1 -e CC=gcc indifox926/build-a3go:linux-so go build -o dist/AttendanceTracker.so -linkshared -ldflags "-w -s -X main.EXTENSION_VERSION=$version" ./cmd
|
docker run --rm -it -v ${PWD}\extension\AttendanceTracker:/app -e GOOS=linux -e GOARCH=386 -e CGO_ENABLED=1 indifox926/build-a3go:linux-so go build -o ./dist/AttendanceTracker.so -linkshared -ldflags "-w -s -X main.EXTENSION_VERSION=$version" ./cmd
|
||||||
|
|
||||||
|
Move-Item -Path ./extension/AttendanceTracker/dist/AttendanceTracker.so -Destination ./AttendanceTracker.so -Force
|
||||||
```
|
```
|
||||||
|
|
||||||
### Compile Addon
|
### Compile Addon
|
||||||
|
|
||||||
First, move the compiled dlls from extension/AttendanceTracker/dist to the project root.
|
First, move the compiled dlls from `extension/AttendanceTracker/dist` to the project root.
|
||||||
|
|
||||||
To prepare the addon, you'll need to download the [HEMTT](https://brettmayson.github.io/HEMTT/commands/build.html) binary, place it in the project root, and run the following command:
|
To prepare the addon, you'll need to download the [HEMTT](https://brettmayson.github.io/HEMTT/commands/build.html) binary, place it in the project root, and run the following command:
|
||||||
|
|
||||||
@@ -1 +1 @@
|
|||||||
x\addons\attendancetracker\main
|
x\attendancetracker\addons\main
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "script_mod.hpp"
|
#include "script_component.hpp"
|
||||||
|
|
||||||
class CfgPatches {
|
class CfgPatches {
|
||||||
class AttendanceTracker {
|
class ADDON {
|
||||||
units[] = {};
|
units[] = {};
|
||||||
weapons[] = {};
|
weapons[] = {};
|
||||||
requiredVersion = 2.10;
|
requiredVersion = 2.10;
|
||||||
@@ -17,20 +17,17 @@ class CfgPatches {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class CfgFunctions {
|
class CfgFunctions {
|
||||||
class attendanceTracker {
|
class ADDON {
|
||||||
class functions {
|
class functions {
|
||||||
file = "x\addons\attendancetracker\main\functions";
|
class postInit {
|
||||||
class postInit {postInit = 1;};
|
file = QPATHTOF(DOUBLES(fnc,postInit).sqf);
|
||||||
class callbackHandler {postInit = 1;};
|
postInit = 1;
|
||||||
class getMissionHash {};
|
};
|
||||||
class getMissionInfo {};
|
PATHTO_FNC(getMissionInfo);
|
||||||
class getSettings {};
|
PATHTO_FNC(getWorldInfo);
|
||||||
class getWorldInfo {};
|
PATHTO_FNC(log);
|
||||||
class log {};
|
PATHTO_FNC(missionLoaded);
|
||||||
class missionLoaded {};
|
PATHTO_FNC(onPlayerConnected);
|
||||||
class onPlayerConnected {};
|
|
||||||
class timestamp {};
|
|
||||||
class writePlayer {};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
13
addons/main/fnc_getMissionInfo.sqf
Normal file
13
addons/main/fnc_getMissionInfo.sqf
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#include "script_component.hpp"
|
||||||
|
[
|
||||||
|
["missionName", missionName],
|
||||||
|
["missionStart", GVAR(missionStart)],
|
||||||
|
["missionHash", GVAR(missionHash)],
|
||||||
|
["briefingName", briefingName],
|
||||||
|
["missionNameSource", missionNameSource],
|
||||||
|
["onLoadName", getMissionConfigValue ["onLoadName", "Unknown"]],
|
||||||
|
["author", getMissionConfigValue ["author", "Unknown"]],
|
||||||
|
["serverName", serverName],
|
||||||
|
["serverProfile", profileName],
|
||||||
|
["worldName", toLower worldName]
|
||||||
|
];
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#include "script_component.hpp"
|
||||||
|
|
||||||
_world = ( configfile >> "CfgWorlds" >> worldName );
|
_world = ( configfile >> "CfgWorlds" >> worldName );
|
||||||
_author = getText( _world >> "author" );
|
_author = getText( _world >> "author" );
|
||||||
_name = getText ( _world >> "description" );
|
_name = getText ( _world >> "description" );
|
||||||
@@ -5,7 +7,6 @@ _name = getText ( _world >> "description" );
|
|||||||
_source = configSourceMod ( _world );
|
_source = configSourceMod ( _world );
|
||||||
|
|
||||||
_workshopID = '';
|
_workshopID = '';
|
||||||
|
|
||||||
{
|
{
|
||||||
if ( ( _x#1 ) == _source ) then {
|
if ( ( _x#1 ) == _source ) then {
|
||||||
_workshopID = _x#7;
|
_workshopID = _x#7;
|
||||||
@@ -13,8 +14,12 @@ _workshopID = '';
|
|||||||
};
|
};
|
||||||
} foreach getLoadedModsInfo;
|
} foreach getLoadedModsInfo;
|
||||||
|
|
||||||
|
if (_workshopID isEqualTo "") then {
|
||||||
|
_workshopID = "0";
|
||||||
|
};
|
||||||
|
|
||||||
// [_name, _author, _workshopID];
|
// [_name, _author, _workshopID];
|
||||||
_return = createHashMapFromArray [
|
_return = [
|
||||||
["author", _author],
|
["author", _author],
|
||||||
["workshopID", _workshopID],
|
["workshopID", _workshopID],
|
||||||
["displayName", _name],
|
["displayName", _name],
|
||||||
@@ -24,5 +29,5 @@ _return = createHashMapFromArray [
|
|||||||
["latitude", -1 * getNumber( _world >> "latitude" )],
|
["latitude", -1 * getNumber( _world >> "latitude" )],
|
||||||
["longitude", getNumber( _world >> "longitude" )]
|
["longitude", getNumber( _world >> "longitude" )]
|
||||||
];
|
];
|
||||||
[format["WorldInfo is: %1", _return]] call attendanceTracker_fnc_log;
|
["DEBUG", format["WorldInfo is: %1", _return]] call FUNC(log);
|
||||||
_return
|
_return
|
||||||
31
addons/main/fnc_log.sqf
Normal file
31
addons/main/fnc_log.sqf
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#include "script_component.hpp"
|
||||||
|
|
||||||
|
if (!isServer) exitWith {};
|
||||||
|
|
||||||
|
if (typeName _this != "ARRAY") exitWith {
|
||||||
|
diag_log format ["[%1]: Invalid log params: %2", GVAR(logPrefix), _this];
|
||||||
|
};
|
||||||
|
|
||||||
|
params [
|
||||||
|
["_level", "INFO", [""]],
|
||||||
|
["_text", "", ["", []]]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_text isEqualType []) then {
|
||||||
|
_text = format ["%1", _text];
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
_level == "DEBUG" &&
|
||||||
|
!GVAR(debug)
|
||||||
|
) exitWith {};
|
||||||
|
|
||||||
|
if (_text isEqualTo "") exitWith {};
|
||||||
|
|
||||||
|
diag_log formatText [
|
||||||
|
"[%1] %2: %3",
|
||||||
|
GVAR(logPrefix),
|
||||||
|
_level,
|
||||||
|
_text
|
||||||
|
];
|
||||||
|
|
||||||
@@ -1 +1,3 @@
|
|||||||
|
#include "script_component.hpp"
|
||||||
|
|
||||||
!(getClientStateNumber <= 5 || getClientStateNumber isEqualTo 11);
|
!(getClientStateNumber <= 5 || getClientStateNumber isEqualTo 11);
|
||||||
84
addons/main/fnc_onPlayerConnected.sqf
Normal file
84
addons/main/fnc_onPlayerConnected.sqf
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
#include "script_component.hpp"
|
||||||
|
|
||||||
|
params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"];
|
||||||
|
|
||||||
|
["DEBUG", format ["(EventHandler) PlayerConnected fired: %1", _this]] call FUNC(log);
|
||||||
|
|
||||||
|
if !(call FUNC(missionLoaded)) exitWith {
|
||||||
|
["DEBUG", format ["(EventHandler) PlayerConnected: Server is in Mission Asked, likely mission selection state. Skipping.."]] call FUNC(log);
|
||||||
|
};
|
||||||
|
|
||||||
|
private _userInfo = (getUserInfo _idstr);
|
||||||
|
if ((count _userInfo) isEqualTo 0) exitWith {
|
||||||
|
["DEBUG", format ["(EventHandler) PlayerConnected: No user info found for %1", _idstr]] call FUNC(log);
|
||||||
|
};
|
||||||
|
|
||||||
|
_userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"];
|
||||||
|
if (_isHC) exitWith {
|
||||||
|
[
|
||||||
|
"DEBUG",
|
||||||
|
format [
|
||||||
|
"(EventHandler) PlayerConnected: %1 is HC, skipping",
|
||||||
|
_playerID
|
||||||
|
]
|
||||||
|
] call FUNC(log);
|
||||||
|
};
|
||||||
|
|
||||||
|
// start CBA PFH
|
||||||
|
[
|
||||||
|
"DEBUG",
|
||||||
|
format [
|
||||||
|
"(EventHandler) PlayerConnected: Starting CBA PFH for %1",
|
||||||
|
_playerID
|
||||||
|
]
|
||||||
|
] call FUNC(log);
|
||||||
|
|
||||||
|
[{
|
||||||
|
params ["_args", "_handle"];
|
||||||
|
|
||||||
|
// every dbUpdateInterval, queue a wait for the mission to be logged
|
||||||
|
// times out after 30 seconds
|
||||||
|
// used to ensure joins at start of mission (during db connect) are logged
|
||||||
|
[{GVAR(missionLogged)}, {
|
||||||
|
// check if player is still connected
|
||||||
|
private _hash = _this;
|
||||||
|
private _clientStateNumber = 0;
|
||||||
|
|
||||||
|
private _userInfo = getUserInfo (_hash get "playerId");
|
||||||
|
if (_userInfo isEqualTo []) exitWith {
|
||||||
|
["DEBUG", format ["(EventHandler) PlayerConnected: %1 (UID) is no longer connected to the mission, exiting CBA PFH", _hash get "playerUID"]] call FUNC(log);
|
||||||
|
[_handle] call CBA_fnc_removePerFrameHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
_clientStateNumber = _userInfo select 6;
|
||||||
|
|
||||||
|
if (_clientStateNumber < 6) exitWith {
|
||||||
|
["DEBUG", format ["(EventHandler) PlayerConnected: %1 (UID) is no longer connected to the mission, exiting CBA PFH", _hash get "playerUID"]] call FUNC(log);
|
||||||
|
[_handle] call CBA_fnc_removePerFrameHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
["DEBUG", format [
|
||||||
|
"(EventHandler) PlayerConnected: %1 (UID) is connected to the mission, logging. data: %2",
|
||||||
|
_hash get "playerUID",
|
||||||
|
_hash
|
||||||
|
]] call FUNC(log);
|
||||||
|
GVAR(extensionName) callExtension [
|
||||||
|
":LOG:PRESENCE:", [
|
||||||
|
_hash
|
||||||
|
]];
|
||||||
|
},
|
||||||
|
_args, // args
|
||||||
|
30 // timeout
|
||||||
|
] call CBA_fnc_waitUntilAndExecute;
|
||||||
|
},
|
||||||
|
GVAR(updateInterval),
|
||||||
|
(createHashMapFromArray [ // args
|
||||||
|
["playerId", _playerID],
|
||||||
|
["playerUID", _playerUID],
|
||||||
|
["profileName", _profileName],
|
||||||
|
["steamName", _steamName],
|
||||||
|
["isJIP", _jip],
|
||||||
|
["roleDescription", if (roleDescription _unit isEqualTo "") then {"None"} else {roleDescription _unit}],
|
||||||
|
["missionHash", GVAR(missionHash)]
|
||||||
|
])
|
||||||
|
] call CBA_fnc_addPerFrameHandler;
|
||||||
94
addons/main/fnc_postInit.sqf
Normal file
94
addons/main/fnc_postInit.sqf
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
#include "script_component.hpp"
|
||||||
|
|
||||||
|
if (!isServer) exitWith {};
|
||||||
|
|
||||||
|
GVAR(attendanceTracker) = true;
|
||||||
|
GVAR(debug) = true;
|
||||||
|
GVAR(logPrefix) = "AttendanceTracker";
|
||||||
|
GVAR(extensionName) = "AttendanceTracker";
|
||||||
|
GVAR(missionLogged) = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
addMissionEventHandler ["ExtensionCallback", {
|
||||||
|
params ["_name", "_function", "_data"];
|
||||||
|
if !(_name isEqualTo GVAR(extensionName)) exitWith {};
|
||||||
|
|
||||||
|
_dataArr = parseSimpleArray _data;
|
||||||
|
if (count _dataArr isEqualTo 0) exitWith {};
|
||||||
|
|
||||||
|
switch (_function) do {
|
||||||
|
case ":LOG:MISSION:SUCCESS:": {
|
||||||
|
GVAR(missionLogged) = true;
|
||||||
|
};
|
||||||
|
case ":LOG:": {
|
||||||
|
diag_log formatText[
|
||||||
|
"[%1] %2",
|
||||||
|
GVAR(logPrefix),
|
||||||
|
_dataArr select 0
|
||||||
|
];
|
||||||
|
};
|
||||||
|
default {
|
||||||
|
["DEBUG", format["%1", _dataArr]] call FUNC(log);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
|
||||||
|
|
||||||
|
// LOAD EXTENSION
|
||||||
|
GVAR(extensionName) callExtension ":START:";
|
||||||
|
|
||||||
|
// GET MISSION START TIMESTAMP AND UNIQUE HASH
|
||||||
|
private _missionHashData = parseSimpleArray ("AttendanceTracker" callExtension ":MISSION:HASH:");
|
||||||
|
if (count _missionHashData isEqualTo 0) exitWith {
|
||||||
|
["ERROR", "Failed to get mission hash, exiting"] call FUNC(log);
|
||||||
|
};
|
||||||
|
|
||||||
|
_missionHashData params ["_timestamp", "_hash"];
|
||||||
|
GVAR(missionStart) = _timestamp;
|
||||||
|
GVAR(missionHash) = _hash;
|
||||||
|
|
||||||
|
|
||||||
|
// PARSE SETTINGS
|
||||||
|
private _settings = parseSimpleArray (GVAR(extensionName) callExtension ":GET:SETTINGS:");
|
||||||
|
if (count _settings isEqualTo 0) exitWith {
|
||||||
|
["ERROR", "Failed to get settings, exiting"] call FUNC(log);
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(settings) = createHashMapFromArray (_settings#0);
|
||||||
|
GVAR(debug) = GVAR(settings) getOrDefault ["debug", GVAR(debug)];
|
||||||
|
private _updateInterval = GVAR(settings) getOrDefault ["dbupdateinterval", 90];
|
||||||
|
// remove duration by removing the last index
|
||||||
|
_updateInterval = _updateInterval select [0, count _updateInterval - 1];
|
||||||
|
GVAR(updateInterval) = parseNumber _updateInterval;
|
||||||
|
|
||||||
|
// add player connected (to mission) handler
|
||||||
|
addMissionEventHandler ["PlayerConnected", {
|
||||||
|
_this call FUNC(onPlayerConnected);
|
||||||
|
}];
|
||||||
|
|
||||||
|
|
||||||
|
// we'll wait for the end of init (DB connect included) of the extension
|
||||||
|
// then we'll log the world and mission
|
||||||
|
// the response to THAT is handled above in the extension callback
|
||||||
|
// and will set GVAR(missionLogged) true
|
||||||
|
addMissionEventHandler ["ExtensionCallback", {
|
||||||
|
params ["_name", "_function", "_data"];
|
||||||
|
if !(_name isEqualTo GVAR(extensionName)) exitWith {};
|
||||||
|
if !(_function isEqualTo ":READY:") exitWith {};
|
||||||
|
|
||||||
|
// LOAD WORLD AND MISSION INFO
|
||||||
|
GVAR(worldInfo) = call FUNC(getWorldInfo);
|
||||||
|
GVAR(missionInfo) = call FUNC(getMissionInfo);
|
||||||
|
|
||||||
|
["INFO", (GVAR(extensionName) callExtension [
|
||||||
|
":LOG:MISSION:",
|
||||||
|
[
|
||||||
|
GVAR(worldInfo),
|
||||||
|
GVAR(missionInfo)
|
||||||
|
]
|
||||||
|
]) select 0] call FUNC(log);
|
||||||
|
|
||||||
|
// remove the handler
|
||||||
|
removeMissionEventHandler ["ExtensionCallback", _thisEventHandler];
|
||||||
|
}];
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
addMissionEventHandler ["ExtensionCallback", {
|
|
||||||
params ["_name", "_function", "_data"];
|
|
||||||
if !(_name isEqualTo "AttendanceTracker") exitWith {};
|
|
||||||
|
|
||||||
if (ATDebug && _function isNotEqualTo ":LOG:") then {
|
|
||||||
diag_log format ["Raw callback: %1 _ %2", _function, _data];
|
|
||||||
};
|
|
||||||
|
|
||||||
_dataArr = parseSimpleArray _data;
|
|
||||||
if (count _dataArr < 1) exitWith {};
|
|
||||||
|
|
||||||
switch (_function) do {
|
|
||||||
case ":LOG:": {
|
|
||||||
diag_log formatText[
|
|
||||||
"[Attendance Tracker] %1",
|
|
||||||
_dataArr select 0
|
|
||||||
];
|
|
||||||
};
|
|
||||||
default {
|
|
||||||
[format["%1", _dataArr]] call attendanceTracker_fnc_log;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
true;
|
|
||||||
}];
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
addMissionEventHandler ["ExtensionCallback", {
|
|
||||||
params ["_extension", "_function", "_data"];
|
|
||||||
if !(_extension isEqualTo "AttendanceTracker") exitWith {};
|
|
||||||
if !(_function isEqualTo ":MISSION:HASH:") exitWith {};
|
|
||||||
|
|
||||||
_dataArr = parseSimpleArray _data;
|
|
||||||
if (count _dataArr < 1) exitWith {};
|
|
||||||
|
|
||||||
_dataArr params ["_startTime", "_hash"];
|
|
||||||
ATNamespace setVariable ["missionStartTime", call attendanceTracker_fnc_timestamp];
|
|
||||||
ATNamespace setVariable ["missionHash", _hash];
|
|
||||||
|
|
||||||
removeMissionEventHandler [
|
|
||||||
"ExtensionCallback",
|
|
||||||
_thisEventHandler
|
|
||||||
];
|
|
||||||
}];
|
|
||||||
|
|
||||||
"AttendanceTracker" callExtension ":MISSION:HASH:";
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
createHashMapFromArray [
|
|
||||||
["missionName", missionName],
|
|
||||||
["missionStart", ATNamespace getVariable "missionStartTime"],
|
|
||||||
["missionHash", ATNamespace getVariable "missionHash"],
|
|
||||||
["briefingName", briefingName],
|
|
||||||
["missionNameSource", missionNameSource],
|
|
||||||
["onLoadName", getMissionConfigValue ["onLoadName", ""]],
|
|
||||||
["author", getMissionConfigValue ["author", ""]],
|
|
||||||
["serverName", serverName],
|
|
||||||
["serverProfile", profileName],
|
|
||||||
["worldName", toLower worldName]
|
|
||||||
];
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
addMissionEventHandler ["ExtensionCallback", {
|
|
||||||
params ["_extension", "_function", "_data"];
|
|
||||||
if !(_extension isEqualTo "AttendanceTracker") exitWith {};
|
|
||||||
if !(_function isEqualTo ":GET:SETTINGS:") exitWith {};
|
|
||||||
|
|
||||||
_dataArr = parseSimpleArray _data;
|
|
||||||
diag_log format ["AT: Settings received: %1", _dataArr];
|
|
||||||
if (count _dataArr < 1) exitWith {};
|
|
||||||
|
|
||||||
private _settingsJSON = _dataArr select 0;
|
|
||||||
private _settingsNamespace = [_settingsJSON] call CBA_fnc_parseJSON;
|
|
||||||
{
|
|
||||||
ATNamespace setVariable [_x, _settingsNamespace getVariable _x];
|
|
||||||
} forEach (allVariables _settingsNamespace);
|
|
||||||
ATDebug = ATNamespace getVariable "debug";
|
|
||||||
ATUpdateDelay = ATNamespace getVariable "dbUpdateInterval";
|
|
||||||
// remove last character (unit of time) and parse to number
|
|
||||||
ATUpdateDelay = parseNumber (ATUpdateDelay select [0, count ATUpdateDelay - 1]);
|
|
||||||
|
|
||||||
|
|
||||||
removeMissionEventHandler [
|
|
||||||
"ExtensionCallback",
|
|
||||||
_thisEventHandler
|
|
||||||
];
|
|
||||||
}];
|
|
||||||
|
|
||||||
"AttendanceTracker" callExtension ":GET:SETTINGS:";
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#include "..\script_mod.hpp"
|
|
||||||
|
|
||||||
params [
|
|
||||||
["_message", "", [""]],
|
|
||||||
["_level", "INFO", [""]],
|
|
||||||
"_function"
|
|
||||||
];
|
|
||||||
|
|
||||||
if (isNil "_message") exitWith {false};
|
|
||||||
if (
|
|
||||||
missionNamespace getVariable ["ATDebug", true] &&
|
|
||||||
_level != "WARN" && _level != "ERROR"
|
|
||||||
) exitWith {};
|
|
||||||
|
|
||||||
LOG_SYS(_level, _message);
|
|
||||||
|
|
||||||
true;
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
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 ((count _userInfo) isEqualTo 0) 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
// start CBA PFH
|
|
||||||
[
|
|
||||||
format [
|
|
||||||
"(EventHandler) PlayerConnected: Starting CBA PFH for %1",
|
|
||||||
_playerID
|
|
||||||
],
|
|
||||||
"DEBUG"
|
|
||||||
] call attendanceTracker_fnc_log;
|
|
||||||
|
|
||||||
[
|
|
||||||
{
|
|
||||||
params ["_args", "_handle"];
|
|
||||||
// check if player is still connected
|
|
||||||
_args params ["_playerID", "_playerUID", "_profileName", "_steamName", "_jip", "_roleDescription"];
|
|
||||||
private _userInfo = getUserInfo _playerID;
|
|
||||||
private _clientStateNumber = 0;
|
|
||||||
if (_userInfo isEqualTo []) exitWith {
|
|
||||||
[_handle] call CBA_fnc_removePerFrameHandler;
|
|
||||||
};
|
|
||||||
|
|
||||||
_clientStateNumber = _userInfo select 6;
|
|
||||||
|
|
||||||
if (_clientStateNumber < 6) exitWith {
|
|
||||||
[format ["(EventHandler) PlayerConnected: %1 (UID) is no longer connected to the mission, exiting CBA PFH", _playerID], "DEBUG"] call attendanceTracker_fnc_log;
|
|
||||||
[_handle] call CBA_fnc_removePerFrameHandler;
|
|
||||||
};
|
|
||||||
|
|
||||||
_args call attendanceTracker_fnc_writePlayer;
|
|
||||||
},
|
|
||||||
ATUpdateDelay,
|
|
||||||
[
|
|
||||||
_playerID,
|
|
||||||
_playerUID,
|
|
||||||
_profileName,
|
|
||||||
_steamName,
|
|
||||||
_jip,
|
|
||||||
roleDescription _unit
|
|
||||||
]
|
|
||||||
] call CBA_fnc_addPerFrameHandler;
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
#include "..\script_mod.hpp"
|
|
||||||
|
|
||||||
if (!isServer) exitWith {};
|
|
||||||
|
|
||||||
ATNamespace = false call CBA_fnc_createNamespace;
|
|
||||||
ATDebug = true;
|
|
||||||
"AttendanceTracker" callExtension ":START:";
|
|
||||||
|
|
||||||
|
|
||||||
// we'll wait for the asynchronous init steps of the extension to finish, to confirm we have a DB connection and our config was loaded. If there are errors with either, the extension won't reply and initiate further during this mission.
|
|
||||||
addMissionEventHandler ["ExtensionCallback", {
|
|
||||||
params ["_name", "_function", "_data"];
|
|
||||||
if !(_name isEqualTo "AttendanceTracker") exitWith {};
|
|
||||||
if !(_function isEqualTo ":READY:") exitWith {};
|
|
||||||
|
|
||||||
call attendanceTracker_fnc_getMissionHash;
|
|
||||||
call attendanceTracker_fnc_getSettings;
|
|
||||||
|
|
||||||
[
|
|
||||||
{// wait until settings have been loaded from extension
|
|
||||||
!isNil {ATNamespace getVariable "missionHash"} &&
|
|
||||||
!isNil {ATDebug}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
// get world and mission context
|
|
||||||
ATNamespace setVariable [
|
|
||||||
"worldContext",
|
|
||||||
call attendanceTracker_fnc_getWorldInfo
|
|
||||||
];
|
|
||||||
ATNamespace setVariable [
|
|
||||||
"missionContext",
|
|
||||||
call attendanceTracker_fnc_getMissionInfo
|
|
||||||
];
|
|
||||||
|
|
||||||
// write them to establish DB rows
|
|
||||||
"AttendanceTracker" callExtension [
|
|
||||||
":LOG:MISSION:",
|
|
||||||
[
|
|
||||||
[ATNamespace getVariable "missionContext"] call CBA_fnc_encodeJSON,
|
|
||||||
[ATNamespace getVariable "worldContext"] call CBA_fnc_encodeJSON
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
// add player connected (to mission) handler
|
|
||||||
addMissionEventHandler ["PlayerConnected", {
|
|
||||||
_this call attendanceTracker_fnc_onPlayerConnected;
|
|
||||||
}];
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
10, // 10 second timeout
|
|
||||||
{ // timeout code
|
|
||||||
["Failed to load settings", "ERROR"] call attendanceTracker_fnc_log;
|
|
||||||
}
|
|
||||||
] call CBA_fnc_waitUntilAndExecute;
|
|
||||||
|
|
||||||
removeMissionEventHandler [
|
|
||||||
"ExtensionCallback",
|
|
||||||
_thisEventHandler
|
|
||||||
];
|
|
||||||
}];
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
// (parseSimpleArray ("AttendanceTracker" callExtension "getTimestamp")) select 0;
|
|
||||||
|
|
||||||
// const time.RFC3339 untyped string = "2006-01-02T15:04:05Z07:00"
|
|
||||||
|
|
||||||
systemTimeUTC apply {if (_x < 10) then {"0" + str _x} else {str _x}} params [
|
|
||||||
"_year",
|
|
||||||
"_month",
|
|
||||||
"_day",
|
|
||||||
"_hour",
|
|
||||||
"_minute",
|
|
||||||
"_second",
|
|
||||||
"_millisecond"
|
|
||||||
];
|
|
||||||
|
|
||||||
format[
|
|
||||||
"%1-%2-%3T%4:%5:%6Z",
|
|
||||||
_year,
|
|
||||||
_month,
|
|
||||||
_day,
|
|
||||||
_hour,
|
|
||||||
_minute,
|
|
||||||
_second
|
|
||||||
];
|
|
||||||
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
params [
|
|
||||||
["_playerId", ""],
|
|
||||||
["_playerUID", ""],
|
|
||||||
["_profileName", ""],
|
|
||||||
["_steamName", ""],
|
|
||||||
["_isJIP", false, [true, false]],
|
|
||||||
["_roleDescription", ""]
|
|
||||||
];
|
|
||||||
|
|
||||||
private _hash = +(ATNamespace getVariable ["missionContext", createHashMap]);
|
|
||||||
|
|
||||||
_hash set ["playerId", _playerId];
|
|
||||||
_hash set ["playerUID", _playerUID];
|
|
||||||
_hash set ["profileName", _profileName];
|
|
||||||
_hash set ["steamName", _steamName];
|
|
||||||
_hash set ["isJIP", _isJIP];
|
|
||||||
_hash set ["roleDescription", _roleDescription];
|
|
||||||
|
|
||||||
"AttendanceTracker" callExtension [":LOG:PRESENCE:", [[_hash] call CBA_fnc_encodeJSON]];
|
|
||||||
|
|
||||||
true;
|
|
||||||
4
addons/main/script_component.hpp
Normal file
4
addons/main/script_component.hpp
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#define COMPONENT main
|
||||||
|
#define COMPONENT_BEAUTIFIED Main
|
||||||
|
|
||||||
|
#include "\x\attendancetracker\addons\main\script_mod.hpp"
|
||||||
@@ -1,12 +1,8 @@
|
|||||||
#include "script_version.hpp"
|
#include "script_version.hpp"
|
||||||
|
|
||||||
#define COMPONENT main
|
|
||||||
#define COMPONENT_BEAUTIFIED Main
|
|
||||||
|
|
||||||
#define MAINPREFIX x
|
#define MAINPREFIX x
|
||||||
|
#define PREFIX attendancetracker
|
||||||
|
#define PREFIX_BEAUTIFIED AttendanceTracker
|
||||||
#define SUBPREFIX addons
|
#define SUBPREFIX addons
|
||||||
#define PREFIX AttendanceTracker
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#include "\x\cba\addons\main\script_macros_common.hpp"
|
#include "\x\cba\addons\main\script_macros_common.hpp"
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#define MAJOR 0
|
#define MAJOR 1
|
||||||
#define MINOR 2
|
#define MINOR 1
|
||||||
#define PATCH 0
|
#define PATCH 0
|
||||||
#define BUILD 20231003
|
#define BUILD 20231012
|
||||||
|
|
||||||
#define VERSION 0.2
|
#define VERSION 1.1
|
||||||
#define VERSION_STR MAJOR##.##MINOR##.##PATCH##.##BUILD
|
#define VERSION_STR MAJOR##.##MINOR##.##PATCH##.##BUILD
|
||||||
#define VERSION_AR MAJOR,MINOR,PATCH,BUILD
|
#define VERSION_AR MAJOR,MINOR,PATCH,BUILD
|
||||||
11
build/Dockerfile.build
Normal file
11
build/Dockerfile.build
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# build Golang app for Linux
|
||||||
|
FROM golang:1.20
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# get gcc-multilib and gcc-mingw-w64
|
||||||
|
RUN apt update
|
||||||
|
RUN apt install -y gcc-multilib gcc-mingw-w64
|
||||||
|
|
||||||
|
CMD ["/bin/sh"]
|
||||||
|
|
||||||
Binary file not shown.
@@ -13,7 +13,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
_ "github.com/go-sql-driver/mysql"
|
_ "github.com/go-sql-driver/mysql"
|
||||||
@@ -22,54 +21,63 @@ import (
|
|||||||
"github.com/indig0fox/Arma3-AttendanceTracker/internal/util"
|
"github.com/indig0fox/Arma3-AttendanceTracker/internal/util"
|
||||||
"github.com/indig0fox/a3go/a3interface"
|
"github.com/indig0fox/a3go/a3interface"
|
||||||
"github.com/indig0fox/a3go/assemblyfinder"
|
"github.com/indig0fox/a3go/assemblyfinder"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
const EXTENSION_NAME string = "AttendanceTracker"
|
const EXTENSION_NAME string = "AttendanceTracker"
|
||||||
const ADDON_NAME string = "AttendanceTracker"
|
const ADDON_NAME string = "AttendanceTracker"
|
||||||
const EXTENSION_VERSION string = "dev"
|
|
||||||
|
|
||||||
// file paths
|
// file paths
|
||||||
const ATTENDANCE_TABLE string = "attendance"
|
const ATTENDANCE_TABLE string = "attendance"
|
||||||
const MISSIONS_TABLE string = "missions"
|
const MISSIONS_TABLE string = "missions"
|
||||||
const WORLDS_TABLE string = "worlds"
|
const WORLDS_TABLE string = "worlds"
|
||||||
|
|
||||||
var currentMissionID uint = 0
|
|
||||||
|
|
||||||
var RVExtensionChannels = map[string]chan string{
|
|
||||||
":START:": make(chan string),
|
|
||||||
":MISSION:HASH:": make(chan string),
|
|
||||||
":GET:SETTINGS:": make(chan string),
|
|
||||||
}
|
|
||||||
|
|
||||||
var RVExtensionArgsChannels = map[string]chan []string{
|
|
||||||
":LOG:MISSION:": make(chan []string),
|
|
||||||
":LOG:PRESENCE:": make(chan []string),
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
EXTENSION_VERSION string = "DEVELOPMENT"
|
||||||
|
|
||||||
modulePath string
|
modulePath string
|
||||||
modulePathDir string
|
modulePathDir string
|
||||||
|
|
||||||
initSuccess bool // default false
|
loadedMission *Mission
|
||||||
|
loadedWorld *World
|
||||||
)
|
)
|
||||||
|
|
||||||
// configure log output
|
// configure log output
|
||||||
func init() {
|
func init() {
|
||||||
|
|
||||||
a3interface.SetVersion(EXTENSION_VERSION)
|
a3interface.SetVersion(EXTENSION_VERSION)
|
||||||
a3interface.RegisterRvExtensionChannels(RVExtensionChannels)
|
a3interface.NewRegistration(":START:").
|
||||||
a3interface.RegisterRvExtensionArgsChannels(RVExtensionArgsChannels)
|
SetFunction(onStartCommand).
|
||||||
|
SetRunInBackground(false).
|
||||||
|
Register()
|
||||||
|
|
||||||
|
a3interface.NewRegistration(":MISSION:HASH:").
|
||||||
|
SetFunction(onMissionHashCommand).
|
||||||
|
SetRunInBackground(false).
|
||||||
|
Register()
|
||||||
|
|
||||||
|
a3interface.NewRegistration(":GET:SETTINGS:").
|
||||||
|
SetFunction(onGetSettingsCommand).
|
||||||
|
SetRunInBackground(false).
|
||||||
|
Register()
|
||||||
|
|
||||||
|
a3interface.NewRegistration(":LOG:MISSION:").
|
||||||
|
SetDefaultResponse(`Logging mission data`).
|
||||||
|
SetArgsFunction(onLogMissionArgsCommand).
|
||||||
|
SetRunInBackground(true).
|
||||||
|
Register()
|
||||||
|
|
||||||
|
a3interface.NewRegistration(":LOG:PRESENCE:").
|
||||||
|
SetDefaultResponse(`Logging presence data`).
|
||||||
|
SetArgsFunction(onLogPresenceArgsCommand).
|
||||||
|
SetRunInBackground(true).
|
||||||
|
Register()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
modulePath = assemblyfinder.GetModulePath()
|
modulePath = assemblyfinder.GetModulePath()
|
||||||
// get absolute path of module path
|
modulePathDir = filepath.Dir(modulePath)
|
||||||
modulePathAbs, err := filepath.Abs(modulePath)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
modulePathDir = filepath.Dir(modulePathAbs)
|
|
||||||
|
|
||||||
result, configErr := util.LoadConfig(modulePathDir)
|
result, configErr := util.LoadConfig(modulePathDir)
|
||||||
logger.InitLoggers(&logger.LoggerOptionsType{
|
logger.InitLoggers(&logger.LoggerOptionsType{
|
||||||
@@ -82,9 +90,11 @@ func init() {
|
|||||||
)),
|
)),
|
||||||
AddonName: ADDON_NAME,
|
AddonName: ADDON_NAME,
|
||||||
ExtensionName: EXTENSION_NAME,
|
ExtensionName: EXTENSION_NAME,
|
||||||
|
ExtensionVersion: EXTENSION_VERSION,
|
||||||
Debug: util.ConfigJSON.GetBool("armaConfig.debug"),
|
Debug: util.ConfigJSON.GetBool("armaConfig.debug"),
|
||||||
Trace: util.ConfigJSON.GetBool("armaConfig.traceLogToFile"),
|
Trace: util.ConfigJSON.GetBool("armaConfig.trace"),
|
||||||
})
|
})
|
||||||
|
logger.RotateLogs()
|
||||||
if configErr != nil {
|
if configErr != nil {
|
||||||
logger.Log.Error().Err(configErr).Msgf(`Error loading config`)
|
logger.Log.Error().Err(configErr).Msgf(`Error loading config`)
|
||||||
return
|
return
|
||||||
@@ -92,9 +102,7 @@ func init() {
|
|||||||
logger.Log.Info().Msgf(result)
|
logger.Log.Info().Msgf(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.RotateLogs()
|
logger.Log.Info().Msgf(`%s v%s started`, EXTENSION_NAME, EXTENSION_VERSION)
|
||||||
|
|
||||||
logger.ArmaOnly.Info().Msgf(`%s v%s started`, EXTENSION_NAME, "0.0.0")
|
|
||||||
logger.ArmaOnly.Info().Msgf(`Log path: %s`, logger.ActiveOptions.Path)
|
logger.ArmaOnly.Info().Msgf(`Log path: %s`, logger.ActiveOptions.Path)
|
||||||
|
|
||||||
db.SetConfig(db.ConfigStruct{
|
db.SetConfig(db.ConfigStruct{
|
||||||
@@ -124,11 +132,10 @@ func init() {
|
|||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log.Error().Err(err).Msgf(`Error migrating database schema`)
|
logger.Log.Error().Err(err).Msgf(`Error migrating database schema`)
|
||||||
|
} else {
|
||||||
|
logger.Log.Info().Msgf(`Database schema migrated`)
|
||||||
}
|
}
|
||||||
|
|
||||||
startA3CallHandlers()
|
|
||||||
|
|
||||||
initSuccess = true
|
|
||||||
a3interface.WriteArmaCallback(
|
a3interface.WriteArmaCallback(
|
||||||
EXTENSION_NAME,
|
EXTENSION_NAME,
|
||||||
":READY:",
|
":READY:",
|
||||||
@@ -138,56 +145,84 @@ func init() {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func startA3CallHandlers() error {
|
func onStartCommand(
|
||||||
go func() {
|
ctx a3interface.ArmaExtensionContext,
|
||||||
for {
|
data string,
|
||||||
select {
|
) (string, error) {
|
||||||
case <-RVExtensionChannels[":START:"]:
|
logger.Log.Debug().Msgf(`RVExtension :START: requested`)
|
||||||
logger.Log.Trace().Msgf(`RVExtension :START: requested`)
|
loadedWorld = nil
|
||||||
if !initSuccess {
|
loadedMission = nil
|
||||||
logger.Log.Warn().Msgf(`Received another :START: command before init was complete, ignoring.`)
|
return fmt.Sprintf(
|
||||||
continue
|
`["%s v%s started"]`,
|
||||||
} else {
|
|
||||||
logger.RotateLogs()
|
|
||||||
a3interface.WriteArmaCallback(
|
|
||||||
EXTENSION_NAME,
|
EXTENSION_NAME,
|
||||||
":READY:",
|
EXTENSION_VERSION,
|
||||||
)
|
), nil
|
||||||
}
|
}
|
||||||
case <-RVExtensionChannels[":MISSION:HASH:"]:
|
|
||||||
logger.Log.Trace().Msgf(`RVExtension :MISSION:HASH: requested`)
|
func onMissionHashCommand(
|
||||||
|
ctx a3interface.ArmaExtensionContext,
|
||||||
|
data string,
|
||||||
|
) (string, error) {
|
||||||
|
logger.Log.Debug().Msgf(`RVExtension :MISSION:HASH: requested`)
|
||||||
timestamp, hash := getMissionHash()
|
timestamp, hash := getMissionHash()
|
||||||
a3interface.WriteArmaCallback(
|
return fmt.Sprintf(
|
||||||
EXTENSION_NAME,
|
`[%q, %q]`,
|
||||||
":MISSION:HASH:",
|
|
||||||
timestamp,
|
timestamp,
|
||||||
hash,
|
hash,
|
||||||
)
|
), nil
|
||||||
case <-RVExtensionChannels[":GET:SETTINGS:"]:
|
|
||||||
logger.Log.Trace().Msg(`Settings requested`)
|
|
||||||
armaConfig, err := util.ConfigArmaFormat()
|
|
||||||
if err != nil {
|
|
||||||
logger.Log.Error().Err(err).Msg(`Error when marshaling arma config`)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
logger.Log.Trace().Str("armaConfig", armaConfig).Send()
|
|
||||||
|
func onGetSettingsCommand(
|
||||||
|
ctx a3interface.ArmaExtensionContext,
|
||||||
|
data string,
|
||||||
|
) (string, error) {
|
||||||
|
logger.Log.Debug().Msg(`RVExtension :GET:SETTINGS: requested`)
|
||||||
|
// get arma config
|
||||||
|
c := util.ConfigJSON.Get("armaConfig")
|
||||||
|
armaConfig := a3interface.ToArmaHashMap(c)
|
||||||
|
return fmt.Sprintf(
|
||||||
|
`[%s]`,
|
||||||
|
armaConfig,
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func onLogMissionArgsCommand(
|
||||||
|
ctx a3interface.ArmaExtensionContext,
|
||||||
|
command string,
|
||||||
|
args []string,
|
||||||
|
) (string, error) {
|
||||||
|
thisLogger := logger.Log.With().Str("command", command).Interface("ctx", ctx).Logger()
|
||||||
|
thisLogger.Debug().Msgf(`RVExtension :LOG:MISSION: requested`)
|
||||||
|
var err error
|
||||||
|
world, err := writeWorldInfo(args[0], thisLogger)
|
||||||
|
if err != nil {
|
||||||
|
return ``, err
|
||||||
|
}
|
||||||
|
loadedWorld = &world
|
||||||
|
|
||||||
|
mission, err := writeMission(args[1], thisLogger)
|
||||||
|
if err != nil {
|
||||||
|
return ``, err
|
||||||
|
}
|
||||||
|
loadedMission = &mission
|
||||||
|
|
||||||
a3interface.WriteArmaCallback(
|
a3interface.WriteArmaCallback(
|
||||||
EXTENSION_NAME,
|
EXTENSION_NAME,
|
||||||
":GET:SETTINGS:",
|
":LOG:MISSION:SUCCESS:",
|
||||||
armaConfig,
|
|
||||||
)
|
)
|
||||||
case v := <-RVExtensionArgsChannels[":LOG:MISSION:"]:
|
|
||||||
go func(data []string) {
|
|
||||||
writeWorldInfo(v[1])
|
|
||||||
writeMission(v[0])
|
|
||||||
}(v)
|
|
||||||
case v := <-RVExtensionArgsChannels[":LOG:PRESENCE:"]:
|
|
||||||
go writeAttendance(v[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
return ``, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func onLogPresenceArgsCommand(
|
||||||
|
ctx a3interface.ArmaExtensionContext,
|
||||||
|
command string,
|
||||||
|
args []string,
|
||||||
|
) (string, error) {
|
||||||
|
thisLogger := logger.Log.With().Str("command", command).Interface("ctx", ctx).Logger()
|
||||||
|
thisLogger.Debug().Msgf(`RVExtension :LOG:PRESENCE: requested`)
|
||||||
|
writeAttendance(args[0], thisLogger)
|
||||||
|
return ``, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getMissionHash will return the current time in UTC and an md5 hash of that time
|
// getMissionHash will return the current time in UTC and an md5 hash of that time
|
||||||
@@ -197,7 +232,7 @@ func getMissionHash() (sqlTime, hashString string) {
|
|||||||
|
|
||||||
nowTime := time.Now().UTC()
|
nowTime := time.Now().UTC()
|
||||||
// mysql format
|
// mysql format
|
||||||
sqlTime = nowTime.Format("2006-01-02 15:04:05")
|
sqlTime = nowTime.Format(time.RFC3339)
|
||||||
hash := md5.Sum([]byte(sqlTime))
|
hash := md5.Sum([]byte(sqlTime))
|
||||||
hashString = fmt.Sprintf(`%x`, hash)
|
hashString = fmt.Sprintf(`%x`, hash)
|
||||||
|
|
||||||
@@ -240,86 +275,162 @@ func finalizeUnendedSessions() {
|
|||||||
logger.Log.Info().Msgf(`Filled disconnect time of %d events.`, len(events))
|
logger.Log.Info().Msgf(`Filled disconnect time of %d events.`, len(events))
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeWorldInfo(worldInfo string) {
|
func writeWorldInfo(worldInfo string, thisLogger zerolog.Logger) (World, error) {
|
||||||
// worldInfo is json, parse it
|
|
||||||
var wi World
|
parsedInterface, err := a3interface.ParseSQF(worldInfo)
|
||||||
fixedString := unescapeArmaQuotes(worldInfo)
|
|
||||||
err := json.Unmarshal([]byte(fixedString), &wi)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log.Error().Err(err).Msgf(`Error when unmarshalling world info`)
|
thisLogger.Error().Err(err).Msgf(`Error when parsing world info`)
|
||||||
return
|
return World{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedMap, err := a3interface.ParseSQFHashMap(parsedInterface)
|
||||||
|
if err != nil {
|
||||||
|
thisLogger.Error().Err(err).Msgf(`Error when parsing world info`)
|
||||||
|
return World{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
thisLogger.Trace().Msgf(`parsedMap: %+v`, parsedMap)
|
||||||
|
|
||||||
|
// create world object from map[string]interface{}
|
||||||
|
var wi = World{}
|
||||||
|
worldBytes, err := json.Marshal(parsedMap)
|
||||||
|
if err != nil {
|
||||||
|
thisLogger.Error().Err(err).Msgf(`Error when marshalling world info`)
|
||||||
|
return World{}, err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(worldBytes, &wi)
|
||||||
|
if err != nil {
|
||||||
|
thisLogger.Error().Err(err).Msgf(`Error when unmarshalling world info`)
|
||||||
|
return World{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
thisLogger.Trace().Msgf(`World info: %+v`, wi)
|
||||||
|
|
||||||
|
var dbWorld World
|
||||||
|
db.Client().Where("world_name = ?", wi.WorldName).First(&dbWorld)
|
||||||
|
// if world exists, use it
|
||||||
|
if dbWorld.ID > 0 {
|
||||||
|
thisLogger.Debug().Msgf(`World %s exists with ID %d.`, wi.WorldName, dbWorld.ID)
|
||||||
|
return dbWorld, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// write world if not exist
|
// write world if not exist
|
||||||
var dbWorld World
|
|
||||||
db.Client().Where("world_name = ?", wi.WorldName).First(&dbWorld)
|
|
||||||
if dbWorld.ID == 0 {
|
|
||||||
db.Client().Create(&wi)
|
db.Client().Create(&wi)
|
||||||
if db.Client().Error != nil {
|
if db.Client().Error != nil {
|
||||||
logger.Log.Error().Err(db.Client().Error).Msgf(`Error when creating world`)
|
thisLogger.Error().Err(db.Client().Error).Msgf(`Error when creating world`)
|
||||||
return
|
return World{}, db.Client().Error
|
||||||
}
|
|
||||||
logger.Log.Info().Msgf(`World %s created.`, wi.WorldName)
|
|
||||||
} else {
|
|
||||||
// don't do anything if exists
|
|
||||||
logger.Log.Debug().Msgf(`World %s exists with ID %d.`, wi.WorldName, dbWorld.ID)
|
|
||||||
}
|
}
|
||||||
|
thisLogger.Info().Msgf(`World %s created.`, wi.WorldName)
|
||||||
|
|
||||||
|
return wi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeMission(missionJSON string) {
|
func writeMission(data string, thisLogger zerolog.Logger) (Mission, error) {
|
||||||
var err error
|
var err error
|
||||||
// writeLog(functionName, fmt.Sprintf(`["%s", "DEBUG"]`, Mission))
|
parsedInterface, err := a3interface.ParseSQF(data)
|
||||||
// Mission is json, parse it
|
|
||||||
var mi Mission
|
|
||||||
fixedString := fixEscapeQuotes(trimQuotes(missionJSON))
|
|
||||||
err = json.Unmarshal([]byte(fixedString), &mi)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log.Error().Err(err).Msgf(`Error when unmarshalling mission`)
|
thisLogger.Error().Err(err).Msgf(`Error when parsing mission info`)
|
||||||
return
|
return Mission{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// get world from WorldName
|
parsedMap, err := a3interface.ParseSQFHashMap(parsedInterface)
|
||||||
var dbWorld World
|
if err != nil {
|
||||||
db.Client().Where("world_name = ?", mi.WorldName).First(&dbWorld)
|
thisLogger.Error().Err(err).Msgf(`Error when parsing mission info`)
|
||||||
if dbWorld.ID == 0 {
|
return Mission{}, err
|
||||||
logger.Log.Error().Msgf(`World %s not found.`, mi.WorldName)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mi.WorldID = dbWorld.ID
|
thisLogger.Trace().Msgf(`parsedMap: %+v`, parsedMap)
|
||||||
|
|
||||||
|
var mi Mission
|
||||||
|
// create mission object from map[string]interface{}
|
||||||
|
missionBytes, err := json.Marshal(parsedMap)
|
||||||
|
if err != nil {
|
||||||
|
thisLogger.Error().Err(err).Msgf(`Error when marshalling mission info`)
|
||||||
|
return Mission{}, err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(missionBytes, &mi)
|
||||||
|
if err != nil {
|
||||||
|
thisLogger.Error().Err(err).Msgf(`Error when unmarshalling mission info`)
|
||||||
|
return Mission{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if loadedWorld == nil {
|
||||||
|
thisLogger.Error().Msgf(`Current world ID not set, cannot create mission`)
|
||||||
|
return Mission{}, err
|
||||||
|
}
|
||||||
|
if loadedWorld.ID == 0 {
|
||||||
|
thisLogger.Error().Msgf(`Current world ID is 0, cannot create mission`)
|
||||||
|
return Mission{}, err
|
||||||
|
}
|
||||||
|
mi.WorldID = loadedWorld.ID
|
||||||
|
|
||||||
// write mission to database
|
// write mission to database
|
||||||
db.Client().Create(&mi)
|
db.Client().Create(&mi)
|
||||||
if db.Client().Error != nil {
|
if db.Client().Error != nil {
|
||||||
logger.Log.Error().Err(db.Client().Error).Msgf(`Error when creating mission`)
|
thisLogger.Error().Err(db.Client().Error).Msgf(`Error when creating mission`)
|
||||||
return
|
return Mission{}, db.Client().Error
|
||||||
}
|
}
|
||||||
logger.Log.Info().Msgf(`Mission %s created with ID %d`, mi.MissionName, mi.ID)
|
thisLogger.Info().Msgf(`Mission %s created with ID %d`, mi.MissionName, mi.ID)
|
||||||
currentMissionID = mi.ID
|
|
||||||
|
a3interface.WriteArmaCallback(
|
||||||
|
EXTENSION_NAME,
|
||||||
|
":LOG:MISSION:SUCCESS:",
|
||||||
|
"World and mission logged successfully.",
|
||||||
|
)
|
||||||
|
|
||||||
|
return mi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeAttendance(data string) {
|
func writeAttendance(data string, thisLogger zerolog.Logger) {
|
||||||
var err error
|
var err error
|
||||||
// data is json, parse it
|
|
||||||
stringjson := unescapeArmaQuotes(data)
|
parsedInterface, err := a3interface.ParseSQF(data)
|
||||||
var event Session
|
|
||||||
err = json.Unmarshal([]byte(stringjson), &event)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log.Error().Err(err).Msgf(`Error when unmarshalling attendance`)
|
thisLogger.Error().Err(err).Str("data", data).Msgf(`Error when parsing attendance info`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parsedMap, err := a3interface.ParseSQFHashMap(parsedInterface)
|
||||||
|
if err != nil {
|
||||||
|
thisLogger.Error().Err(err).Str("data", data).Msgf(`Error when parsing attendance info`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
thisLogger.Trace().Msgf(`parsedMap: %+v`, parsedMap)
|
||||||
|
|
||||||
|
var thisSession Session
|
||||||
|
// create session object from map[string]interface{}
|
||||||
|
sessionBytes, err := json.Marshal(parsedMap)
|
||||||
|
if err != nil {
|
||||||
|
thisLogger.Error().Err(err).Str("data", data).Msgf(`Error when marshalling attendance info`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(sessionBytes, &thisSession)
|
||||||
|
if err != nil {
|
||||||
|
thisLogger.Error().Err(err).Str("data", data).Msgf(`Error when unmarshalling attendance info`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
thisLogger2 := thisLogger.With().
|
||||||
|
Str("playerId", thisSession.PlayerId).
|
||||||
|
Str("playerUID", thisSession.PlayerUID).
|
||||||
|
Str("profileName", thisSession.ProfileName).
|
||||||
|
Logger()
|
||||||
|
|
||||||
// search existing event
|
// search existing event
|
||||||
var dbEvent Session
|
var dbEvent Session
|
||||||
|
|
||||||
db.Client().
|
db.Client().
|
||||||
Where(
|
Where(
|
||||||
"player_uid = ? AND mission_hash = ?",
|
"player_id = ? AND mission_hash = ?",
|
||||||
event.PlayerUID,
|
thisSession.PlayerId,
|
||||||
event.MissionHash,
|
thisSession.MissionHash,
|
||||||
).
|
).
|
||||||
Order("join_time_utc desc").
|
Order("join_time_utc desc").
|
||||||
First(&dbEvent)
|
First(&dbEvent)
|
||||||
if dbEvent.ID != 0 {
|
|
||||||
|
if dbEvent.ID > 0 {
|
||||||
// update disconnect time
|
// update disconnect time
|
||||||
dbEvent.DisconnectTimeUTC = sql.NullTime{
|
dbEvent.DisconnectTimeUTC = sql.NullTime{
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
@@ -327,34 +438,32 @@ func writeAttendance(data string) {
|
|||||||
}
|
}
|
||||||
err = db.Client().Save(&dbEvent).Error
|
err = db.Client().Save(&dbEvent).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log.Error().Err(err).
|
thisLogger2.Error().Err(err).
|
||||||
Msgf(`Error when updating disconnect time for event %d`, dbEvent.ID)
|
Msgf(`Error when updating disconnect time for event %d`, dbEvent.ID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Log.Debug().Msgf(`Attendance updated for %s (%s)`,
|
thisLogger2.Debug().Msgf(`Attendance updated with ID %d`,
|
||||||
dbEvent.ProfileName,
|
dbEvent.ID,
|
||||||
dbEvent.PlayerUID,
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// insert new row
|
// insert new row
|
||||||
event.JoinTimeUTC = sql.NullTime{
|
thisSession.JoinTimeUTC = sql.NullTime{
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
Valid: true,
|
Valid: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
if currentMissionID == 0 {
|
if loadedMission == nil {
|
||||||
logger.Log.Error().Msgf(`Current mission ID not set, cannot create attendance event`)
|
thisLogger2.Error().Msgf(`Current mission ID not set, cannot create attendance event`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
event.MissionID = currentMissionID
|
thisSession.MissionID = loadedMission.ID
|
||||||
err = db.Client().Create(&event).Error
|
err = db.Client().Create(&thisSession).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log.Error().Err(err).Msgf(`Error when creating attendance event`)
|
thisLogger2.Error().Err(err).Msgf(`Error when creating attendance event`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Log.Debug().Msgf(`Attendance created for %s (%s)`,
|
thisLogger2.Info().Msgf(`Attendance created with ID %d`,
|
||||||
event.ProfileName,
|
thisSession.ID,
|
||||||
event.PlayerUID,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -365,20 +474,6 @@ func getTimestamp() string {
|
|||||||
return time.Now().Format("2006-01-02 15:04:05")
|
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 unescapeArmaQuotes(s string) string {
|
|
||||||
return fixEscapeQuotes(trimQuotes(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// loadConfig()
|
// loadConfig()
|
||||||
// fmt.Println("Running DB connect/migrate to build schema...")
|
// fmt.Println("Running DB connect/migrate to build schema...")
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ go 1.20
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-sql-driver/mysql v1.7.1
|
github.com/go-sql-driver/mysql v1.7.1
|
||||||
github.com/indig0fox/a3go v0.2.0
|
github.com/indig0fox/a3go v0.3.2
|
||||||
github.com/rs/zerolog v1.30.0
|
github.com/rs/zerolog v1.31.0
|
||||||
github.com/spf13/viper v1.16.0
|
github.com/spf13/viper v1.17.0
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
gorm.io/driver/mysql v1.5.1
|
gorm.io/driver/mysql v1.5.2
|
||||||
gorm.io/gorm v1.25.4
|
gorm.io/gorm v1.25.5
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -22,12 +22,18 @@ require (
|
|||||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||||
github.com/spf13/afero v1.9.5 // indirect
|
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
|
github.com/spf13/afero v1.10.0 // indirect
|
||||||
github.com/spf13/cast v1.5.1 // indirect
|
github.com/spf13/cast v1.5.1 // indirect
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
golang.org/x/sys v0.12.0 // indirect
|
go.uber.org/atomic v1.11.0 // indirect
|
||||||
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
||||||
|
golang.org/x/sys v0.13.0 // indirect
|
||||||
golang.org/x/text v0.13.0 // indirect
|
golang.org/x/text v0.13.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|||||||
@@ -127,8 +127,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
|||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/indig0fox/a3go v0.2.0 h1:2r1fyUePCH9KLMBzVXBigkQT2zS39mpHZAm5egmIrNk=
|
github.com/indig0fox/a3go v0.3.2 h1:bNL90pffeOnS6Qtjoo5JHpdpZn1f0BZmRZR8nz/xcvQ=
|
||||||
github.com/indig0fox/a3go v0.2.0/go.mod h1:8htVwBiIAVKpT1Jyb+5dm7GuLAAevTXgw7UKxSlOawY=
|
github.com/indig0fox/a3go v0.3.2/go.mod h1:8htVwBiIAVKpT1Jyb+5dm7GuLAAevTXgw7UKxSlOawY=
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
@@ -165,8 +165,18 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV
|
|||||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c=
|
github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c=
|
||||||
github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
|
github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
|
||||||
|
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
|
||||||
|
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||||
|
github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ=
|
||||||
|
github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U=
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||||
|
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||||
|
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||||
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
|
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
|
||||||
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||||
|
github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY=
|
||||||
|
github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
||||||
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||||
@@ -175,10 +185,13 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
|||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
|
github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
|
||||||
github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
|
github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
|
||||||
|
github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI=
|
||||||
|
github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
@@ -198,6 +211,14 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||||
|
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||||
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
|
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||||
|
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||||
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
@@ -215,6 +236,10 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
|||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||||
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||||
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||||
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
@@ -331,6 +356,8 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@@ -498,9 +525,14 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw=
|
gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw=
|
||||||
gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o=
|
gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o=
|
||||||
|
gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs=
|
||||||
|
gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8=
|
||||||
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||||
|
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||||
gorm.io/gorm v1.25.4 h1:iyNd8fNAe8W9dvtlgeRI5zSVZPsq3OpcTu37cYcpCmw=
|
gorm.io/gorm v1.25.4 h1:iyNd8fNAe8W9dvtlgeRI5zSVZPsq3OpcTu37cYcpCmw=
|
||||||
gorm.io/gorm v1.25.4/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
gorm.io/gorm v1.25.4/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||||
|
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||||
|
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|||||||
@@ -52,9 +52,9 @@ func InitLoggers(o *LoggerOptionsType) {
|
|||||||
ll = &lumberjack.Logger{
|
ll = &lumberjack.Logger{
|
||||||
Filename: ActiveOptions.Path,
|
Filename: ActiveOptions.Path,
|
||||||
MaxSize: 5,
|
MaxSize: 5,
|
||||||
MaxBackups: 10,
|
MaxBackups: 8,
|
||||||
MaxAge: 14,
|
MaxAge: 14,
|
||||||
Compress: true,
|
Compress: false,
|
||||||
LocalTime: true,
|
LocalTime: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ func InitLoggers(o *LoggerOptionsType) {
|
|||||||
armaLogFormatLevel := func(i interface{}) string {
|
armaLogFormatLevel := func(i interface{}) string {
|
||||||
return strings.ToUpper(
|
return strings.ToUpper(
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
"(%s)",
|
"%s:",
|
||||||
i,
|
i,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -117,13 +117,17 @@ func InitLoggers(o *LoggerOptionsType) {
|
|||||||
NoColor: true,
|
NoColor: true,
|
||||||
FormatTimestamp: armaLogFormatTimestamp,
|
FormatTimestamp: armaLogFormatTimestamp,
|
||||||
FormatLevel: armaLogFormatLevel,
|
FormatLevel: armaLogFormatLevel,
|
||||||
|
FieldsExclude: []string{zerolog.CallerFieldName, "ctx"},
|
||||||
},
|
},
|
||||||
)).With().Timestamp().Logger()
|
)).With().Timestamp().Caller().Logger()
|
||||||
|
|
||||||
if ActiveOptions.Debug {
|
if ActiveOptions.Debug {
|
||||||
Log = Log.Level(zerolog.DebugLevel)
|
Log = Log.Level(zerolog.DebugLevel)
|
||||||
} else {
|
} else {
|
||||||
Log = Log.Level(zerolog.InfoLevel)
|
Log = Log.Level(zerolog.InfoLevel)
|
||||||
}
|
}
|
||||||
|
if ActiveOptions.Trace {
|
||||||
|
Log = Log.Level(zerolog.TraceLevel)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
15
mariadb/docker-compose.yaml
Normal file
15
mariadb/docker-compose.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
version: '3.1'
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: mariadb
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
- "MARIADB_ROOT_PASSWORD=example"
|
||||||
|
- "MARIADB_DATABASE=a3attendance"
|
||||||
|
volumes:
|
||||||
|
- ./db:/var/lib/mysql
|
||||||
|
ports:
|
||||||
|
- "3306:3306"
|
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user