From 92e4dadf4aa5c8e422ade8f436b63a29afed3b68 Mon Sep 17 00:00:00 2001 From: JurajKubrican Date: Mon, 30 Dec 2024 15:18:59 +0100 Subject: [PATCH] sqlite --- data/.gitignore | 1 + go.mod | 1 + go.sum | 2 + src/box-db.go | 73 ++++++++++++++++++++++++++++++++++ src/boxes.go | 102 ++++++++++++++++++++++++++++++++++++++++++++++++ src/main.go | 100 +++-------------------------------------------- 6 files changed, 184 insertions(+), 95 deletions(-) create mode 100644 data/.gitignore create mode 100644 src/box-db.go create mode 100644 src/boxes.go diff --git a/data/.gitignore b/data/.gitignore new file mode 100644 index 0000000..e604003 --- /dev/null +++ b/data/.gitignore @@ -0,0 +1 @@ +data.db \ No newline at end of file diff --git a/go.mod b/go.mod index 6f8ffbc..6bc35a8 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index cadc1f3..b6f5dfa 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/src/box-db.go b/src/box-db.go new file mode 100644 index 0000000..9dbba25 --- /dev/null +++ b/src/box-db.go @@ -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) +} diff --git a/src/boxes.go b/src/boxes.go new file mode 100644 index 0000000..669aea8 --- /dev/null +++ b/src/boxes.go @@ -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 +} diff --git a/src/main.go b/src/main.go index 3c97d9a..6bbcaff 100644 --- a/src/main.go +++ b/src/main.go @@ -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() + 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() }