Skip to content

Commit d4eff27

Browse files
committed
Refactor replace_user_data() and clean up global user/channel list access
1 parent 8e15f92 commit d4eff27

1 file changed

Lines changed: 110 additions & 80 deletions

File tree

plugins/chan_track.py

Lines changed: 110 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@
1111
from operator import attrgetter
1212
from weakref import WeakValueDictionary
1313

14+
import cloudbot.bot
1415
from cloudbot import hook
1516
from cloudbot.util.parsers.irc import Prefix
1617

18+
logger = cloudbot.bot.logger
19+
1720

1821
class WeakDict(dict):
1922
# Subclass dict to allow it to be weakly referenced
@@ -69,12 +72,54 @@ def getuser(self, nick):
6972
return value
7073

7174

75+
# region util functions
76+
77+
78+
def get_users(conn):
79+
"""
80+
:type conn: cloudbot.client.Client
81+
:rtype: UsersDict
82+
"""
83+
return conn.memory.setdefault("users", UsersDict)
84+
85+
86+
def get_chans(conn):
87+
"""
88+
:type conn: cloudbot.client.Client
89+
:rtype: ChanDict
90+
"""
91+
return conn.memory.setdefault("chan_data", ChanDict())
92+
93+
94+
def get_channel_member(chan_data, user_data):
95+
"""
96+
:type chan_data: dict
97+
:type user_data: dict
98+
:rtype: dict
99+
"""
100+
nick = user_data['nick']
101+
try:
102+
data = chan_data['users'][nick]
103+
except KeyError:
104+
chan_data['users'][nick] = data = WeakDict(
105+
user=user_data, chan=weakref.proxy(chan_data), status=[]
106+
)
107+
108+
# make sure the membership is stored on the user too
109+
# just in case
110+
user_data['channels'][chan_data['name']] = data
111+
return data
112+
113+
114+
# endregion util functions
115+
116+
72117
def update_chan_data(conn, chan):
73118
"""
74119
:type conn: cloudbot.client.Client
75120
:type chan: str
76121
"""
77-
chan_data = conn.memory["chan_data"].getchan(chan)
122+
chan_data = get_chans(conn).getchan(chan)
78123
chan_data["receiving_names"] = False
79124
conn.cmd("NAMES", chan)
80125

@@ -147,10 +192,10 @@ def clean_conn_data(conn):
147192
"""
148193
:type conn: cloudbot.client.Client
149194
"""
150-
for user in conn.memory.get("users", {}).values():
195+
for user in get_users(conn).values():
151196
clean_user_data(user)
152197

153-
for chan in conn.memory.get("chan_data", {}).values():
198+
for chan in get_chans(conn).values():
154199
clean_chan_data(chan)
155200

156201

