From c4fdb2942e1ae94b270f59349a39f39fe553699a Mon Sep 17 00:00:00 2001 From: Joscha Date: Sun, 2 Apr 2017 14:26:03 +0000 Subject: [PATCH] Change switching rooms --- yaboli/connection.py | 227 +++++++++++++++++++++---------------------- yaboli/session.py | 89 +++++++++++++---- 2 files changed, 182 insertions(+), 134 deletions(-) diff --git a/yaboli/connection.py b/yaboli/connection.py index 542aea4..ec36aee 100644 --- a/yaboli/connection.py +++ b/yaboli/connection.py @@ -1,5 +1,6 @@ import json import logging +import socket import ssl import time import threading @@ -29,17 +30,19 @@ class Connection(): - "stop" """ - def __init__(self, room, url_format=None): + def __init__(self, url_format=ROOM_FORMAT, tries=10, delay=30): """ - room - name of the room to connect to url_format - url the bot will connect to, where the room name is represented by {} + tries - how often to try to reconnect when connection is lost (-1 - try forever) + delay - time (in seconds) to wait between tries """ - self.room = room + self.room = None + self.tries = tries + self.delay = delay + self.url_format = url_format - self._url_format = url_format or ROOM_FORMAT - - self._stopping = False + self._stopping = True self._ws = None self._thread = None @@ -55,7 +58,7 @@ class Connection(): def __exit__(self, exc_type, exc_value, traceback): self._lock.release() - def _connect(self, tries=20, delay=10): + def _connect(self, tries=10, delay=30): """ _connect(tries, delay) -> bool @@ -70,7 +73,7 @@ class Connection(): while tries != 0: try: - url = self._url_format.format(self.room) + url = self.url_format.format(self.room) logger.info("Connecting to url: {!r} ({} {} left)".format( url, tries-1 if tries > 0 else "infinite", @@ -82,7 +85,7 @@ class Connection(): sslopt=SSLOPT ) - except WSException: + except (WSException, socket.gaierror, TimeoutError): if tries > 0: tries -= 1 if tries != 0: @@ -99,27 +102,9 @@ class Connection(): return False - def disconnect(self): + def _launch(self): """ - disconnect() -> None - - Disconnect from the room. - This will cause the connection to reconnect. - To completely disconnect, use stop(). - """ - - if self._ws: - logger.debug("Closing connection!") - self._ws.abort() - self._ws.close() - self._ws = None - - logger.debug("Disconnected") - self._callbacks.call("disconnect") - - def launch(self): - """ - launch() -> Thread + _launch() -> Thread Connect to the room and spawn a new thread. """ @@ -142,9 +127,6 @@ class Connection(): logger.debug("Running") - if not self.switch_to(self.room): - return - while not self._stopping: try: j = self._ws.recv() @@ -152,95 +134,14 @@ class Connection(): except (WSException, ConnectionResetError): if not self._stopping: self.disconnect() - self._connect() + self._connect(self.tries, self.delay) logger.debug("Finished running") - - def stop(self): - """ - stop() -> None - - Close the connection to the room. - Joins the thread launched by self.launch(). - """ - - logger.debug("Stopping") - self._stopping = True - self.disconnect() - - self._callbacks.call("stop") - - if self._thread and self._thread != threading.current_thread(): - self._thread.join() - - def switch_to(self, new_room): - """ - switch_to(new_room) -> bool - - Returns True on success, False on failure. - - Attempts to connect to new_room. - """ - - old_room = self.room if self._ws else None - self.room = new_room - self.disconnect() - if old_room: - logger.info("Switching to &{} from &{}.".format(old_room, new_room)) - else: - logger.info("Switching to &{}.".format(new_room)) - - if not self._connect(tries=1): - if old_room: - logger.info("Could not connect to &{}: Connecting to ${} again.".format(new_room, old_room)) - self.room = old_room - self._connect() - else: - logger.info("Could not connect to &{}.".format(new_room)) - - return False - - return True - - def next_id(self): - """ - next_id() -> id - - Returns the id that will be used for the next package. - """ - - return str(self._send_id) - - def subscribe(self, ptype, callback, *args, **kwargs): - """ - subscribe(ptype, callback, *args, **kwargs) -> None - - Add a function to be called when a packet of type ptype is received. - """ - - self._callbacks.add(ptype, callback, *args, **kwargs) - - def subscribe_to_id(self, pid, callback, *args, **kwargs): - """ - subscribe_to_id(pid, callback, *args, **kwargs) -> None - - Add a function to be called when a packet with id pid is received. - """ - - self._id_callbacks.add(pid, callback, *args, **kwargs) - - def subscribe_to_next(self, callback, *args, **kwargs): - """ - subscribe_to_next(callback, *args, **kwargs) -> None - - Add a function to be called when the answer to the next message sent is received. - """ - - self._id_callbacks.add(self.next_id(), callback, *args, **kwargs) + self._thread = None def _handle_json(self, data): """ - handle_json(data) -> None + _handle_json(data) -> None Handle incoming 'raw' data. """ @@ -281,6 +182,100 @@ class Connection(): except WSException: self.disconnect() + def connected(self): + return self._ws + + def connect_to(self, room): + """ + connect_to(room) -> bool + + Attempts to connect to the room and spawns a new thread if it's successful. + Returns True if connection was sucessful, else False. + """ + + self.stop() + + logger.debug("Connecting to &{}.".format(room)) + self.room = room + if self._connect(1): + self._launch() + return True + else: + logger.warn("Could not connect to &{}.".format(room)) + return False + + def disconnect(self): + """ + disconnect() -> None + + Disconnect from the room. + This will cause the connection to reconnect. + To completely disconnect, use stop(). + """ + + if self._ws: + logger.debug("Closing connection!") + self._ws.abort() + self._ws.close() + self._ws = None + + logger.debug("Disconnected") + self._id_callbacks = Callbacks() # we don't need the old id callbacks any more + self._callbacks.call("disconnect") + + def stop(self): + """ + stop() -> None + + Close the connection to the room. + Joins the thread launched by self.launch(). + """ + + logger.debug("Stopping") + self._stopping = True + self.disconnect() + + self._callbacks.call("stop") + + if self._thread and self._thread != threading.current_thread(): + self._thread.join() + + def next_id(self): + """ + next_id() -> id + + Returns the id that will be used for the next package. + """ + + return str(self._send_id) + + def subscribe(self, ptype, callback, *args, **kwargs): + """ + subscribe(ptype, callback, *args, **kwargs) -> None + + Add a function to be called when a packet of type ptype is received. + """ + + self._callbacks.add(ptype, callback, *args, **kwargs) + + def subscribe_to_id(self, pid, callback, *args, **kwargs): + """ + subscribe_to_id(pid, callback, *args, **kwargs) -> None + + Add a function to be called when a packet with id pid is received. + """ + + self._id_callbacks.add(pid, callback, *args, **kwargs) + + def subscribe_to_next(self, callback, *args, **kwargs): + """ + subscribe_to_next(callback, *args, **kwargs) -> None + + Add a function to be called when the answer to the next message sent is received. + """ + + self._id_callbacks.add(self.next_id(), callback, *args, **kwargs) + def send_packet(self, ptype, **kwargs): """ send_packet(ptype, **kwargs) -> None diff --git a/yaboli/session.py b/yaboli/session.py index c770482..dea1613 100644 --- a/yaboli/session.py +++ b/yaboli/session.py @@ -1,4 +1,5 @@ import logging +import threading from .callbacks import Callbacks from .connection import Connection @@ -14,19 +15,25 @@ class Session(): - seeing other clients - sending and receiving messages - event (args) | meaning - -------------------|------------------------------------------------- - enter | can view the room - ready | can view the room and post messages (has a nick) - sessions-update | self.sessions has changed - own-session-update | your own message has changed - message (msg) | a message has been received (no own messages) - own-message (msg) | a message that you have sent + event (args) | meaning + --------------------|------------------------------------------------- + join (bool) | joining the room was successful/not successful + | Callbacks for this event are cleared whenever it is called. + enter | can view the room + ready | can view the room and post messages (has a nick) + sessions-update | self.sessions has changed + own-session-update | your own message has changed + message (msg) | a message has been received (no own messages) + own-message (msg) | a message that you have sent """ - def __init__(self, room, password=None, name=None): - self._connection = Connection(room) + def __init__(self, name=None): + self._room_accessible = False + self._room_accessible_event = threading.Event() + self._room_accessible_timeout = None + + self._connection = Connection() self._connection.subscribe("disconnect", self._reset_variables) self._connection.subscribe("bounce-event", self._handle_bounce_event) @@ -46,8 +53,8 @@ class Session(): self._callbacks = Callbacks() self.subscribe("enter", self._on_enter) - self.password = password - self._wish_name = name + self.password = None + self.real_name = name #self._hello_event_completed = False #self._snapshot_event_completed = False @@ -59,10 +66,31 @@ class Session(): self._reset_variables() + def switch_to(self, room, password=None, timeout=10): + logger.info("Switching to &{}.".format(room)) + self.password = password + + if self._room_accessible_timeout: + self._room_accessible_timeout.cancel() + self._room_accessible_timeout = threading.Timer(timeout, self.stop) + self._room_accessible_timeout.start() + + if self._connection.connect_to(room): + logger.debug("Connection established. Waiting for correct events") + self._room_accessible_event.wait() + return self._room_accessible + else: + logger.warn("Could not connect to room url.") + return False + def _reset_variables(self): logger.debug("Resetting room-related variables") + self._room_accessible = False + self.my_session = SessionView(None, None, None, None, None) self.sessions = {} + self._room_accessible_event.clear() + self._hello_event_completed = False self._snapshot_event_completed = False self._ready = False @@ -78,15 +106,24 @@ class Session(): def _on_enter(self): logger.info("Connected and authenticated.") + self._room_accessible_timeout.cancel() + self._room_accessible = True + self._room_accessible_event.set() + self._room_accessible_event.clear() - if self._wish_name: - self._set_name(self._wish_name) + if self.real_name: + self._set_name(self.real_name) def launch(self): return self._connection.launch() def stop(self): logger.info("Stopping") + self._room_accessible_timeout.cancel() + self._room_accessible = False + self._room_accessible_event.set() + self._room_accessible_event.clear() + with self._connection as conn: conn.stop() @@ -107,16 +144,20 @@ class Session(): @name.setter def name(self, new_name): - self._wish_name = new_name + self.real_name = new_name if not self._ready: self._set_name(new_name) + @property + def room(self): + return self._connection.room + def refresh_sessions(self): logger.debug("Refreshing sessions") self._connection.send_packet("who") - def _listing_to_sessions(self, listing): + def _set_sessions_from_listing(self, listing): self.sessions = {} for item in listing: @@ -125,6 +166,18 @@ class Session(): self._callbacks.call("sessions-update") + def _revert_to_revious_room(self): + self._callbacks.call("join", False) + + if self._prev_room: + self.password = self._prev_password + self.room = self._prev_room # shouldn't do this + + self._prev_room = None + self._prev_password = None + else: + self.stop() + def _handle_bounce_event(self, data, packet): if data.get("reason") == "authentication required": if self.password: @@ -237,7 +290,7 @@ class Session(): def _handle_snapshot_event(self, data, packet): # deal with connected sessions - self._listing_to_sessions(data.get("listing")) + self._set_sessions_from_listing(data.get("listing")) # deal with messages # TODO: implement @@ -289,4 +342,4 @@ class Session(): self._callbacks.call("own-message", msg) def _handle_who_reply(self, data, packet): - self._listing_to_sessions(data.get("listing")) + self._set_sessions_from_listing(data.get("listing"))