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:
parent
f8155ff74c
commit
df18e318a8
251
classes.py
251
classes.py
@ -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
|
||||
|
@ -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
2
pylink
@ -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())))
|
||||
|
Loading…
Reference in New Issue
Block a user