class FlowIRCGateway(object): """A Flow-IRC gateway.""" def __init__(self, options): """Arguments: options : optparse.OptionParser, with the following attributes: irc_ports, verbose, debug, show_timestamps, daemon, flowappglue, username, server, port, db, schema, uri """ self.irc_ports = options.irc_ports self.verbose = options.verbose self.debug = options.debug self.show_timestamps = options.show_timestamps gateway_name_limit = 63 # From the RFC. self.name = socket.getfqdn()[:gateway_name_limit] self.channels = {} # channelId --> Channel instance. self.clients = {} # Socket --> IRCClient instance. self.organizations = {} # orgId --> Organization Name self.pending_channels = {} # channelId --> PendingChannel instance self.client_connected = True self.flow_service = None self.flow_initialized = False self.flow_username = "" self.flow_account_id = "" self.notification_handler = NotificationHandler(self) def terminate(self): """Terminates the Flow service.""" self.flow_service.terminate() def get_member(self, irc_nickname): """Gets a channel member from its IRC nickname. Arguments: irc_nickname : string, IRC nickname of a 'ChannelMember'. Returns a ChannelMember instance or 'None' if not found. """ for channel in self.channels.values(): for member in channel.members: if member.get_irc_nickname() == irc_nickname: return member return None def get_oid_from_name(self, org_name): """Returns an the orgId given an organization name. Arguments: org_name : string, organization name. Returns an empty string if not found. """ for oid, oname in self.organizations.iteritems(): if oname == org_name: return oid return "" def get_member_account_id(self, username): """Returns an accountId string from Flow given the username. Arguments: username : string, Flow username of the account. Returns an empty string if not found. """ try: member_peer_data = self.flow_service.get_peer(username) if member_peer_data: return member_peer_data["accountId"] except Flow.FlowError as flow_err: self.print_debug("get_peer: '%s'" % str(flow_err)) return "" def transmit_message_to_channel(self, channel, message_text): """Sends a message using Flow.send_message. Arguments: channel : Channel instance message_test : Text to be sent to the channel. Returns True if the message was sent successfully. """ try: message_id = self.flow_service.send_message( channel.organization_id, channel.channel_id, message_text) return message_id != "" except Flow.FlowError as flow_err: self.print_debug("send_message: '%s'" % str(flow_err)) return False def create_direct_channel(self, account_id, account_username, oid, organization_name): """Creates a direct conversation channel. Arguments: account_id : string, receiver's accountId. account_username : string, receiver's username. oid : string, orgId the two members share. organization_name : Name of the Organization the two members share. Returns a 'Channel' instance. If there's an error in the channel creation, then 'None' is returned. """ direct_conversation_channel = None try: direct_conversation_cid = \ self.flow_service.new_direct_conversation( oid, account_id) if direct_conversation_cid: direct_conversation_channel = DirectChannel( self, direct_conversation_cid, oid, organization_name, True) current_user = ChannelMember(self.flow_username, self.flow_account_id, organization_name) other_member = ChannelMember(account_username, account_id, organization_name) direct_conversation_channel.add_member(current_user) direct_conversation_channel.add_member(other_member) self.add_channel(direct_conversation_channel) except Flow.FlowError as flow_err: self.print_debug("NewDirectConversation: '%s'" % str(flow_err)) return direct_conversation_channel def get_channel(self, channel_id): """Returns a 'Channel' instance given a channelId. Returns 'None' if not found. """ try: return self.channels[channel_id] except KeyError: return None def get_channel_from_irc_name(self, channel_irc_name): """Returns a 'Channel' instance given the IRC name. Returns 'None' if not found. """ for channel in self.channels.values(): if channel.get_irc_name() == channel_irc_name: return channel return None def check_channel_collision(self, channel): """Checks and sets if a 'Channel' instance IRC name collides with other channel. """ if self.get_channel_from_irc_name(channel.get_irc_name()): channel.name_collides = True def get_channels(self, oid, org_name): """Loads all channels of a given organization Arguments: oid : string, orgId of the Organization org_name : string, Name of the Organization """ channels = self.flow_service.enumerate_channels(oid) for channel in channels: channel_name = channel["name"] direct_channel = channel["purpose"] == "direct message" if direct_channel: irc_channel = DirectChannel(self, channel["id"], oid, org_name) else: irc_channel = Channel(self, channel["id"], channel_name, oid, org_name) self.check_channel_collision(irc_channel) self.get_channel_members(irc_channel) self.add_channel(irc_channel) def get_orgs_and_channels(self): """Loads all Organizations and Channels the account is member of.""" self.organizations = {} self.channels = {} orgs = self.flow_service.enumerate_orgs() for org in orgs: oid = org["id"] org_name = org["name"] self.organizations[oid] = org_name self.get_channels(oid, org_name) def get_local_account(self): """Returns the first local Flow account on this device. It returns the username string. Returns 'None' if there is no account on the device. """ local_accounts = self.flow_service.enumerate_local_accounts() if not local_accounts: return None return local_accounts[0]["username"] def register_callbacks(self): """Registers all the notification types this gateway supports.""" self.flow_service.register_callback( Flow.ORG_NOTIFICATION, self.notification_handler.org_notification) self.flow_service.register_callback( Flow.CHANNEL_NOTIFICATION, self.notification_handler.channel_notification) self.flow_service.register_callback( Flow.MESSAGE_NOTIFICATION, self.notification_handler.message_notification) self.flow_service.register_callback( Flow.CHANNEL_MEMBER_NOTIFICATION, self.notification_handler.channel_member_notification) def unregister_callbacks(self): """Unregisters all the notification types this gateway supports.""" self.flow_service.unregister_callback(Flow.ORG_NOTIFICATION) self.flow_service.unregister_callback(Flow.CHANNEL_NOTIFICATION) self.flow_service.unregister_callback(Flow.MESSAGE_NOTIFICATION) self.flow_service.unregister_callback(Flow.CHANNEL_MEMBER_NOTIFICATION) def initialize_flow_service(self, options): """Initializes the Flow Service by logging into the user account and starting up the Flow session. """ self.flow_initialized = False try: self.flow_username = options.username self.flow_service = Flow("", "", options.flowappglue, options.debug, options.server, options.port, options.db, options.schema, options.attachment_dir) if not self.flow_username: self.flow_username = self.get_local_account() if not self.flow_username: raise Flow.FlowError("Local account not found.") self.flow_service.start_up(self.flow_username, options.uri) self.flow_initialized = True except Flow.FlowError as flow_err: self.print_error("Flow Initialization: '%s'" % str(flow_err)) def add_channel(self, channel): """Adds a 'Channel' instance to the gateway's channel list.""" self.channels[channel.channel_id] = channel def print_info(self, msg): """Prints a info msg string to stdout (if self.verbose is set to True). """ if self.verbose: print(msg) sys.stdout.flush() def print_debug(self, msg): """Prints a debug msg string to stdout (if self.debug is set to True). """ if self.debug: print(msg) sys.stdout.flush() @staticmethod def print_error(msg): """Prints an error msg string to stdout.""" sys.stderr.write("%s\n" % msg) def remove_client(self, client): """Removes 'Client' instance 'client' from the clients map. The gateway stops processing notifications until reconnection. """ del self.clients[client.client_socket] self.unregister_callbacks() self.client_connected = False def notify_clients(self, irc_msg): """Sends 'msg' string to all IRC client connections.""" for client in self.clients.values(): client.message(irc_msg) def get_username_from_id(self, account_id): """Returns the username of a given account. Arguments: account_id : string """ peer_data = self.flow_service.get_peer_from_id(account_id) return peer_data["username"] if peer_data else "" def get_channel_members(self, channel): """Retrieves (via Flow.enumerate_channel_members) and sets all channel members on a given 'Channel' instance. """ members = self.flow_service.enumerate_channel_members( channel.channel_id) for member in members: account_id = member["accountId"] account_username = self.get_username_from_id(account_id) assert account_username if account_username == self.flow_username: self.flow_account_id = account_id channel_member = ChannelMember(account_username, account_id, channel.organization_name) channel.add_member(channel_member) def start(self): """FlowIRCGateway initialization and main loop""" if not self.flow_initialized: self.terminate() return gatewaysockets = [] for port in self.irc_ports: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: # Listen to local connections only sock.bind(("localhost", port)) except socket.error as sock_err: self.print_error("Could not bind port %s: %s." % (port, sock_err)) sys.exit(1) sock.listen(5) gatewaysockets.append(sock) del sock self.print_info("Listening on port %d." % port) last_aliveness_check = time.time() while True: # Process Flow notifications if self.client_connected: while self.flow_service.process_one_notification(0.05): pass # Process IRC client socket connections (iwtd, owtd, _) = select.select( gatewaysockets + [client.client_socket for client in self.clients.values()], [ client.client_socket for client in self.clients.values() if client.write_queue_size() > 0 ], [], 0.05) for sock in iwtd: if sock in self.clients: self.clients[sock].socket_readable_notification() else: (conn, addr) = sock.accept() try: self.clients[conn] = IRCClient(self, conn) self.print_info("Accepted connection from %s:%s." % (addr[0], addr[1])) except socket.error: try: conn.close() except: pass for sock in owtd: if sock in self.clients: # client may have been disconnected self.clients[sock].socket_writable_notification() now = time.time() if last_aliveness_check + 10 < now: for client in self.clients.values(): client.check_aliveness() last_aliveness_check = now