@@ -168,8 +213,8 @@ def init_chan_data(conn, _clear=True):
168213
:type conn: cloudbot.client.Client
169214
:type _clear: bool
170215
"""
171-
chan_data = conn.memory.setdefault("chan_data", ChanDict())
172-
users = conn.memory.setdefault("users", UsersDict())
216+
chan_data = get_chans(conn)
217+
users = get_users(conn)
173218

174219
if not (isinstance(chan_data, ChanDict) and isinstance(users, UsersDict)):
175220
del conn.memory["chan_data"]
@@ -182,13 +227,23 @@ def init_chan_data(conn, _clear=True):
182227
users.clear()
183228

184229

185-
def add_user_membership(user, chan, membership):
186-
"""
187-
:type user: dict
188-
:type chan: str
189-
:type membership: dict
190-
"""
191-
user["channels"][chan] = membership
230+
def parse_names_item(item, statuses, has_multi_prefix, has_userhost):
231+
user_status = []
232+
while item[:1] in statuses:
233+
status, item = item[:1], item[1:]
234+
user_status.append(statuses[status])
235+
if not has_multi_prefix:
236+
# Only remove one status prefix if we don't have multi prefix enabled
237+
break
238+
239+
user_status.sort(key=attrgetter('level'), reverse=True)
240+
241+
if has_userhost:
242+
prefix = Prefix.parse(item)
243+
else:
244+
prefix = Prefix(item)
245+
246+
return prefix.nick, prefix.user, prefix.host, user_status
192247

193248

194249
def replace_user_data(conn, chan_data):
@@ -197,48 +252,23 @@ def replace_user_data(conn, chan_data):
197252
:type chan_data: dict
198253
"""
199254
statuses = {status.prefix: status for status in set(conn.memory["server_info"]["statuses"].values())}
200-
users = conn.memory["users"]
201-
old_users = chan_data["users"]
202255
new_data = chan_data.pop("new_users", [])
203256
new_users = KeyFoldDict()
204257
has_uh_i_n = is_cap_available(conn, "userhost-in-names")
205258
has_multi_pfx = is_cap_available(conn, "multi-prefix")
206259
for name in new_data:
207-
user_data = WeakDict(channels=KeyFoldWeakValueDict())
208-
memb_data = WeakDict(user=user_data, chan=weakref.proxy(chan_data))
209-
user_statuses = []
210-
while name[:1] in statuses:
211-
status, name = name[:1], name[1:]
212-
user_statuses.append(statuses[status])
213-
if not has_multi_pfx:
214-
# Only run once if we don't have multi-prefix enabled
215-
break
216-
217-
user_statuses.sort(key=attrgetter("level"), reverse=True)
218-
# At this point, user_status[0] will the the Status object representing the highest status the user has
219-
# in the channel
220-
memb_data["status"] = user_statuses
221-
222-
if has_uh_i_n:
223-
pfx = Prefix.parse(name)
224-
user_data.update(nick=pfx.nick, ident=pfx.user, host=pfx.host)
225-
else:
226-
user_data["nick"] = name
227-
228-
nick = user_data["nick"]
229-
new_users[nick] = memb_data
230-
if nick in old_users:
231-
old_data = old_users[nick] # Old membership object
232-
old_data.update(memb_data) # New data takes priority over old data
233-
memb_data.update(old_data)
234-
235-
old_user_data = users.getuser(nick)
236-
user_data["channels"] = old_user_data["channels"]
237-
add_user_membership(user_data, chan_data["name"], memb_data)
238-
old_user_data.update(user_data)
239-
user_data = old_user_data
240-
memb_data["user"] = user_data
260+
nick, ident, host, status = parse_names_item(name, statuses, has_multi_pfx, has_uh_i_n)
261+
user_data = get_users(conn).getuser(nick)
262+
user_data.update(
263+
nick=nick,
264+
ident=ident,
265+
host=host,
266+
)
241267

268+
new_users[nick] = memb_data = get_channel_member(chan_data, user_data)
269+
memb_data['status'] = status
270+
271+
old_users = chan_data["users"]
242272
old_users.clear()
243273
old_users.update(new_users) # Reassigning the dict would break other references to the data, so just update instead
244274

