This commit is contained in:
JurajKubrican
2024-12-30 15:18:59 +01:00
parent 6a5a9dad90
commit 92e4dadf4a
6 changed files with 184 additions and 95 deletions

1
data/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
data.db

1
go.mod
View File

@@ -5,6 +5,7 @@ go 1.23.4
require (
github.com/labstack/echo/v4 v4.12.0
github.com/labstack/gommon v0.4.2
github.com/mattn/go-sqlite3 v1.14.24
golang.org/x/net v0.24.0
)

2
go.sum
View File

@@ -11,6 +11,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=

73
src/box-db.go Normal file
View File

@@ -0,0 +1,73 @@
package main
import (
"database/sql"
_ "github.com/mattn/go-sqlite3" // Import the SQLite driver
)
func initDb() *sql.DB {
db, err := sql.Open("sqlite3", "./data/data.db")
if err != nil {
e.Logger.Fatal(err)
}
// Create a table
sqlStmt := `
CREATE TABLE IF NOT EXISTS boxes (id INTEGER PRIMARY KEY, value BOOLEAN);
`
_, err = db.Exec(sqlStmt)
if err != nil {
e.Logger.Fatalf("%q: %s\n", err, sqlStmt)
}
return db
}
func getBoxes() map[int]bool {
rows, err := db.Query("SELECT id, value FROM boxes")
if err != nil {
e.Logger.Fatal(err)
}
boxes := make(map[int]bool, 1000)
for i := 0; i < 1000; i++ {
boxes[i] = false
}
for rows.Next() {
var id int
var value bool
err = rows.Scan(&id, &value)
if err != nil {
e.Logger.Fatal(err)
}
boxes[id] = value
}
defer rows.Close()
return boxes
}
func setBox(id int, value bool) {
e.Logger.Info("START INSERT")
stmt, err := db.Prepare("INSERT OR REPLACE INTO boxes(id, value) VALUES(?, ?)")
if err != nil {
e.Logger.Fatal(err)
}
e.Logger.Info("PREPARED")
res, err := stmt.Exec(id, value)
if err != nil {
e.Logger.Fatal(err)
}
stmt.Close()
e.Logger.Info("INSERTED")
e.Logger.Info(res)
}

102
src/boxes.go Normal file
View File

@@ -0,0 +1,102 @@
package main
import (
"strconv"
"strings"
"sync"
"github.com/labstack/echo/v4"
"github.com/labstack/gommon/log"
"golang.org/x/net/websocket"
)
var (
wsConnections = make(map[*websocket.Conn]bool)
wsMutex sync.Mutex
db = initDb()
)
func handleWsError(err error, conn *websocket.Conn) {
if err != nil {
log.Errorf("Failed to handle websocket: %v", err)
conn.Close()
wsMutex.Lock()
defer wsMutex.Unlock()
delete(wsConnections, conn)
}
}
func broadcastMessage(message string) {
wsMutex.Lock()
defer wsMutex.Unlock()
for conn := range wsConnections {
err := websocket.Message.Send(conn, message)
handleWsError(err, conn)
}
}
func formatMessage(index int, checked bool) string {
message := "u:" + strconv.Itoa(index)
if checked {
message += ":+"
} else {
message += ":-"
}
return message
}
func handleMessage(msg string, ws *websocket.Conn) error {
if strings.Contains(msg, "u:") {
parts := strings.Split(msg, ":")
index, err := strconv.Atoi(parts[1])
if err != nil {
return err
}
checked := parts[2] == "+"
message := formatMessage(index, checked)
broadcastMessage(message)
setBox(index, checked)
}
if strings.Contains(msg, "ping") {
len := len(wsConnections)
websocket.Message.Send(ws, "pong-"+strconv.Itoa(len))
}
return nil
}
func initWs(c echo.Context) error {
websocket.Handler(func(ws *websocket.Conn) {
wsMutex.Lock()
wsConnections[ws] = true
wsMutex.Unlock()
boxes := getBoxes()
initialState := "i:"
for index, checked := range boxes {
if checked {
initialState += strconv.Itoa(index) + ";"
}
}
err := websocket.Message.Send(ws, initialState)
handleWsError(err, ws)
for {
var msg string
err := websocket.Message.Receive(ws, &msg)
handleWsError(err, ws)
if err != nil {
break
}
handleMessage(msg, ws)
log.Infof("Received message: %s", msg)
}
}).ServeHTTP(c.Response(), c.Request())
return nil
}

View File

@@ -3,14 +3,10 @@ package main
import (
"html/template"
"io"
"strconv"
"strings"
"sync"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/labstack/gommon/log"
"golang.org/x/net/websocket"
)
type Templates struct {
@@ -38,113 +34,27 @@ func newPage(boxes map[int]bool) Page {
}
var (
wsConnections = make(map[*websocket.Conn]bool)
wsMutex sync.Mutex
boxes = make(map[int]bool, 1000)
e = echo.New()
)
func broadcastMessage(message string) {
wsMutex.Lock()
defer wsMutex.Unlock()
for conn := range wsConnections {
if err := websocket.Message.Send(conn, message); err != nil {
conn.Close()
delete(wsConnections, conn)
}
}
}
func main() {
e.Renderer = NewTemplates()
e.Logger.SetLevel(log.DEBUG)
e.Use(middleware.Logger())
for i := 0; i < 1000; i++ {
boxes[i] = false
}
page := newPage(boxes)
boxes := getBoxes()
e.Static("/images", "images")
e.Static("/css", "css")
e.GET("/", func(c echo.Context) error {
return c.Render(200, "index", page)
return c.Render(200, "index", newPage(boxes))
})
e.GET("/ws", initWs)
e.Logger.Fatal(e.Start(":54321"))
}
func sendUpdate(index int, checked bool) {
message := formatMessage(index, checked)
broadcastMessage(message)
}
func formatMessage(index int, checked bool) string {
message := "u:" + strconv.Itoa(index)
if checked {
message += ":+"
} else {
message += ":-"
}
return message
}
func handleMessage(msg string, ws *websocket.Conn) error {
if strings.Contains(msg, "u:") {
parts := strings.Split(msg, ":")
index, err := strconv.Atoi(parts[1])
if err != nil {
return err
}
checked := parts[2] == "+"
boxes[index] = checked
sendUpdate(index, checked)
}
if strings.Contains(msg, "ping") {
len := len(wsConnections)
websocket.Message.Send(ws, "pong-"+strconv.Itoa(len))
}
return nil
}
func initWs(c echo.Context) error {
websocket.Handler(func(ws *websocket.Conn) {
wsMutex.Lock()
wsConnections[ws] = true
wsMutex.Unlock()
initialState := "i:"
for index, checked := range boxes {
if checked {
initialState += strconv.Itoa(index) + ";"
}
}
if err := websocket.Message.Send(ws, initialState); err != nil {
log.Errorf("Failed to send initial data: %v", err)
return
}
for {
var msg string
if err := websocket.Message.Receive(ws, &msg); err != nil {
wsMutex.Lock()
delete(wsConnections, ws)
wsMutex.Unlock()
ws.Close()
break
}
handleMessage(msg, ws)
log.Infof("Received message: %s", msg)
}
}).ServeHTTP(c.Response(), c.Request())
return nil
defer db.Close()
}