diff --git a/yaboli/basic_types.py b/yaboli/basic_types.py index b884593..5ba0110 100644 --- a/yaboli/basic_types.py +++ b/yaboli/basic_types.py @@ -26,22 +26,25 @@ class SessionView(): self.manager = is_manager @classmethod - def from_data(self, data): + def from_data(cls, data): """ Creates and returns a session created from the data. data - a euphoria SessionView """ - return self( - data.get("id"), - data.get("name"), - data.get("server_id"), - data.get("server_era"), - data.get("session_id"), - data.get("is_staff"), - data.get("is_manager") - ) + view = cls(None, None, None, None, None) + view.read_data(data) + return view + + def read_data(self, data): + if "id" in data: self.id = data.get("id") + if "name" in data: self.name = data.get("name") + if "server_id" in data: self.server_id = data.get("server_id") + if "server_era" in data: self.server_era = data.get("server_era") + if "session_id" in data: self.session_id = data.get("session_id") + if "is_staff" in data: self.is_staff = data.get("is_staff") + if "is_manager" in data: self.is_manager = data.get("is_manager") def session_type(self): """ diff --git a/yaboli/connection.py b/yaboli/connection.py index d2aa03b..71f1029 100644 --- a/yaboli/connection.py +++ b/yaboli/connection.py @@ -8,7 +8,9 @@ from websocket import WebSocketException as WSException from .callbacks import Callbacks -SSLOPT_CA_CERTS = {'ca_certs': ssl.get_default_verify_paths().cafile} +SSLOPT = {"ca_certs": ssl.get_default_verify_paths().cafile} +#SSLOPT = {"cert_reqs": ssl.CERT_NONE} +ROOM_FORMAT = "wss://euphoria.io/room/{}/ws" logger = logging.getLogger(__name__) class Connection(): @@ -25,8 +27,6 @@ class Connection(): - "stop" """ - ROOM_FORMAT = "wss://euphoria.io/room/{}/ws" - def __init__(self, room, url_format=None): """ room - name of the room to connect to @@ -35,7 +35,7 @@ class Connection(): self.room = room - self._url_format = url_format or Connection.ROOM_FORMAT + self._url_format = url_format or ROOM_FORMAT self._stopping = False @@ -44,7 +44,7 @@ class Connection(): self._send_id = 0 self._callbacks = Callbacks() self._id_callbacks = Callbacks() - self._lock = threading.Lock() + self._lock = threading.RLock() def __enter__(self): self._lock.acquire() @@ -77,7 +77,7 @@ class Connection(): self._ws = websocket.create_connection( url, enable_multithread=True, - sslopt=SSLOPT_CA_CERTS + sslopt=SSLOPT ) except WSException: @@ -107,6 +107,8 @@ class Connection(): """ if self._ws: + logger.debug("Closing connection!") + self._ws.abort() self._ws.close() self._ws = None @@ -120,8 +122,11 @@ class Connection(): Connect to the room and spawn a new thread. """ - self._thread = threading.Thread(target=self._run, - name="{}-{}".format(int(time.time()), self.room)) + self._stopping = False + self._thread = threading.Thread( + target=self._run, + name="{}-{}".format(int(time.time()), self.room) + ) logger.debug("Launching new thread: {}".format(self._thread.name)) self._thread.start() return self._thread @@ -134,16 +139,20 @@ class Connection(): """ logger.debug("Running") + if not self.switch_to(self.room): return while not self._stopping: try: - self._handle_json(self._ws.recv()) + j = self._ws.recv() + self._handle_json(j) except (WSException, ConnectionResetError): if not self._stopping: self.disconnect() self._connect() + + logger.debug("Finished running") def stop(self): """ @@ -248,15 +257,14 @@ class Connection(): logger.debug("Handling packet of type {}.".format(ptype)) data = packet.get("data") - error = packet.get("error") - if error: + if "error" in packet: logger.debug("Error in packet: {!r}".format(error)) if "id" in packet: - self._id_callbacks.call(packet["id"], data, error) + self._id_callbacks.call(packet["id"], data, packet) self._id_callbacks.remove(packet["id"]) - self._callbacks.call(packet["type"], data, error) + self._callbacks.call(packet["type"], data, packet) def _send_json(self, data): """ diff --git a/yaboli/session.py b/yaboli/session.py index 212c348..09ef470 100644 --- a/yaboli/session.py +++ b/yaboli/session.py @@ -2,6 +2,7 @@ import logging from .callbacks import Callbacks from .connection import Connection +from .basic_types import Message, SessionView logger = logging.getLogger(__name__) @@ -13,64 +14,84 @@ class Session(): - seeing other clients - sending and receiving messages - Things to wait for before being properly connected: - hello-event - snapshot-event - -> connected + Events: + enter - can view the room + ready - can view the room and post messages (has a nick) - Things to wait for before being ready: - hello-event - snapshot-event - nick-reply - -> ready - - When connecting to a new room: - _hello_event_completed = False - _snapshot_event_completed = False """ - def __init__(self, room, password=None): + def __init__(self, room, password=None, name=None): self._connection = Connection(room) - self._connection.subscribe("disconnect", self._on_disconnect) + self._connection.subscribe("disconnect", self._reset_variables) self._connection.subscribe("bounce-event", self.handle_bounce_event) + self._connection.subscribe("disconnect-event", self.handle_disconnect_event) + self._connection.subscribe("hello-event", self.handle_hello_event) self._connection.subscribe("ping-event", self.handle_ping_event) + self._connection.subscribe("snapshot-event", self.handle_snapshot_event) self._callbacks = Callbacks() - self._hello_event_completed = False - self._snapshot_event_completed = False + self.subscribe("enter", self._on_enter) self.password = password + self._wish_name = name - self.my_session = None - self.sessions = {} # sessions in the room + #self._hello_event_completed = False + #self._snapshot_event_completed = False + #self._ready = False + #self.my_session = SessionView(None, None, None, None, None) + #self.sessions = {} # sessions in the room + #self.room_is_private = None + #self.server_version = None + + self._reset_variables() + + def _reset_variables(self): + logger.debug("Resetting room-related variables") + self.my_session = SessionView(None, None, None, None, None) + self.sessions = {} + self._hello_event_completed = False + self._snapshot_event_completed = False + self._ready = False + + self.room_is_private = None + self.server_version = None + + def _set_name(self, new_name): + with self._connection as conn: + logger.debug("setting name to {!r}".format(new_name)) + conn.subscribe_to_next(self.handle_nick_reply) + conn.send_packet("nick", name=new_name) + + def _on_enter(self): + logger.info("Connected and authenticated.") + + if self._wish_name: + self._set_name(self._wish_name) def launch(self): return self._connection.launch() def stop(self): logger.info("Stopping") - self._connection.stop() + with self._connection as conn: + conn.stop() + + def subscribe(self, event, callback, *args, **kwargs): + logger.debug("Adding callback {} to {}".format(callback, event)) + self._callbacks.add(event, callback, *args, **kwargs) @property def name(self): - if self.my_session: - return self.my_session.name + return self.my_session.name @name.setter def name(self, new_name): - with self._connection as conn: - logger.debug("setting name to {!r}".format(new_name)) - conn.subscribe_to_next(self.handle_nick_reply) - conn.send_packet("nick", name=new_name) + self._wish_name = new_name + + if not self._ready: + self._set_name(new_name) - def _on_disconnect(self): - logger.debug("Disconnected. Resetting related variables") - self.my_session = None - self.sessions = {} - self._hello_event_completed = False - self._snapshot_event_completed = False - - def handle_bounce_event(self, data, error): + def handle_bounce_event(self, data, packet): if data.get("reason") == "authentication required": if self.password: with self._connection as conn: @@ -80,30 +101,60 @@ class Session(): logger.warn("Could not access &{}: No password.".format(self._connection.room)) self.stop() - def handle_disconnect_event(self, data, error): + def handle_disconnect_event(self, data, packet): self._connection.disconnect() # should reconnect - def handle_ping_event(self, data, error): + def handle_hello_event(self, data, packet): + self.my_session.read_data(data.get("session")) + + self.room_is_private = data.get("room_is_private") + self.server_version = data.get("version") + + self._hello_event_completed = True + if self._snapshot_event_completed: + self._callbacks.call("enter") + + def handle_ping_event(self, data, packet): with self._connection as conn: logger.debug("playing ping pong") conn.send_packet("ping-reply", time=data.get("time")) - def handle_auth_reply(self, data, error): - if data.get("success"): - logger.debug("Authetication complete, password was correct.") - else: + def handle_snapshot_event(self, data, packet): + # deal with connected sessions + for item in data.get("listing"): + view = SessionView.from_data(item) + self.sessions[view.session_id] = view + + # deal with messages + # TODO: this + + # deal with other info + self.server_version = data.get("version") + if "nick" in data: + self.my_session.name = data.get("nick") + + self._snapshot_event_completed = True + if self._hello_event_completed: + self._callbacks.call("enter") + + + def handle_auth_reply(self, data, packet): + if not data.get("success"): logger.warn("Could not authenticate, reason: {!r}".format(data.get("reason"))) self.stop() + else: + logger.debug("Authetication complete, password was correct.") - def handle_nick_reply(self, data, error): - if error: - logger.error("nick-reply error: {!r}".format(error)) - return - + def handle_nick_reply(self, data, packet): first_name = not self.name - logger.info("Changed name fro {!r} to {!r}.".format(data.get("from"), data.get("to"))) + if first_name: + logger.info("Changed name to {!r}.".format(data.get("to"))) + else: + logger.info("Changed name from {!r} to {!r}.".format(data.get("from"), data.get("to"))) + self.my_session.name = data.get("to") if first_name: + self._ready = True self._callbacks.call("ready")