3
0
mirror of https://github.com/jlu5/PyLink.git synced 2024-11-01 01:09:22 +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):
"""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
(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.name = netname
self.conf = conf
self.conf = conf.conf
self.sid = None
self.serverdata = conf['servers'][netname]
self.botdata = conf['bot']
self.protoname = proto.__name__.split('.')[-1] # Remove leading pylinkirc.protocols.
self.proto = proto.Class(self)
self.serverdata = conf.conf['servers'][netname]
self.botdata = conf.conf['bot']
self.protoname = None # This is updated in init_vars()
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.
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
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.pingfreq = self.serverdata.get('pingfreq') or 90
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)
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):
def to_lower(self, text):
"""Returns a lowercase representation of text based on the IRC object's
@ -1009,6 +1050,85 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
result = not 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):
def __init__(self, *args):
super().__init__(*args)
@ -1433,122 +1553,3 @@ class Channel():
return sorted(result, key=self.sortPrefixes)
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
from collections import defaultdict
from pylinkirc.classes import Protocol
from pylinkirc.classes import IRCNetwork
from pylinkirc.log import log
from pylinkirc import utils
class IRCCommonProtocol(Protocol):
class IRCCommonProtocol(IRCNetwork):
def validateServerConf(self):
"""Validates that the server block given contains the required keys."""
for k in self.conf_keys:

2
pylink
View File

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