3
0
mirror of https://github.com/jlu5/PyLink.git synced 2024-12-25 20:22:45 +01:00

Merge branch 'wip/rework-endburst' into devel

This commit is contained in:
James Lu 2017-08-31 13:27:54 -07:00
commit 19bd3ec0b2
10 changed files with 87 additions and 64 deletions

View File

@ -468,16 +468,13 @@ class PyLinkNetworkCore(structures.DeprecatedAttributesObject, structures.CamelC
if not userobj: if not userobj:
return False return False
# Look for the "service" attribute in the User object, if one exists. # Look for the "service" attribute in the User object,sname = userobj.service
try:
sname = userobj.service
# Warn if the service name we fetched isn't a registered service. # Warn if the service name we fetched isn't a registered service.
if sname not in world.services.keys(): sname = userobj.service
if sname is not None and sname not in world.services.keys():
log.warning("(%s) User %s / %s had a service bot record to a service that doesn't " log.warning("(%s) User %s / %s had a service bot record to a service that doesn't "
"exist (%s)!", self.name, uid, userobj.nick, sname) "exist (%s)!", self.name, uid, userobj.nick, sname)
return world.services.get(sname) return world.services.get(sname)
except AttributeError:
return False
structures._BLACKLISTED_COPY_TYPES.append(PyLinkNetworkCore) structures._BLACKLISTED_COPY_TYPES.append(PyLinkNetworkCore)
@ -1596,6 +1593,9 @@ class User():
# Cloaked host for IRCds that use it # Cloaked host for IRCds that use it
self.cloaked_host = None self.cloaked_host = None
# Stores service bot name if applicable
self.service = None
def __repr__(self): def __repr__(self):
return 'User(%s/%s)' % (self.uid, self.nick) return 'User(%s/%s)' % (self.uid, self.nick)
IrcUser = User IrcUser = User
@ -1619,6 +1619,9 @@ class Server():
self.desc = desc self.desc = desc
self._irc = irc self._irc = irc
# Has the server finished bursting yet?
self.has_eob = False
def __repr__(self): def __repr__(self):
return 'Server(%s)' % self.name return 'Server(%s)' % self.name

View File

@ -22,8 +22,17 @@ def spawn_service(irc, source, command, args):
# Get the ServiceBot object. # Get the ServiceBot object.
sbot = world.services[name] sbot = world.services[name]
old_userobj = irc.users.get(sbot.uids.get(irc.name))
if old_userobj and old_userobj.service:
# A client already exists, so don't respawn it.
log.debug('(%s) spawn_service: Not respawning service %r as service client %r already exists.', irc.name, name,
irc.pseudoclient.nick)
return
if name == 'pylink' and irc.pseudoclient: if name == 'pylink' and irc.pseudoclient:
# irc.pseudoclient already exists, for protocols like clientbot # irc.pseudoclient already exists, reuse values from it but
# spawn a new client. This is used for protocols like Clientbot,
# so that they can override the main service nick, among other things.
log.debug('(%s) spawn_service: Using existing nick %r for service %r', irc.name, irc.pseudoclient.nick, name) log.debug('(%s) spawn_service: Using existing nick %r for service %r', irc.name, irc.pseudoclient.nick, name)
userobj = irc.pseudoclient userobj = irc.pseudoclient
userobj.opertype = "PyLink Service" userobj.opertype = "PyLink Service"

View File

@ -217,7 +217,7 @@ def get_prefix_modes(irc, remoteirc, channel, user, mlist=None):
return modes return modes
def spawn_relay_server(irc, remoteirc): def spawn_relay_server(irc, remoteirc):
if irc.connected.wait(TCONDITION_TIMEOUT): if irc.connected.is_set():
try: try:
# ENDBURST is delayed by 3 secs on supported IRCds to prevent # ENDBURST is delayed by 3 secs on supported IRCds to prevent
# triggering join-flood protection and the like. # triggering join-flood protection and the like.
@ -358,7 +358,7 @@ def get_remote_user(irc, remoteirc, user, spawn_if_missing=True, times_tagged=0)
spawning one if it doesn't exist and spawn_if_missing is True.""" spawning one if it doesn't exist and spawn_if_missing is True."""
# Wait until the network is working before trying to spawn anything. # Wait until the network is working before trying to spawn anything.
if irc.connected.wait(TCONDITION_TIMEOUT): if irc.connected.is_set():
# Don't spawn clones for registered service bots. # Don't spawn clones for registered service bots.
sbot = irc.get_service_bot(user) sbot = irc.get_service_bot(user)
if sbot: if sbot:

View File

@ -633,9 +633,8 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
self.send(line) self.send(line)
# Virtual endburst hook. # Virtual endburst hook.
self.connected.set() # Note, this should always be set before sending ENDBURST self.connected.set() # Note, this should always be set before the actual ENDBURST hook
if not self.has_eob: self.servers[self.uplink].has_eob = True
self.has_eob = True
return {'parse_as': 'ENDBURST'} return {'parse_as': 'ENDBURST'}
handle_422 = handle_376 handle_422 = handle_376

View File

@ -17,13 +17,11 @@ class HybridProtocol(TS6Protocol):
self.casemapping = 'ascii' self.casemapping = 'ascii'
self.caps = {} self.caps = {}
self.hook_map = {'EOB': 'ENDBURST', 'TBURST': 'TOPIC', 'SJOIN': 'JOIN'} self.hook_map = {'EOB': 'ENDBURST', 'TBURST': 'TOPIC', 'SJOIN': 'JOIN'}
self.has_eob = False
self.protocol_caps -= {'slash-in-hosts'} self.protocol_caps -= {'slash-in-hosts'}
def post_connect(self): def post_connect(self):
"""Initializes a connection to a server.""" """Initializes a connection to a server."""
ts = self.start_ts ts = self.start_ts
self.has_eob = False
f = self.send f = self.send
# https://github.com/grawity/irc-docs/blob/master/server/ts6.txt#L80 # https://github.com/grawity/irc-docs/blob/master/server/ts6.txt#L80
@ -227,11 +225,15 @@ class HybridProtocol(TS6Protocol):
return {'channel': channel, 'setter': setter, 'ts': ts, 'text': topic} return {'channel': channel, 'setter': setter, 'ts': ts, 'text': topic}
def handle_eob(self, numeric, command, args): def handle_eob(self, numeric, command, args):
log.debug('(%s) end of burst received', self.name) """EOB (end-of-burst) handler."""
if not self.has_eob: # Only call ENDBURST hooks if we haven't already. log.debug('(%s) end of burst received from %s', self.name, numeric)
return {} if not self.servers[numeric].has_eob:
# Don't fight with TS6's generic PING-as-EOB
self.servers[numeric].has_eob = True
self.has_eob = True if numeric == self.uplink:
self.connected.set()
return {}
def handle_svsmode(self, numeric, command, args): def handle_svsmode(self, numeric, command, args):
""" """

View File

@ -520,9 +520,6 @@ class InspIRCdProtocol(TS6BaseProtocol):
log.debug('(%s) self.prefixmodes set to %r', self.name, log.debug('(%s) self.prefixmodes set to %r', self.name,
self.prefixmodes) self.prefixmodes)
# Finally, set the irc.connected (protocol negotiation complete)
# state to True.
self.connected.set()
elif args[0] == 'MODSUPPORT': elif args[0] == 'MODSUPPORT':
# <- CAPAB MODSUPPORT :m_alltime.so m_check.so m_chghost.so m_chgident.so m_chgname.so m_fullversion.so m_gecosban.so m_knock.so m_muteban.so m_nicklock.so m_nopartmsg.so m_opmoderated.so m_sajoin.so m_sanick.so m_sapart.so m_serverban.so m_services_account.so m_showwhois.so m_silence.so m_swhois.so m_uninvite.so m_watch.so # <- CAPAB MODSUPPORT :m_alltime.so m_check.so m_chghost.so m_chgident.so m_chgname.so m_fullversion.so m_gecosban.so m_knock.so m_muteban.so m_nicklock.so m_nopartmsg.so m_opmoderated.so m_sajoin.so m_sanick.so m_sapart.so m_serverban.so m_services_account.so m_showwhois.so m_silence.so m_swhois.so m_uninvite.so m_watch.so
self.modsupport = args[-1].split() self.modsupport = args[-1].split()
@ -716,6 +713,9 @@ class InspIRCdProtocol(TS6BaseProtocol):
def handle_endburst(self, numeric, command, args): def handle_endburst(self, numeric, command, args):
"""ENDBURST handler; sends a hook with empty contents.""" """ENDBURST handler; sends a hook with empty contents."""
self.servers[numeric].has_eob = True
if numeric == self.uplink:
self.connected.set()
return {} return {}
def handle_away(self, numeric, command, args): def handle_away(self, numeric, command, args):

View File

@ -229,26 +229,32 @@ class NgIRCdProtocol(IRCS2SProtocol):
raise LookupError('No such PyLink client exists.') raise LookupError('No such PyLink client exists.')
log.debug('(%s) sjoin: got %r for users', self.name, users) log.debug('(%s) sjoin: got %r for users', self.name, users)
njoin_prefix = ':%s NJOIN %s :' % (self._expandPUID(self.sid), channel) njoin_prefix = ':%s NJOIN %s :' % (self._expandPUID(server), channel)
# Format the user list into strings such as @user1, +user2, user3, etc. # Format the user list into strings such as @user1, +user2, user3, etc.
nicks_to_send = ['%s%s' % (''.join(self.prefixmodes[modechar] for modechar in userpair[0] if modechar in self.prefixmodes), nicks_to_send = []
self._expandPUID(userpair[1])) for userpair in users] for userpair in users:
prefixes, uid = userpair
if uid not in self.users:
log.warning('(%s) Trying to NJOIN missing user %s?', self.name, uid)
continue
elif uid in self._channels[channel].users:
# Don't rejoin users already in the channel, this causes errors with ngIRCd.
continue
self._channels[channel].users.add(uid)
self.users[uid].channels.add(channel)
self.apply_modes(channel, (('+%s' % prefix, uid) for prefix in userpair[0]))
nicks_to_send.append(''.join(self.prefixmodes[modechar] for modechar in userpair[0]) + \
self._expandPUID(userpair[1]))
if nicks_to_send:
# Use 13 args max per line: this is equal to the max of 15 minus the command name and target channel. # Use 13 args max per line: this is equal to the max of 15 minus the command name and target channel.
for message in utils.wrapArguments(njoin_prefix, nicks_to_send, self.S2S_BUFSIZE, separator=',', max_args_per_line=13): for message in utils.wrapArguments(njoin_prefix, nicks_to_send, self.S2S_BUFSIZE, separator=',', max_args_per_line=13):
self.send(message) self.send(message)
# Add the affected users to our state.
for userpair in users:
uid = userpair[1]
self._channels[channel].users.add(uid)
try:
self.users[uid].channels.add(channel)
except KeyError: # Not initialized yet?
log.warning("(%s) sjoin: KeyError trying to add %r to %r's channel list?", self.name, channel, uid)
self.apply_modes(channel, (('+%s' % prefix, uid) for prefix in userpair[0]))
if modes: if modes:
# Burst modes separately if there are any. # Burst modes separately if there are any.
log.debug("(%s) sjoin: bursting modes %r for channel %r now", self.name, modes, channel) log.debug("(%s) sjoin: bursting modes %r for channel %r now", self.name, modes, channel)
@ -504,12 +510,16 @@ class NgIRCdProtocol(IRCS2SProtocol):
assert 'IRC+' in args[1], "Linking to non-ngIRCd server using this protocol module is not supported" assert 'IRC+' in args[1], "Linking to non-ngIRCd server using this protocol module is not supported"
def handle_ping(self, source, command, args): def handle_ping(self, source, command, args):
if source == self.uplink: """
Handles incoming PINGs (and implicit end of burst).
"""
self._send_with_prefix(self.sid, 'PONG %s :%s' % (self._expandPUID(self.sid), args[-1]), queue=False) self._send_with_prefix(self.sid, 'PONG %s :%s' % (self._expandPUID(self.sid), args[-1]), queue=False)
if not self.has_eob: if not self.servers[source].has_eob:
# Treat the first PING we receive as end of burst. # Treat the first PING we receive as end of burst.
self.has_eob = True self.servers[source].has_eob = True
if source == self.uplink:
self.connected.set() self.connected.set()
# Return the endburst hook. # Return the endburst hook.

View File

@ -824,7 +824,6 @@ class P10Protocol(IRCS2SProtocol):
self.send('SERVER %s 1 %s %s J10 %s]]] +s6 :%s' % (name, ts, ts, sid, desc)) self.send('SERVER %s 1 %s %s J10 %s]]] +s6 :%s' % (name, ts, ts, sid, desc))
self._send_with_prefix(sid, "EB") self._send_with_prefix(sid, "EB")
self.connected.set()
def handle_server(self, source, command, args): def handle_server(self, source, command, args):
"""Handles incoming server introductions.""" """Handles incoming server introductions."""
@ -1110,14 +1109,18 @@ class P10Protocol(IRCS2SProtocol):
return {'channel': channel, 'users': [source], 'modes': return {'channel': channel, 'users': [source], 'modes':
self._channels[channel].modes, 'ts': ts or int(time.time())} self._channels[channel].modes, 'ts': ts or int(time.time())}
handle_create = handle_join handle_create = handle_join
def handle_end_of_burst(self, source, command, args): def handle_end_of_burst(self, source, command, args):
"""Handles end of burst from our uplink.""" """Handles end of burst from servers."""
# Send EOB acknowledgement; this is required by the P10 specification, # Send EOB acknowledgement; this is required by the P10 specification,
# and needed if we want to be able to receive channel messages, etc. # and needed if we want to be able to receive channel messages, etc.
if source == self.uplink: if source == self.uplink:
self._send_with_prefix(self.sid, 'EA') self._send_with_prefix(self.sid, 'EA')
self.connected.set()
self.servers[source].has_eob = True
return {} return {}
def handle_kick(self, source, command, args): def handle_kick(self, source, command, args):

View File

@ -20,9 +20,6 @@ class TS6Protocol(TS6BaseProtocol):
self.hook_map = {'SJOIN': 'JOIN', 'TB': 'TOPIC', 'TMODE': 'MODE', 'BMASK': 'MODE', self.hook_map = {'SJOIN': 'JOIN', 'TB': 'TOPIC', 'TMODE': 'MODE', 'BMASK': 'MODE',
'EUID': 'UID', 'RSFNC': 'SVSNICK', 'ETB': 'TOPIC', 'USERMODE': 'MODE'} 'EUID': 'UID', 'RSFNC': 'SVSNICK', 'ETB': 'TOPIC', 'USERMODE': 'MODE'}
# Track whether we've received end-of-burst from the uplink.
self.has_eob = False
self.required_caps = {'EUID', 'SAVE', 'TB', 'ENCAP', 'QS', 'CHW'} self.required_caps = {'EUID', 'SAVE', 'TB', 'ENCAP', 'QS', 'CHW'}
# From ChatIRCd: https://github.com/ChatLounge/ChatIRCd/blob/master/doc/technical/ChatIRCd-extra.txt # From ChatIRCd: https://github.com/ChatLounge/ChatIRCd/blob/master/doc/technical/ChatIRCd-extra.txt
@ -264,7 +261,6 @@ class TS6Protocol(TS6BaseProtocol):
def post_connect(self): def post_connect(self):
"""Initializes a connection to a server.""" """Initializes a connection to a server."""
ts = self.start_ts ts = self.start_ts
self.has_eob = False
f = self.send f = self.send
@ -423,9 +419,6 @@ class TS6Protocol(TS6BaseProtocol):
if 'SERVICES' in caps: if 'SERVICES' in caps:
self.cmodes['regonly'] = 'r' self.cmodes['regonly'] = 'r'
log.debug('(%s) self.connected set!', self.name)
self.connected.set()
def handle_ping(self, source, command, args): def handle_ping(self, source, command, args):
"""Handles incoming PING commands.""" """Handles incoming PING commands."""
# PING: # PING:
@ -445,10 +438,14 @@ class TS6Protocol(TS6BaseProtocol):
if self.is_internal_server(destination): if self.is_internal_server(destination):
self._send_with_prefix(destination, 'PONG %s %s' % (destination, source), queue=False) self._send_with_prefix(destination, 'PONG %s %s' % (destination, source), queue=False)
if destination == self.sid and not self.has_eob: if not self.servers[source].has_eob:
# Charybdis' endburst is just sending a PING to the other server. # TS6 endburst is just sending a PING to the other server.
# https://github.com/charybdis-ircd/charybdis/blob/dc336d1/modules/core/m_server.c#L484-L485 # https://github.com/charybdis-ircd/charybdis/blob/dc336d1/modules/core/m_server.c#L484-L485
self.has_eob = True self.servers[source].has_eob = True
if source == self.uplink:
log.debug('(%s) self.connected set!', self.name)
self.connected.set()
# Return the endburst hook. # Return the endburst hook.
return {'parse_as': 'ENDBURST'} return {'parse_as': 'ENDBURST'}

View File

@ -379,6 +379,9 @@ class UnrealProtocol(TS6BaseProtocol):
def handle_eos(self, numeric, command, args): def handle_eos(self, numeric, command, args):
"""EOS is used to denote end of burst.""" """EOS is used to denote end of burst."""
self.servers[numeric].has_eob = True
if numeric == self.uplink:
self.connected.set()
return {} return {}
def handle_uid(self, numeric, command, args): def handle_uid(self, numeric, command, args):
@ -482,9 +485,6 @@ class UnrealProtocol(TS6BaseProtocol):
"(Unreal 4.x), got %s)" % (self.min_proto_ver, protover)) "(Unreal 4.x), got %s)" % (self.min_proto_ver, protover))
self.servers[numeric] = Server(self, None, sname, desc=sdesc) self.servers[numeric] = Server(self, None, sname, desc=sdesc)
# Set irc.connected to True, meaning that protocol negotiation passed.
log.debug('(%s) self.connected set!', self.name)
self.connected.set()
else: else:
# Legacy (non-SID) servers can still be introduced using the SERVER command. # Legacy (non-SID) servers can still be introduced using the SERVER command.
# <- :services.int SERVER a.bc 2 :(H) [GL] a # <- :services.int SERVER a.bc 2 :(H) [GL] a