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}) async def getisettings(self,interface: str): return await super().ask({"type": "getisettings","interface": interface}) async def setisettings(self,interface: str,settings: dict): return await super().ask({"type": "setisettings","interface": interface,"settings":settings})