codekingpro/portable-devtools
114k
1import itertools2import logging3 4from bidict import bidict, ValueDuplicationError5 6default_logger = logging.getLogger('socketio')7 8 9class BaseManager:10 def __init__(self):11 self.logger = None12 self.server = None13 self.rooms = {} # self.rooms[namespace][room][sio_sid] = eio_sid14 self.eio_to_sid = {}15 self.callbacks = {}16 self.pending_disconnect = {}17 18 def set_server(self, server):19 self.server = server20 21 def initialize(self):22 """Invoked before the first request is received. Subclasses can add23 their initialization code here.24 """25 pass26 27 def get_namespaces(self):28 """Return an iterable with the active namespace names."""29 return self.rooms.keys()30 31 def get_participants(self, namespace, room):32 """Return an iterable with the active participants in a room."""33 ns = self.rooms.get(namespace, {})34 if hasattr(room, '__len__') and not isinstance(room, str):35 participants = ns[room[0]]._fwdm.copy() if room[0] in ns else {}36 for r in room[1:]:37 participants.update(ns[r]._fwdm if r in ns else {})38 else:39 participants = ns[room]._fwdm.copy() if room in ns else {}40 for sid, eio_sid in participants.items():41 yield sid, eio_sid42 43 def connect(self, eio_sid, namespace):44 """Register a client connection to a namespace."""45 sid = self.server.eio.generate_id()46 try:47 self.basic_enter_room(sid, namespace, None, eio_sid=eio_sid)48 except ValueDuplicationError:49 # already connected50 return None51 self.basic_enter_room(sid, namespace, sid, eio_sid=eio_sid)52 return sid53 54 def is_connected(self, sid, namespace):55 if namespace in self.pending_disconnect and \56 sid in self.pending_disconnect[namespace]:57 # the client is in the process of being disconnected58 return False59 try:60 return self.rooms[namespace][None][sid] is not None61 except KeyError:62 pass63 return False64 65 def sid_from_eio_sid(self, eio_sid, namespace):66 try:67 return self.rooms[namespace][None]._invm[eio_sid]68 except KeyError:69 pass70 71 def eio_sid_from_sid(self, sid, namespace):72 if namespace in self.rooms:73 return self.rooms[namespace][None].get(sid)74 75 def pre_disconnect(self, sid, namespace):76 """Put the client in the to-be-disconnected list.77 78 This allows the client data structures to be present while the79 disconnect handler is invoked, but still recognize the fact that the80 client is soon going away.81 """82 if namespace not in self.pending_disconnect:83 self.pending_disconnect[namespace] = []84 self.pending_disconnect[namespace].append(sid)85 return self.rooms[namespace][None].get(sid)86 87 def basic_disconnect(self, sid, namespace, **kwargs):88 if namespace not in self.rooms:89 return90 rooms = []91 for room_name, room in self.rooms[namespace].copy().items():92 if sid in room:93 rooms.append(room_name)94 for room in rooms:95 self.basic_leave_room(sid, namespace, room)96 if sid in self.callbacks:97 del self.callbacks[sid]98 if namespace in self.pending_disconnect and \99 sid in self.pending_disconnect[namespace]:100 self.pending_disconnect[namespace].remove(sid)101 if len(self.pending_disconnect[namespace]) == 0:102 del self.pending_disconnect[namespace]103 104 def basic_enter_room(self, sid, namespace, room, eio_sid=None):105 if eio_sid is None and namespace not in self.rooms:106 raise ValueError('sid is not connected to requested namespace')107 if namespace not in self.rooms:108 self.rooms[namespace] = {}109 if room not in self.rooms[namespace]:110 self.rooms[namespace][room] = bidict()111 if eio_sid is None:112 eio_sid = self.rooms[namespace][None][sid]113 self.rooms[namespace][room][sid] = eio_sid114 115 def basic_leave_room(self, sid, namespace, room):116 try:117 del self.rooms[namespace][room][sid]118 if len(self.rooms[namespace][room]) == 0:119 del self.rooms[namespace][room]120 if len(self.rooms[namespace]) == 0:121 del self.rooms[namespace]122 except KeyError:123 pass124 125 def basic_close_room(self, room, namespace):126 try:127 for sid, _ in self.get_participants(namespace, room):128 self.basic_leave_room(sid, namespace, room)129 except KeyError: # pragma: no cover130 pass131 132 def get_rooms(self, sid, namespace):133 """Return the rooms a client is in."""134 r = []135 try:136 for room_name, room in self.rooms[namespace].items():137 if room_name is not None and sid in room:138 r.append(room_name)139 except KeyError:140 pass141 return r142 143 def _generate_ack_id(self, sid, callback):144 """Generate a unique identifier for an ACK packet."""145 if sid not in self.callbacks:146 self.callbacks[sid] = {0: itertools.count(1)}147 id = next(self.callbacks[sid][0])148 self.callbacks[sid][id] = callback149 return id150 151 def _get_logger(self):152 """Get the appropriate logger153 154 Prevents uninitialized servers in write-only mode from failing.155 """156 157 if self.logger:158 return self.logger159 elif self.server:160 return self.server.logger161 else:162 return default_logger163 