Exemple #1
0
	def run(self):
		while True:
			# Connect to given server
			address = (self.server.host, self.server.port)
			try:
				self.server_socket = socket.create_connection(address)
			except (ConnectionRefusedError, socket.gaierror):
				# Tell controller we failed
				self.logging_channel.send((logmessage_types.internal, internal_submessage_types.error, "Can't connect to %s:%s" % address))

				# Try reconnecting in a minute
				cron.reschedule(self.cron_control_channel, 60, self.control_channel, (controlmessage_types.reconnect,))

				# Handle messages
				reconnect = True
				while True:
					command_type, *arguments = self.control_channel.recv()

					if command_type == controlmessage_types.reconnect:
						break

					elif command_type == controlmessage_types.quit:
						reconnect = False
						break

					else:
						error_message = 'Control message not supported when not connected: %s' % repr((command_type, *arguments))
						self.logging_channel.send((logmessage_types.internal, internal_submessage_types.error, error_message))

				# Remove the reconnect message in case we were told to reconnnect manually
				cron.delete(self.cron_control_channel, self.control_channel, (controlmessage_types.reconnect,))

				if reconnect:
					continue
				else:
					break

			# Create an API object to give to outside line handler
			self.api = API(self)

			try:
				# Run initialization
				self.send_line_raw(b'USER %s a a :%s' % (self.server.username.encode('utf-8'), self.server.realname.encode('utf-8')))

				# Set up nick
				self.api.nick(self.server.nick.encode('utf-8'))

				# Run the on_connect hook, to allow further setup
				botcmd.on_connect(irc = self.api)

				# Join channels
				for channel in self.server.channels:
					self.api.join(channel.encode('utf-8'))

				# Schedule a ping to be sent in 3 minutes of no activity
				cron.reschedule(self.cron_control_channel, 3 * 60, self.control_channel, (controlmessage_types.ping,))

				# Run mainloop
				reconnecting = self.mainloop()

				if not reconnecting:
					# Run bot cleanup code
					botcmd.on_quit(irc = self.api)

					# Tell the server we're quiting
					self.send_line_raw(b'QUIT :%s exiting normally' % self.server.username.encode('utf-8'))
					self.server_socket.close()

					break

				else:
					# Tell server we're reconnecting
					self.send_line_raw(b'QUIT :Reconnecting')
					self.server_socket.close()

			except (BrokenPipeError, TimeoutError) as err:
				# Connection broke, log it and try to reconnect
				self.logging_channel.send((logmessage_types.internal, internal_submessage_types.error, 'Broken socket/pipe or timeout'))
				self.server_socket.close()

		# Tell controller we're quiting
		self.logging_channel.send((logmessage_types.internal, internal_submessage_types.quit))

		# Tell cron we're quiting
		cron.quit(cron_control_channel)
Exemple #2
0
	def mainloop(self):
		# Register both the server socket and the control channel to a polling object
		poll = select.poll()
		poll.register(self.server_socket, select.POLLIN)
		poll.register(self.control_channel, select.POLLIN)

		# Keep buffer for input
		server_input_buffer = bytearray()

		quitting = False
		reconnecting = False
		while not quitting and not reconnecting:
			# Wait until we can do something
			for fd, event in poll.poll():
				# Server
				if fd == self.server_socket.fileno():
					# Ready to receive, read into buffer and handle full messages
					if event | select.POLLIN:
						data = self.server_socket.recv(1024)

						# Mo data to be read even as POLLIN triggered → connection has broken
						# Log it and try reconnecting
						if data == b'':
							self.logging_channel.send((logmessage_types.internal, internal_submessage_types.error, 'Empty read'))
							reconnecting = True
							break

						server_input_buffer.extend(data)

						# Try to see if we have a full line ending with \r\n in the buffer
						# If yes, handle it
						while b'\r\n' in server_input_buffer:
							# Newline was found, split buffer
							line, _, server_input_buffer = server_input_buffer.partition(b'\r\n')

							self.handle_line(line)

						# Remove possible pending ping timeout timer and reset ping timer to 3 minutes
						cron.delete(self.cron_control_channel, self.control_channel, (controlmessage_types.ping_timeout,))
						cron.reschedule(self.cron_control_channel, 3 * 60, self.control_channel, (controlmessage_types.ping,))

					else:
						error_message = 'Event on server socket: %s' % event
						self.logging_channel.send((logmessage_types.internal, internal_submessage_types.error, error_message))

				# Control
				elif fd == self.control_channel.fileno():
					command_type, *arguments = self.control_channel.recv()
					if command_type == controlmessage_types.quit:
						quitting = True

					elif command_type == controlmessage_types.send_line:
						assert len(arguments) == 1
						irc_command, space, arguments = arguments[0].encode('utf-8').partition(b' ')
						line = irc_command.upper() + space + arguments
						self.send_line_raw(line)

					elif command_type == controlmessage_types.ping:
						assert len(arguments) == 0
						self.send_line_raw(b'PING :foo')
						# Reset ping timeout timer to 2 minutes
						cron.reschedule(self.cron_control_channel, 2 * 60, self.control_channel, (controlmessage_types.ping_timeout,))

					elif command_type == controlmessage_types.ping_timeout:
						self.logging_channel.send((logmessage_types.internal, internal_submessage_types.error, 'Ping timeout'))
						reconnecting = True

					elif command_type == controlmessage_types.reconnect:
						reconnecting = True

					else:
						error_message = 'Unknown control message: %s' % repr((command_type, *arguments))
						self.logging_channel.send((logmessage_types.internal, internal_submessage_types.error, error_message))

				else:
					assert False #unreachable

		if reconnecting:
			return True
		else:
			return False