Data system refactor and core redesign for public.

Redesigned data and core systems to be public-capable.
This commit is contained in:
2021-09-12 11:04:49 +03:00
parent 459a728968
commit 0183b63c55
33 changed files with 1170 additions and 790 deletions

145
bot/core/lion.py Normal file
View File

@@ -0,0 +1,145 @@
import pytz
from meta import client
from data import tables as tb
from settings import UserSettings
class Lion:
"""
Class representing a guild Member.
Mostly acts as a transparent interface to the corresponding Row,
but also adds some transaction caching logic to `coins` and `tracked_time`.
"""
__slots__ = ('guildid', 'userid', '_pending_coins', '_pending_time', '_member')
# Members with pending transactions
_pending = {} # userid -> User
# Lion cache. Currently lions don't expire
_lions = {} # (guildid, userid) -> Lion
def __init__(self, guildid, userid):
self.guildid = guildid
self.userid = userid
self._pending_coins = 0
self._pending_time = 0
self._member = None
self._lions[self.key] = self
@classmethod
def fetch(cls, guildid, userid):
"""
Fetch a Lion with the given member.
If they don't exist, creates them.
If possible, retrieves the user from the user cache.
"""
key = (guildid, userid)
if key in cls._lions:
return cls._lions[key]
else:
tb.lions.fetch_or_create(key)
return cls(guildid, userid)
@property
def key(self):
return (self.guildid, self.userid)
@property
def member(self):
"""
The discord `Member` corresponding to this user.
May be `None` if the member is no longer in the guild or the caches aren't populated.
Not guaranteed to be `None` if the member is not in the guild.
"""
if self._member is None:
guild = client.get_guild(self.guildid)
if guild:
self._member = guild.get_member(self.userid)
return self._member
@property
def data(self):
"""
The Row corresponding to this user.
"""
return tb.lions.fetch(self.key)
@property
def settings(self):
"""
The UserSettings object for this user.
"""
return UserSettings(self.userid)
@property
def time(self):
"""
Amount of time the user has spent studying, accounting for pending values.
"""
return int(self.data.tracked_time + self._pending_time)
@property
def coins(self):
"""
Number of coins the user has, accounting for the pending value.
"""
return int(self.data.coins + self._pending_coins)
def localize(self, naive_utc_dt):
"""
Localise the provided naive UTC datetime into the user's timezone.
"""
timezone = self.settings.timezone.value
return naive_utc_dt.replace(tzinfo=pytz.UTC).astimezone(timezone)
def addCoins(self, amount, flush=True):
"""
Add coins to the user, optionally store the transaction in pending.
"""
self._pending_coins += amount
self._pending[self.key] = self
if flush:
self.flush()
def addTime(self, amount, flush=True):
"""
Add time to a user (in seconds), optionally storing the transaction in pending.
"""
self._pending_time += amount
self._pending[self.key] = self
if flush:
self.flush()
def flush(self):
"""
Flush any pending transactions to the database.
"""
self.sync(self)
@classmethod
def sync(cls, *lions):
"""
Flush pending transactions to the database.
Also refreshes the Row cache for updated lions.
"""
lions = lions or list(cls._pending.values())
if lions:
# Build userid to pending coin map
pending = [
(lion.guildid, lion.userid, int(lion._pending_coins), int(lion._pending_time))
for lion in lions
]
# Write to database
tb.lions.queries.add_pending(pending)
# Cleanup pending users
for lion in lions:
lion._pending_coins -= int(lion._pending_coins)
lion._pending_time -= int(lion._pending_time)
cls._pending.pop(lion.key, None)