Change switching rooms

This commit is contained in:
Joscha 2017-04-02 14:26:03 +00:00
parent 75b2108b47
commit c4fdb2942e
2 changed files with 182 additions and 134 deletions

View file

@ -1,5 +1,6 @@
import json import json
import logging import logging
import socket
import ssl import ssl
import time import time
import threading import threading
@ -29,17 +30,19 @@ class Connection():
- "stop" - "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 {} 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 = True
self._stopping = False
self._ws = None self._ws = None
self._thread = None self._thread = None
@ -55,7 +58,7 @@ class Connection():
def __exit__(self, exc_type, exc_value, traceback): def __exit__(self, exc_type, exc_value, traceback):
self._lock.release() self._lock.release()
def _connect(self, tries=20, delay=10): def _connect(self, tries=10, delay=30):
""" """
_connect(tries, delay) -> bool _connect(tries, delay) -> bool
@ -70,7 +73,7 @@ class Connection():
while tries != 0: while tries != 0:
try: try:
url = self._url_format.format(self.room) url = self.url_format.format(self.room)
logger.info("Connecting to url: {!r} ({} {} left)".format( logger.info("Connecting to url: {!r} ({} {} left)".format(
url, url,
tries-1 if tries > 0 else "infinite", tries-1 if tries > 0 else "infinite",
@ -82,7 +85,7 @@ class Connection():
sslopt=SSLOPT sslopt=SSLOPT
) )
except WSException: except (WSException, socket.gaierror, TimeoutError):
if tries > 0: if tries > 0:
tries -= 1 tries -= 1
if tries != 0: if tries != 0:
@ -99,27 +102,9 @@ class Connection():
return False return False
def disconnect(self): def _launch(self):
""" """
disconnect() -> None _launch() -> Thread
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
Connect to the room and spawn a new thread. Connect to the room and spawn a new thread.
""" """
@ -142,9 +127,6 @@ class Connection():
logger.debug("Running") logger.debug("Running")
if not self.switch_to(self.room):
return
while not self._stopping: while not self._stopping:
try: try:
j = self._ws.recv() j = self._ws.recv()
@ -152,95 +134,14 @@ class Connection():
except (WSException, ConnectionResetError): except (WSException, ConnectionResetError):
if not self._stopping: if not self._stopping:
self.disconnect() self.disconnect()
self._connect() self._connect(self.tries, self.delay)
logger.debug("Finished running") logger.debug("Finished running")
self._thread = None
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)
def _handle_json(self, data): def _handle_json(self, data):
""" """
handle_json(data) -> None _handle_json(data) -> None
Handle incoming 'raw' data. Handle incoming 'raw' data.
""" """
@ -281,6 +182,100 @@ class Connection():
except WSException: except WSException:
self.disconnect() 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): def send_packet(self, ptype, **kwargs):
""" """
send_packet(ptype, **kwargs) -> None send_packet(ptype, **kwargs) -> None

View file

@ -1,4 +1,5 @@
import logging import logging
import threading
from .callbacks import Callbacks from .callbacks import Callbacks
from .connection import Connection from .connection import Connection
@ -15,7 +16,9 @@ class Session():
- sending and receiving messages - sending and receiving messages
event (args) | meaning 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 enter | can view the room
ready | can view the room and post messages (has a nick) ready | can view the room and post messages (has a nick)
sessions-update | self.sessions has changed sessions-update | self.sessions has changed
@ -25,8 +28,12 @@ class Session():
""" """
def __init__(self, room, password=None, name=None): def __init__(self, name=None):
self._connection = Connection(room) 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("disconnect", self._reset_variables)
self._connection.subscribe("bounce-event", self._handle_bounce_event) self._connection.subscribe("bounce-event", self._handle_bounce_event)
@ -46,8 +53,8 @@ class Session():
self._callbacks = Callbacks() self._callbacks = Callbacks()
self.subscribe("enter", self._on_enter) self.subscribe("enter", self._on_enter)
self.password = password self.password = None
self._wish_name = name self.real_name = name
#self._hello_event_completed = False #self._hello_event_completed = False
#self._snapshot_event_completed = False #self._snapshot_event_completed = False
@ -59,10 +66,31 @@ class Session():
self._reset_variables() 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): def _reset_variables(self):
logger.debug("Resetting room-related variables") logger.debug("Resetting room-related variables")
self._room_accessible = False
self.my_session = SessionView(None, None, None, None, None) self.my_session = SessionView(None, None, None, None, None)
self.sessions = {} self.sessions = {}
self._room_accessible_event.clear()
self._hello_event_completed = False self._hello_event_completed = False
self._snapshot_event_completed = False self._snapshot_event_completed = False
self._ready = False self._ready = False
@ -78,15 +106,24 @@ class Session():
def _on_enter(self): def _on_enter(self):
logger.info("Connected and authenticated.") 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: if self.real_name:
self._set_name(self._wish_name) self._set_name(self.real_name)
def launch(self): def launch(self):
return self._connection.launch() return self._connection.launch()
def stop(self): def stop(self):
logger.info("Stopping") 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: with self._connection as conn:
conn.stop() conn.stop()
@ -107,16 +144,20 @@ class Session():
@name.setter @name.setter
def name(self, new_name): def name(self, new_name):
self._wish_name = new_name self.real_name = new_name
if not self._ready: if not self._ready:
self._set_name(new_name) self._set_name(new_name)
@property
def room(self):
return self._connection.room
def refresh_sessions(self): def refresh_sessions(self):
logger.debug("Refreshing sessions") logger.debug("Refreshing sessions")
self._connection.send_packet("who") self._connection.send_packet("who")
def _listing_to_sessions(self, listing): def _set_sessions_from_listing(self, listing):
self.sessions = {} self.sessions = {}
for item in listing: for item in listing:
@ -125,6 +166,18 @@ class Session():
self._callbacks.call("sessions-update") 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): def _handle_bounce_event(self, data, packet):
if data.get("reason") == "authentication required": if data.get("reason") == "authentication required":
if self.password: if self.password:
@ -237,7 +290,7 @@ class Session():
def _handle_snapshot_event(self, data, packet): def _handle_snapshot_event(self, data, packet):
# deal with connected sessions # deal with connected sessions
self._listing_to_sessions(data.get("listing")) self._set_sessions_from_listing(data.get("listing"))
# deal with messages # deal with messages
# TODO: implement # TODO: implement
@ -289,4 +342,4 @@ class Session():
self._callbacks.call("own-message", msg) self._callbacks.call("own-message", msg)
def _handle_who_reply(self, data, packet): def _handle_who_reply(self, data, packet):
self._listing_to_sessions(data.get("listing")) self._set_sessions_from_listing(data.get("listing"))