Put the relay client in a thread, and get buffer information

master
Ekdohibs 2016-04-04 18:18:45 +02:00
parent 6c874ecd2b
commit a5dc3ccb0a
3 changed files with 146 additions and 90 deletions

View File

@ -23,7 +23,7 @@ Each of these options can be passed, prefixed with `--`, directly through the co
* `port`: port of the Weechat relay.
* `ensure-background`: runs the following command in the background. Periodically checks whether it is still open, reruns it if necessary, and resets the connection to the server if it was lost in the process. Mostly useful to establish a SSH tunnel: eg., to ensure that a SSH tunnel will be opened and closed with the application, set `ensure-background` to `ssh irc@example.com -L [LOCALPORT]:localhost:[RELAYPORT] -N`.
* `reconnect-delay`: delay between two attempts to reconnect after being disconnected from the server.
* `highlight-action`: program to invoke when highlighted. It will be called with the IRC line that triggered the highlight as its first argument and the message sender as its second argument.
* `highlight-action`: program to invoke when highlighted. It will be called with the IRC line that triggered the highlight as its first argument, the message sender as its second argument, and the buffer name as its third.
* `privmsg-action`: program to invoke when receiving a private message. Has the same behavior as `highlight-action`.
* `log-file`: log file path. If omitted, the logs will be directly printed.

View File

@ -34,7 +34,8 @@ def read_str(data):
def read_ptr(data):
ptrLen = data[0]
return 0,data[ptrLen+1:] # FIXME not implemented. Do we need it?
ptrData = data[1:ptrLen+1]
return int(ptrData.decode('utf-8'), 16), data[ptrLen+1:]
def read_tim(data):
timLen = data[0]
@ -73,8 +74,11 @@ def read_hda(data):
out = []
for dataSet in range(count):
curSet = dict()
path = []
for k in range(len(hpathSplit)):
_,data = read_ptr(data)
ptr, data = read_ptr(data)
path.append(ptr)
curSet['__path'] = path
for pair in keysArray:
curSet[pair[0]],data = pair[1](data)
out.append(curSet)

View File

