Exemple #1
0
	def initialize(self, logger=None):
		#setup log options
		if not self.console:
			logger.stop()
		if self.logfile:
			log.startLogging(open(join(self.botdir, self.logfile), 'a'), setStdout=False)
		
		# setup global database and databasemanager
		self.databasemanager = DBManager(self.datadir, self.datafile)
		self.reloadStage2()
		#start dbcommittimer
		# TODO: figure out if actually need this, and what SQLite transaction/journaling mode we should be using
		Timers._addTimer("_dbcommit", 60*60, self.databasemanager.dbcommit, reps=-1) #every hour (60*60)
Exemple #2
0
class SettingsBase(object):
	nick = "BurlyBot"
	altnicks = None # []
	nicksuffix = "_"
	nickservpass = None
	commandprefix = "!"
	datadir = "data"
	debug = False
	datafile = "BurlyBot.db"
	enablestate = False
	encoding = "utf-8"
	cert = None
	verify = False
	console = True
	logfile = None
	modules = None # OrderedSet(["core"])
	_admins = None
	servers = None # {}
	botdir = None
	configfile = None
	moduleopts = None # {}
	databasemanager = None
	
	@property
	def admins(self):
		return self._admins
	
	@admins.setter
	def admins(self, value):
		# When we reset defaults, we grab values from SettingsBase... But 'admins' tries to get the property.
		if isinstance(value, property): return # TODO: Don't know how to handle this more cleanly
		self._admins = [x.lower() for x in value]
	
	#TODO: not sure if the following is needed or not. Class.dict seems to behave strangely
	def _setDefaults(self):
		self.altnicks = []
		self.modules = OrderedSet(["core"])
		self._admins = []
		self.moduleopts = {}
	
	def __init__(self):
		self.servers = {}
		self._setDefaults()

	def _loadsettings(self):
		# TODO: need some exception handling for loading JSON
		try:
			newsets = load(open(self.configfile, "r"))
		except ValueError as e:
			raise ConfigException("Config file (%s) contains errors: %s"
				"\nTry http://jsonlint.com/ and make sure no trailing commas." % (self.configfile, e))
		
		self.newservers = newservers = []
		self.oldservers = oldservers = []
		# Only look for options we care about
		for opt in KEYS_MAIN:
			if opt in newsets:
				if opt == "servers":
					# calculate difference to know which servers to disconnect:
					oldservers = set(self.servers.iterkeys())
					# Create servers and put them in the server map
					for serveropts in newsets["servers"]:
						if "serverlabel" not in serveropts: 
							# TODO: instead of raise, create warning and continue loading.
							print "Missing serverlabel in config. Skipping server"
							continue
						label = serveropts["serverlabel"]
						if label in self.servers:
							#refresh server settings
							try:
								self.servers[label].setup(serveropts)
							except Exception as e:
								print "Error in server setup for (%s), server settings may be in inconsistent state. %s" % (label, e)
								continue
						else:
							try:
								s = Server(serveropts)
							except Exception as e:
								print "Error in server setup for (%s), skipping. %s" % (label, e)
								continue
							self.servers[label] = s
							newservers.append(s)
						try: oldservers.remove(label) #remove new server from old set
						except KeyError: pass
				elif opt == "modules":
					setattr(self, opt, OrderedSet(newsets[opt]))
				else:
					setattr(self, opt, newsets[opt])
		# store servers for connection/disconnection at a latter time
		self.newservers = newservers
		self.oldservers = oldservers
		
	def _connect(self, servers):
		for server in servers:
			if server.ssl:
				if not SSL:
					print "Error: Cannot connect to '%s', pyOpenSSL not installed" % server.serverlabel
					self.databasemanager.delServer(server.serverlabel)
					continue
				try: reactor.connectSSL(server.host, server.port, server._factory, createCertOptions(server))
				except Exception as e:
					print "SSL Error: Cannot connect to '%s' (%s)" % (server.serverlabel, e)
					self.databasemanager.delServer(server.serverlabel)
			else:
				reactor.connectTCP(server.host, server.port, server._factory)
			
	def createDatabases(self, servers):
		for server in servers:
			self.databasemanager.addServer(server.serverlabel, server.datafile)
			
	def _disconnect(self, servers):
		#NOTE: this is serverlabel
		for server in servers:
			print "DISCONNECTING: %s" % server
			server = self.servers[server]
			if server.container._botinst:
				server.container._botinst.quit()
			server._factory.stopTrying()
			#callLater delserver so that just incase some modules catch quit or error event, and use DB for it
			# May cause race condition when connecting to new server that uses same name but different DBfile
			# Hope someone doesn't do that...
			reactor.callLater(1.0, self.databasemanager.delServer, server.serverlabel)
			#remove oldservers from servers dict
			try: del self.servers[server.serverlabel]
			except KeyError: print "Warning: tried to remove server that didn't exist"
		
	def load(self):
		self.reloadStage1()
	
	def reloadStage1(self):
		#restore "defaults"
		for key in KEYS_MAIN:
			if key == "servers": continue #never nuke servers
			setattr(self, key, getattr(SettingsBase, key))
		self._setDefaults()
				
		if self.configfile:
			#attempt to load user options
			self._loadsettings()
	
	def reloadStage2(self):
		#disconnect before reloading dispatchers
		self._disconnect(self.oldservers)
		# Reset Dispatcher loaded modules
		# get a list of previously loaded modules so can track stale modules
		oldmodules = set(Dispatcher.MODULEDICT.keys())
		Dispatcher.reset()
		#create databases so init() can do database things.
		self.createDatabases(self.newservers)
		for server in self.servers.itervalues():
			server.initializeReload()
		Dispatcher.showLoadErrors()
		#compare currently loaded modules to oldmodules
		oldmodules = oldmodules.difference(set(Dispatcher.MODULEDICT.keys()))
		#remove oldmodules from sys.modules
		for module in oldmodules:
			print "Removing module: %s" % module
			try: del modules[module]
			except KeyError:
				print "WARNING: module was never in modules %s" % module
		# connect after load dispatchers
		self._connect(self.newservers)
		self.oldservers = self.newservers = []
		
	# TODO: when twisted supports good logger, consider allowing per-server logfile
	# NOTE: logfile is not chat logging
	# This must be called only once
	def initialize(self, logger=None):
		#setup log options
		if not self.console:
			logger.stop()
		if self.logfile:
			log.startLogging(open(join(self.botdir, self.logfile), 'a'), setStdout=False)
		
		# setup global database and databasemanager
		self.databasemanager = DBManager(self.datadir, self.datafile)
		self.reloadStage2()
		#start dbcommittimer
		# TODO: figure out if actually need this, and what SQLite transaction/journaling mode we should be using
		Timers._addTimer("_dbcommit", 60*60, self.databasemanager.dbcommit, reps=-1) #every hour (60*60)
	
	def saveOptions(self):
		d = OrderedDict()
		for key in KEYS_MAIN:
			if key == "servers": continue
			val = getattr(self, key)
			if val:
				d[key] = val
		if self.servers:
			d["servers"] = [serv._getDict() for serv in self.servers.itervalues()]
		else:
			EXAMPLE_SERVER = DummyServer(EXAMPLE_OPTS)
			EXAMPLE_SERVER2 = DummyServer(EXAMPLE_OPTS2)
			d["servers"] = [EXAMPLE_SERVER._getDict(), EXAMPLE_SERVER2._getDict()]
		dump(d, open(self.configfile, "wb"), indent=4, separators=(',', ': '), cls=ConfigEncoder)
	
	def shutdown(self, relaunch=False):
		self._disconnect(self.servers.keys())
		#stop timers or just not care...
		Timers._stopall()
		reactor.callLater(2.0, self.databasemanager.shutdown) # to give time for individual shutdown
		Dispatcher.unloadModules()
		reactor.callLater(2.5, reactor.stop) # to give time for individual shutdown
		# TODO: make sure this works properly
		# 	it may act odd on Windows due to execv not replacing current process.
		if relaunch:
			register(relaunchfunc, executable, argv)
			
	def hardshutdown(self):
		Timers._stopall()
		Dispatcher.unloadModules()
		self.databasemanager.shutdown()