16 Commits

Author SHA1 Message Date
Adrian Zuercher
5990e8ee11 tipo .gitea
All checks were successful
Build DBM Service / build (amd64, .exe, windows) (push) Successful in 2m41s
Build DBM Service / build (amd64, , linux) (push) Successful in 2m44s
Build DBM Service / build (arm, 6, , linux) (push) Successful in 2m36s
Build DBM Service / build (arm64, , linux) (push) Successful in 2m34s
2025-08-07 12:20:10 +02:00
Adrian Zuercher
48d05eec7e repo name change 2025-08-07 12:18:57 +02:00
Adrian Zuercher
bc2e582203 send value for correct table update 2025-08-06 22:02:18 +02:00
Adrian Zuercher
21e4231e24 add and improve remove driver function and rename datapoint 2025-07-31 12:20:49 +02:00
Adrian Zuercher
b21101958d add write ping before pinghandler starts 2025-07-27 09:05:53 +02:00
Adrian Zuercher
c3a3060129 improve websocket ping and remodel for rename datapoint 2025-07-25 18:26:36 +02:00
Adrian Zuercher
a23f82e9fe Merge branch 'main' of https://github.com/tecamino/tecamino-dbm
This is a neccesary pull merge
2025-07-23 09:15:01 +02:00
Adrian Zuercher
c37dd87a37 new respond to send all create change delete different bug fixes 2025-07-23 09:10:56 +02:00
Adrian Zuercher
8be5c80a22 add broadcast to all client and modify lingpong handler 2025-07-23 09:09:14 +02:00
zuadi
47a065aaf9 Update build.yml add flags to make smaller executable 2025-07-13 20:10:05 +02:00
Adrian Zuercher
5e1e4b9daf make new model for stringSlices allowOrigin helper 2025-07-13 20:01:13 +02:00
Adrian Zuercher
0f06128ce8 new send datapoint type and one fix typo 2025-07-11 17:47:12 +02:00
Adrian Zuercher
be07dc8749 add new argument for remote port 2025-07-11 17:46:32 +02:00
Adrian Zuercher
e75d7c8b03 add automatic localhost and local ip to allow origin 2025-06-29 21:18:15 +02:00
Adrian Zürcher
59c7705ca1 fix concurrent write new sendPong channel 2025-06-22 08:58:14 +02:00
Adrian Zürcher
91ea59ed6e improvement websocket according to gorilla example 2025-06-22 07:46:24 +02:00
26 changed files with 684 additions and 340 deletions

View File

