You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
225 lines
6.9 KiB
225 lines
6.9 KiB
# -*- 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:
|
|
|