Denis V. Dedkov 2 lat temu
commit
2e615a2f69
5 zmienionych plików z 178 dodań i 0 usunięć
  1. 14 0
      Dockerfile
  2. 95 0
      beerlog-srv.py
  3. 24 0
      docker-compose.yml
  4. 45 0
      storage.py
  5. BIN
      users.obj

+ 14 - 0
Dockerfile

@@ -0,0 +1,14 @@
+FROM python:3-alpine
+
+RUN pip install websockets
+
+WORKDIR /beerlog-srv
+COPY beerlog-srv.py .
+COPY storage.py .
+COPY users.obj .
+
+ARG BEERLOG_PORT
+ENV BEERLOG_PORT $BEERLOG_PORT
+EXPOSE $BEERLOG_PORT
+CMD ["python", "./beerlog-srv.py"]
+

+ 95 - 0
beerlog-srv.py

@@ -0,0 +1,95 @@
+#!/usr/bin/env python
+
+import asyncio
+import websockets
+import json
+import sys
+import os
+from storage import Storage
+
+CONNECTIONS = set()
+STORAGES = { "users": Storage("users") }
+
+class UserInfoProtocol(websockets.BasicAuthWebSocketServerProtocol):
+    async def check_credentials(self, username, password):
+        all_users = storage("users").read()
+        self.user = all_users[username] if username in all_users else None
+        return self.user != None
+
+def storage(name):
+    if not name in STORAGES:
+        STORAGES[name] = Storage(name)
+    return STORAGES[name]
+
+def _get_entity(event):
+    return event.get("entity", None)
+
+def _get_data(event):
+    return event.get("data", {})
+
+async def add_entity(websocket, event):
+    name = _get_entity(event)
+    data = _get_data(event)
+    if name and data:
+        storage(name).create(data)
+        await broadcast(websocket, { "entity": name, "event": "created", "data": data })
+
+async def get_entity(websocket, event):
+    name = _get_entity(event) 
+    ts = _get_data(event).get("ts", 0)
+    data = storage(name).read(ts)
+    await websocket.send(json.dumps({ "entity": name, "event": "received", "data": data }))
+
+async def mod_entity(websocket, event):
+    name = _get_entity(event)
+    data = _get_data(event)
+    if name and data:
+        storage(name).update(data)
+        await broadcast(websocket, { "entity": name, "event": "modified", "data": data })
+
+async def del_entity(websocket, event):
+    name = _get_entity(event)
+    entity_id = _get_data(event).get("id", None)
+    if storage(name).delete(entity_id):
+        await broadcast(websocket, { "entity": name, "event": "deleted", "data": entity_id })
+
+async def describe(websocket, event):
+    connections = list(CONNECTIONS)
+    data = { c.user["id"]: connections.count(c) for c in connections }
+    await websocket.send(json.dumps({ "entity": "connections", "event": "described", "data": data }))
+
+async def broadcast(websocket, event):
+    websockets.broadcast(CONNECTIONS, json.dumps(event))
+
+COMMANDS = {
+        "add": add_entity,
+        "get": get_entity,
+        "mod": mod_entity,
+        "del": del_entity,
+        "describe": describe
+    }
+
+async def handle(websocket):
+    try:
+        CONNECTIONS.add(websocket)
+        await broadcast(websocket, { "event": "connected", "entity": "users", "data": websocket.user})
+
+        async for message in websocket:
+            event = json.loads(message)
+            print(event, file=sys.stderr)
+            handler = COMMANDS.get(event["action"], broadcast)
+            await handler(websocket, event)
+
+    finally:
+        CONNECTIONS.remove(websocket)
+        await broadcast(websocket, { "event": "disconnected", "entity": "users", "data": websocket.user})
+
+async def main():
+    port = os.environ.get("BEERLOG_PORT", 8000)
+    host = os.environ.get("BEERLOG_HOST", "0.0.0.0")
+    print(f"Start on {host}:{port}", file=sys.stderr)
+    async with websockets.serve(handle, host, port, create_protocol=UserInfoProtocol):
+        await asyncio.Future()
+
+asyncio.run(main())
+

+ 24 - 0
docker-compose.yml

@@ -0,0 +1,24 @@
+version: "3.7"
+
+services:
+    beerlog-srv-dev:
+        container_name: beerlog-srv-dev
+        restart: always
+        build:
+            context: ./
+            args:
+              BEERLOG_PORT: 8000
+            network: host
+        ports:
+            - 8000:8000
+    beerlog-srv-prod:
+        container_name: beerlog-srv-prod
+        restart: always
+        build:
+            context: ./
+            args:
+              BEERLOG_PORT: 8080
+            network: host
+        ports:
+            - 8080:8080
+

+ 45 - 0
storage.py

@@ -0,0 +1,45 @@
+import os
+import pickle
+import uuid
+import time
+
+class Storage():
+    objects = {}
+
+    def __init__(self, name):
+        self.name = f"./{name}.obj"
+        self._load()
+
+    def _load(self):
+        if not os.path.exists(self.name):
+            self._save()
+
+        with open(self.name, "rb") as s:
+            self.objects = pickle.load(s)
+
+    def _save(self):
+        with open(self.name, "wb") as s:
+            pickle.dump(self.objects, s, pickle.HIGHEST_PROTOCOL)
+
+    def create(self, obj):
+        obj["id"] = uuid.uuid1().hex
+        self.update(obj)
+
+    def read(self, ts = 0):
+        if ts == 0:
+            return self.objects
+        else:
+            return { k: v for k, v in self.objects.items() if v["ts"] > ts }
+
+    def update(self, obj):
+        obj["ts"] = time.time()
+        self.objects[obj["id"]] = obj
+        self._save()
+
+    def delete(self, obj_id):
+        if not obj_id in self.objects:
+            return False
+
+        del self.objects[obj_id]
+        self._save()
+        return True

BIN
users.obj