@@ -251,7 +281,7 @@ def on_names(conn, irc_paramlist, irc_command):
251281
:type irc_command: str
252282
"""
253283
chan = irc_paramlist[2 if irc_command == '353' else 1]
254-
chan_data = conn.memory["chan_data"].getchan(chan)
284+
chan_data = get_chans(conn).getchan(chan)
255285
if irc_command == '366':
256286
chan_data["receiving_names"] = False
257287
replace_user_data(conn, chan_data)
@@ -301,7 +331,7 @@ def perm_check(chan, conn, nick):
301331
if not (chan and conn):
302332
return False
303333

304-
chans = conn.memory["chan_data"]
334+
chans = get_chans(conn)
305335
try:
306336
chan_data = chans[chan]
307337
except KeyError:
@@ -324,7 +354,7 @@ def dumpchans(conn):
324354
"""- Dumps all stored channel data for this connection to the console
325355
:type conn: cloudbot.client.Client
326356
"""
327-
data = conn.memory["chan_data"]
357+
data = get_chans(conn)
328358
lines = list(dump_dict(data))
329359
print('\n'.join(lines))
330360
return "Printed {} channel records totalling {} lines of data to the console.".format(len(data), len(lines))
@@ -335,7 +365,7 @@ def dumpusers(conn):
335365
"""- Dumps all stored user data for this connection to the console
336366
:type conn: cloudbot.client.Client
337367
"""
338-
data = conn.memory["users"]
368+
data = get_users(conn)
339369
lines = list(dump_dict(data))
340370
print('\n'.join(lines))
341371
return "Printed {} user records totalling {} lines of data to the console.".format(len(data), len(lines))
@@ -395,15 +425,13 @@ def on_join(nick, user, host, conn, irc_paramlist):
395425

396426
data.update(account=acct, realname=realname)
397427

398-
users = conn.memory['users']
428+
users = get_users(conn)
399429

400430
user_data = users.getuser(nick)
401431
user_data.update(data)
402432

403-
chan_data = conn.memory["chan_data"].getchan(chan)
404-
memb_data = WeakDict(chan=weakref.proxy(chan_data), user=user_data, status=[])
405-
chan_data["users"][nick] = memb_data
406-
add_user_membership(user_data, chan, memb_data)
433+
chan_data = get_chans(conn).getchan(chan)
434+
get_channel_member(chan_data, user_data)
407435

408436

409437
@hook.irc_raw('MODE')
@@ -420,14 +448,9 @@ def on_mode(chan, irc_paramlist, conn):
420448
statuses = serv_info["statuses"]
421449
status_modes = {status.mode for status in statuses.values()}
422450
mode_types = serv_info["channel_modes"]
423-
chans = conn.memory["chan_data"]
424451

425-
try:
426-
chan_data = chans[chan]
427-
except KeyError:
428-
return
452+
chan_data = get_chans(conn).getchan(chan)
429453

430-
chan_users = chan_data["users"]
431454
modes = irc_paramlist[1]
432455
mode_params = irc_paramlist[2:]
433456
new_modes = {}
@@ -450,14 +473,21 @@ def on_mode(chan, irc_paramlist, conn):
450473
param = mode_params.pop(0)
451474

452475
if is_status:
453-
memb = chan_users[param]
476+
user = get_users(conn).getuser(param)
477+
memb = get_channel_member(chan_data, user)
454478
status = statuses[c]
455479
memb_status = memb["status"]
456480
if adding:
457481
memb_status.append(status)
458482
memb_status.sort(key=attrgetter("level"), reverse=True)
459483
else:
460-
memb_status.remove(status)
484+
if status in memb_status:
485+
memb_status.remove(status)
486+
else:
487+
logger.debug(
488+
"[%s|chantrack] Attempt to remove status %s from user %s in channel %s",
489+
conn.name, status, user['nick'], chan
490+
)
461491

462492

463493
@hook.irc_raw('PART')
@@ -470,7 +500,7 @@ def on_part(chan, nick, conn):
470500
if chan.startswith(':'):
471501
chan = chan[1:]
472502

473-
channels = conn.memory["chan_data"]
503+
channels = get_chans(conn)
474504
if nick.casefold() == conn.nick.casefold():
475505
del channels[chan]
476506
else:
@@ -494,10 +524,10 @@ def on_quit(nick, conn):
494524
:type nick: str
495525
:type conn: cloudbot.client.Client
496526
"""
497-
users = conn.memory["users"]
527+
users = get_users(conn)
498528
if nick in users:
499-
user = users[nick]
500-
for memb in user.get("channels", {}).values():
529+
user = users.pop(nick)
530+
for memb in user['channels'].values():
501531
chan = memb["chan"]
502532
del chan["users"][nick]
503533

