continuous updates

This commit is contained in:
Benedek László 2024-06-13 19:25:45 +02:00
parent 133b7749d4
commit 7888c5a7d1
8 changed files with 117 additions and 30 deletions

View File

@ -2,6 +2,7 @@ package api
import ( import (
"net/http" "net/http"
"path/filepath"
"time" "time"
"git.tek.govt.hu/dowerx/place/api/read" "git.tek.govt.hu/dowerx/place/api/read"
@ -14,6 +15,7 @@ func Listen(address string, staticPath string) {
router.GET("/info", read.Info) router.GET("/info", read.Info)
router.GET("/tile", read.Tile) router.GET("/tile", read.Tile)
router.GET("/updates", read.Continuous)
router.POST("/set", write.Pixel) router.POST("/set", write.Pixel)
router.OPTIONS("/set", func(c *gin.Context) { router.OPTIONS("/set", func(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Origin", "*")
@ -24,6 +26,7 @@ func Listen(address string, staticPath string) {
}) })
if staticPath != "" { if staticPath != "" {
router.StaticFile("/", filepath.Join(staticPath, "index.html"))
router.Static("/static", staticPath) router.Static("/static", staticPath)
} }

View File

@ -1,14 +1,37 @@
package read package read
import ( import (
"errors" "log"
"sync"
"time"
"git.tek.govt.hu/dowerx/place/api/structs" "git.tek.govt.hu/dowerx/place/api/structs"
"git.tek.govt.hu/dowerx/place/config" "git.tek.govt.hu/dowerx/place/config"
"git.tek.govt.hu/dowerx/place/storage" "git.tek.govt.hu/dowerx/place/storage"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
) )
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
type ConnectionsMap struct {
sync.Mutex
Items map[*websocket.Conn]chan structs.Pixel
}
func (c *ConnectionsMap) Send(pixel structs.Pixel) {
c.Lock()
for _, v := range c.Items {
v <- pixel
}
c.Unlock()
}
var Connections ConnectionsMap = ConnectionsMap{Items: make(map[*websocket.Conn]chan structs.Pixel)}
func Info(c *gin.Context) { func Info(c *gin.Context) {
conf := config.GetConfig() conf := config.GetConfig()
c.JSON(200, gin.H{ c.JSON(200, gin.H{
@ -37,5 +60,60 @@ func Tile(c *gin.Context) {
} }
func Continuous(c *gin.Context) { func Continuous(c *gin.Context) {
panic(errors.New("unimplomented")) conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
return
}
Connections.Lock()
Connections.Items[conn] = make(chan structs.Pixel)
Connections.Unlock()
conn.SetReadDeadline(time.Now().Add(time.Second * 60))
conn.SetPongHandler(func(string) error {
conn.SetReadDeadline(time.Now().Add(time.Second * 60))
return nil
})
// send pings
go func() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for range ticker.C {
if err := conn.WriteControl(websocket.PingMessage, nil, time.Now().Add(10*time.Second)); err != nil {
return
}
}
}()
// defer: close connection, remove channel
defer func() {
Connections.Lock()
for k, v := range Connections.Items {
if k == conn {
close(v)
delete(Connections.Items, k)
break
}
}
Connections.Unlock()
conn.Close()
log.Println("removed connection")
}()
// send updates
go func() {
for pixel := range Connections.Items[conn] {
conn.WriteJSON(pixel)
}
}()
// read loop to keep the connetion alive
for {
_, _, err := conn.ReadMessage()
if err != nil {
break
}
}
} }

View File

@ -11,6 +11,11 @@ type Color struct {
B uint32 `form:"b" json:"b"` B uint32 `form:"b" json:"b"`
} }
type Pixel struct {
Coordinates
Color
}
func (c *Color) RGBA() (r, g, b, a uint32) { func (c *Color) RGBA() (r, g, b, a uint32) {
return c.R, c.G, c.B, 65535 return c.R, c.G, c.B, 65535
} }

View File

@ -3,6 +3,7 @@ package write
import ( import (
"errors" "errors"
"git.tek.govt.hu/dowerx/place/api/read"
"git.tek.govt.hu/dowerx/place/api/structs" "git.tek.govt.hu/dowerx/place/api/structs"
"git.tek.govt.hu/dowerx/place/auth" "git.tek.govt.hu/dowerx/place/auth"
"git.tek.govt.hu/dowerx/place/config" "git.tek.govt.hu/dowerx/place/config"
@ -13,10 +14,7 @@ import (
var timeout int = config.GetConfig().Timeout var timeout int = config.GetConfig().Timeout
func Pixel(c *gin.Context) { func Pixel(c *gin.Context) {
var info struct { var info structs.Pixel
structs.Color
structs.Coordinates
}
if c.ShouldBind(&info) != nil { if c.ShouldBind(&info) != nil {
c.AbortWithStatus(400) c.AbortWithStatus(400)
return return
@ -38,6 +36,8 @@ func Pixel(c *gin.Context) {
return return
} }
go read.Connections.Send(info)
c.Status(200) c.Status(200)
} }

View File

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"image" "image"
"image/color" "image/color"
"log"
"path/filepath" "path/filepath"
"time" "time"
) )
@ -34,13 +33,7 @@ func Load(path string, canvasSize int, tileSize int) {
for x := range tiles[y] { for x := range tiles[y] {
err := tiles[y][x].load(filepath.Join(path, fmt.Sprintf("%d-%d.png", x, y)), tileSize) err := tiles[y][x].load(filepath.Join(path, fmt.Sprintf("%d-%d.png", x, y)), tileSize)
if err != nil { if err != nil {
tiles[y][x].fill() panic(err)
if err = tiles[y][x].save(); err != nil {
panic(err)
}
log.Printf("Created tile (%d-%d)\n", x, y)
} else {
log.Printf("Loaded tile (%d-%d)\n", x, y)
} }
} }
} }

View File

@ -19,13 +19,15 @@ func (t *tile) load(path string, tileSize int) error {
t.path = path t.path = path
file, err := os.Open(path) file, err := os.Open(path)
if err != nil { if err != nil {
return err t.fill()
return t.save()
} }
defer file.Close() defer file.Close()
img, _, err := image.Decode(file) img, _, err := image.Decode(file)
if err != nil { if err != nil {
return err t.fill()
return t.save()
} }
draw.Draw(t.image, img.Bounds(), img, img.Bounds().Min, draw.Src) draw.Draw(t.image, img.Bounds(), img, img.Bounds().Min, draw.Src)

View File

@ -4,8 +4,8 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Place</title> <title>Place</title>
<script defer src="place.js"></script> <script defer src="static/place.js"></script>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="static/style.css">
</head> </head>
<body> <body>
<canvas id="place" width="512" height="512"></canvas> <canvas id="place" width="512" height="512"></canvas>

View File

@ -1,4 +1,5 @@
const apiBase = "http://localhost:8080"; const apiBase = "localhost:8080";
const ssl = false;
var tiles = []; var tiles = [];
var color = { var color = {
@ -8,16 +9,16 @@ var color = {
}; };
async function getInfo() { async function getInfo() {
let response = await fetch(`${apiBase}/info`); let response = await fetch(`${ssl ? "https" : "http"}://${apiBase}/info`);
return await response.json(); return await response.json();
} }
async function setPixel(canvas, ctx, x, y, c) { async function sendPixel(canvas, x, y, c) {
let bounds = canvas.getBoundingClientRect(); let bounds = canvas.getBoundingClientRect();
x = x - bounds.left; x = x - bounds.left;
y = y - bounds.top; y = y - bounds.top;
let response = await fetch(`${apiBase}/set`, { let response = await fetch(`${ssl ? "https" : "http"}://${apiBase}/set`, {
method: "POST", method: "POST",
credentials: 'include', credentials: 'include',
headers: { headers: {
@ -32,13 +33,7 @@ async function setPixel(canvas, ctx, x, y, c) {
}) })
}); });
console.log(response);
switch (response.status) { switch (response.status) {
case 200:
ctx.fillStyle = `rgb(${c.r}, ${c.g}, ${c.b})`;
ctx.fillRect(x, y, 1, 1);
break;
case 400: case 400:
alert("missing parameters in request"); alert("missing parameters in request");
break; break;
@ -57,12 +52,21 @@ function init(info, ctx) {
for (let x = 0; x < info.canvasSize; x++) { for (let x = 0; x < info.canvasSize; x++) {
let tile = new Image(); let tile = new Image();
tile.onload = () => ctx.drawImage(tile, x * info.tileSize, y * info.tileSize); tile.onload = () => ctx.drawImage(tile, x * info.tileSize, y * info.tileSize);
tile.src = `${apiBase}/tile?x=${x}&y=${y}`; tile.src = `${ssl ? "https" : "http"}://${apiBase}/tile?x=${x}&y=${y}`;
tiles[y].push(tile); tiles[y].push(tile);
} }
} }
} }
async function updates(ctx) {
let socket = new WebSocket(`ws://${apiBase}/updates`);
socket.onmessage = async e => {
let pixel = await JSON.parse(e.data);
ctx.fillStyle = `rgb(${pixel.r}, ${pixel.g}, ${pixel.b})`;
ctx.fillRect(pixel.x, pixel.y, 1, 1);
}
}
async function main() { async function main() {
let info = await getInfo(); let info = await getInfo();
console.log(info); console.log(info);
@ -71,7 +75,9 @@ async function main() {
let ctx = canvas.getContext("2d"); let ctx = canvas.getContext("2d");
init(info, ctx); init(info, ctx);
canvas.onclick = e => setPixel(canvas, ctx, e.clientX, e.clientY, color); updates(ctx);
canvas.onclick = e => sendPixel(canvas, ctx, e.clientX, e.clientY, color);
} }
main(); main();