@@ -0,0 +1,89 @@
name: Build DBM Service
on:
push:
tags:
- '*'
env:
APP_NAME: tecamino-dbm
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- os: windows
arch: amd64
ext: .exe
- os: linux
arch: amd64
ext: ""
- os: linux
arch: arm64
ext: ""
- os: linux
arch: arm
arm_version: 6
ext: ""
steps:
- uses: actions/checkout@v4
- name: Ensure latest Go is installed in /data/go
run: |
export GOROOT=/data/go/go
export PATH=$GOROOT/bin:$PATH
export GOCACHE=/data/gocache
export GOMODCACHE=/data/gomodcache
mkdir -p $GOCACHE $GOMODCACHE
if [ ! -x "$GOROOT/bin/go" ]; then
echo "Go not found in $GOROOT, downloading latest stable..."
GO_VERSION=$(curl -s https://go.dev/VERSION?m=text | head -n1)
echo "Latest version is $GO_VERSION"
mkdir -p /data/go
curl -sSL "https://go.dev/dl/${GO_VERSION}.linux-amd64.tar.gz" -o /tmp/go.tar.gz
tar -C /data/go -xzf /tmp/go.tar.gz
else
echo "Using cached Go from $GOROOT"
fi
go version
- name: Download Go dependencies
run: |
export GOROOT=/data/go/go
export PATH=$GOROOT/bin:$PATH
export GOCACHE=/data/gocache
export GOMODCACHE=/data/gomodcache
mkdir -p $GOCACHE $GOMODCACHE
go mod download
- name: Build binary
run: |
export GOROOT=/data/go/go
export PATH=$GOROOT/bin:$PATH
export GOCACHE=/data/gocache
export GOMODCACHE=/data/gomodcache
mkdir -p $GOCACHE $GOMODCACHE
OUTPUT="bin/${APP_NAME}-${{ matrix.os }}-${{ matrix.arch }}"
if [ -n "${{ matrix.arm_version }}" ]; then
OUTPUT="${OUTPUT}v${{ matrix.arm_version }}"
export GOARM=${{ matrix.arm_version }}
fi
OUTPUT="${OUTPUT}${{ matrix.ext }}"
echo "Building $OUTPUT"
GOOS=${{ matrix.os }} GOARCH=${{ matrix.arch }} go build -ldflags="-s -w" -trimpath -o "$OUTPUT"
shell: bash
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: ${{ env.APP_NAME }}-${{ matrix.os }}-${{ matrix.arch }}
path: bin/

View File

@@ -1,48 +0,0 @@
name: Build Go Binaries
on:
push:
branches: [ main ]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
goos: [linux, windows]
goarch: [amd64, arm, arm64]
exclude:
- goos: windows
goarch: arm
- goos: windows
goarch: arm64
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.24.0'
- name: Set up Git credentials for private modules
run: |
git config --global url."https://${{ secrets.GH_PAT }}@github.com/".insteadOf "https://github.com/"
echo "GOPRIVATE=github.com/tecamino/*" >> $GITHUB_ENV
- name: Build binary
run: |
mkdir -p build
if [ "${{ matrix.goos }}" == "windows" ]; then
GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build -o build/tecamino-dbm-${{ matrix.goos }}-${{ matrix.goarch }}.exe main.go
else
GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build -o build/tecamino-dbm-${{ matrix.goos }}-${{ matrix.goarch }} main.go
fi
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: binaries-${{ matrix.goos }}-${{ matrix.goarch }}
path: build/

View File

@@ -2,10 +2,9 @@ package args
import ( import (
"flag" "flag"
"strings"
"github.com/tecamino/tecamino-dbm/cert" "gitea.tecamino.com/paadi/tecamino-dbm/cert"
"github.com/tecamino/tecamino-dbm/models" "gitea.tecamino.com/paadi/tecamino-dbm/models"
) )
// DBM cli arguments // DBM cli arguments
@@ -19,21 +18,10 @@ type Args struct {
Debug bool Debug bool
} }
type StringSlice []string
func (s *StringSlice) String() string {
return strings.Join(*s, ",")
}
func (s *StringSlice) Set(value string) error {
*s = append(*s, value)
return nil
}
// initialte cli arguments // initialte cli arguments
func Init() *Args { func Init() *Args {
var allowOrigins StringSlice var allowOrigins models.StringSlice
flag.Var(&allowOrigins, "allowOrigin", "Allowed origin (can repeat this flag)") flag.Var(&allowOrigins, "allowOrigin", "Allowed origin (can repeat this flag)")
@@ -43,6 +31,7 @@ func Init() *Args {
keyFile := flag.String("keyFile", "./cert/key.pem", "path of keyfile") keyFile := flag.String("keyFile", "./cert/key.pem", "path of keyfile")
portHttp := flag.Uint("http-port", 8100, "json server communication for http/ws") portHttp := flag.Uint("http-port", 8100, "json server communication for http/ws")
portHttps := flag.Uint("https-port", 8101, "json server communication for http/wss") portHttps := flag.Uint("https-port", 8101, "json server communication for http/wss")
remotePort := flag.Uint("remotePort", 9500, "remote Port of gui user interface")
rootDir := flag.String("workingDir", "./", "working directory") rootDir := flag.String("workingDir", "./", "working directory")
dbmFile := flag.String("dbm", "/test/test", "dbm file name") dbmFile := flag.String("dbm", "/test/test", "dbm file name")
debug := flag.Bool("debug", false, "debug flag") debug := flag.Bool("debug", false, "debug flag")
@@ -56,8 +45,9 @@ func Init() *Args {
KeyFile: *keyFile, KeyFile: *keyFile,
}, },
Port: models.Port{ Port: models.Port{
Http: *portHttp, Http: *portHttp,
Https: *portHttps, Https: *portHttps,
Remote: *remotePort,
}, },
RootDir: *rootDir, RootDir: *rootDir,
DBMFile: *dbmFile, DBMFile: *dbmFile,
@@ -65,7 +55,7 @@ func Init() *Args {
} }
if len(allowOrigins) == 0 { if len(allowOrigins) == 0 {
allowOrigins = StringSlice{"http://localhost:9500"} allowOrigins = models.StringSlice{"http://localhost:9500"}
} }
a.AllowOrigins = allowOrigins a.AllowOrigins = allowOrigins

View File

@@ -5,8 +5,8 @@ import (
"net/http" "net/http"
"time" "time"
json_dataModels "gitea.tecamino.com/paadi/tecamino-json_data/models"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
json_dataModels "github.com/tecamino/tecamino-json_data/models"
) )
func (d *DBMHandler) SaveData(c *gin.Context) { func (d *DBMHandler) SaveData(c *gin.Context) {

View File

@@ -9,10 +9,10 @@ import (
"sync" "sync"
"time" "time"
"github.com/tecamino/tecamino-dbm/args" "gitea.tecamino.com/paadi/tecamino-dbm/args"
"github.com/tecamino/tecamino-dbm/models" "gitea.tecamino.com/paadi/tecamino-dbm/models"
ws "github.com/tecamino/tecamino-dbm/websocket" ws "gitea.tecamino.com/paadi/tecamino-dbm/websocket"
"github.com/tecamino/tecamino-logger/logging" "gitea.tecamino.com/paadi/tecamino-logger/logging"
) )
type DBMHandler struct { type DBMHandler struct {

View File

@@ -1,8 +1,8 @@
package dbm package dbm
import ( import (
json_data "github.com/tecamino/tecamino-json_data" json_data "gitea.tecamino.com/paadi/tecamino-json_data"
json_dataModels "github.com/tecamino/tecamino-json_data/models" json_dataModels "gitea.tecamino.com/paadi/tecamino-json_data/models"
) )
func (d *DBMHandler) Get(req *json_dataModels.Request, id string) { func (d *DBMHandler) Get(req *json_dataModels.Request, id string) {

View File

@@ -3,9 +3,9 @@ package dbm
import ( import (
"net/http" "net/http"
json_data "gitea.tecamino.com/paadi/tecamino-json_data"
json_dataModels "gitea.tecamino.com/paadi/tecamino-json_data/models"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
json_data "github.com/tecamino/tecamino-json_data"
json_dataModels "github.com/tecamino/tecamino-json_data/models"
) )
func (d *DBMHandler) Json_Data(c *gin.Context) { func (d *DBMHandler) Json_Data(c *gin.Context) {
@@ -29,12 +29,14 @@ func (d *DBMHandler) Json_Data(c *gin.Context) {
if get.Query != nil { if get.Query != nil {
depth = get.Query.Depth depth = get.Query.Depth
} }
for _, res := range d.DBM.QueryDatapoints(depth, get.Uuid, get.Path) { for _, res := range d.DBM.QueryDatapoints(depth, get.Uuid, get.Path) {
resp.AddGet(json_dataModels.Get{ resp.AddGet(json_dataModels.Get{
Uuid: res.Uuid, Uuid: res.Uuid,
Path: res.Path, Path: res.Path,
Type: res.Type, Type: res.Type,
Value: res.Value, Value: res.Value,
Drivers: &res.Drivers,
HasChild: res.Datapoints != nil, HasChild: res.Datapoints != nil,
Rights: res.ReadWrite, Rights: res.ReadWrite,
}) })
@@ -51,6 +53,7 @@ func (d *DBMHandler) Json_Data(c *gin.Context) {
return return
} }
} }
c.JSON(200, resp) c.JSON(200, resp)
} }
@@ -66,9 +69,7 @@ func (d *DBMHandler) Delete(c *gin.Context) {
} }
response := json_data.NewResponse() response := json_data.NewResponse()
if payload.Set != nil { if payload.Set != nil {
response.Set, err = d.DBM.RemoveDatapoint(payload.Set...) response.Set, err = d.DBM.RemoveDatapoint(payload.Set...)
if err != nil { if err != nil {
r := json_data.NewResponse() r := json_data.NewResponse()

View File

@@ -3,7 +3,7 @@ package dbm
import ( import (
"fmt" "fmt"
json_dataModels "github.com/tecamino/tecamino-json_data/models" json_dataModels "gitea.tecamino.com/paadi/tecamino-json_data/models"
) )
func (d *DBMHandler) Set(req *json_dataModels.Request, id string) { func (d *DBMHandler) Set(req *json_dataModels.Request, id string) {
@@ -20,6 +20,7 @@ func (d *DBMHandler) Set(req *json_dataModels.Request, id string) {
for _, set := range req.Set { for _, set := range req.Set {
dps := d.DBM.QueryDatapoints(1, set.Uuid, set.Path) dps := d.DBM.QueryDatapoints(1, set.Uuid, set.Path)
if len(dps) == 0 { if len(dps) == 0 {
resp.SetError() resp.SetError()
if resp.Message == "" { if resp.Message == "" {
@@ -27,8 +28,10 @@ func (d *DBMHandler) Set(req *json_dataModels.Request, id string) {
} }
continue continue
} }
for _, dp := range dps { for _, dp := range dps {
dp.UpdateValue(d.Conns, set.Value) dp.UpdateValue(set.Value)
resp.AddSet(json_dataModels.Set{ resp.AddSet(json_dataModels.Set{
Uuid: dp.Uuid, Uuid: dp.Uuid,
Path: dp.Path, Path: dp.Path,
@@ -36,6 +39,7 @@ func (d *DBMHandler) Set(req *json_dataModels.Request, id string) {
}) })
} }
} }
if err := d.Conns.SendResponse(id, resp); err != nil { if err := d.Conns.SendResponse(id, resp); err != nil {
d.Log.Error("get.Set", err.Error()) d.Log.Error("get.Set", err.Error())
} }

View File

@@ -3,7 +3,7 @@ package dbm
import ( import (
"fmt" "fmt"
json_dataModels "github.com/tecamino/tecamino-json_data/models" json_dataModels "gitea.tecamino.com/paadi/tecamino-json_data/models"
) )
func (d *DBMHandler) Subscribe(req *json_dataModels.Request, id string) { func (d *DBMHandler) Subscribe(req *json_dataModels.Request, id string) {
@@ -49,12 +49,12 @@ func (d *DBMHandler) Subscribe(req *json_dataModels.Request, id string) {
Path: dp.Path, Path: dp.Path,
Value: dp.Value, Value: dp.Value,
HasChild: dp.Datapoints != nil, HasChild: dp.Datapoints != nil,
Rights: dp.ReadWrite.GetRights(),
Driver: sub.Driver, Driver: sub.Driver,
Drivers: &dp.Drivers, Drivers: &dp.Drivers,
}) })
} }
} }
if err := d.Conns.SendResponse(id, resp); err != nil { if err := d.Conns.SendResponse(id, resp); err != nil {
d.Log.Error("subscribe.Subscribe", err.Error()) d.Log.Error("subscribe.Subscribe", err.Error())
} }

View File

@@ -1,9 +1,9 @@
package dbm package dbm
import ( import (
"gitea.tecamino.com/paadi/tecamino-dbm/models"
json_dataModels "gitea.tecamino.com/paadi/tecamino-json_data/models"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/tecamino/tecamino-dbm/models"
json_dataModels "github.com/tecamino/tecamino-json_data/models"
) )
func (d *DBMHandler) AddSystemDps() (err error) { func (d *DBMHandler) AddSystemDps() (err error) {

View File

@@ -3,9 +3,9 @@ package dbm
import ( import (
"encoding/json" "encoding/json"
"gitea.tecamino.com/paadi/tecamino-dbm/auth"
json_dataModels "gitea.tecamino.com/paadi/tecamino-json_data/models"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/tecamino/tecamino-dbm/auth"
json_dataModels "github.com/tecamino/tecamino-json_data/models"
) )
const ( const (
@@ -33,16 +33,23 @@ func (d *DBMHandler) WebSocket(c *gin.Context) {
if err != nil { if err != nil {
d.Log.Error("dbmHandler.webSocket.Websocket", "read json: "+err.Error()) d.Log.Error("dbmHandler.webSocket.Websocket", "read json: "+err.Error())
} }
// Sets // Sets
d.Get(request, id) d.Get(request, id)
// Sets // Sets
d.Set(request, id) d.Set(request, id)
// Subscribe // Subscribe
d.Subscribe(request, id) d.Subscribe(request, id)
// Unsubscribe // Unsubscribe
d.Unsubscribe(request, id) d.Unsubscribe(request, id)
} }
client.OnWarning = func(s string) {
d.Log.Warning("dbmHandler.webSocket.Websocket", "warning on websocket connection: "+s)
}
client.OnError = func(err error) { client.OnError = func(err error) {
d.Log.Error("dbmHandler.webSocket.Websocket", "error on websocket connection: "+err.Error()) d.Log.Error("dbmHandler.webSocket.Websocket", "error on websocket connection: "+err.Error())
} }

View File

@@ -3,5 +3,8 @@ package drivers
type Drivers []Driver type Drivers []Driver
type Driver interface { type Driver interface {
NewArtNetDriver(string)
AddAddress() AddAddress()
} }
//func Add

6
go.mod
View File

@@ -1,14 +1,14 @@
module github.com/tecamino/tecamino-dbm module gitea.tecamino.com/paadi/tecamino-dbm
go 1.24.0 go 1.24.0
require ( require (
gitea.tecamino.com/paadi/tecamino-json_data v0.1.0
gitea.tecamino.com/paadi/tecamino-logger v0.2.1
github.com/gin-contrib/cors v1.7.5 github.com/gin-contrib/cors v1.7.5
github.com/gin-gonic/gin v1.10.0 github.com/gin-gonic/gin v1.10.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
github.com/tecamino/tecamino-json_data v0.0.16
github.com/tecamino/tecamino-logger v0.2.0
) )
require ( require (

8
go.sum
View File

@@ -1,3 +1,7 @@
gitea.tecamino.com/paadi/tecamino-json_data v0.1.0 h1:zp3L8qUvkVqzuesQdMh/SgZZZbX3pGD9NYa6jtz+JvA=
gitea.tecamino.com/paadi/tecamino-json_data v0.1.0/go.mod h1:/FKhbVYuhiNlQp4552rJJQIhynjLarDzfrgXpupkwZU=
gitea.tecamino.com/paadi/tecamino-logger v0.2.1 h1:sQTBKYPdzn9mmWX2JXZBtGBvNQH7cuXIwsl4TD0aMgE=
gitea.tecamino.com/paadi/tecamino-logger v0.2.1/go.mod h1:FkzRTldUBBOd/iy2upycArDftSZ5trbsX5Ira5OzJgM=
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
@@ -71,10 +75,6 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tecamino/tecamino-json_data v0.0.16 h1:aZFxnhm4g6WMDPoqy4HosUk7vl0DB0iIcVs8bbT4MzU=
github.com/tecamino/tecamino-json_data v0.0.16/go.mod h1:LLlyD7Wwqplb2BP4PeO86EokEcTRidlW5MwgPd1T2JY=
github.com/tecamino/tecamino-logger v0.2.0 h1:NPH/Gg9qRhmVoW8b39i1eXu/LEftHc74nyISpcRG+XU=
github.com/tecamino/tecamino-logger v0.2.0/go.mod h1:0M1E9Uei/qw3e3WA1x3lBo1eP3H5oeYE7GjYrMahnj8=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=

10
main.go
View File

@@ -3,10 +3,10 @@ package main
import ( import (
"fmt" "fmt"
"gitea.tecamino.com/paadi/tecamino-dbm/args"
"gitea.tecamino.com/paadi/tecamino-dbm/dbm"
ws "gitea.tecamino.com/paadi/tecamino-dbm/websocket"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/tecamino/tecamino-dbm/args"
"github.com/tecamino/tecamino-dbm/dbm"
ws "github.com/tecamino/tecamino-dbm/websocket"
) )
func main() { func main() {
@@ -18,12 +18,12 @@ func main() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
//save database after exeutabe ends //save database after executabe ends
defer dbmHandler.SaveDb() defer dbmHandler.SaveDb()
//initialize new server //initialize new server
dbmHandler.Log.Debug("main", "initialize new server instance") dbmHandler.Log.Debug("main", "initialize new server instance")
s := ws.NewServer(a.AllowOrigins) s := ws.NewServer(a.AllowOrigins, a.Port.Remote)
//set routes //set routes
dbmHandler.Log.Debug("main", "setting routes") dbmHandler.Log.Debug("main", "setting routes")

View File

@@ -2,19 +2,17 @@ package models
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"regexp" "regexp"
"strings" "strings"
"sync"
"time" "time"
"gitea.tecamino.com/paadi/tecamino-dbm/utils"
ws "gitea.tecamino.com/paadi/tecamino-dbm/websocket"
wsModels "gitea.tecamino.com/paadi/tecamino-dbm/websocket/models"
json_data "gitea.tecamino.com/paadi/tecamino-json_data"
json_dataModels "gitea.tecamino.com/paadi/tecamino-json_data/models"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/tecamino/tecamino-dbm/utils"
ws "github.com/tecamino/tecamino-dbm/websocket"
wsModels "github.com/tecamino/tecamino-dbm/websocket/models"
json_data "github.com/tecamino/tecamino-json_data"
json_dataModels "github.com/tecamino/tecamino-json_data/models"
) )
const ( const (
@@ -24,17 +22,17 @@ const (
) )
type Datapoint struct { type Datapoint struct {
Datapoints map[string]*Datapoint `json:"-"`
Uuid uuid.UUID `json:"uuid"` Uuid uuid.UUID `json:"uuid"`
Path string `json:"path"`
Value any `json:"value,omitempty"` Value any `json:"value,omitempty"`
Path string `json:"path"`
CreateDateTime int64 `json:"createDateTime,omitempty"` CreateDateTime int64 `json:"createDateTime,omitempty"`
UpdateDateTime int64 `json:"updateDateTime,omitempty"` UpdateDateTime int64 `json:"updateDateTime,omitempty"`
Type json_dataModels.Type `json:"type"` Datapoints map[string]*Datapoint `json:"-"`
ReadWrite json_dataModels.Rights `json:"readWrite"`
Drivers json_dataModels.Drivers `json:"drivers,omitempty"` Drivers json_dataModels.Drivers `json:"drivers,omitempty"`
Subscriptions Subscriptions `json:"-"` Subscriptions Subscriptions `json:"-"`
sync.RWMutex `json:"-"` Type json_dataModels.Type `json:"type"`
ReadWrite json_dataModels.Rights `json:"readWrite"`
HasChild bool `json:"-"`
} }
func (d *Datapoint) Set(path string, set json_dataModels.Set) (bool, error) { func (d *Datapoint) Set(path string, set json_dataModels.Set) (bool, error) {
@@ -57,8 +55,8 @@ func (d *Datapoint) Set(path string, set json_dataModels.Set) (bool, error) {
changed = true changed = true
d.Value = d.Type.ConvertValue(set.Value) d.Value = d.Type.ConvertValue(set.Value)
} }
} }
if set.Rights != "" { if set.Rights != "" {
changed = true changed = true
d.ReadWrite = set.Rights.GetRights() d.ReadWrite = set.Rights.GetRights()
@@ -71,17 +69,13 @@ func (d *Datapoint) Set(path string, set json_dataModels.Set) (bool, error) {
if set.Driver == nil { if set.Driver == nil {
return changed, nil return changed, nil
} }
if set.Driver.Type == "" {
return changed, errors.New("driver type missing")
}
if set.Driver.Bus == "" {
return changed, errors.New("driver bus name missing")
}
if d.Drivers == nil { if d.Drivers == nil {
d.Drivers = json_dataModels.NewDrivers() d.Drivers = json_dataModels.Drivers{}
} }
d.Drivers.AddDriver(set.Driver.Type, set.Driver.Bus, set.Driver.Address) d.Drivers.AddDriver(set.Driver)
changed = true
d.UpdateDateTime = time.Now().UnixMilli() d.UpdateDateTime = time.Now().UnixMilli()
return changed, nil return changed, nil
@@ -91,14 +85,36 @@ func (d *Datapoint) GetValueUint64() uint64 {
return utils.Uint64From(d.Value) return utils.Uint64From(d.Value)
} }
func (d *Datapoint) CreateDatapoints(sets ...json_dataModels.Set) (created []json_dataModels.Set, uuids Uuids, err error) { func (d *Datapoint) CreateDatapoints(uuids *Uuids, sets ...json_dataModels.Set) (created []json_dataModels.Set, err error) {
if len(sets) == 0 { if len(sets) == 0 {
return return
} }
uuids = make(Uuids, 1) publishes := []json_dataModels.Publish{}
for _, dp := range sets { for _, dp := range sets {
if dp.Path == "" && dp.Uuid != uuid.Nil {
existing := uuids.GetDatapoint(dp.Uuid)
_, err := existing.Set("", dp)
if err != nil {
return nil, err
}
created = append(created, json_dataModels.Set{
Uuid: existing.Uuid,
Path: existing.Path,
Type: existing.Type,
Value: existing.Value,
Rights: existing.ReadWrite,
Drivers: &existing.Drivers,
Updated: true,
HasChild: existing.HasChild,
})
existing.Publish(OnChange)
continue
}
parts := strings.Split(dp.Path, ":") parts := strings.Split(dp.Path, ":")
current := d current := d
@@ -110,48 +126,62 @@ func (d *Datapoint) CreateDatapoints(sets ...json_dataModels.Set) (created []jso
if i == len(parts)-1 { if i == len(parts)-1 {
// Leaf node: create or update datapoint // Leaf node: create or update datapoint
if existing, ok := current.Datapoints[part]; ok { if existing, ok := current.Datapoints[part]; ok {
publish, err := existing.Set("", dp)
_, err := existing.Set("", dp)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
created = append(created, json_dataModels.Set{ created = append(created, json_dataModels.Set{
Uuid: existing.Uuid, Uuid: existing.Uuid,
Path: existing.Path, Path: existing.Path,
Type: existing.Type, Type: existing.Type,
Value: existing.Value, Value: existing.Value,
Rights: existing.ReadWrite, Rights: existing.ReadWrite,
Drivers: &existing.Drivers, Drivers: &existing.Drivers,
Updated: true, Updated: true,
HasChild: existing.HasChild,
}) })
if publish { existing.Publish(OnChange)
existing.Publish(OnChange)
}
} else { } else {
ndp := Datapoint{ var uid uuid.UUID = uuid.New()
Uuid: uuid.New(), if dp.Uuid != uuid.Nil {
uid = dp.Uuid
}
ndp := &Datapoint{
Uuid: uid,
CreateDateTime: time.Now().UnixMilli(), CreateDateTime: time.Now().UnixMilli(),
Subscriptions: InitSubscribtion(), Subscriptions: InitSubscribtion(),
} }
// Create new // Create new
current.Datapoints[part] = &ndp current.Datapoints[part] = ndp
publish, err := ndp.Set(strings.Join(parts, ":"), dp)
_, err := ndp.Set(strings.Join(parts, ":"), dp)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
//add uuid to flat map for faster lookup
renamedDps := uuids.AddDatapoint(current, ndp)
created = append(created, renamedDps...)
created = append(created, json_dataModels.Set{ created = append(created, json_dataModels.Set{
Uuid: ndp.Uuid, Uuid: ndp.Uuid,
Path: ndp.Path, Path: ndp.Path,
Type: ndp.Type, Type: ndp.Type,
Value: ndp.Value, Value: ndp.Value,
Rights: ndp.ReadWrite, Rights: ndp.ReadWrite,
Driver: dp.Driver, Driver: dp.Driver,
HasChild: ndp.HasChild,
})
publishes = append(publishes, json_dataModels.Publish{
Event: OnCreate,
Uuid: ndp.Uuid,
Path: ndp.Path,
Type: ndp.Type,
Value: ndp.Value,
}) })
if publish {
current.Publish(OnChange)
}
//add uuid to flat map for faster lookuo
uuids[ndp.Uuid] = &ndp
} }
} }
@@ -168,34 +198,51 @@ func (d *Datapoint) CreateDatapoints(sets ...json_dataModels.Set) (created []jso
Subscriptions: InitSubscribtion(), Subscriptions: InitSubscribtion(),
} }
//add uuid to flat map for faster lookup
renamedDps := uuids.AddDatapoint(current, newDp)
created = append(created, renamedDps...)
created = append(created, json_dataModels.Set{ created = append(created, json_dataModels.Set{
Uuid: newDp.Uuid, Uuid: newDp.Uuid,
Path: newDp.Path, Path: newDp.Path,
Type: newDp.Type, Type: newDp.Type,
Value: newDp.Value, Value: newDp.Value,
Rights: newDp.ReadWrite, Rights: newDp.ReadWrite,
HasChild: newDp.HasChild,
}) })
if dp.Rights != "" { if dp.Rights != "" {
newDp.ReadWrite = dp.Rights.GetRights() newDp.ReadWrite = dp.Rights.GetRights()
} }
publishes = append(publishes, json_dataModels.Publish{
Event: OnCreate,
Uuid: newDp.Uuid,
Path: newDp.Path,
Type: newDp.Type,
Value: newDp.Value,
})
current.Datapoints[part] = newDp current.Datapoints[part] = newDp
current = newDp current = newDp
//add uuid to flat map for faster lookuo
uuids[newDp.Uuid] = newDp
} }
} }
} }
r := json_data.NewResponse()
r.Publish = append(r.Publish, publishes...)
b, err := json.Marshal(r)
if err != nil {
return created, err
}
ws.SendBroadcast(b)
return return
} }
func (d *Datapoint) ImportDatapoint(dp *Datapoint, path string) (uuids Uuids, err error) { func (d *Datapoint) ImportDatapoint(uuids *Uuids, dp *Datapoint, path string) (err error) {
parts := strings.Split(dp.Path, ":") parts := strings.Split(dp.Path, ":")
uuids = make(Uuids, 1)
current := d current := d
for i, part := range parts { for i, part := range parts {
if current.Datapoints == nil { if current.Datapoints == nil {
@@ -216,12 +263,13 @@ func (d *Datapoint) ImportDatapoint(dp *Datapoint, path string) (uuids Uuids, er
dp.UpdateDateTime = time.Now().UnixMilli() dp.UpdateDateTime = time.Now().UnixMilli()
dp.Subscriptions = InitSubscribtion() dp.Subscriptions = InitSubscribtion()
current.Datapoints[part] = dp current.Datapoints[part] = dp
//add uuid to flat map for faster lookuo //add uuid to flat map for faster lookup
uuids[dp.Uuid] = dp uuids.AddDatapoint(current, dp)
dp.Publish(OnChange) dp.Publish(OnChange)
} }
return uuids, nil return nil
} }
// Traverse or create intermediate nodes // Traverse or create intermediate nodes
@@ -238,10 +286,10 @@ func (d *Datapoint) ImportDatapoint(dp *Datapoint, path string) (uuids Uuids, er
current.Datapoints[part] = newDp current.Datapoints[part] = newDp
current = newDp current = newDp
//add uuid to flat map for faster lookup //add uuid to flat map for faster lookup
uuids[newDp.Uuid] = newDp uuids.AddDatapoint(current, newDp)
} }
} }
return uuids, nil return nil
} }
func (d *Datapoint) UpdateDatapointValue(value any, path string) error { func (d *Datapoint) UpdateDatapointValue(value any, path string) error {
@@ -265,57 +313,110 @@ func (d *Datapoint) UpdateDatapointValue(value any, path string) error {
return nil return nil
} }
func (d *Datapoint) UpdateValue(conns *ws.ClientHandler, value any) error { func (d *Datapoint) UpdateValue(value any) error {
d.Value = d.Type.ConvertValue(value) d.Value = d.Type.ConvertValue(value)
d.UpdateDateTime = time.Now().UnixMilli() d.UpdateDateTime = time.Now().UnixMilli()
d.Publish(OnChange) d.Publish(OnChange)
return nil return nil
} }
func (d *Datapoint) RemoveDatapoint(conns *ws.ClientHandler, set json_dataModels.Set) (json_dataModels.Set, error) { // removes datapoint and its children if path is given
// if path and
func (d *Datapoint) RemoveDatapoint(set json_dataModels.Set) (sets []json_dataModels.Set, err error) {
parts := strings.Split(set.Path, ":") parts := strings.Split(set.Path, ":")
if len(parts) < 1 { if len(parts) < 1 {
return json_dataModels.Set{}, fmt.Errorf("invalid path: '%s'", set.Path) return sets, fmt.Errorf("invalid path: '%s'", set.Path)
} }
publishes := []json_dataModels.Publish{}
current := d current := d
for i := 0; i < len(parts)-1; i++ { for i := 0; i < len(parts)-1; i++ {
next, ok := current.Datapoints[parts[i]] next, ok := current.Datapoints[parts[i]]
if !ok { if !ok {
return json_dataModels.Set{}, fmt.Errorf("path not found: '%s'", strings.Join(parts[:i+1], ":")) return sets, fmt.Errorf("path not found: '%s'", strings.Join(parts[:i+1], ":"))
} }
current = next current = next
} }
toDelete := parts[len(parts)-1] toDelete := parts[len(parts)-1]
if dp, ok := current.Datapoints[toDelete]; ok { if dp, ok := current.Datapoints[toDelete]; ok {
dp.Publish(OnDelete) //if driver information found, remoove only driver information
delete(current.Datapoints, toDelete) if set.Driver != nil {
return json_dataModels.Set{ dp.RemoveDriver(set.Driver)
Uuid: set.Uuid, publishes = append(publishes, json_dataModels.Publish{
Path: set.Path, Event: OnChange,
}, nil Uuid: dp.Uuid,
Path: dp.Path,
Value: dp.Value,
Drivers: &dp.Drivers,
})
sets = append(sets, json_dataModels.Set{
Uuid: dp.Uuid,
Path: dp.Path,
Drivers: &dp.Drivers,
Value: dp.Value,
})
} else {
s, pubs := removeChildren(dp)
sets = append(sets, s...)
publishes = append(publishes, pubs...)
publishes = append(publishes, json_dataModels.Publish{
Event: OnDelete,
Uuid: dp.Uuid,
Path: dp.Path,
})
sets = append(sets, json_dataModels.Set{
Uuid: dp.Uuid,
Path: dp.Path,
})
delete(current.Datapoints, toDelete)
}
r := json_data.NewResponse()
r.Publish = append(r.Publish, publishes...)
b, err := json.Marshal(r)
if err != nil {
return sets, err
}
ws.SendBroadcast(b)
return sets, nil
} }
return json_dataModels.Set{}, fmt.Errorf("datapoint '%s' not found", set.Path) return sets, fmt.Errorf("datapoint '%s' not found", set.Path)
}
// removes all children and grandchlidren of datapoint
func removeChildren(dp *Datapoint) (sets []json_dataModels.Set, pubs []json_dataModels.Publish) {
for name, d := range dp.Datapoints {
s, p := removeChildren(d)
sets = append(sets, s...)
pubs = append(pubs, p...)
sets = append(sets, json_dataModels.Set{
Uuid: d.Uuid,
Path: d.Path,
})
delete(d.Datapoints, name)
}
return sets, pubs
} }
func (d *Datapoint) GetAllDatapoints(depth uint) (dps Datapoints) { func (d *Datapoint) GetAllDatapoints(depth uint) (dps Datapoints) {
dps = append(dps, d)
if depth == 1 {
return
} else if depth > 0 {
depth--
}
var dfs func(dp *Datapoint, currentDepth uint) var dfs func(dp *Datapoint, currentDepth uint)
dfs = func(dp *Datapoint, currentDepth uint) { dfs = func(dp *Datapoint, currentDepth uint) {
if depth == 1 { switch depth {
return case 0:
} else if depth == 0 {
// Return all descendants // Return all descendants
for _, child := range dp.Datapoints { case currentDepth:
dps = append(dps, child)
dfs(child, currentDepth+1)
}
return
}
if currentDepth == depth-1 {
return return
} }
@@ -324,7 +425,7 @@ func (d *Datapoint) GetAllDatapoints(depth uint) (dps Datapoints) {
dfs(child, currentDepth+1) dfs(child, currentDepth+1)
} }
} }
dps = append(dps, d)
dfs(d, 0) dfs(d, 0)
dps.SortSlice() dps.SortSlice()
return return
@@ -376,30 +477,86 @@ func (d *Datapoint) AddSubscribtion(conn *wsModels.Client, sub json_dataModels.S
} }
} }
func (d *Datapoint) RenamePaths(oldPath string) (renamed []json_dataModels.Set) {
visited := make(map[*Datapoint]bool)
if len(d.Datapoints) == 0 {
return
}
for _, dp := range d.Datapoints {
dp.Path = strings.Replace(dp.Path, oldPath, d.Path, 1)
renamedDps := dp.renameSubPaths(oldPath, d.Path, visited)
renamed = append(renamed, renamedDps...)
renamed = append(renamed, json_dataModels.Set{
Uuid: dp.Uuid,
Path: dp.Path,
Type: dp.Type,
Value: dp.Value,
Rights: dp.ReadWrite,
Drivers: &dp.Drivers,
HasChild: dp.HasChild,
})
}
return
}
func (d *Datapoint) renameSubPaths(oldPath, newPath string, visited map[*Datapoint]bool) (renamed []json_dataModels.Set) {
if visited[d] {
return
}
visited[d] = true
if len(d.Datapoints) == 0 {
return
}
for _, dp := range d.Datapoints {
dp.Path = strings.Replace(dp.Path, oldPath, newPath, 1)
renamedDps := dp.renameSubPaths(oldPath, newPath, visited)
renamed = append(renamed, renamedDps...)
renamed = append(renamed, json_dataModels.Set{
Uuid: dp.Uuid,
Path: dp.Path,
Type: dp.Type,
Value: dp.Value,
Rights: dp.ReadWrite,
Drivers: &dp.Drivers,
HasChild: dp.HasChild,
})
}
return
}
func (d *Datapoint) RemoveSubscribtion(client *wsModels.Client) { func (d *Datapoint) RemoveSubscribtion(client *wsModels.Client) {
delete(d.Subscriptions, client) delete(d.Subscriptions, client)
} }
func (d *Datapoint) RemoveDriver(driver *json_dataModels.Driver) {
d.Drivers.RemoveDriver(driver)
}
func (d *Datapoint) Publish(eventType string) error { func (d *Datapoint) Publish(eventType string) error {
d.RLock() r := json_data.NewResponse()
defer d.RUnlock() r.AddPublish(json_dataModels.Publish{
Event: eventType,
Uuid: d.Uuid,
Path: d.Path,
Type: d.Type,
Value: d.Value,
})
for client := range d.Subscriptions { b, err := json.Marshal(r)
r := json_data.NewResponse() if err != nil {
r.AddPublish(json_dataModels.Publish{ return err
Event: eventType, }
Uuid: d.Uuid, switch eventType {
Path: d.Path, case OnCreate, OnDelete:
Value: d.Value, ws.SendBroadcast(b)
}) default:
b, err := json.Marshal(r) for client := range d.Subscriptions {
if err != nil { client.SendResponse(b)
return err
} }
if err := client.SendResponse(b, 5); err != nil {
return err
}
} }
return nil return nil
} }

View File

@@ -5,18 +5,16 @@ import (
"runtime" "runtime"
"time" "time"
"maps" "gitea.tecamino.com/paadi/tecamino-dbm/utils"
ws "gitea.tecamino.com/paadi/tecamino-dbm/websocket"
json_dataModels "gitea.tecamino.com/paadi/tecamino-json_data/models"
"gitea.tecamino.com/paadi/tecamino-logger/logging"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/tecamino/tecamino-dbm/utils"
ws "github.com/tecamino/tecamino-dbm/websocket"
json_dataModels "github.com/tecamino/tecamino-json_data/models"
"github.com/tecamino/tecamino-logger/logging"
) )
type DBM struct { type DBM struct {
Datapoints Datapoint Datapoints Datapoint
Uuids Uuids Uuids *Uuids
Conns *ws.ClientHandler Conns *ws.ClientHandler
Log *logging.Logger Log *logging.Logger
} }
@@ -25,7 +23,7 @@ var SystemDatapoints uuid.UUID
func NewDBM(conns *ws.ClientHandler, log *logging.Logger) *DBM { func NewDBM(conns *ws.ClientHandler, log *logging.Logger) *DBM {
return &DBM{ return &DBM{
Uuids: make(Uuids), Uuids: &Uuids{},
Conns: conns, Conns: conns,
Log: log, Log: log,
} }
@@ -36,11 +34,7 @@ func (d *DBM) CreateDatapoints(sets ...json_dataModels.Set) ([]json_dataModels.S
return nil, nil return nil, nil
} }
dps, uuids, err := d.Datapoints.CreateDatapoints(sets...) dps, err := d.Datapoints.CreateDatapoints(d.Uuids, sets...)
//save uuid in seperate map for fast look up
maps.Copy(d.Uuids, uuids)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -58,11 +52,10 @@ func (d *DBM) CreateDatapoints(sets ...json_dataModels.Set) ([]json_dataModels.S
func (d *DBM) ImportDatapoints(dps ...*Datapoint) error { func (d *DBM) ImportDatapoints(dps ...*Datapoint) error {
for _, dp := range dps { for _, dp := range dps {
uuids, err := d.Datapoints.ImportDatapoint(dp, dp.Path) err := d.Datapoints.ImportDatapoint(d.Uuids, dp, dp.Path)
if err != nil { if err != nil {
return err return err
} }
maps.Copy(d.Uuids, uuids)
d.ModifyCountedDatapoints(1, false) d.ModifyCountedDatapoints(1, false)
} }
@@ -71,10 +64,10 @@ func (d *DBM) ImportDatapoints(dps ...*Datapoint) error {
func (d *DBM) UpdateDatapointValue(value any, uid uuid.UUID, path ...string) error { func (d *DBM) UpdateDatapointValue(value any, uid uuid.UUID, path ...string) error {
if uid != uuid.Nil { if uid != uuid.Nil {
if _, ok := d.Uuids[uid]; !ok { dp := d.Uuids.GetDatapoint(uid)
if dp == nil {
return fmt.Errorf("uuid %s not found", uid.String()) return fmt.Errorf("uuid %s not found", uid.String())
} }
dp := d.Uuids[uid]
dp.Value = dp.Type.ConvertValue(value) dp.Value = dp.Type.ConvertValue(value)
dp.UpdateDateTime = time.Now().UnixMilli() dp.UpdateDateTime = time.Now().UnixMilli()
dp.Publish(OnChange) dp.Publish(OnChange)
@@ -87,27 +80,29 @@ func (d *DBM) UpdateDatapointValue(value any, uid uuid.UUID, path ...string) err
return d.Datapoints.UpdateDatapointValue(value, path[0]) return d.Datapoints.UpdateDatapointValue(value, path[0])
} }
func (d *DBM) RemoveDatapoint(sets ...json_dataModels.Set) ([]json_dataModels.Set, error) { func (d *DBM) RemoveDatapoint(sets ...json_dataModels.Set) (lsRemoved []json_dataModels.Set, err error) {
var lsRemoved []json_dataModels.Set
for _, set := range sets { for _, set := range sets {
removed, err := d.Datapoints.RemoveDatapoint(d.Conns, set) if set.Path == "" {
if err != nil { if dp := d.Uuids.GetDatapoint(set.Uuid); dp != nil {
return lsRemoved, err set.Path = dp.Path
}
} }
lsRemoved = append(lsRemoved, removed)
d.ModifyCountedDatapoints(1, true)
}
return lsRemoved, nil lsRemoved, err = d.Datapoints.RemoveDatapoint(set)
if err != nil {
return
}
d.ModifyCountedDatapoints(uint64(len(lsRemoved)), true)
}
return
} }
func (d *DBM) QueryDatapoints(depth uint, uid uuid.UUID, key ...string) []*Datapoint { func (d *DBM) QueryDatapoints(depth uint, uid uuid.UUID, key ...string) []*Datapoint {
if uid != uuid.Nil { if uid != uuid.Nil {
if _, ok := d.Uuids[uid]; !ok { dp := d.Uuids.GetDatapoint(uid)
if dp == nil {
return nil return nil
} } else if depth == 1 {
dp := d.Uuids[uid]
if depth == 1 {
return []*Datapoint{dp} return []*Datapoint{dp}
} }
return append([]*Datapoint{}, dp.QueryDatapoints(depth, key[0])...) return append([]*Datapoint{}, dp.QueryDatapoints(depth, key[0])...)
@@ -159,8 +154,6 @@ func (d *DBM) GoSystemTime() error {
func (d *DBM) GoSystemMemory() error { func (d *DBM) GoSystemMemory() error {
path := "System:UsedMemory" path := "System:UsedMemory"
var m runtime.MemStats
var mOld uint64
typ := json_dataModels.STR typ := json_dataModels.STR
rights := json_dataModels.Read rights := json_dataModels.Read
@@ -172,13 +165,11 @@ func (d *DBM) GoSystemMemory() error {
go func() { go func() {
for { for {
var m runtime.MemStats
runtime.ReadMemStats(&m) runtime.ReadMemStats(&m)
if m.Sys != mOld { mem := fmt.Sprintf("%.2f MB", float64(m.Alloc)/1024/1024)
mem := fmt.Sprintf("%.2f MB", float64(m.Sys)/1024/1024) if er := d.UpdateDatapointValue(mem, uuid.Nil, path); er != nil {
if er := d.UpdateDatapointValue(mem, uuid.Nil, path); er != nil { d.Log.Error("dmb.Handler.AddSystemDps.UpdateDatapointValue", er.Error())
d.Log.Error("dmb.Handler.AddSystemDps.UpdateDatapointValue", er.Error())
}
mOld = m.Sys
} }
time.Sleep(time.Second) time.Sleep(time.Second)
} }

View File

@@ -1,6 +1,7 @@
package models package models
type Port struct { type Port struct {
Http uint Http uint
Https uint Https uint
Remote uint
} }

14
models/stringSlices.go Normal file
View File

@@ -0,0 +1,14 @@
package models
import "strings"
type StringSlice []string
func (s *StringSlice) String() string {
return strings.Join(*s, ",")
}
func (s *StringSlice) Set(value string) error {
*s = append(*s, value)
return nil
}

View File

@@ -1,6 +1,6 @@
package models package models
import wsModels "github.com/tecamino/tecamino-dbm/websocket/models" import wsModels "gitea.tecamino.com/paadi/tecamino-dbm/websocket/models"
type Subscriptions map[*wsModels.Client]*Subscription type Subscriptions map[*wsModels.Client]*Subscription

View File

@@ -1,5 +1,50 @@
package models package models
import "github.com/google/uuid" import (
json_dataModels "gitea.tecamino.com/paadi/tecamino-json_data/models"
"github.com/google/uuid"
)
type Uuids map[uuid.UUID]*Datapoint type Uuids map[uuid.UUID]*Datapoint
func NewUuids() *Uuids {
return &Uuids{}
}
func (u *Uuids) AddDatapoint(parentDp, newDp *Datapoint) (renamed []json_dataModels.Set) {
if odp, ok := (*u)[newDp.Uuid]; ok {
if odp.Path == newDp.Path {
return
}
newDp.Datapoints = odp.Datapoints
newDp.HasChild = len(odp.Datapoints) > 0
odp.Datapoints = map[string]*Datapoint{}
renamed = newDp.RenamePaths(odp.Path)
rmDps, _ := parentDp.RemoveDatapoint(json_dataModels.Set{Path: odp.Path})
datapoints := u.GetDatapointByPath("System:Datapoints")
datapoints.UpdateValue(datapoints.Value.(uint64) - uint64(len(rmDps)))
}
(*u)[newDp.Uuid] = newDp
return
}
func (u *Uuids) GetDatapoint(uuid uuid.UUID) *Datapoint {
if dp, ok := (*u)[uuid]; ok {
return dp
}
return nil
}
func (u *Uuids) GetDatapointByPath(path string) *Datapoint {
for _, dp := range *u {
if dp.Path == path {
return dp
}
}
return nil
}
func (u *Uuids) RemoveDatapoint(uuid uuid.UUID) {
delete(*u, uuid)
}

View File

@@ -7,13 +7,13 @@ import (
"testing" "testing"
"time" "time"
"gitea.tecamino.com/paadi/tecamino-dbm/args"
"gitea.tecamino.com/paadi/tecamino-dbm/cert"
"gitea.tecamino.com/paadi/tecamino-dbm/dbm"
"gitea.tecamino.com/paadi/tecamino-dbm/models"
"gitea.tecamino.com/paadi/tecamino-dbm/utils"
ws "gitea.tecamino.com/paadi/tecamino-dbm/websocket"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/tecamino/tecamino-dbm/args"
"github.com/tecamino/tecamino-dbm/cert"
"github.com/tecamino/tecamino-dbm/dbm"
"github.com/tecamino/tecamino-dbm/models"
"github.com/tecamino/tecamino-dbm/utils"
ws "github.com/tecamino/tecamino-dbm/websocket"
) )
func TestCreateDps(t *testing.T) { func TestCreateDps(t *testing.T) {
@@ -142,7 +142,7 @@ func TestUpdateDps(t *testing.T) {
func TestServer(t *testing.T) { func TestServer(t *testing.T) {
fmt.Println("start") fmt.Println("start")
server := ws.NewServer([]string{".*"}) server := ws.NewServer([]string{".*"}, 9500)
t.Fatal(server.ServeHttp("http://localhost", 8100)) t.Fatal(server.ServeHttp("http://localhost", 8100))
} }

View File

@@ -1,5 +1,10 @@
package utils package utils
import (
"fmt"
"net"
)
func ListofA2ZZ() (list []string) { func ListofA2ZZ() (list []string) {
for i := 'A'; i <= 'Z'; i++ { for i := 'A'; i <= 'Z'; i++ {
list = append(list, string(i)) list = append(list, string(i))
@@ -9,12 +14,21 @@ func ListofA2ZZ() (list []string) {
list = append(list, string(i)+string(j)) list = append(list, string(i)+string(j))
} }
} }
// for i := 'A'; i <= 'Z'; i++ {
// for j := 'A'; j <= 'Z'; j++ {
// for k := 'A'; k <= 'Z'; k++ {
// list = append(list, string(i)+string(j)+string(k))
// }
// }
// }
return return
} }
func GetLocalIP() (string, error) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return "", err
}
for _, addr := range addrs {
if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
if ipNet.IP.To4() != nil {
return ipNet.IP.String(), nil
}
}
}
return "", fmt.Errorf("no local IP address found")
}

View File

@@ -5,10 +5,10 @@ import (
"fmt" "fmt"
"sync" "sync"
"gitea.tecamino.com/paadi/tecamino-dbm/websocket/models"
wsModels "gitea.tecamino.com/paadi/tecamino-dbm/websocket/models"
json_dataModels "gitea.tecamino.com/paadi/tecamino-json_data/models"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/tecamino/tecamino-dbm/websocket/models"
wsModels "github.com/tecamino/tecamino-dbm/websocket/models"
json_dataModels "github.com/tecamino/tecamino-json_data/models"
) )
// serves as connection handler of websocket // serves as connection handler of websocket
@@ -17,6 +17,12 @@ type ClientHandler struct {
Clients models.Clients Clients models.Clients
} }
func SendBroadcast(msg []byte) {
for _, c := range models.Broadcast {
c.SendResponse(msg)
}
}
// initaiates new conections with client map // initaiates new conections with client map
func NewConnectionHandler() *ClientHandler { func NewConnectionHandler() *ClientHandler {
return &ClientHandler{ return &ClientHandler{
@@ -41,7 +47,6 @@ func (cH *ClientHandler) ConnectNewClient(id string, c *gin.Context) (client *mo
cH.Lock() cH.Lock()
cH.Clients[id] = client cH.Clients[id] = client
cH.Unlock() cH.Unlock()
return client, nil return client, nil
} }
@@ -60,14 +65,12 @@ func (c *ClientHandler) SendResponse(id string, r *json_dataModels.Response) err
return fmt.Errorf("client not found for id %s", id) return fmt.Errorf("client not found for id %s", id)
} }
b, err := json.Marshal(r)
b, err := json.Marshal(*r)
if err != nil { if err != nil {
return err return err
} }
client.SendResponse(b)
if err := client.SendResponse(b, 5); err != nil {
return err
}
return nil return nil
} }

View File

@@ -1,10 +1,10 @@
package models package models
import ( import (
"encoding/json"
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"slices"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -13,34 +13,42 @@ import (
var Origins []string = []string{"*"} var Origins []string = []string{"*"}
var Broadcast Clients = make(Clients)
const (
// Time allowed to write a message to the peer.
writeWait = 10 * time.Second
// Time allowed to read the next pong message from the peer.
pongWait = 30 * time.Second
// Send pings to peer with this period. Must be less than pongWait.
pingPeriod = (pongWait * 9) / 10
)
type Client struct { type Client struct {
Id string Id string
Connected bool `json:"connected"` Connected bool `json:"connected"`
Conn *websocket.Conn `json:"-"` conn *websocket.Conn `json:"-"`
OnOpen func() OnOpen func()
OnMessage func(data []byte) OnMessage func(data []byte)
OnClose func(code int, reason string) OnClose func(code int, reason string)
OnError func(err error) OnError func(err error)
OnPing func() OnWarning func(warn string)
OnPong func() OnPing func()
timeout time.Duration OnPong func()
send chan []byte
unregister chan []byte
} }
var upgrader = websocket.Upgrader{ var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { CheckOrigin: func(r *http.Request) bool {
if len(Origins) == 0 { if len(Origins) == 0 {
return false return false
} } else if Origins[0] == "*" {
if Origins[0] == "*" {
return true return true
} }
origin := r.Header.Get("Origin") return slices.Contains(Origins, r.Header.Get("Origin"))
for _, o := range Origins {
if o == origin {
return true
}
}
return false
}, },
EnableCompression: false, EnableCompression: false,
} }
@@ -52,58 +60,63 @@ func ConnectNewClient(id string, c *gin.Context) (*Client, error) {
} }
client := &Client{ client := &Client{
Id: id, Id: id,
Connected: true, Connected: true,
Conn: conn, conn: conn,
timeout: 5, send: make(chan []byte, 512),
unregister: make(chan []byte, 256),
} }
Broadcast[client.Id] = client
conn.SetReadDeadline(time.Now().Add(pongWait))
conn.SetPingHandler(func(appData string) error { conn.SetPingHandler(func(appData string) error {
if client.OnPing != nil { if client.OnPing != nil {
client.OnPing() client.OnPing()
} }
conn.SetWriteDeadline(time.Now().Add(client.timeout))
conn.SetReadDeadline(time.Now().Add(client.timeout)) conn.SetReadDeadline(time.Now().Add(pongWait))
return conn.WriteMessage(websocket.PongMessage, []byte(appData)) conn.SetWriteDeadline(time.Now().Add(writeWait))
if err := client.conn.WriteControl(websocket.PongMessage, []byte(appData), time.Now().Add(pongWait)); err != nil {
client.OnError(err)
}
return nil
}) })
conn.SetPongHandler(func(appData string) error { conn.SetPongHandler(func(appData string) error {
conn.SetReadDeadline(time.Now().Add(pongWait))
if client.OnPong != nil { if client.OnPong != nil {
client.OnPong() client.OnPong()
} }
conn.SetReadDeadline(time.Now().Add(client.timeout))
return nil return nil
}) })
// Start reading messages from client // Start reading messages from client
go client.Listen(7) go client.Read()
go client.Write()
go client.PingInterval(pingPeriod)
return client, nil return client, nil
} }
func (c *Client) Listen(timeout uint) { func (c *Client) Read() {
if timeout > 0 {
c.timeout = time.Duration(timeout) * time.Second
}
if c.OnOpen != nil { if c.OnOpen != nil {
c.OnOpen() c.OnOpen()
} }
c.Conn.SetReadDeadline(time.Now().Add(c.timeout)) c.conn.SetReadDeadline(time.Now().Add(writeWait))
for c.Connected { for c.Connected {
msgType, msg, err := c.Conn.ReadMessage() msgType, msg, err := c.conn.ReadMessage()
if err != nil { if err != nil {
c.handleError(fmt.Errorf("read error (id:%s): %w", c.Id, err)) c.handleError(fmt.Errorf("read error (id:%s): %w", c.Id, err))
return return
} }
switch msgType { switch msgType {
case websocket.CloseMessage: case websocket.CloseMessage:
c.handleClose(1000, "Client closed") c.Close(websocket.CloseNormalClosure, "Client closed")
return return
case websocket.TextMessage: case websocket.TextMessage:
if isPing := c.handleJsonPing(msg); isPing {
continue
}
if c.OnMessage != nil { if c.OnMessage != nil {
c.OnMessage(msg) c.OnMessage(msg)
} else { } else {
@@ -115,50 +128,99 @@ func (c *Client) Listen(timeout uint) {
} }
} }
func (c *Client) handleJsonPing(msg []byte) (isPing bool) { func (c *Client) Write() {
var wsMsg WSMessage defer c.conn.Close()
err := json.Unmarshal(msg, &wsMsg)
if err == nil && wsMsg.IsPing() { for {
c.Conn.SetReadDeadline(time.Now().Add(c.timeout)) select {
// Respond with pong JSON case message, ok := <-c.send:
c.Conn.SetWriteDeadline(time.Now().Add(c.timeout)) c.conn.SetWriteDeadline(time.Now().Add(writeWait))
err = c.Conn.WriteMessage(websocket.TextMessage, GetPongByteSlice()) if !ok {
if err != nil { // The hub closed the channel.
c.handleError(fmt.Errorf("write pong error: %w", err)) if err := c.conn.WriteMessage(websocket.CloseMessage, []byte("ping")); err != nil {
c.handleError(err)
return
}
c.handleError(fmt.Errorf("server %s closed channel", c.Id))
return
} else {
if err := c.conn.WriteMessage(websocket.TextMessage, message); err != nil {
c.handleError(err)
return
}
}
case message := <-c.unregister:
c.conn.WriteMessage(websocket.CloseMessage, message)
c.Connected = false
close(c.send)
delete(Broadcast, c.Id)
close(c.unregister)
return return
} }
}
}
func (c *Client) PingInterval(interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
if err := c.conn.WriteControl(websocket.PingMessage, []byte("ping"), time.Now().Add(pongWait)); err != nil {
c.OnError(err)
return
}
for range ticker.C {
if c.OnPing != nil { if c.OnPing != nil {
c.OnPing() c.OnPing()
} }
isPing = true
if err := c.conn.WriteControl(websocket.PingMessage, []byte("ping"), time.Now().Add(pongWait)); err != nil {
c.OnError(err)
return
}
} }
return
} }
func (c *Client) SendResponse(data []byte, timeout uint) error { func (c *Client) SendResponse(data []byte) {
c.Conn.SetWriteDeadline(time.Now().Add(time.Duration(timeout) * time.Second))
return c.Conn.WriteMessage(websocket.TextMessage, data)
}
func (c *Client) Close(code int, reason string) {
c.handleClose(code, reason)
}
func (c *Client) handleClose(code int, text string) {
if !c.Connected { if !c.Connected {
return return
} }
c.Connected = false select {
if c.OnClose != nil { case c.send <- data:
c.OnClose(code, text) // sent successfully
default:
// channel full, drop or log
if c.OnWarning != nil {
c.OnWarning("Dropping message: channel full")
}
} }
c.Conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(code, text)) }
c.Conn.Close()
func (c *Client) Close(code int, reason string) error {
closeMsg := websocket.FormatCloseMessage(code, reason)
select {
case c.unregister <- closeMsg: // Attempt to send
default: // If the channel is full, this runs
return fmt.Errorf("attempt close client socket failed")
}
if c.OnClose != nil {
c.OnClose(code, reason)
}
return nil
} }
func (c *Client) handleError(err error) { func (c *Client) handleError(err error) {
if c.OnError != nil { if c.OnError != nil {
c.OnError(err) c.OnError(err)
} }
c.Close(websocket.CloseInternalServerErr, err.Error())
if err := c.Close(websocket.CloseInternalServerErr, err.Error()); err != nil {
if c.OnError != nil {
c.OnError(err)
} else {
fmt.Println("error: ", err)
}
}
} }

View File

@@ -2,13 +2,15 @@ package websocket
import ( import (
"fmt" "fmt"
"log"
"sync" "sync"
"time" "time"
"gitea.tecamino.com/paadi/tecamino-dbm/cert"
"gitea.tecamino.com/paadi/tecamino-dbm/utils"
"gitea.tecamino.com/paadi/tecamino-logger/logging"
"github.com/gin-contrib/cors" "github.com/gin-contrib/cors"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/tecamino/tecamino-dbm/cert"
"github.com/tecamino/tecamino-logger/logging"
) )
// server model for database manager websocket // server model for database manager websocket
@@ -19,8 +21,17 @@ type Server struct {
} }
// initalizes new dbm server // initalizes new dbm server
func NewServer(allowOrigins []string) *Server { func NewServer(allowOrigins []string, port uint) *Server {
r := gin.Default() r := gin.Default()
allowOrigins = append(allowOrigins, fmt.Sprintf("http://localhost:%d", port))
localIP, err := utils.GetLocalIP()
if err != nil {
log.Printf("get local ip : %s", err.Error())
} else {
allowOrigins = append(allowOrigins, fmt.Sprintf("http://%s:%d", localIP, port))
}
r.Use(cors.New(cors.Config{ r.Use(cors.New(cors.Config{
AllowOrigins: allowOrigins, AllowOrigins: allowOrigins,
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},