@ -29,6 +29,7 @@ import socket
import subprocess
import sys
import time
import threading
import packetRead
@ -48,75 +49,143 @@ def safeCall(callArray):
except:
logging.error("Could not execute "+callArray[0])
def gotHighlight(message, nick, conf):
if not 'highlight-action' in conf or not conf['highlight-action']:
return # No action defined: do nothing.
class RelayClient(threading.Thread):
def __init__(self, conf):
threading.Thread.__init__(self)
self.daemon = True # Stop when the program terminates
self.conf = conf
self.sock = None
self.packet_actions = {
'ask_buffers' : self.asked_buffers,
'_buffer_line_added' : self.buffer_line_added
}
self.buffers = {}
logging.debug("Notifying highlight message.")
highlightProcessCmd = expandPaths(conf['highlight-action'])
safeCall([highlightProcessCmd, message, nick])
def run(self):
self.connect()
while True:
READ_AT_ONCE = 4096
data = self.recv(READ_AT_ONCE)
if len(data) < 5:
logging.warning("Packet shorter than 5 bytes received. Ignoring.")
continue
def gotPrivMsg(message, nick, conf):
if not 'privmsg-action' in conf or not conf['privmsg-action']:
return # No action defined: do nothing.
logging.debug("Notifying private message.")
privmsgProcessCmd = expandPaths(conf['privmsg-action'])
safeCall([privmsgProcessCmd, message, nick])
def getResponse(sock, conf):
READ_AT_ONCE=4096
sockBytes = sock.recv(READ_AT_ONCE)
if not sockBytes:
return False # Connection closed
if(len(sockBytes) < 5):
logging.warning("Packet shorter than 5 bytes received. Ignoring.")
return True
if sockBytes[4] != 0:
logging.warning("Received compressed message. Ignoring.")
return True
mLen,_ = packetRead.read_int(sockBytes)
lastPacket = sockBytes
while(len(sockBytes) < mLen):
if(len(lastPacket) < READ_AT_ONCE):
logging.warning("Incomplete packet received. Ignoring.")
return True
lastPacket = sock.recv(READ_AT_ONCE)
sockBytes += lastPacket
body = sockBytes[5:]
ident,body = packetRead.read_str(body)
if ident != "_buffer_line_added":
return True
logging.debug("Received buffer line.")
dataTyp,body = packetRead.read_typ(body)
if(dataTyp != "hda"):
logging.warning("Unknown buffer_line_added format. Ignoring.")
return True
hdaData,body = packetRead.read_hda(body)
for hda in hdaData:
msg=hda['message']
nick=""
for tag in hda['tags_array']:
if tag.startswith('nick_'):
nick = tag[5:]
if hda['highlight'] > 0:
gotHighlight(msg, nick, conf)
continue
for tag in hda['tags_array']:
if tag.startswith('notify_'):
notifLevel = tag[7:]
if notifLevel == 'private':
gotPrivMsg(msg, nick, conf)
dataLen, _ = packetRead.read_int(data)
lastPacket = data
while len(data) < dataLen:
if len(lastPacket) < READ_AT_ONCE:
logging.warning("Incomplete packet received. Ignoring.")
break
lastPacket = self.recv(READ_AT_ONCE)
data += lastPacket
if len(data) < dataLen:
continue
self.process_packet(data)
def process_packet(self, packet):
if packet[4] != 0:
logging.warning("Received compressed message. Ignoring.")
return
body = packet[5:]
ident, body = packetRead.read_str(body)
if ident in self.packet_actions:
self.packet_actions[ident](body)
def connect(self):
while True:
try:
self.sock = socket.socket()
logging.info("Connecting to " + self.conf['server'] + ":" + self.conf['port'] + "...")
self.sock.connect((self.conf['server'], int(self.conf['port'])))
logging.info("Connected")
self.init_connection()
return
except ConnectionRefusedError:
self.sock = None
logging.error("Connection refused. Retrying...")
except socket.error as exn:
self.sock = None
logging.error("Connection error: %s. Retrying..." % exn)
time.sleep(float(self.conf['reconnect-delay']))
def init_connection(self):
password = self.conf.get('password', None)
if password != None:
self.sock.sendall(b'init compression=off,password='+password.encode("utf-8")+b'\n')
else:
self.sock.sendall(b'init compression=off\n')
self.sock.sendall(b'sync *\n')
# Ask for name of buffers
self.sock.sendall(b'(ask_buffers) hdata buffer:gui_buffers(*) name\n')
def recv(self, n):
while True:
try:
data = self.sock.recv(n)
if data:
return data
logging.warning("Connection lost. Retrying...")
except socked.error as exn:
logging.error("Connection error: %s. Retrying..." % exn)
self.connect()
def asked_buffers(self, body):
data_type, body = packetRead.read_typ(body)
if(data_type != "hda"):
logging.warning("Unknown asked_buffers format. Ignoring.")
return
hdaData, _ = packetRead.read_hda(body)
for hda in hdaData:
self.buffers[hda['__path'][-1]] = hda['name']
def buffer_line_added(self, body):
data_type, body = packetRead.read_typ(body)
if(data_type != "hda"):
logging.warning("Unknown buffer_line_added format. Ignoring.")
return
hdaData, _ = packetRead.read_hda(body)
for hda in hdaData:
msg = hda['message']
buffer = hda.get('buffer', 0)
if buffer not in self.buffers:
self.sock.sendall(b'(ask_buffers) hdata buffer:gui_buffers(*) name\n')
buffer_name = '<unknown>'
else:
buffer_name = self.buffers[buffer]
nick = ""
for tag in hda['tags_array']:
if tag.startswith('nick_'):
nick = tag[5:]
if hda['highlight'] > 0:
self.gotHighlight(msg, nick, buffer_name)
continue
for tag in hda['tags_array']:
if tag.startswith('notify_'):
notifLevel = tag[7:]
if notifLevel == 'private':
self.gotPrivMsg(msg, nick, buffer_name)
break
def gotHighlight(self, message, nick, buffer_name):
if not selt.conf.get('highlight-action', None):
return # No action defined: do nothing.
logging.debug("Notifying highlight message.")
highlightProcessCmd = expandPaths(self.conf['highlight-action'])
safeCall([highlightProcessCmd, message, nick, buffer_name])
def gotPrivMsg(self, message, nick, buffer_name):
if not self.conf.get('privmsg-action', None):
return # No action defined: do nothing.
logging.debug("Notifying private message.")
privmsgProcessCmd = expandPaths(self.conf['privmsg-action'])
safeCall([privmsgProcessCmd, message, nick, buffer_name])
return True
CONFIG_ITEMS = [
('-c','config', 'Use the given configuration file.', DEFAULT_CONF),
@ -245,31 +314,14 @@ def main():
signal.signal(signal.SIGINT, sigint)
signal.signal(signal.SIGTERM, sigint)
bgProcess = None
client = RelayClient(conf)
bgProcess = ensureBackgroundCheckRun(None, conf)
logging.info("Entering main loop.")
client.start()
while True:
try:
bgProcess = ensureBackgroundCheckRun(bgProcess, conf)
sock = socket.socket()
logging.info("Connecting to "+conf['server']+":"+conf['port']+"...")
sock.connect((conf['server'], int(conf['port'])))
logging.info("Connected")
password = conf.get('password', None)
if password != None:
sock.sendall(b'init compression=off,password='+password.encode("utf-8")+b'\n')
else:
sock.sendall(b'init compression=off\n')
sock.sendall(b'sync *\n')
while getResponse(sock,conf):
bgProcess = ensureBackgroundCheckRun(bgProcess, conf)
logging.warning("Connection lost. Retrying...")
except ConnectionRefusedError:
logging.error("Connection refused. Retrying...")
except socket.error as exn:
logging.error("Connection error: %s. Retrying..." % exn)
time.sleep(float(conf['reconnect-delay']))
bgProcess = ensureBackgroundCheckRun(bgProcess, conf)
time.sleep(0.5)
if __name__=='__main__':
main()