3
0
mirror of https://github.com/jlu5/PyLink.git synced 2025-01-24 11:14:07 +01:00

WIP: merge IRCNetwork and Protocol classes together

Eventually, the goal is to have both of these hotswappable with inheritance, so this distinction becomes moot.
This commit is contained in:
James Lu 2017-06-24 23:27:24 -07:00
parent f8155ff74c
commit df18e318a8
3 changed files with 129 additions and 128 deletions

View File

@ -35,7 +35,7 @@ class ProtocolError(RuntimeError):
class PyLinkNetworkCore(utils.DeprecatedAttributesObject, utils.CamelCaseToSnakeCase): class PyLinkNetworkCore(utils.DeprecatedAttributesObject, utils.CamelCaseToSnakeCase):
"""Base IRC object for PyLink.""" """Base IRC object for PyLink."""
def __init__(self, netname, proto, conf): def __init__(self, netname):
""" """
Initializes an IRC object. This takes 3 variables: the network name Initializes an IRC object. This takes 3 variables: the network name
(a string), the name of the protocol module to use for this connection, (a string), the name of the protocol module to use for this connection,
@ -48,12 +48,26 @@ class PyLinkNetworkCore(utils.DeprecatedAttributesObject, utils.CamelCaseToSnake
self.loghandlers = [] self.loghandlers = []
self.name = netname self.name = netname
self.conf = conf self.conf = conf.conf
self.sid = None self.sid = None
self.serverdata = conf['servers'][netname] self.serverdata = conf.conf['servers'][netname]
self.botdata = conf['bot'] self.botdata = conf.conf['bot']
self.protoname = proto.__name__.split('.')[-1] # Remove leading pylinkirc.protocols. self.protoname = None # This is updated in init_vars()
self.proto = proto.Class(self) self.proto = self.irc = self # Backwards compat
# Protocol stuff
self.casemapping = 'rfc1459'
self.hook_map = {}
# Lock for updateTS to make sure only one thread can change the channel TS at one time.
self.ts_lock = threading.Lock()
# Lists required conf keys for the server block.
self.conf_keys = {'ip', 'port', 'hostname', 'sid', 'sidrange', 'protocol', 'sendpass',
'recvpass'}
# Defines a set of PyLink protocol capabilities
self.protocol_caps = set()
# These options depend on self.serverdata from above to be set. # These options depend on self.serverdata from above to be set.
self.encoding = None self.encoding = None
@ -105,6 +119,8 @@ class PyLinkNetworkCore(utils.DeprecatedAttributesObject, utils.CamelCaseToSnake
(Re)sets an IRC object to its default state. This should be called when (Re)sets an IRC object to its default state. This should be called when
an IRC object is first created, and on every reconnection to a network. an IRC object is first created, and on every reconnection to a network.
""" """
self.protoname = __name__.split('.')[-1] # Remove leading pylinkirc.protocols.
self.encoding = self.serverdata.get('encoding') or 'utf-8' self.encoding = self.serverdata.get('encoding') or 'utf-8'
self.pingfreq = self.serverdata.get('pingfreq') or 90 self.pingfreq = self.serverdata.get('pingfreq') or 90
self.pingtimeout = self.pingfreq * 3 self.pingtimeout = self.pingfreq * 3
@ -394,6 +410,31 @@ class PyLinkNetworkCore(utils.DeprecatedAttributesObject, utils.CamelCaseToSnake
log.debug('(%s) _post_disconnect: Clearing state via init_vars().', self.name) log.debug('(%s) _post_disconnect: Clearing state via init_vars().', self.name)
self.init_vars() self.init_vars()
def validate_server_conf(self):
return
def has_cap(self, capab):
"""
Returns whether this protocol module instance has the requested capability.
"""
return capab.lower() in self.protocol_caps
def remove_client(self, numeric):
"""Internal function to remove a client from our internal state."""
for c, v in self.irc.channels.copy().items():
v.removeuser(numeric)
# Clear empty non-permanent channels.
if not (self.irc.channels[c].users or ((self.irc.cmodes.get('permanent'), None) in self.irc.channels[c].modes)):
del self.irc.channels[c]
assert numeric not in v.users, "IrcChannel's removeuser() is broken!"
sid = self.irc.get_server(numeric)
log.debug('Removing client %s from self.irc.users', numeric)
del self.irc.users[numeric]
log.debug('Removing client %s from self.irc.servers[%s].users', numeric, sid)
self.irc.servers[sid].users.discard(numeric)
class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore): class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
def to_lower(self, text): def to_lower(self, text):
"""Returns a lowercase representation of text based on the IRC object's """Returns a lowercase representation of text based on the IRC object's
@ -1009,6 +1050,85 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
result = not result result = not result
return result return result
def updateTS(self, sender, channel, their_ts, modes=None):
"""
Merges modes of a channel given the remote TS and a list of modes.
"""
# Okay, so the situation is that we have 6 possible TS/sender combinations:
# | our TS lower | TS equal | their TS lower
# mode origin is us | OVERWRITE | MERGE | IGNORE
# mode origin is uplink | IGNORE | MERGE | OVERWRITE
if modes is None:
modes = []
def _clear():
log.debug("(%s) Clearing local modes from channel %s due to TS change", self.irc.name,
channel)
self.irc.channels[channel].modes.clear()
for p in self.irc.channels[channel].prefixmodes.values():
for user in p.copy():
if not self.irc.is_internal_client(user):
p.discard(user)
def _apply():
if modes:
log.debug("(%s) Applying modes on channel %s (TS ok)", self.irc.name,
channel)
self.irc.apply_modes(channel, modes)
# Use a lock so only one thread can change a channel's TS at once: this prevents race
# conditions from desyncing the channel list.
with self.ts_lock:
our_ts = self.irc.channels[channel].ts
assert type(our_ts) == int, "Wrong type for our_ts (expected int, got %s)" % type(our_ts)
assert type(their_ts) == int, "Wrong type for their_ts (expected int, got %s)" % type(their_ts)
# Check if we're the mode sender based on the UID / SID given.
our_mode = self.irc.is_internal_client(sender) or self.irc.is_internal_server(sender)
log.debug("(%s/%s) our_ts: %s; their_ts: %s; is the mode origin us? %s", self.irc.name,
channel, our_ts, their_ts, our_mode)
if their_ts == our_ts:
log.debug("(%s/%s) remote TS of %s is equal to our %s; mode query %s",
self.irc.name, channel, their_ts, our_ts, modes)
# Their TS is equal to ours. Merge modes.
_apply()
elif (their_ts < our_ts):
if their_ts < 750000:
log.warning('(%s) Possible desync? Not setting bogus TS %s on channel %s', self.irc.name, their_ts, channel)
else:
log.debug('(%s) Resetting channel TS of %s from %s to %s (remote has lower TS)',
self.irc.name, channel, our_ts, their_ts)
self.irc.channels[channel].ts = their_ts
# Remote TS was lower and we're receiving modes. Clear the modelist and apply theirs.
_clear()
_apply()
# TODO: these wrappers really need to be standardized
def _get_SID(self, sname):
"""Returns the SID of a server with the given name, if present."""
name = sname.lower()
for k, v in self.irc.servers.items():
if v.name.lower() == name:
return k
else:
return sname # Fall back to given text instead of None
_getSid = _get_SID
def _get_UID(self, target):
"""Converts a nick argument to its matching UID. This differs from irc.nick_to_uid()
in that it returns the original text instead of None, if no matching nick is found."""
target = self.irc.nick_to_uid(target) or target
return target
_getUid = _get_UID
class IRCNetwork(PyLinkNetworkCoreWithUtils): class IRCNetwork(PyLinkNetworkCoreWithUtils):
def __init__(self, *args): def __init__(self, *args):
super().__init__(*args) super().__init__(*args)
@ -1433,122 +1553,3 @@ class Channel():
return sorted(result, key=self.sortPrefixes) return sorted(result, key=self.sortPrefixes)
IrcChannel = Channel IrcChannel = Channel
class Protocol():
"""Base Protocol module class for PyLink."""
def __init__(self, irc):
self.irc = irc
self.casemapping = 'rfc1459'
self.hook_map = {}
# Lock for updateTS to make sure only one thread can change the channel TS at one time.
self.ts_lock = threading.Lock()
# Lists required conf keys for the server block.
self.conf_keys = {'ip', 'port', 'hostname', 'sid', 'sidrange', 'protocol', 'sendpass',
'recvpass'}
# Defines a set of PyLink protocol capabilities
self.protocol_caps = set()
def validateServerConf(self):
return
def hasCap(self, capab):
"""
Returns whether this protocol module instance has the requested capability.
"""
return capab.lower() in self.protocol_caps
def removeClient(self, numeric):
"""Internal function to remove a client from our internal state."""
for c, v in self.irc.channels.copy().items():
v.removeuser(numeric)
# Clear empty non-permanent channels.
if not (self.irc.channels[c].users or ((self.irc.cmodes.get('permanent'), None) in self.irc.channels[c].modes)):
del self.irc.channels[c]
assert numeric not in v.users, "IrcChannel's removeuser() is broken!"
sid = self.irc.get_server(numeric)
log.debug('Removing client %s from self.irc.users', numeric)
del self.irc.users[numeric]
log.debug('Removing client %s from self.irc.servers[%s].users', numeric, sid)
self.irc.servers[sid].users.discard(numeric)
def updateTS(self, sender, channel, their_ts, modes=None):
"""
Merges modes of a channel given the remote TS and a list of modes.
"""
# Okay, so the situation is that we have 6 possible TS/sender combinations:
# | our TS lower | TS equal | their TS lower
# mode origin is us | OVERWRITE | MERGE | IGNORE
# mode origin is uplink | IGNORE | MERGE | OVERWRITE
if modes is None:
modes = []
def _clear():
log.debug("(%s) Clearing local modes from channel %s due to TS change", self.irc.name,
channel)
self.irc.channels[channel].modes.clear()
for p in self.irc.channels[channel].prefixmodes.values():
for user in p.copy():
if not self.irc.is_internal_client(user):
p.discard(user)
def _apply():
if modes:
log.debug("(%s) Applying modes on channel %s (TS ok)", self.irc.name,
channel)
self.irc.apply_modes(channel, modes)
# Use a lock so only one thread can change a channel's TS at once: this prevents race
# conditions from desyncing the channel list.
with self.ts_lock:
our_ts = self.irc.channels[channel].ts
assert type(our_ts) == int, "Wrong type for our_ts (expected int, got %s)" % type(our_ts)
assert type(their_ts) == int, "Wrong type for their_ts (expected int, got %s)" % type(their_ts)
# Check if we're the mode sender based on the UID / SID given.
our_mode = self.irc.is_internal_client(sender) or self.irc.is_internal_server(sender)
log.debug("(%s/%s) our_ts: %s; their_ts: %s; is the mode origin us? %s", self.irc.name,
channel, our_ts, their_ts, our_mode)
if their_ts == our_ts:
log.debug("(%s/%s) remote TS of %s is equal to our %s; mode query %s",
self.irc.name, channel, their_ts, our_ts, modes)
# Their TS is equal to ours. Merge modes.
_apply()
elif (their_ts < our_ts):
if their_ts < 750000:
log.warning('(%s) Possible desync? Not setting bogus TS %s on channel %s', self.irc.name, their_ts, channel)
else:
log.debug('(%s) Resetting channel TS of %s from %s to %s (remote has lower TS)',
self.irc.name, channel, our_ts, their_ts)
self.irc.channels[channel].ts = their_ts
# Remote TS was lower and we're receiving modes. Clear the modelist and apply theirs.
_clear()
_apply()
def _get_SID(self, sname):
"""Returns the SID of a server with the given name, if present."""
name = sname.lower()
for k, v in self.irc.servers.items():
if v.name.lower() == name:
return k
else:
return sname # Fall back to given text instead of None
_getSid = _get_SID
def _get_UID(self, target):
"""Converts a nick argument to its matching UID. This differs from irc.nick_to_uid()
in that it returns the original text instead of None, if no matching nick is found."""
target = self.irc.nick_to_uid(target) or target
return target
_getUid = _get_UID

View File

@ -6,11 +6,11 @@ import time
import re import re
from collections import defaultdict from collections import defaultdict
from pylinkirc.classes import Protocol from pylinkirc.classes import IRCNetwork
from pylinkirc.log import log from pylinkirc.log import log
from pylinkirc import utils from pylinkirc import utils
class IRCCommonProtocol(Protocol): class IRCCommonProtocol(IRCNetwork):
def validateServerConf(self): def validateServerConf(self):
"""Validates that the server block given contains the required keys.""" """Validates that the server block given contains the required keys."""
for k in self.conf_keys: for k in self.conf_keys:

2
pylink
View File

@ -85,7 +85,7 @@ if __name__ == '__main__':
else: else:
# Fetch the correct protocol module # Fetch the correct protocol module
proto = utils.getProtocolModule(protoname) proto = utils.getProtocolModule(protoname)
world.networkobjects[network] = irc = classes.Irc(network, proto, conf.conf) world.networkobjects[network] = proto.Class(network)
world.started.set() world.started.set()
log.info("Loaded plugins: %s", ', '.join(sorted(world.plugins.keys()))) log.info("Loaded plugins: %s", ', '.join(sorted(world.plugins.keys())))