Compare commits

...
2 Commits
2 changed files with 177 additions and 1 deletions
+157
View File
@@ -0,0 +1,157 @@
import asyncio, queue, json, threading, hashlib, websockets, inspect
from websockets.asyncio.client import connect as wsconnect
class BasicServerConnection():
def __init__(self,uri: str) -> None:
self.uri = uri
self.callbacks = {}
self.resq = queue.Queue()
self.asklock = asyncio.Lock()
self.needsClose = threading.Event()
def addCallback(self,name,func):
# print("addcallback",name,func)
if name in self.callbacks:
self.callbacks[name].append(func)
else:
self.callbacks[name] = [func]
def removeCallback(self,name,func):
if not name in self.callbacks: return
self.callbacks[name].remove(func)
async def __runCallback(self,name,*args):
# print("runcallback",name,args)
if not name in self.callbacks: return
for func in self.callbacks[name]:
# print(func)
if inspect.iscoroutinefunction(func):
await func(*args)
else:
func(*args)
def __runCallbackInThread(self,name,*args):
def __run():
asyncio.run(self.__runCallback(name,*args))
thread = threading.Thread(target=__run)
thread.start()
async def connect(self) -> None:
uri = self.uri
async with wsconnect(uri) as ws:
self.ws = ws
while True:
if self.needsClose.is_set():
await self.ws.close()
break
try:
txt = await asyncio.wait_for(ws.recv(), timeout=0.1)
# print("recv",txt)
res = json.loads(txt)
if "connected" == res["type"]:
self.__runCallbackInThread("connect")
if res["type"] in ["response","error"]:
self.resq.put(res)
except websockets.exceptions.ConnectionClosed:
break
except TimeoutError:
pass
except Exception as e:
pass
async def disconnect(self) -> None:
if not self.ws: return
self.needsClose.set()
await self.__runCallback("connect")
async def waitConnect(self) -> None:
# wait for it to reconnect
# print("waiting for reconnection.")
event = threading.Event()
def evset():
event.set()
self.addCallback("connect",evset)
self.addCallback("disconnect",evset)
#self.callbacks["connect"].insert(0,evset)
await asyncio.to_thread(event.wait)
# print("reconnected!! hell yeah")
self.removeCallback("connect",evset)
self.removeCallback("disconnect",evset)
async def ask(self,obj: dict,t:int=None) -> dict | None:
# print("ask",obj)
while True:
if self.needsClose.is_set():
return None
try:
async with self.asklock:
await self.ws.send(json.dumps(obj))
e = self.resq.get(timeout=t)
except queue.Empty:
e = None
except websockets.exceptions.ConnectionClosed:
await self.waitConnect()
continue
return e
class ServerConnection(BasicServerConnection):
def __init__(self,uri: str = "ws://localhost:30341"):
super().__init__(uri)
self.logincmd = None
super().addCallback("connect",self.__login)
async def __connectloop(self):
self.mustConnect = True
first = True
while self.mustConnect:
if not first:
await asyncio.sleep(1)
first=False
try:
await super().connect()
except OSError:
pass
async def connect(self):
self.thread = threading.Thread(target=lambda: asyncio.run(self.__connectloop()))
self.thread.start()
await super().waitConnect()
async def disconnect(self):
self.mustConnect = False
await super().disconnect()
self.thread.join()
async def status(self):
return await super().ask({"type": "status"})
async def __login(self):
# print("__login")
if self.logincmd == None: return {"type": "error", "error": "No credentials found.", "server": False}
stat = await self.status()
if stat==None: return
if stat["type"]=="error" or stat["response"]["login"]==False:
return await super().ask(self.logincmd)
async def login(self,name: str,pwrd: str):
res = hashlib.md5(pwrd.encode("utf8"))
psum = res.hexdigest()
self.logincmd = {"type": "login", "name": name, "pass": psum}
return await self.__login()
async def logout(self):
self.logincmd = None
return await super().ask({"type": "logout"})
async def createuser(self,name: str,pwrd: str):
res = hashlib.md5(pwrd.encode("utf8"))
psum = res.hexdigest()
return await super().ask({"type": "createuser", "name": name, "pass": psum})
async def changename(self,name: str):
return await super().ask({"type": "changename", "name": name})
async def changepass(self,old: str,new: str):
osum = hashlib.md5(old.encode("utf8")).hexdigest()
nsum = hashlib.md5(new.encode("utf8")).hexdigest()
return await super().ask({"type": "changepass", "oldpass": osum, "newpass": nsum})
+20 -1
View File
@@ -2,6 +2,7 @@
import asyncio import asyncio
import json, random, re, math, time, traceback import json, random, re, math, time, traceback
from websockets.exceptions import ConnectionClosed, ConnectionClosedOK
from websockets.asyncio.server import serve from websockets.asyncio.server import serve
from db import Database from db import Database
@@ -48,6 +49,12 @@ def serverError(msg,**kwargs) -> str:
sessionUsers = {} sessionUsers = {}
sessionCreatedAccount = [] sessionCreatedAccount = []
def forgetSession(sid):
if sid in sessionUsers:
del sessionUsers[sid]
if sid in sessionCreatedAccount:
sessionCreatedAccount.remove(sid)
async def handleCommand(ws,cmd): async def handleCommand(ws,cmd):
global sessionUsers global sessionUsers
print(f"recieved command: {cmd} (from {id(ws)})") print(f"recieved command: {cmd} (from {id(ws)})")
@@ -59,6 +66,8 @@ async def handleCommand(ws,cmd):
match cmd["type"]: match cmd["type"]:
case "ping": case "ping":
return await ws.send(response("Pong!")) return await ws.send(response("Pong!"))
# accounts
case "createuser": case "createuser":
if uid != None: if uid != None:
return await ws.send(clientError("You are already logged in.")) return await ws.send(clientError("You are already logged in."))
@@ -163,9 +172,19 @@ async def serveClient(ws):
except Exception as e: except Exception as e:
traceback.print_exception(e) traceback.print_exception(e)
await ws.send(serverError(f"Cannot process command ({cmd["type"]})")) await ws.send(serverError(f"Cannot process command ({cmd["type"]})"))
except json.decoder.JSONDecodeError:
await ws.send(serverError("Cannot process JSON."))
except ConnectionClosedOK:
print(f"connection {id(ws)} has closed")
forgetSession(id(ws))
break
except ConnectionClosed as e:
print(f"connection {id(ws)} has closed with a non-OK exit code:")
traceback.print_exception(e)
break
except Exception as e: except Exception as e:
traceback.print_exception(e) traceback.print_exception(e)
await ws.send(serverError("Cannot process JSON.")) await ws.send(serverError("Cannot parse command."))
async def main(): async def main():
async with serve(serveClient, "", 30341) as server: async with serve(serveClient, "", 30341) as server: