Example #1
0
class DCHub(object, Job, EventGenerator):
	STATUS_CONNECTING = 1
	STATUS_CONNECTED = 2
	STATUS_CLOSED = 3

	LOG_STATUS = 1
	LOG_CHAT = 2
	LOG_PRIVCHAT = 3
	LOG_ERROR = 4

	USER_NORMAL = 1
	USER_FILESERVER = 4
	USER_SPEEDUSER = 8
	USER_AWAY_FLAG = 2

	MAX_LOG_ROWS = 2000
	RECONNECTION_DELAY = 30

	def __init__(self, addr):
		EventGenerator.__init__(self)

		self._addr = None
		self._status = 0
		self.logged = 0
		self.userNick = DCWorker.getWorker().user.nick
		self.userStatus = DCHub.USER_NORMAL
		self.delay = None
		self._users = {}
		self._log = []
		self.opLock = Lock()

		self._addr = self.parseAddress(addr)
		self.reset()

	def reset(self):
		if self._addr != None:
			self._name = self._addr[0]
		else:
			self._name = 'unknown'
		self.sock = None
		self._size = long(0)
		self.cmdQueue = []
		self.buf = ''

	def disconnect(self):
		self.stop()

	def startSearch(self, search, active):
		self.opLock.acquire()

		worker = DCWorker.getWorker()
		cmd = '$Search '
		if active:
			cmd += '%s:%d ' % (worker.getLocalAddress(), worker.getSettings().port)
		else:
			cmd += 'Hub:%s ' % self.userNick

		cmd += 'F?F?0?1?'
		for i in search.getPattern().split(' '):
			cmd += i + "$"

		self.cmdQueue.append(cmd[:-1] + '|')
		self.opLock.release()

	def requireConnection(self, nick):
		self.opLock.acquire()
		worker = DCWorker.getWorker()

		if worker.getSettings().active:
			self.cmdQueue.append('$ConnectToMe %s %s:%d|' % (nick, worker.getLocalAddress(), worker.getSettings().port))
		else:
			self.cmdQueue.append('$RevConnectToMe %s %s|' % (self.userNick, nick))

		self.opLock.release()

	def updateInfo(self):
		info = self.buildInfo()
		self.appendLogRow(self.LOG_STATUS, 'Updating user info...')

		self.opLock.acquire()
		self.cmdQueue.append('$MyINFO %s' % info)
		self.opLock.release()

	def setAway(self, away):
		if away:
			if self.userStatus & DCHub.USER_AWAY_FLAG:
				return
			else:
				txt = 'on'
				self.userStatus += DCHub.USER_AWAY_FLAG
		else:
			if self.userStatus & DCHub.USER_AWAY_FLAG:
				txt = 'off'
				self.userStatus -= DCHub.USER_AWAY_FLAG
			else:
				return

		info = self.buildInfo()
		self.appendLogRow(self.LOG_STATUS, 'Away flag: %s.' % txt)

		self.opLock.acquire()
		self.cmdQueue.append('$MyINFO %s' % info)
		self.opLock.release()

	def sendMsg(self, msg, nick=None):
		if self._status != 2:
			self.appendLogRow(self.LOG_ERROR, 'You must be connected to send messages.')
			return

		self.opLock.acquire()
		if nick == None:
			self.cmdQueue.append('<%s> %s|' % (self.userNick, msg.replace('|', '_')))
		else:
			self.cmdQueue.append('$To: %s From: %s $<%s> %s|' % (nick, self.userNick, self.userNick, msg.replace('|', '_')))
		self.opLock.release()

	def poll(self):
		try:
			if self._status == 0:
				if self.delay != None:
					curtime = time.time()

					self.delay[0] -= curtime - self.delay[1]
					if self.delay[0] <= 0:
						self.delay = None
					else:
						self.delay[1] = curtime
						return 0

				self.appendLogRow(self.LOG_STATUS, "Connecting to " + self._addr[0] + ":" + str(self._addr[1]) + "...")
				self.sock = AsyncSocket()
				self.sock.connect(self._addr)
				self.setStatus(1)

			elif self._status == 1:
				self.sock.poll()
				if self.sock.isConnected():
					self.appendLogRow(self.LOG_STATUS, "Connection established.")
					self.setStatus(2)

			elif self._status == 2:
				self.opLock.acquire()
				if self.logged:
					for cmd in self.cmdQueue:
						self.sock.send(cmd)
						
				self.cmdQueue = []
				self.opLock.release()

				finished = self.sock.poll()
				self.buf += self.sock.recv()
				if len(self.buf) > 0:
					cmds = self.buf.split('|')
					if self.buf[-1] != "|":
						self.buf = cmds[-1]
						cmds = cmds[:-1]
					else:
						self.buf = ""

					for i in cmds:
						if len(i) > 0:
							res = self.parseCmd(i)
							if res == 0:
								self.setStatus(6)
								return 0
							elif res == 1:
								self.setStatus(3)
								return 0

				if finished:
					self.appendLogRow(self.LOG_ERROR, "Connection closed.")
					self.checkReconnection()

			elif self._status == 3:
				self.sock.close()
				if self.sock.poll():
					self.removeAllUsers()

					self.opLock.acquire()
					self.reset()
					if self._status == 3: self._status = 0
					self.opLock.release()

					self.lock.acquire()
					for listener in self.listeners:
						listener.onHubInfo(self)
					self.lock.release()

			elif self._status == 4:
				self.sendCmd('Quit', self.userNick)
				self.sock.poll()
				self.setStatus(5)
				
			elif self._status == 5:
				if self.sock.getBytesToSend() > 0:
					self.sock.poll()
				else:
					self.setStatus(6)

			elif self._status == 6:
				if self.sock != None:
					self.sock.close()
					if not self.sock.poll():
						return 0

				self.opLock.acquire()
				self.reset()
				self._status = 7
				self.opLock.release()

				self.removeAllUsers()

				self.lock.acquire()
				for listener in self.listeners:
					listener.onHubDisconnection(self)
				self.lock.release()

				return 1

			else:
				return 1

		except DNSError:
			self.appendLogRow(self.LOG_ERROR, 'Connection closed because of DNS error.')
			self.setStatus(6)

		except error:
			self.appendLogRow(self.LOG_ERROR, 'Connection closed because of socket error.')
			self.checkReconnection()

		return 0

	def stop(self):
		self.opLock.acquire()

		log = None
		if self._status < 4:
			self._status = (6, 6, 4, 6)[self._status]

			log = (self.LOG_STATUS, 'Connection aborted by user.\n')
			self._log.append(log)

		self.opLock.release()

		if log:
			self.lock.acquire()
			for listener in self.listeners:
				listener.onLogUpdate(self, log)
			self.lock.release()

	def getAddress(self):
		return self._addr

	def getName(self):
		return self._name

	def getUserNum(self):
		return len(self._users)

	def getUsers(self):
		self.opLock.acquire()
		users = []
		for user in self._users:
			users.append(self._users[user])
		self.opLock.release()

		return users

	def getUserByNick(self, nick):
		self.opLock.acquire()
		try:
			user = self._users[nick]
		except KeyError:
			user = None
		self.opLock.release()

		return user

	def getSize(self):
		return self._size

	def getLogRows(self):
		return self._log

	def getStatus(self):
		return (self.STATUS_CONNECTING,
			  self.STATUS_CONNECTING,
			  self.STATUS_CONNECTED,
			  self.STATUS_CONNECTING,
			  self.STATUS_CLOSED,
			  self.STATUS_CLOSED,
			  self.STATUS_CLOSED,
			  self.STATUS_CLOSED)[self._status]

	def isAlive(self):
		return (1,1,1,1,0,0,0,0)[self._status]

	#=================#
	# Private members #
	#=================#

	def parseAddress(self, addr):
		if addr.__class__ == str:
			pos = addr.find(':') 
			if pos != -1:
				try:
					return (addr[:pos], int(addr[pos+1:]))
				except ValueError:
					self.appendLogRow(self.LOG_ERROR, "Invalid address: \"" + addr + "\".")
					self.setStatus(6)
					return None
			else:
				return (addr, 411)
		else:
			return addr
			
	def checkReconnection(self):
		if self._status == 2 and self.logged and self.getUserNum() > 0:
			self.delay = [self.RECONNECTION_DELAY, time.time()]
			self.setStatus(3)
			self.appendLogRow(self.LOG_STATUS, "Reconnecting in %d seconds..." % self.delay[0])
		else:
			self.setStatus(6)

	def setStatus(self, status):
		self.opLock.acquire()
		if status > self._status:
			self._status = status
		self.opLock.release()

	def buildInfo(self):
		user = DCWorker.getWorker().getUser()
		info = "$ALL " + self.userNick + " "
		if user.description != None:
			info += user.description

		info += "$ $"
		if user.connection == DCUser.CONN_56K:
			info += "56Kbps"
		elif user.connection == DCUser.CONN_SATELLITE:
			info += "Satellite"
		elif user.connection == DCUser.CONN_DSL:
			info += "DSL"
		elif user.connection == DCUser.CONN_CABLE:
			info += "Cable"
		elif user.connection == DCUser.CONN_T1:
			info += "LAN(T1)"
		elif user.connection == DCUser.CONN_T3:
			info += "LAN(T3)"
		info += chr(self.userStatus) + "$"

		if user.email != None:
			info += user.email
		info += "$"

		if user.share == -1:
			info += "0"
		else:
			info += str(user.share)
		info += "$"

		return info

	def appendLogRow(self, type, text):
		self.opLock.acquire()
		row = (type, text + '\n')
		self._log.append(row)
		if len(self._log) > DCHub.MAX_LOG_ROWS:
			del self._log[0]
		self.opLock.release()

		self.lock.acquire()
		for listener in self.listeners:
			listener.onLogUpdate(self, row)
		self.lock.release()

	def addUser(self, user, askinfo = 1):
		if len(user.nick) == 0 or user.nick == self.userNick: return
		if self._users.has_key(user.nick):
			if user.op: self._users[user.nick].op = 1
			return

		user.hub = self
		if askinfo and user.connection == DCUser.CONN_NONE:
			self.sendCmd("GetINFO", user.nick, self.userNick)

		self.opLock.acquire()
		self._users[user.nick] = user
		self.opLock.release()

		self.lock.acquire()
		for listener in self.listeners:
			listener.onNewUser(self, user)
		self.lock.release()

	def removeUser(self, nick):
		self.opLock.acquire()
		try:
			user = self._users[nick]
		except KeyError:
			self.opLock.release()
			return

		user.hub = None
		if user.share > 0:
			self._size -= user.share

		del self._users[nick]
		self.opLock.release()

		self.lock.acquire()
		for listener in self.listeners:
			listener.onUserDisconnection(self, user)
		self.lock.release()

	def removeAllUsers(self):
		self.opLock.acquire()
		users = self._users
		self._users = {}
		self._size = 0
		self.opLock.release()

		self.lock.acquire()
		for listener in self.listeners:
			for nick in users:
				listener.onUserDisconnection(self, users[nick])
		self.lock.release()

	def parseCmd(self, cmd):
		if cmd[0] == '<': #Chat message
			self.appendLogRow(self.LOG_CHAT, cmd)
			return 2
		elif cmd[0] != "$":
			self.appendLogRow(self.LOG_ERROR, "Unknown command: " + cmd)
			return 2
			
		cmd = cmd[1:]
		try:
			pos = cmd.index(' ')
			cmdname = cmd[:pos]
		except ValueError:
			if cmd == "HubIsFull":
				self.appendLogRow(self.LOG_ERROR, "Hub is full; disconnected.")
				return 0
			else:
				#Discard silently
				return 0

		if cmdname == "Hello":
			user = DCUser()
			user.nick = cmd[pos+1:]
			self.addUser(user)

		elif cmdname == "MyINFO":
			try:
				if cmd[pos+1:pos+6] != "$ALL ": return 2
				cmd = cmd[pos+6:]

				#Nick
				pos = cmd.index(" ")
				nick = cmd[:pos]
				cmd = cmd[pos+1:]

				if nick == self.userNick:
					self.logged = 1
					return 2

				try:
					user = self._users[nick]
				except KeyError:
					user = DCUser()
					user.nick = nick
					self.addUser(user, 0)

				#Description
				pos = cmd.index("$ $")
				if pos != 0:
					user.description = cmd[:pos]
				else:
					user.description = None
				cmd = cmd[pos+3:]

				#Split remaining fields
				fields = cmd.split("$")
				if len(fields) != 4:
					raise ValueError
				if len(fields[3]) > 0:
					raise ValueError

				#Connection
				fields[0] = fields[0][:-1]	#get rid of speed class byte
				if fields[0] == "56Kbps":
					user.connection = DCUser.CONN_56K
				elif fields[0] == "Satellite":
					user.connection = DCUser.CONN_SATELLITE
				elif fields[0] == "DSL":
					user.connection = DCUser.CONN_DSL
				elif fields[0] == "Cable":
					user.connection = DCUser.CONN_CABLE
				elif fields[0] == "LAN(T1)":
					user.connection = DCUser.CONN_T1
				elif fields[0] == "LAN(T3)":
					user.connection = DCUser.CONN_T3

				#Email
				if len(fields[1]) != 0:
					user.email = fields[1]
				else:
					user.email = None

				#Share
				user.share = long(fields[2])
				self._size += user.share

				#Notify listeners of changes
				self.lock.acquire()
				for listener in self.listeners:
					listener.onUserInfo(self, user)
				self.lock.release()

			except ValueError:
				pass

		elif cmdname == "Search":
			cmd = cmd[7:]

			try:
				p1, p2 = cmd.index(':'), cmd.index(' ')
				addr = cmd[:p1]
				if addr == "Hub":
					activeSearch = 0
					addr = cmd[p1+1:p2]
				else:
					activeSearch = 1
					addr = (addr, int(cmd[p1+1:p2]))

				params = cmd[p2+1:].split('?')
				if len(params) != 5:
					raise ValueError
					
				if params[0] == 'F':
					limit = DCFileList.LIMIT_NONE
					size = 0
				else:
					if params[1] == 'F':
						limit = DCFileList.LIMIT_ATLEAST
					else:
						limit = DCFileList.LIMIT_ATMOST

					size = long(params[2])

				worker = DCWorker.getWorker()
				slots = [worker.getSettings().slots - worker.getUsedSlots(), worker.getSettings().slots]
				hubAddr = self.sock.getpeername()
				
				#Safety check
				if hubAddr == None: return 0

				for i in worker.getLocalLists():
					for r in i.search(params[4].replace('$', ' '), limit, size):
						data =  '$SR %s %s\5%d %d/%d\5%s (%s:%d)' % (self.userNick, r.getPath(), r.size, slots[0], slots[1], self._name, hubAddr[0], hubAddr[1])
						if activeSearch:
							worker.getSearchWorker().sendResult(addr, data + '|')
						else:
							data += '\5%s|' % addr
							self.sock.send(data)

			except ValueError:
				pass
				
		elif cmdname == 'SR':
			DCWorker.getWorker().getSearchWorker().passiveResult(cmd[pos+1:])

		elif cmdname == "Quit":
			self.removeUser(cmd[pos+1:])

		elif cmdname == "To:":
			try:
				idx = cmd.index('$') - 1
				nick = cmd[cmd[:idx].rindex(' ')+1:idx]
				self.appendLogRow(self.LOG_PRIVCHAT, '<%s> %s' % (nick, cmd[idx+2:]))
			except ValueError: pass

		elif cmdname == "ConnectToMe":
			addr = cmd[cmd.rindex(' ')+1:].split(':')
			try: addr = (addr[0], int(addr[1]))
			except ValueError: pass

			sock = AsyncSocket()
			sock.connect(addr)
			DCWorker.getWorker().addJob(DCXfer(sock))
			
		elif cmdname == 'RevConnectToMe':
                	if not DCWorker.getWorker().getSettings().active: return 2
			self.requireConnection(cmd[cmd.index(' ')+1:cmd.rindex(' ')])
			
		elif cmdname == "LogedIn":
			user = DCUser()
			user.op = 1
			user.nick = cmd[pos+1:]
			self.addUser(user)

		elif cmdname == "NickList":
			nicks = cmd[pos+1:].split("$$")

			for nick in nicks:
				user = DCUser()
				user.nick = nick
				self.addUser(user)

		elif cmdname == "OpList":
			nicks = cmd[pos+1:].split("$$")

			for nick in nicks:
				user = DCUser()
				user.op = 1
				user.nick = nick
				self.addUser(user)

		elif cmdname == "ForceMove":
			newAddr = cmd[pos+1:]
			if len(newAddr) == 0: return 0

			addr = self.parseAddress(newAddr)
			if addr == None: return 0

			self.appendLogRow(self.LOG_STATUS, "Redirected to \"" + newAddr + "\".")
			self._addr = addr
			
			return 1

		elif cmdname == "Lock":
			key = solveChallenge(cmd[pos+1:])
			if len(key) == 0:
				self.appendLogRow(self.LOG_ERROR, "Error while generating authkey.")
				return 0

			self.sendCmd("Key", key)
			self.sendCmd("ValidateNick", self.userNick)
			self.sendCmd("Version", "1.3")

			self.sendCmd("MyINFO", self.buildInfo())
			self.sendCmd("GetNickList")

		elif cmdname == "HubName":
			self._name = cmd[pos+1:]

			self.lock.acquire()
			for i in self.listeners:
				i.onHubInfo(self)
			self.lock.release()

		elif cmdname == "GetPass":
			self.appendLogRow(self.LOG_ERROR, "Hub requires password. Function not yet implemented.")
			return 0

		elif cmdname == 'ValidateDenide':
			self.appendLogRow(self.LOG_ERROR, "Authentication error; disconnected.")
			return 0

		#else:
		#	self.appendLogRow(self.LOG_ERROR, "Unknown command: " + cmd)

		return 2

	def sendCmd(self, cmd, *args):
		cmdstr = "$" + cmd
		for i in args:
			cmdstr += " " + i
		cmdstr += "|"

		self.sock.send(cmdstr)

	address = property(getAddress, None, None, None)
	name = property(getName, None, None, None)
	userNum = property(getUserNum, None, None, None)
	users = property(getUsers, None, None, None)
	size = property(getSize, None, None, None)
	logRows = property(getLogRows, None, None, None)
	status = property(getStatus, None, None, None)