@@ -509,15 +539,15 @@ def on_nick(nick, irc_paramlist, conn):
509539
:type irc_paramlist: cloudbot.util.parsers.irc.ParamList
510540
:type conn: cloudbot.client.Client
511541
"""
512-
users = conn.memory["users"]
542+
users = get_users(conn)
513543
new_nick = irc_paramlist[0]
514544
if new_nick.startswith(':'):
515545
new_nick = new_nick[1:]
516546

517547
user = users.pop(nick)
518548
users[new_nick] = user
519549
user["nick"] = new_nick
520-
for memb in user.get("channels", {}).values():
550+
for memb in user['channels'].values():
521551
chan_users = memb["chan"]["users"]
522552
chan_users[new_nick] = chan_users.pop(nick)
523553

@@ -529,7 +559,7 @@ def on_account(conn, nick, irc_paramlist):
529559
:type irc_paramlist: cloudbot.util.parsers.irc.ParamList
530560
:type conn: cloudbot.client.Client
531561
"""
532-
conn.memory["users"][nick]["account"] = irc_paramlist[0]
562+
get_users(conn).getuser(nick)["account"] = irc_paramlist[0]
533563

534564

535565
@hook.irc_raw('CHGHOST')
@@ -540,7 +570,7 @@ def on_chghost(conn, nick, irc_paramlist):
540570
:type conn: cloudbot.client.Client
541571
"""
542572
ident, host = irc_paramlist
543-
conn.memory["users"][nick].update(ident=ident, host=host)
573+
get_users(conn).getuser(nick).update(ident=ident, host=host)
544574

545575

546576
@hook.irc_raw('AWAY')
@@ -555,7 +585,7 @@ def on_away(conn, nick, irc_paramlist):
555585
else:
556586
reason = None
557587

558-
conn.memory["users"][nick].update(is_away=(reason is not None), away_message=reason)
588+
get_users(conn).getuser(nick).update(is_away=(reason is not None), away_message=reason)
559589

560590

561591
@hook.irc_raw('352')
@@ -566,7 +596,7 @@ def on_who(conn, irc_paramlist):
566596
"""
567597
_, _, ident, host, server, nick, status, realname = irc_paramlist
568598
realname = realname.split(None, 1)[1]
569-
user = conn.memory["users"][nick]
599+
user = get_users(conn).getuser(nick)
570600
status = list(status)
571601
is_away = status.pop(0) == "G"
572602
is_oper = status[:1] == "*"
@@ -587,7 +617,7 @@ def on_whois_name(conn, irc_paramlist):
587617
:type conn: cloudbot.client.Client
588618
"""
589619
_, nick, ident, host, _, realname = irc_paramlist
590-
conn.memory["users"][nick].update(ident=ident, host=host, realname=realname)
620+
get_users(conn).getuser(nick).update(ident=ident, host=host, realname=realname)
591621

592622

593623
@hook.irc_raw('330')
@@ -597,7 +627,7 @@ def on_whois_acct(conn, irc_paramlist):
597627
:type conn: cloudbot.client.Client
598628
"""
599629
_, nick, acct = irc_paramlist[:2]
600-
conn.memory["users"][nick]["account"] = acct
630+
get_users(conn).getuser(nick)["account"] = acct
601631

602632

603633
@hook.irc_raw('301')
@@ -607,7 +637,7 @@ def on_whois_away(conn, irc_paramlist):
607637
:type conn: cloudbot.client.Client
608638
"""
609639
_, nick, msg = irc_paramlist
610-
conn.memory["users"][nick].update(is_away=True, away_message=msg)
640+
get_users(conn).getuser(nick).update(is_away=True, away_message=msg)
611641

612642

613643
@hook.irc_raw('312')
@@ -617,7 +647,7 @@ def on_whois_server(conn, irc_paramlist):
617647
:type conn: cloudbot.client.Client
618648
"""
619649
_, nick, server, _ = irc_paramlist
620-
conn.memory["users"][nick].update(server=server)
650+
get_users(conn).getuser(nick).update(server=server)
621651

622652

623653
@hook.irc_raw('313')
@@ -627,4 +657,4 @@ def on_whois_oper(conn, irc_paramlist):
627657
:type conn: cloudbot.client.Client
628658
"""
629659
nick = irc_paramlist[1]
630-
conn.memory["users"][nick].update(is_oper=True)
660+
get_users(conn).getuser(nick).update(is_oper=True)

0 commit comments

Comments
 (0)