python library for communicating with the server through websocket
This commit is contained in:
@@ -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})
|
||||||
Reference in New Issue
Block a user