]> git.ipfire.org Git - ipfire.org.git/commitdiff
users: Store avatars in redis
authorMichael Tremer <michael.tremer@ipfire.org>
Wed, 25 Oct 2023 15:00:30 +0000 (15:00 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Wed, 25 Oct 2023 15:00:30 +0000 (15:00 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/backend/accounts.py
src/templates/users/show.html
src/web/users.py

index b6a3e1b3d8ae0bfe437e66a875b954e866efd0a6..a9942085fcbb1a18d0df619284b339df8f3b824b 100644 (file)
@@ -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):
index e806885b564ae77c8a6182e7221b09a7f8f2b489..edfedbd2f0485fd4d9421f002abd24ad54f617a4 100644 (file)
 
                                                <a href="/users/{{ account.uid }}/edit#description">{{ _("Edit Profile") }}</a>
                                        </div>
-
-                               {# Suggest uploading an avatar if this user does not have one #}
-                               {% elif not current_user.has_avatar() %}
-                                       <div class="notification is-info">
-                                               <strong>{{ _("Upload An Avatar!") }}</strong>
-
-                                               {{ _("A picture says more than a thousand words") }}
-
-                                               <a href="/users/{{ account.uid }}/edit#avatar">{{ _("Upload Avatar") }}</a>
-                                       </div>
                                {% end %}
                        {% end %}
                </div>
index 53b3434122901cf48418afc424558ccbf6f6c2c9..246a930070a711672e004755e0a540a5d8047b62 100644 (file)
@@ -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)