# -*- coding: utf-8 -*- from Queue import Queue from twisted.internet.protocol import ClientFactory from twisted.protocols.basic import LineReceiver from twisted.internet.task import LoopingCall from twisted.python import log from frn.utility import * class FRNClient(LineReceiver): client_version = 2010002 def connectionMade(self): self.txq = Queue() self.login() def ready(self): self.status = 'READY' self.msgbuffer = [] self.phase = 0 self.setRawMode() def startMultiLineMessage(self, msgtype, rest=''): self.status = msgtype self.phase = None self.setLineMode(rest) def collectMultiLineMessage(self, line): if self.phase is None: while line[0] not in '0123456789': # needed for client list line = line[1:] self.expected_lines = int(line.strip()) self.msgbuffer = [] self.phase = 0 else: self.msgbuffer.append(line) self.phase += 1 if self.phase >= self.expected_lines: handler = getattr(self, 'decode'+self.status, self.unimplemented) message = self.msgbuffer self.ready() handler(message) def startAudioMessage(self, rest=''): self.status = 'AUDIO' self.phase = None self.msgbuffer = '' if len(rest) > 0: self.collectAudioMessage(rest) def collectAudioMessage(self, data): needed = min(327, max(0, 327-len(self.msgbuffer))) if len(data) > 0: self.msgbuffer += data[:needed] if len(self.msgbuffer) >= 327: audio_data = self.msgbuffer source = ord(audio_data[0])*256+ord(audio_data[1]) self.ready() self.decodeAUDIO(source, audio_data[2:]) if len(data) > needed: self.dataReceived(data[needed:]) def lineReceived(self, line): if self.status == 'AUTH': if self.phase == 0: self.latest_client_version = int(line.strip()) self.phase = 1 else: self.serverdata = parseSimpleXML(line.strip()) self.loginResponse(self.serverdata) if int(self.serverdata['sv']) > 2009004: self.sendLine(makeAuthKey(self.serverdata['kp'])) self.ready() else: self.collectMultiLineMessage(line) def rawDataReceived(self, data): if self.status == 'READY': packet_type = ord(data[0]) if packet_type == 0: # Keepalive self.pong() elif packet_type == 1: # TX ack self.status = 'TX' self.phase = 0 if len(data) > 1: self.dataReceived(data[1:]) elif packet_type == 2: # Audio self.startAudioMessage(data[1:]) elif packet_type == 3: # Client list self.startMultiLineMessage('CLIENTS', data[1:]) elif packet_type == 4: # Text self.startMultiLineMessage('TEXT', data[1:]) elif packet_type == 5: # Channel list self.startMultiLineMessage('NETWORKS', data[1:]) else: log.err("Unknown packet type %d" % packet_type) elif self.status == 'AUDIO': self.collectAudioMessage(data) elif self.status == 'TX': if self.phase == 0: self.phase = 1 self.decodeTX(ord(data[0])*256+ord(data[1])) self.ready() if len(data) > 2: self.dataReceived(data[2:]) def login(self): d = self.factory.client_id fields = [ ('VX', self.client_version), ('EA', d['email']), ('PW', d['password']), ('ON', d['operator']), ('BC', d['transmission']), ('DS', d['description']), ('NN', d['country']), ('CT', d['city']), ('NT', d['network']) ] ap = "CT:"+formatSimpleXML(fields) self.status = 'AUTH' self.phase = 0 self.sendLine(ap) def pong(self): self.sendLine('P') def setStatus(self, status): self.sendLine('ST:%s' % str(status)) def stopTransmission(self): self.sendLine('RX0') def startTransmission(self): self.sendLine('TX0') def sendAudioFrame(self, frame): self.sendLine('TX1') self.transport.write(frame) def streamStep(self, count): if count > 1: log.msg("WARNING: lost %d ticks" % (count-1)) for i in range(count): self.sendAudioFrame(self.txq.get_nowait()) def stopStreaming(self): self.txtimer.stop() def _streamAck(self): self.txtimer = LoopingCall.withCount(self.streamStep) self.txtimer.start(0.20).addCallback( lambda _: self.stopTransmission()).addErrback( lambda _: self.stopTransmission()) def feedStreaming(self, frames): if type(frames) == list: for frame in frames: self.txq.put_nowait(frame) else: self.txq.put_nowait(frames) def startStreaming(self): self.startTransmission() def sendTextMessage(self, dest, text): self.sendLine('TM:'+formatSimpleXML(dict(ID=dest, MS=text))) def unimplemented(self, msg): log.msg("Unimplemented: %s" % msg) def decodeAUDIO(self, from_id, frames): self.audioFrameReceived(from_id, frames) def decodeTX(self, my_id): self._streamAck() def decodeTEXT(self, msg): self.textMessageReceived(msg[0], msg[1], msg[2]) def decodeCLIENTS(self, msg): self.clientsListUpdated([parseSimpleXML(x) for x in msg]) def decodeNETWORKS(self, msg): self.networksListUpdated(msg) def loginResponse(self, info): pass def audioFrameReceived(self, from_id, frame): pass def textMessageReceived(self, client, message, target): pass def clientsListUpdated(self, clients): pass def networksListUpdated(self, networks): pass class FRNClientFactory(ClientFactory): protocol = FRNClient def __init__(self, **kw): self.client_id = kw def startedConnecting(self, connector): log.msg('Started to connect') def buildProtocol(self, addr): log.msg('Connected') return ClientFactory.buildProtocol(self, addr) def clientConnectionLost(self, connector, reason): log.msg('Lost connection. Reason: %s' % reason) def clientConnectionFailed(self, connector, reason): log.err('Connection failed. Reason: %s' % reason) # vim: set et ai sw=4 ts=4 sts=4: