continuous updates
This commit is contained in:
parent
133b7749d4
commit
7888c5a7d1
@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"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("/tile", read.Tile)
|
||||
router.GET("/updates", read.Continuous)
|
||||
router.POST("/set", write.Pixel)
|
||||
router.OPTIONS("/set", func(c *gin.Context) {
|
||||
c.Header("Access-Control-Allow-Origin", "*")
|
||||
@ -24,6 +26,7 @@ func Listen(address string, staticPath string) {
|
||||
})
|
||||
|
||||
if staticPath != "" {
|
||||
router.StaticFile("/", filepath.Join(staticPath, "index.html"))
|
||||
router.Static("/static", staticPath)
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,37 @@
|
||||
package read
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.tek.govt.hu/dowerx/place/api/structs"
|
||||
"git.tek.govt.hu/dowerx/place/config"
|
||||
"git.tek.govt.hu/dowerx/place/storage"
|
||||
"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) {
|
||||
conf := config.GetConfig()
|
||||
c.JSON(200, gin.H{
|
||||
@ -37,5 +60,60 @@ func Tile(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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,11 @@ type Color struct {
|
||||
B uint32 `form:"b" json:"b"`
|
||||
}
|
||||
|
||||
type Pixel struct {
|
||||
Coordinates
|
||||
Color
|
||||
}
|
||||
|
||||
func (c *Color) RGBA() (r, g, b, a uint32) {
|
||||
return c.R, c.G, c.B, 65535
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package write
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"git.tek.govt.hu/dowerx/place/api/read"
|
||||
"git.tek.govt.hu/dowerx/place/api/structs"
|
||||
"git.tek.govt.hu/dowerx/place/auth"
|
||||
"git.tek.govt.hu/dowerx/place/config"
|
||||
@ -13,10 +14,7 @@ import (
|
||||
var timeout int = config.GetConfig().Timeout
|
||||
|
||||
func Pixel(c *gin.Context) {
|
||||
var info struct {
|
||||
structs.Color
|
||||
structs.Coordinates
|
||||
}
|
||||
var info structs.Pixel
|
||||
if c.ShouldBind(&info) != nil {
|
||||
c.AbortWithStatus(400)
|
||||
return
|
||||
@ -38,6 +36,8 @@ func Pixel(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
go read.Connections.Send(info)
|
||||
|
||||
c.Status(200)
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
@ -34,13 +33,7 @@ func Load(path string, canvasSize int, tileSize int) {
|
||||
for x := range tiles[y] {
|
||||
err := tiles[y][x].load(filepath.Join(path, fmt.Sprintf("%d-%d.png", x, y)), tileSize)
|
||||
if err != nil {
|
||||
tiles[y][x].fill()
|
||||
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)
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,13 +19,15 @@ func (t *tile) load(path string, tileSize int) error {
|
||||
t.path = path
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
t.fill()
|
||||
return t.save()
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
img, _, err := image.Decode(file)
|
||||
if err != nil {
|
||||
return err
|
||||
t.fill()
|
||||
return t.save()
|
||||
}
|
||||
|
||||
draw.Draw(t.image, img.Bounds(), img, img.Bounds().Min, draw.Src)
|
||||
|
@ -4,8 +4,8 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Place</title>
|
||||
<script defer src="place.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script defer src="static/place.js"></script>
|
||||
<link rel="stylesheet" href="static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="place" width="512" height="512"></canvas>
|
||||
|
30
web/place.js
30
web/place.js
@ -1,4 +1,5 @@
|
||||
const apiBase = "http://localhost:8080";
|
||||
const apiBase = "localhost:8080";
|
||||
const ssl = false;
|
||||
|
||||
var tiles = [];
|
||||
var color = {
|
||||
@ -8,16 +9,16 @@ var color = {
|
||||
};
|
||||
|
||||
async function getInfo() {
|
||||
let response = await fetch(`${apiBase}/info`);
|
||||
let response = await fetch(`${ssl ? "https" : "http"}://${apiBase}/info`);
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async function setPixel(canvas, ctx, x, y, c) {
|
||||
async function sendPixel(canvas, x, y, c) {
|
||||
let bounds = canvas.getBoundingClientRect();
|
||||
x = x - bounds.left;
|
||||
y = y - bounds.top;
|
||||
|
||||
let response = await fetch(`${apiBase}/set`, {
|
||||
let response = await fetch(`${ssl ? "https" : "http"}://${apiBase}/set`, {
|
||||
method: "POST",
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
@ -32,13 +33,7 @@ async function setPixel(canvas, ctx, x, y, c) {
|
||||
})
|
||||
});
|
||||
|
||||
console.log(response);
|
||||
|
||||
switch (response.status) {
|
||||
case 200:
|
||||
ctx.fillStyle = `rgb(${c.r}, ${c.g}, ${c.b})`;
|
||||
ctx.fillRect(x, y, 1, 1);
|
||||
break;
|
||||
case 400:
|
||||
alert("missing parameters in request");
|
||||
break;
|
||||
@ -57,12 +52,21 @@ function init(info, ctx) {
|
||||
for (let x = 0; x < info.canvasSize; x++) {
|
||||
let tile = new Image();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
let info = await getInfo();
|
||||
console.log(info);
|
||||
@ -71,7 +75,9 @@ async function main() {
|
||||
let ctx = canvas.getContext("2d");
|
||||
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();
|
Loading…
Reference in New Issue
Block a user