From 14a30250ca2f146ba37df2cf0881cf6121a55875 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Wed, 25 Oct 2023 15:00:30 +0000 Subject: [PATCH] users: Store avatars in redis Signed-off-by: Michael Tremer --- src/backend/accounts.py | 53 +++++++++++------------------------ src/templates/users/show.html | 10 ------- src/web/users.py | 14 ++++----- 3 files changed, 24 insertions(+), 53 deletions(-) diff --git a/src/backend/accounts.py b/src/backend/accounts.py index b6a3e1b3..a9942085 100644 --- a/src/backend/accounts.py +++ b/src/backend/accounts.py @@ -178,14 +178,7 @@ class Accounts(Object): self.search_base = self.settings.get("ldap_search_base") def __len__(self): - count = self.memcache.get("accounts:count") - - if count is None: - count = self._count("(objectClass=person)") - - self.memcache.set("accounts:count", count, 300) - - return count + return self._count("(objectClass=person)") def __iter__(self): accounts = self._search("(objectClass=person)") @@ -1227,17 +1220,23 @@ class Account(LDAPObject): # Avatar - def has_avatar(self): - has_avatar = self.memcache.get("accounts:%s:has-avatar" % self.uid) - if has_avatar is None: - has_avatar = True if self.get_avatar() else False + @property + def avatar_hash(self): + payload = ( + self.uid, + "%s" % self.modified_at, + ) + + # String the payload together + payload = "-".join(payload) - # Cache avatar status for up to 24 hours - self.memcache.set("accounts:%s:has-avatar" % self.uid, has_avatar, 3600 * 24) + # Run MD5() over the payload + h = hashlib.new("md5", payload.encode()) - return has_avatar + return h.hexdigest()[:7] def avatar_url(self, size=None, absolute=False): + # This cannot be async because we are calling it from the template engine url = "/users/%s.jpg?h=%s" % (self.uid, self.avatar_hash) # Return an absolute URL @@ -1249,7 +1248,7 @@ class Account(LDAPObject): return url - def get_avatar(self, size=None): + async def get_avatar(self, size=None): photo = self._get_bytes("jpegPhoto") # Exit if no avatar is available @@ -1261,7 +1260,7 @@ class Account(LDAPObject): return photo # Try to retrieve something from the cache - avatar = self.memcache.get("accounts:%s:avatar:%s" % (self.dn, size)) + avatar = await self.backend.cache.get("accounts:%s:avatar:%s" % (self.dn, size)) if avatar: return avatar @@ -1269,31 +1268,13 @@ class Account(LDAPObject): avatar = util.generate_thumbnail(photo, size, square=True) # Save to cache for 15m - self.memcache.set("accounts:%s:avatar:%s" % (self.dn, size), avatar, 900) + await self.backend.cache.set("accounts:%s:avatar:%s" % (self.dn, size), avatar, 900) return avatar - @property - def avatar_hash(self): - hash = self.memcache.get("accounts:%s:avatar-hash" % self.dn) - if not hash: - h = hashlib.new("md5") - h.update(self.get_avatar() or b"") - hash = h.hexdigest()[:7] - - self.memcache.set("accounts:%s:avatar-hash" % self.dn, hash, 86400) - - return hash - def upload_avatar(self, avatar): self._set("jpegPhoto", avatar) - # Delete cached avatar status - self.memcache.delete("accounts:%s:has-avatar" % self.dn) - - # Delete avatar hash - self.memcache.delete("accounts:%s:avatar-hash" % self.dn) - # Consent to promotional emails def get_consents_to_promotional_emails(self): diff --git a/src/templates/users/show.html b/src/templates/users/show.html index e806885b..edfedbd2 100644 --- a/src/templates/users/show.html +++ b/src/templates/users/show.html @@ -105,16 +105,6 @@ {{ _("Edit Profile") }} - - {# Suggest uploading an avatar if this user does not have one #} - {% elif not current_user.has_avatar() %} -
- {{ _("Upload An Avatar!") }} - - {{ _("A picture says more than a thousand words") }} - - {{ _("Upload Avatar") }} -
{% end %} {% end %} diff --git a/src/web/users.py b/src/web/users.py index 53b34341..246a9300 100644 --- a/src/web/users.py +++ b/src/web/users.py @@ -45,7 +45,7 @@ class ShowHandler(base.BaseHandler): class AvatarHandler(base.BaseHandler): - def get(self, uid): + async def get(self, uid): # Get the desired size of the avatar file size = self.get_argument("size", None) @@ -65,14 +65,14 @@ class AvatarHandler(base.BaseHandler): self.set_expires(31536000) # Resize avatar - avatar = account.get_avatar(size) + avatar = await account.get_avatar(size) # If there is no avatar, we serve a default image if not avatar: logging.debug("No avatar uploaded for %s" % account) # Generate a random avatar with only one letter - avatar = self._get_avatar(account, size=size) + avatar = await self._get_avatar(account, size=size) # Guess content type type = imghdr.what(None, avatar) @@ -91,7 +91,7 @@ class AvatarHandler(base.BaseHandler): # Deliver payload self.finish(avatar) - def _get_avatar(self, account, size=None, **args): + async def _get_avatar(self, account, size=None, **args): letter = ("%s" % account)[0].upper() if size is None: @@ -105,12 +105,12 @@ class AvatarHandler(base.BaseHandler): key = "avatar:letter:%s:%s" % (letter, size) # Fetch avatar from the cache - avatar = self.memcached.get(key) + avatar = await self.backend.cache.get(key) if not avatar: avatar = self._make_avatar(letter, size=size, **args) # Cache for forever - self.memcached.set(key, avatar) + await self.backend.cache.set(key, avatar) return avatar @@ -162,7 +162,7 @@ class EditHandler(base.BaseHandler): self.render("users/edit.html", account=account, countries=countries.get_all()) @tornado.web.authenticated - def post(self, uid): + async def post(self, uid): account = self.backend.accounts.get_by_uid(uid) if not account: raise tornado.web.HTTPError(404, "Could not find account %s" % uid) -- 2.47.3