13 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
25 changed files with 566 additions and 321 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")
@@ -58,6 +47,7 @@ func Init() *Args {
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) {
@@ -30,7 +30,7 @@ func (d *DBMHandler) Set(req *json_dataModels.Request, id string) {
} }
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,

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,6 +49,7 @@ 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,
}) })

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 (
@@ -46,6 +46,10 @@ func (d *DBMHandler) WebSocket(c *gin.Context) {
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, 9000) 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,18 +2,17 @@ package models
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"regexp" "regexp"
"strings" "strings"
"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 (
@@ -23,16 +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:"-"`
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) {
@@ -55,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()
@@ -69,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
@@ -89,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
@@ -108,9 +126,10 @@ 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,
@@ -120,23 +139,32 @@ func (d *Datapoint) CreateDatapoints(sets ...json_dataModels.Set) (created []jso
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,
@@ -144,12 +172,16 @@ func (d *Datapoint) CreateDatapoints(sets ...json_dataModels.Set) (created []jso
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
} }
} }
@@ -166,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 r := json_data.NewResponse()
uuids[newDp.Uuid] = newDp 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 {
@@ -214,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
@@ -236,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 {
@@ -263,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
if set.Driver != nil {
dp.RemoveDriver(set.Driver)
publishes = append(publishes, json_dataModels.Publish{
Event: OnChange,
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) delete(current.Datapoints, toDelete)
return json_dataModels.Set{
Uuid: set.Uuid,
Path: set.Path,
}, nil
} }
return json_dataModels.Set{}, fmt.Errorf("datapoint '%s' not found", set.Path) 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 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) {
var dfs func(dp *Datapoint, currentDepth uint) dps = append(dps, d)
dfs = func(dp *Datapoint, currentDepth uint) {
if depth == 1 { if depth == 1 {
return return
} else if depth == 0 { } else if depth > 0 {
depth--
}
var dfs func(dp *Datapoint, currentDepth uint)
dfs = func(dp *Datapoint, currentDepth uint) {
switch depth {
case 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
} }
@@ -322,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
@@ -374,17 +477,72 @@ 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 {
for client := range d.Subscriptions {
r := json_data.NewResponse() r := json_data.NewResponse()
r.AddPublish(json_dataModels.Publish{ r.AddPublish(json_dataModels.Publish{
Event: eventType, Event: eventType,
Uuid: d.Uuid, Uuid: d.Uuid,
Path: d.Path, Path: d.Path,
Type: d.Type,
Value: d.Value, Value: d.Value,
}) })
@@ -392,8 +550,13 @@ func (d *Datapoint) Publish(eventType string) error {
if err != nil { if err != nil {
return err return err
} }
switch eventType {
case OnCreate, OnDelete:
ws.SendBroadcast(b)
default:
for client := range d.Subscriptions {
client.SendResponse(b) client.SendResponse(b)
} }
}
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,14 +165,12 @@ 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

@@ -3,4 +3,5 @@ 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

@@ -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
} }

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,12 +13,14 @@ import (
var Origins []string = []string{"*"} var Origins []string = []string{"*"}
var Broadcast Clients = make(Clients)
const ( const (
// Time allowed to write a message to the peer. // Time allowed to write a message to the peer.
writeWait = 10 * time.Second writeWait = 10 * time.Second
// Time allowed to read the next pong message from the peer. // Time allowed to read the next pong message from the peer.
pongWait = 10 * time.Second pongWait = 30 * time.Second
// Send pings to peer with this period. Must be less than pongWait. // Send pings to peer with this period. Must be less than pongWait.
pingPeriod = (pongWait * 9) / 10 pingPeriod = (pongWait * 9) / 10
@@ -32,9 +34,9 @@ type Client struct {
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)
OnWarning func(warn string)
OnPing func() OnPing func()
OnPong func() OnPong func()
sendPong chan string
send chan []byte send chan []byte
unregister chan []byte unregister chan []byte
} }
@@ -43,17 +45,10 @@ 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,
} }
@@ -68,22 +63,28 @@ func ConnectNewClient(id string, c *gin.Context) (*Client, error) {
Id: id, Id: id,
Connected: true, Connected: true,
conn: conn, conn: conn,
sendPong: make(chan string), send: make(chan []byte, 512),
send: make(chan []byte), unregister: make(chan []byte, 256),
unregister: make(chan []byte),
} }
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.SetReadDeadline(time.Now().Add(pongWait))
conn.SetWriteDeadline(time.Now().Add(writeWait)) conn.SetWriteDeadline(time.Now().Add(writeWait))
conn.SetReadDeadline(time.Now().Add(writeWait)) if err := client.conn.WriteControl(websocket.PongMessage, []byte(appData), time.Now().Add(pongWait)); err != nil {
client.sendPong <- appData client.OnError(err)
}
return nil return nil
}) })
conn.SetPongHandler(func(string) error { conn.SetPongHandler(func(appData string) error {
conn.SetReadDeadline(time.Now().Add(pongWait)) conn.SetReadDeadline(time.Now().Add(pongWait))
if client.OnPong != nil { if client.OnPong != nil {
client.OnPong() client.OnPong()
@@ -94,6 +95,7 @@ func ConnectNewClient(id string, c *gin.Context) (*Client, error) {
// Start reading messages from client // Start reading messages from client
go client.Read() go client.Read()
go client.Write() go client.Write()
go client.PingInterval(pingPeriod)
return client, nil return client, nil
} }
@@ -105,6 +107,7 @@ func (c *Client) Read() {
c.conn.SetReadDeadline(time.Now().Add(writeWait)) 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
@@ -114,13 +117,11 @@ func (c *Client) Read() {
c.Close(websocket.CloseNormalClosure, "Client closed") c.Close(websocket.CloseNormalClosure, "Client closed")
return return
case websocket.TextMessage: case websocket.TextMessage:
if isPing := c.handleJsonPing(msg); !isPing {
if c.OnMessage != nil { if c.OnMessage != nil {
c.OnMessage(msg) c.OnMessage(msg)
} else { } else {
log.Printf("Received message but no handler set (id:%s): %s", c.Id, string(msg)) log.Printf("Received message but no handler set (id:%s): %s", c.Id, string(msg))
} }
}
default: default:
log.Printf("Unhandled message type %d (id:%s)", msgType, c.Id) log.Printf("Unhandled message type %d (id:%s)", msgType, c.Id)
} }
@@ -128,19 +129,15 @@ func (c *Client) Read() {
} }
func (c *Client) Write() { func (c *Client) Write() {
ticker := time.NewTicker(pingPeriod) defer c.conn.Close()
defer func() {
ticker.Stop()
c.conn.Close()
}()
for {
for {
select { select {
case message, ok := <-c.send: case message, ok := <-c.send:
c.conn.SetWriteDeadline(time.Now().Add(writeWait)) c.conn.SetWriteDeadline(time.Now().Add(writeWait))
if !ok { if !ok {
// The hub closed the channel. // The hub closed the channel.
if err := c.conn.WriteMessage(websocket.CloseMessage, []byte{}); err != nil { if err := c.conn.WriteMessage(websocket.CloseMessage, []byte("ping")); err != nil {
c.handleError(err) c.handleError(err)
return return
} }
@@ -152,26 +149,11 @@ func (c *Client) Write() {
return return
} }
} }
case <-ticker.C:
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
c.handleError(err)
return
}
if c.OnPing != nil {
c.OnPing()
}
case message, ok := <-c.sendPong:
if ok {
c.conn.WriteMessage(websocket.PongMessage, []byte(message))
}
case message := <-c.unregister: case message := <-c.unregister:
c.conn.WriteMessage(websocket.CloseMessage, message) c.conn.WriteMessage(websocket.CloseMessage, message)
c.Connected = false c.Connected = false
close(c.sendPong)
close(c.send) close(c.send)
delete(Broadcast, c.Id)
close(c.unregister) close(c.unregister)
return return
} }
@@ -179,42 +161,45 @@ func (c *Client) Write() {
} }
} }
func (c *Client) handleJsonPing(msg []byte) (isPing bool) { func (c *Client) PingInterval(interval time.Duration) {
var wsMsg WSMessage ticker := time.NewTicker(interval)
err := json.Unmarshal(msg, &wsMsg) defer ticker.Stop()
if err == nil && wsMsg.IsPing() { if err := c.conn.WriteControl(websocket.PingMessage, []byte("ping"), time.Now().Add(pongWait)); err != nil {
c.conn.SetReadDeadline(time.Now().Add(writeWait)) c.OnError(err)
// Respond with pong JSON
select {
case c.send <- GetPongByteSlice():
default:
// optional: log or handle if send buffer is full
c.handleError(fmt.Errorf("failed to queue pong message"))
return
}
if err != nil {
c.handleError(fmt.Errorf("write pong error: %w", err))
return 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) { func (c *Client) SendResponse(data []byte) {
if !c.Connected { if !c.Connected {
return return
} }
c.send <- data select {
case c.send <- data:
// sent successfully
default:
// channel full, drop or log
if c.OnWarning != nil {
c.OnWarning("Dropping message: channel full")
}
}
} }
func (c *Client) Close(code int, reason string) error { func (c *Client) Close(code int, reason string) error {
closeMsg := websocket.FormatCloseMessage(code, reason) closeMsg := websocket.FormatCloseMessage(code, reason)
select { select {
case c.unregister <- closeMsg: // Attempt to send case c.unregister <- closeMsg: // Attempt to send
default: // If the channel is full, this runs default: // If the channel is full, this runs
@@ -230,6 +215,7 @@ func (c *Client) handleError(err error) {
if c.OnError != nil { if c.OnError != nil {
c.OnError(err) c.OnError(err)
} }
if err := c.Close(websocket.CloseInternalServerErr, err.Error()); err != nil { if err := c.Close(websocket.CloseInternalServerErr, err.Error()); err != nil {
if c.OnError != nil { if c.OnError != nil {
c.OnError(err) c.OnError(err)

View File

@@ -6,11 +6,11 @@ import (
"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-dbm/utils"
"github.com/tecamino/tecamino-logger/logging"
) )
// server model for database manager websocket // server model for database manager websocket