Update.
This commit is contained in:
		
							parent
							
								
									a4bea94495
								
							
						
					
					
						commit
						ba3152f553
					
				
							
								
								
									
										312
									
								
								src/snek/research/serpentarium.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										312
									
								
								src/snek/research/serpentarium.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,312 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import asyncio
 | 
				
			||||||
 | 
					import aiohttp
 | 
				
			||||||
 | 
					from aiohttp import web
 | 
				
			||||||
 | 
					import dataset
 | 
				
			||||||
 | 
					import dataset.util
 | 
				
			||||||
 | 
					import traceback
 | 
				
			||||||
 | 
					import socket
 | 
				
			||||||
 | 
					import base64
 | 
				
			||||||
 | 
					import uuid 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DatasetMethod:
 | 
				
			||||||
 | 
					    def __init__(self, dt, name):
 | 
				
			||||||
 | 
					        self.dt = dt
 | 
				
			||||||
 | 
					        self.name = name 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __call__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        return self.dt.ds.call(
 | 
				
			||||||
 | 
					            self.dt.name,
 | 
				
			||||||
 | 
					            self.name,
 | 
				
			||||||
 | 
					            *args,
 | 
				
			||||||
 | 
					            **kwargs
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DatasetTable:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, ds, name):
 | 
				
			||||||
 | 
					        self.ds = ds 
 | 
				
			||||||
 | 
					        self.name = name 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __getattr__(self, name):
 | 
				
			||||||
 | 
					        return DatasetMethod(self, name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WebSocketClient:
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        self.buffer = b''
 | 
				
			||||||
 | 
					        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 | 
				
			||||||
 | 
					        self.connect()     
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def connect(self):
 | 
				
			||||||
 | 
					        self.socket.connect(("127.0.0.1", 3131))
 | 
				
			||||||
 | 
					        key = base64.b64encode(b'1234123412341234').decode('utf-8')
 | 
				
			||||||
 | 
					        handshake = (
 | 
				
			||||||
 | 
					            f"GET /db HTTP/1.1\r\n"
 | 
				
			||||||
 | 
					            f"Host: localhost:3131\r\n"
 | 
				
			||||||
 | 
					            f"Upgrade: websocket\r\n"
 | 
				
			||||||
 | 
					            f"Connection: Upgrade\r\n"
 | 
				
			||||||
 | 
					            f"Sec-WebSocket-Key: {key}\r\n"
 | 
				
			||||||
 | 
					            f"Sec-WebSocket-Version: 13\r\n\r\n"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self.socket.sendall(handshake.encode('utf-8'))
 | 
				
			||||||
 | 
					        response = self.read_until(b'\r\n\r\n')
 | 
				
			||||||
 | 
					        if b'101 Switching Protocols' not in response:
 | 
				
			||||||
 | 
					            raise Exception("Failed to connect to WebSocket")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def write(self, message):
 | 
				
			||||||
 | 
					        message_bytes = message.encode('utf-8')
 | 
				
			||||||
 | 
					        length = len(message_bytes)
 | 
				
			||||||
 | 
					        if length <= 125:
 | 
				
			||||||
 | 
					            self.socket.sendall(b'\x81' + bytes([length]) + message_bytes)
 | 
				
			||||||
 | 
					        elif length >= 126 and length <= 65535:
 | 
				
			||||||
 | 
					            self.socket.sendall(b'\x81' + bytes([126]) + length.to_bytes(2, 'big') + message_bytes)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.socket.sendall(b'\x81' + bytes([127]) + length.to_bytes(8, 'big') + message_bytes)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def read_until(self, delimiter): 
 | 
				
			||||||
 | 
					        while True:
 | 
				
			||||||
 | 
					            find_pos = self.buffer.find(delimiter)
 | 
				
			||||||
 | 
					            if find_pos != -1:
 | 
				
			||||||
 | 
					                data = self.buffer[:find_pos+4]
 | 
				
			||||||
 | 
					                self.buffer = self.buffer[find_pos+4:]
 | 
				
			||||||
 | 
					                return data 
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            chunk = self.socket.recv(1024)
 | 
				
			||||||
 | 
					            if not chunk:
 | 
				
			||||||
 | 
					                return None
 | 
				
			||||||
 | 
					            self.buffer += chunk
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					    def read_exactly(self, length):
 | 
				
			||||||
 | 
					        while len(self.buffer) < length:
 | 
				
			||||||
 | 
					            chunk = self.socket.recv(length - len(self.buffer))
 | 
				
			||||||
 | 
					            if not chunk:
 | 
				
			||||||
 | 
					                return None
 | 
				
			||||||
 | 
					            self.buffer += chunk 
 | 
				
			||||||
 | 
					        response = self.buffer[: length]
 | 
				
			||||||
 | 
					        self.buffer = self.buffer[length:]
 | 
				
			||||||
 | 
					        return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def read(self):
 | 
				
			||||||
 | 
					        frame = None 
 | 
				
			||||||
 | 
					        frame = self.read_exactly(2)
 | 
				
			||||||
 | 
					        length = frame[1] & 127
 | 
				
			||||||
 | 
					        if length == 126:
 | 
				
			||||||
 | 
					            length = int.from_bytes(self.read_exactly(2), 'big')
 | 
				
			||||||
 | 
					        elif length == 127:
 | 
				
			||||||
 | 
					            length = int.from_bytes(self.read_exactly(8), 'big')
 | 
				
			||||||
 | 
					        message = self.read_exactly(length)
 | 
				
			||||||
 | 
					        return message
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def close(self):
 | 
				
			||||||
 | 
					        self.socket.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WebSocketClient2:
 | 
				
			||||||
 | 
					    def __init__(self, uri):
 | 
				
			||||||
 | 
					        self.uri = uri
 | 
				
			||||||
 | 
					        self.loop = asyncio.get_event_loop()
 | 
				
			||||||
 | 
					        self.websocket = None
 | 
				
			||||||
 | 
					        self.receive_queue = asyncio.Queue()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Schedule connection setup
 | 
				
			||||||
 | 
					        if self.loop.is_running():
 | 
				
			||||||
 | 
					            # Schedule connect in the existing loop
 | 
				
			||||||
 | 
					            self._connect_future = asyncio.run_coroutine_threadsafe(self._connect(), self.loop)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # If loop isn't running, connect synchronously
 | 
				
			||||||
 | 
					            self.loop.run_until_complete(self._connect())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def _connect(self):
 | 
				
			||||||
 | 
					        self.websocket = await websockets.connect(self.uri)
 | 
				
			||||||
 | 
					        # Start listening for messages
 | 
				
			||||||
 | 
					        asyncio.create_task(self._receive_loop())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def _receive_loop(self):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            async for message in self.websocket:
 | 
				
			||||||
 | 
					                await self.receive_queue.put(message)
 | 
				
			||||||
 | 
					        except Exception:
 | 
				
			||||||
 | 
					            pass  # Handle exceptions as needed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def send(self, message: str):
 | 
				
			||||||
 | 
					        if self.loop.is_running():
 | 
				
			||||||
 | 
					            # Schedule send in the existing loop
 | 
				
			||||||
 | 
					            asyncio.run_coroutine_threadsafe(self.websocket.send(message), self.loop)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # If loop isn't running, run directly
 | 
				
			||||||
 | 
					            self.loop.run_until_complete(self.websocket.send(message))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def receive(self):
 | 
				
			||||||
 | 
					        # Wait for a message synchronously
 | 
				
			||||||
 | 
					        future = asyncio.run_coroutine_threadsafe(self.receive_queue.get(), self.loop)
 | 
				
			||||||
 | 
					        return future.result()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def close(self):
 | 
				
			||||||
 | 
					        if self.websocket:
 | 
				
			||||||
 | 
					            if self.loop.is_running():
 | 
				
			||||||
 | 
					                asyncio.run_coroutine_threadsafe(self.websocket.close(), self.loop)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.loop.run_until_complete(self.websocket.close())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import websockets 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DatasetWrapper(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        self.ws = WebSocketClient() 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def begin(self):
 | 
				
			||||||
 | 
					        self.call(None, 'begin')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def commit(self):
 | 
				
			||||||
 | 
					        self.call(None, 'commit')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __getitem__(self, name):
 | 
				
			||||||
 | 
					        return DatasetTable(self, name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def query(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        return self.call(None, 'query', *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def call(self, table, method, *args, **kwargs):
 | 
				
			||||||
 | 
					        payload = {"table": table, "method": method, "args": args, "kwargs": kwargs,"call_uid":None}
 | 
				
			||||||
 | 
					        #if method in ['find','find_one']:
 | 
				
			||||||
 | 
					        payload["call_uid"] = str(uuid.uuid4())    
 | 
				
			||||||
 | 
					        self.ws.write(json.dumps(payload))
 | 
				
			||||||
 | 
					        if payload["call_uid"]:
 | 
				
			||||||
 | 
					            response = self.ws.read()
 | 
				
			||||||
 | 
					            return json.loads(response)['result']
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DatasetWebSocketView:
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        self.ws = None
 | 
				
			||||||
 | 
					        self.db = dataset.connect('sqlite:///snek.db')
 | 
				
			||||||
 | 
					        self.setattr(self, "db", self.get)
 | 
				
			||||||
 | 
					        self.setattr(self, "db", self.set)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        super()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def format_result(self, result):
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            return dict(result)
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            return [dict(row) for row in result]
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        return result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def send_str(self, msg):
 | 
				
			||||||
 | 
					        return await self.ws.send_str(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get(self, key):
 | 
				
			||||||
 | 
					        returnl loads(dict(self.db['_kv'].get(key=key)['value']))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def set(self, key, value):
 | 
				
			||||||
 | 
					        return self.db['_kv'].upsert({'key': key, 'value': json.dumps(value)}, ['key'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def handle(self, request):
 | 
				
			||||||
 | 
					        ws = web.WebSocketResponse()
 | 
				
			||||||
 | 
					        await ws.prepare(request)
 | 
				
			||||||
 | 
					        self.ws = ws
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        async for msg in ws:
 | 
				
			||||||
 | 
					            if msg.type == aiohttp.WSMsgType.TEXT:
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    data = json.loads(msg.data)
 | 
				
			||||||
 | 
					                    call_uid = data.get("call_uid")
 | 
				
			||||||
 | 
					                    method = data.get("method")
 | 
				
			||||||
 | 
					                    table_name = data.get("table")
 | 
				
			||||||
 | 
					                    args = data.get("args", {})
 | 
				
			||||||
 | 
					                    kwargs = data.get("kwargs", {})
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    function = getattr(self.db, method, None)
 | 
				
			||||||
 | 
					                    if table_name:
 | 
				
			||||||
 | 
					                        function = getattr(self.db[table_name], method, None)
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    print(method, table_name, args, kwargs,flush=True)
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    if function:
 | 
				
			||||||
 | 
					                        response = {}
 | 
				
			||||||
 | 
					                        try:
 | 
				
			||||||
 | 
					                            result = function(*args, **kwargs)
 | 
				
			||||||
 | 
					                            print(result) 
 | 
				
			||||||
 | 
					                            response['result'] = self.format_result(result)
 | 
				
			||||||
 | 
					                            response["call_uid"] = call_uid
 | 
				
			||||||
 | 
					                            response["success"] = True
 | 
				
			||||||
 | 
					                        except Exception as e:
 | 
				
			||||||
 | 
					                            response["call_uid"] = call_uid
 | 
				
			||||||
 | 
					                            response["success"] = False
 | 
				
			||||||
 | 
					                            response["error"] = str(e)
 | 
				
			||||||
 | 
					                            response["traceback"] = traceback.format_exc()
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        if call_uid:
 | 
				
			||||||
 | 
					                            await self.send_str(json.dumps(response,default=str))
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        await self.send_str(json.dumps({"status": "error", "error":"Method not found.","call_uid": call_uid}))
 | 
				
			||||||
 | 
					                except Exception as e:
 | 
				
			||||||
 | 
					                    await self.send_str(json.dumps({"success": False,"call_uid": call_uid, "error": str(e), "error": str(e), "traceback": traceback.format_exc()},default=str))
 | 
				
			||||||
 | 
					            elif msg.type == aiohttp.WSMsgType.ERROR:
 | 
				
			||||||
 | 
					                print('ws connection closed with exception %s' % ws.exception())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return ws
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app = web.Application()
 | 
				
			||||||
 | 
					view = DatasetWebSocketView()
 | 
				
			||||||
 | 
					app.router.add_get('/db', view.handle)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def run_server():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    runner = web.AppRunner(app)
 | 
				
			||||||
 | 
					    await runner.setup()
 | 
				
			||||||
 | 
					    site = web.TCPSite(runner, 'localhost', 3131)
 | 
				
			||||||
 | 
					    await site.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    print("Server started at http://localhost:8080")
 | 
				
			||||||
 | 
					    await asyncio.Event().wait()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def client():
 | 
				
			||||||
 | 
					    print("x")
 | 
				
			||||||
 | 
					    d = DatasetWrapper()
 | 
				
			||||||
 | 
					    print("y")
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    for x in range(100):
 | 
				
			||||||
 | 
					        for x in range(100):
 | 
				
			||||||
 | 
					            if d['test'].insert({"name": "test", "number":x}):
 | 
				
			||||||
 | 
					                print(".",end="",flush=True)
 | 
				
			||||||
 | 
					        print("")    
 | 
				
			||||||
 | 
					        print(d['test'].find_one(name="test", order_by="-number")) 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    print("DONE")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import time 
 | 
				
			||||||
 | 
					async def main():
 | 
				
			||||||
 | 
					    await run_server()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sys 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    if sys.argv[1] == 'server':
 | 
				
			||||||
 | 
					        asyncio.run(main())
 | 
				
			||||||
 | 
					    if sys.argv[1] == 'client':
 | 
				
			||||||
 | 
					        asyncio.run(client())
 | 
				
			||||||
							
								
								
									
										51
									
								
								src/snek/research/serptest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/snek/research/serptest.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					import snek.serpentarium
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import time 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from concurrent.futures import ProcessPoolExecutor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					durations = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def task1():
 | 
				
			||||||
 | 
					    global durations 
 | 
				
			||||||
 | 
					    client = snek.serpentarium.DatasetWrapper()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    start=time.time()
 | 
				
			||||||
 | 
					    for x in range(1500):
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					        client['a'].delete()
 | 
				
			||||||
 | 
					        client['a'].insert({"foo": x})
 | 
				
			||||||
 | 
					        client['a'].find(foo=x) 
 | 
				
			||||||
 | 
					        client['a'].find_one(foo=x)
 | 
				
			||||||
 | 
					        client['a'].count()
 | 
				
			||||||
 | 
					        #print(client['a'].find(foo=x) )
 | 
				
			||||||
 | 
					        #print(client['a'].find_one(foo=x) )
 | 
				
			||||||
 | 
					        #print(client['a'].count())
 | 
				
			||||||
 | 
					    client.close()
 | 
				
			||||||
 | 
					    duration1 = f"{time.time()-start}"
 | 
				
			||||||
 | 
					    durations.append(duration1)
 | 
				
			||||||
 | 
					    print(durations)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					with ProcessPoolExecutor(max_workers=4) as executor:
 | 
				
			||||||
 | 
					    tasks = [executor.submit(task1),
 | 
				
			||||||
 | 
					        executor.submit(task1),
 | 
				
			||||||
 | 
					        executor.submit(task1),
 | 
				
			||||||
 | 
					        executor.submit(task1)
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					    for task in tasks:
 | 
				
			||||||
 | 
					        task.result()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dataset 
 | 
				
			||||||
 | 
					client = dataset.connect("sqlite:///snek.db")
 | 
				
			||||||
 | 
					start=time.time()
 | 
				
			||||||
 | 
					for x in range(1500):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    client['a'].delete()
 | 
				
			||||||
 | 
					    client['a'].insert({"foo": x})
 | 
				
			||||||
 | 
					    print([dict(row) for row in client['a'].find(foo=x)])
 | 
				
			||||||
 | 
					    print(dict(client['a'].find_one(foo=x) ))
 | 
				
			||||||
 | 
					    print(client['a'].count())
 | 
				
			||||||
 | 
					duration2 = f"{time.time()-start}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print(duration1,duration2)
 | 
				
			||||||
							
								
								
									
										103
									
								
								src/snek/schema.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/snek/schema.sql
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,103 @@
 | 
				
			|||||||
 | 
					CREATE TABLE IF NOT EXISTS http_access (
 | 
				
			||||||
 | 
						id INTEGER NOT NULL, 
 | 
				
			||||||
 | 
						created TEXT, 
 | 
				
			||||||
 | 
						path TEXT, 
 | 
				
			||||||
 | 
						duration FLOAT, 
 | 
				
			||||||
 | 
						PRIMARY KEY (id)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					CREATE TABLE IF NOT EXISTS user (
 | 
				
			||||||
 | 
						id INTEGER NOT NULL, 
 | 
				
			||||||
 | 
						color TEXT, 
 | 
				
			||||||
 | 
						created_at TEXT, 
 | 
				
			||||||
 | 
						deleted_at TEXT, 
 | 
				
			||||||
 | 
						email TEXT, 
 | 
				
			||||||
 | 
						is_admin TEXT, 
 | 
				
			||||||
 | 
						last_ping TEXT, 
 | 
				
			||||||
 | 
						nick TEXT, 
 | 
				
			||||||
 | 
						password TEXT, 
 | 
				
			||||||
 | 
						uid TEXT, 
 | 
				
			||||||
 | 
						updated_at TEXT, 
 | 
				
			||||||
 | 
						username TEXT, 
 | 
				
			||||||
 | 
						PRIMARY KEY (id)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					CREATE INDEX IF NOT EXISTS ix_user_e2577dd78b54fe28 ON user (uid);
 | 
				
			||||||
 | 
					CREATE TABLE IF NOT EXISTS channel (
 | 
				
			||||||
 | 
						id INTEGER NOT NULL, 
 | 
				
			||||||
 | 
						created_at TEXT, 
 | 
				
			||||||
 | 
						created_by_uid TEXT, 
 | 
				
			||||||
 | 
						deleted_at TEXT, 
 | 
				
			||||||
 | 
						description TEXT, 
 | 
				
			||||||
 | 
						"index" BIGINT, 
 | 
				
			||||||
 | 
						is_listed BOOLEAN, 
 | 
				
			||||||
 | 
						is_private BOOLEAN, 
 | 
				
			||||||
 | 
						label TEXT, 
 | 
				
			||||||
 | 
						last_message_on TEXT, 
 | 
				
			||||||
 | 
						tag TEXT, 
 | 
				
			||||||
 | 
						uid TEXT, 
 | 
				
			||||||
 | 
						updated_at TEXT, 
 | 
				
			||||||
 | 
						PRIMARY KEY (id)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					CREATE INDEX IF NOT EXISTS ix_channel_e2577dd78b54fe28 ON channel (uid);
 | 
				
			||||||
 | 
					CREATE TABLE IF NOT EXISTS channel_member (
 | 
				
			||||||
 | 
						id INTEGER NOT NULL, 
 | 
				
			||||||
 | 
						channel_uid TEXT, 
 | 
				
			||||||
 | 
						created_at TEXT, 
 | 
				
			||||||
 | 
						deleted_at TEXT, 
 | 
				
			||||||
 | 
						is_banned BOOLEAN, 
 | 
				
			||||||
 | 
						is_moderator BOOLEAN, 
 | 
				
			||||||
 | 
						is_muted BOOLEAN, 
 | 
				
			||||||
 | 
						is_read_only BOOLEAN, 
 | 
				
			||||||
 | 
						label TEXT, 
 | 
				
			||||||
 | 
						new_count BIGINT, 
 | 
				
			||||||
 | 
						uid TEXT, 
 | 
				
			||||||
 | 
						updated_at TEXT, 
 | 
				
			||||||
 | 
						user_uid TEXT, 
 | 
				
			||||||
 | 
						PRIMARY KEY (id)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					CREATE INDEX IF NOT EXISTS ix_channel_member_e2577dd78b54fe28 ON channel_member (uid);
 | 
				
			||||||
 | 
					CREATE TABLE IF NOT EXISTS broadcast (
 | 
				
			||||||
 | 
						id INTEGER NOT NULL, 
 | 
				
			||||||
 | 
						channel_uid TEXT, 
 | 
				
			||||||
 | 
						message TEXT, 
 | 
				
			||||||
 | 
						created_at TEXT, 
 | 
				
			||||||
 | 
						PRIMARY KEY (id)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					CREATE TABLE IF NOT EXISTS channel_message (
 | 
				
			||||||
 | 
						id INTEGER NOT NULL, 
 | 
				
			||||||
 | 
						channel_uid TEXT, 
 | 
				
			||||||
 | 
						created_at TEXT, 
 | 
				
			||||||
 | 
						deleted_at TEXT, 
 | 
				
			||||||
 | 
						html TEXT, 
 | 
				
			||||||
 | 
						message TEXT, 
 | 
				
			||||||
 | 
						uid TEXT, 
 | 
				
			||||||
 | 
						updated_at TEXT, 
 | 
				
			||||||
 | 
						user_uid TEXT, 
 | 
				
			||||||
 | 
						PRIMARY KEY (id)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					CREATE INDEX IF NOT EXISTS ix_channel_message_e2577dd78b54fe28 ON channel_message (uid);
 | 
				
			||||||
 | 
					CREATE TABLE IF NOT EXISTS notification (
 | 
				
			||||||
 | 
						id INTEGER NOT NULL, 
 | 
				
			||||||
 | 
						created_at TEXT, 
 | 
				
			||||||
 | 
						deleted_at TEXT, 
 | 
				
			||||||
 | 
						message TEXT, 
 | 
				
			||||||
 | 
						object_type TEXT, 
 | 
				
			||||||
 | 
						object_uid TEXT, 
 | 
				
			||||||
 | 
						read_at TEXT, 
 | 
				
			||||||
 | 
						uid TEXT, 
 | 
				
			||||||
 | 
						updated_at TEXT, 
 | 
				
			||||||
 | 
						user_uid TEXT, 
 | 
				
			||||||
 | 
						PRIMARY KEY (id)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					CREATE INDEX IF NOT EXISTS ix_notification_e2577dd78b54fe28 ON notification (uid);
 | 
				
			||||||
 | 
					CREATE TABLE IF NOT EXISTS repository (
 | 
				
			||||||
 | 
						id INTEGER NOT NULL, 
 | 
				
			||||||
 | 
						created_at TEXT, 
 | 
				
			||||||
 | 
						deleted_at TEXT, 
 | 
				
			||||||
 | 
						is_private BIGINT, 
 | 
				
			||||||
 | 
						name TEXT, 
 | 
				
			||||||
 | 
						uid TEXT, 
 | 
				
			||||||
 | 
						updated_at TEXT, 
 | 
				
			||||||
 | 
						user_uid TEXT, 
 | 
				
			||||||
 | 
						PRIMARY KEY (id)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					CREATE INDEX IF NOT EXISTS ix_repository_e2577dd78b54fe28 ON repository (uid);
 | 
				
			||||||
							
								
								
									
										116
									
								
								src/snek/snode.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								src/snek/snode.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,116 @@
 | 
				
			|||||||
 | 
					import aiohttp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ENABLED = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import aiohttp
 | 
				
			||||||
 | 
					import asyncio
 | 
				
			||||||
 | 
					from aiohttp import web
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sqlite3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dataset
 | 
				
			||||||
 | 
					from sqlalchemy import event
 | 
				
			||||||
 | 
					from sqlalchemy.engine import Engine
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import json 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					queue = asyncio.Queue()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class State:
 | 
				
			||||||
 | 
					    do_not_sync = False 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def sync_service(app):
 | 
				
			||||||
 | 
					    if not ENABLED:
 | 
				
			||||||
 | 
					        return  
 | 
				
			||||||
 | 
					    session = aiohttp.ClientSession()
 | 
				
			||||||
 | 
					    async with session.ws_connect('http://localhost:3131/ws') as ws:
 | 
				
			||||||
 | 
					        async def receive():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            queries_synced = 0
 | 
				
			||||||
 | 
					            async for msg in ws:
 | 
				
			||||||
 | 
					                if msg.type == aiohttp.WSMsgType.TEXT:
 | 
				
			||||||
 | 
					                    try:
 | 
				
			||||||
 | 
					                        data = json.loads(msg.data)
 | 
				
			||||||
 | 
					                        State.do_not_sync = True 
 | 
				
			||||||
 | 
					                        app.db.execute(*data)
 | 
				
			||||||
 | 
					                        app.db.commit()
 | 
				
			||||||
 | 
					                        State.do_not_sync = False
 | 
				
			||||||
 | 
					                        queries_synced += 1
 | 
				
			||||||
 | 
					                        print("queries synced: " + str(queries_synced))
 | 
				
			||||||
 | 
					                        print(*data)
 | 
				
			||||||
 | 
					                        await app.services.socket.broadcast_event()
 | 
				
			||||||
 | 
					                    except Exception as e:
 | 
				
			||||||
 | 
					                        print(e)
 | 
				
			||||||
 | 
					                    pass 
 | 
				
			||||||
 | 
					                    #print(f"Received: {msg.data}")
 | 
				
			||||||
 | 
					                elif msg.type == aiohttp.WSMsgType.ERROR:
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					        async def write():
 | 
				
			||||||
 | 
					            while True:
 | 
				
			||||||
 | 
					                msg = await queue.get()
 | 
				
			||||||
 | 
					                await ws.send_str(json.dumps(msg,default=str))
 | 
				
			||||||
 | 
					                queue.task_done()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await asyncio.gather(receive(), write())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await session.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					queries_queued = 0
 | 
				
			||||||
 | 
					# Attach a listener to log all executed statements
 | 
				
			||||||
 | 
					@event.listens_for(Engine, "before_cursor_execute")
 | 
				
			||||||
 | 
					def before_cursor_execute(conn, cursor, statement, parameters, context, executemany):
 | 
				
			||||||
 | 
					    if not ENABLED:
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    global queries_queued
 | 
				
			||||||
 | 
					    if State.do_not_sync:
 | 
				
			||||||
 | 
					        print(statement,parameters)
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    if statement.startswith("SELECT"):
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    queue.put_nowait((statement, parameters))
 | 
				
			||||||
 | 
					    queries_queued += 1
 | 
				
			||||||
 | 
					    print("Queries queued: " + str(queries_queued))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def websocket_handler(request):
 | 
				
			||||||
 | 
					    queries_broadcasted = 0 
 | 
				
			||||||
 | 
					    ws = web.WebSocketResponse()
 | 
				
			||||||
 | 
					    await ws.prepare(request)
 | 
				
			||||||
 | 
					    request.app['websockets'].append(ws)
 | 
				
			||||||
 | 
					    async for msg in ws:
 | 
				
			||||||
 | 
					        if msg.type == aiohttp.WSMsgType.TEXT:
 | 
				
			||||||
 | 
					            for client in request.app['websockets']:
 | 
				
			||||||
 | 
					                if client != ws:
 | 
				
			||||||
 | 
					                    await client.send_str(msg.data)
 | 
				
			||||||
 | 
					            cursor = request.app['db'].cursor()
 | 
				
			||||||
 | 
					            data = json.loads(msg.data)
 | 
				
			||||||
 | 
					            queries_broadcasted += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            cursor.execute(*data)
 | 
				
			||||||
 | 
					            cursor.close()
 | 
				
			||||||
 | 
					            print("Queries broadcasted: " + str(queries_broadcasted))
 | 
				
			||||||
 | 
					        elif msg.type == aiohttp.WSMsgType.ERROR:
 | 
				
			||||||
 | 
					            print(f'WebSocket connection closed with exception {ws.exception()}')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    request.app['websockets'].remove(ws)
 | 
				
			||||||
 | 
					    return ws
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app = web.Application()
 | 
				
			||||||
 | 
					app['websockets'] = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.router.add_get('/ws', websocket_handler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def on_startup(app):
 | 
				
			||||||
 | 
					    app['db'] = sqlite3.connect('snek.db')
 | 
				
			||||||
 | 
					    print("Server starting...")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def on_cleanup(app):
 | 
				
			||||||
 | 
					    for ws in app['websockets']:
 | 
				
			||||||
 | 
					        await ws.close()
 | 
				
			||||||
 | 
					    app['db'].close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.on_startup.append(on_startup)
 | 
				
			||||||
 | 
					app.on_cleanup.append(on_cleanup)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    web.run_app(app, host='127.0.0.1', port=3131)
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user