From 0deedbecf568ead79b680bb2b7ef2a3676adf0ea Mon Sep 17 00:00:00 2001
From: the-djmaze <>
Date: Wed, 23 Nov 2022 10:22:49 +0100
Subject: [PATCH] Use caching and a fetch queue to fetch avatars slowly without
overloading server #115
---
plugins/avatars/avatars.js | 96 ++++++++++++++++++++++++++------------
plugins/avatars/index.php | 51 +++++++++++++++-----
plugins/avatars/style.css | 19 ++++++--
3 files changed, 122 insertions(+), 44 deletions(-)
diff --git a/plugins/avatars/avatars.js b/plugins/avatars/avatars.js
index f18dbdf7f..7265be123 100644
--- a/plugins/avatars/avatars.js
+++ b/plugins/avatars/avatars.js
@@ -1,8 +1,57 @@
(rl => {
// if (rl.settings.get('Nextcloud'))
const
- templateId = 'MailMessageView';
-// DATA_IMAGE_USER_DOT_PIC = 'data:image/svg+xml;utf8,';
+ queue = [],
+ avatars = new Map,
+ templateId = 'MailMessageView',
+ getAvatarUid = msg => {
+ let from = msg.from[0],
+ bimi = 'pass' == from.dkimStatus ? 1 : 0;
+ return `${bimi}/${from.email}`;
+ },
+ getAvatar = msg => avatars.get(getAvatarUid(msg)),
+ runQueue = (() => {
+ let item = queue.shift();
+ while (item) {
+ let url = getAvatar(item[0]);
+ if (url) {
+ item[1](url);
+ item = queue.shift();
+ } else {
+ // TODO: fetch vCard from Nextcloud contacts
+// let cfg = rl.settings.get('Nextcloud'),
+
+ let from = item[0].from[0];
+ rl.pluginRemoteRequest((iError, data) => {
+ if (!iError && data?.Result.type) {
+ url = `data:${data.Result.type};base64,${data.Result.data}`;
+ avatars.set(getAvatarUid(item[0]), url);
+ item[1](url);
+ }
+ runQueue();
+ }, 'Avatar', {
+ bimi: 'pass' == from.dkimStatus ? 1 : 0,
+ email: from.email
+ });
+ break;
+ }
+ }
+ }).debounce(1000);
+
+ ko.bindingHandlers.fromPic = {
+ init: (element, self, dummy, msg) => {
+ if (msg) {
+ let url = getAvatar(msg),
+ fn = url=>{element.src = url};
+ if (url) {
+ fn(url);
+ } else {
+ queue.push([msg, fn]);
+ runQueue();
+ }
+ }
+ }
+ };
addEventListener('rl-view-model.create', e => {
if (templateId === e.detail.viewModelTemplateID) {
@@ -24,39 +73,28 @@
view.message.subscribe(msg => {
view.viewUserPicVisible(false);
if (msg) {
- let from = msg.from[0],
- bimi = 'pass' == from.dkimStatus ? 1 : 0;
-// view.viewUserPic(`?Avatar/${bimi}/${encodeURIComponent(from.email)}`);
-// view.viewUserPicVisible(true);
- rl.pluginRemoteRequest((iError, data) => {
- if (!iError && data?.Result.type) {
- view.viewUserPic(`data:${data.Result.type};base64,${data.Result.data}`);
+ let url = getAvatar(msg),
+ fn = url => {
+ view.viewUserPic(url);
view.viewUserPicVisible(true);
- }
- }, 'Avatar', {
- bimi: bimi,
- email: from.email
- });
+ };
+ if (url) {
+ fn(url);
+ } else {
+// let from = msg.from[0], bimi = 'pass' == from.dkimStatus ? 1 : 0;
+// view.viewUserPic(`?Avatar/${bimi}/${encodeURIComponent(from.email)}`);
+// view.viewUserPicVisible(true);
+ queue.push([msg, fn]);
+ runQueue();
+ }
}
});
}
-/*
+
if ('MailMessageList' === e.detail.viewModelTemplateID) {
- const
- template = document.getElementById('MailMessageList' ),
- messageCheckbox = template.content.querySelector('.messageCheckbox');
- messageCheckbox.dataset.bind = 'attr:{style:$root.viewUserPic($data)}';
- e.detail.viewUserPic = msg => {
- let from = msg.from[0],
- bimi = 'pass' == from.dkimStatus ? 1 : 0;
- return `background:no-repeat url("?Avatar/${bimi}/${encodeURIComponent(from.email)}") center / contain`;
- return `background:no-repeat url("?Avatar/${bimi}/${encodeURIComponent(from.email)}") right / 32px;width:68px`;
- };
- .checkboxMessage {
- background: #000;
- }
+ document.getElementById('MailMessageList').content.querySelector('.messageCheckbox')
+ .append(Element.fromHTML(`
`));
}
-*/
});
})(window.rl);
diff --git a/plugins/avatars/index.php b/plugins/avatars/index.php
index 61a8547c4..f23acc1f1 100644
--- a/plugins/avatars/index.php
+++ b/plugins/avatars/index.php
@@ -58,20 +58,38 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
$aResult = null;
- // TODO: lookup contacts vCard
- $oAccount = $oActions->getAccountFromToken();
- if ($oAccount) {
- $oAddressBookProvider = $oActions->AddressBookProvider($oAccount);
- if ($oAddressBookProvider) {
- $oContact = $oAddressBookProvider->GetContactByEmail($sEmail);
- if ($oContact && $oContact->vCard && $oContact->vCard['PHOTO']) {
- $aResult = [
- 'text/vcard',
- $oContact->vCard
- ];
+ $sAsciiEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true);
+ $sEmailId = \sha1(\strtolower($sAsciiEmail));
+
+ $sFile = \APP_PRIVATE_DATA . 'avatars/' . $sEmailId;
+ $aFiles = \glob("{$sFile}.*");
+ if ($aFiles) {
+ $aResult = [
+ \mime_content_type($aFiles[0]),
+ \file_get_contents($aFiles[0])
+ ];
+ $oActions->cacheByKey($sEmail);
+ return $aResult;
+ }
+
+ // TODO: lookup contacts vCard and return PHOTO value
+ /*
+ if (!$aResult) {
+ $oAccount = $oActions->getAccountFromToken();
+ if ($oAccount) {
+ $oAddressBookProvider = $oActions->AddressBookProvider($oAccount);
+ if ($oAddressBookProvider) {
+ $oContact = $oAddressBookProvider->GetContactByEmail($sEmail);
+ if ($oContact && $oContact->vCard && $oContact->vCard['PHOTO']) {
+ $aResult = [
+ 'text/vcard',
+ $oContact->vCard
+ ];
+ }
}
}
}
+ */
if (!$aResult) {
$sDomain = \explode('@', $sEmail);
@@ -89,7 +107,6 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
}
// TODO: make Gravatar optional
- $sAsciiEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true);
$aUrls[] = 'http://gravatar.com/avatar/'.\md5(\strtolower($sAsciiEmail)).'?s=80&d=404';
foreach ($aUrls as $sUrl) {
@@ -99,6 +116,16 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
}
}
+ if ($aResult) {
+ if (!\is_dir(\APP_PRIVATE_DATA . 'avatars')) {
+ \mkdir(\APP_PRIVATE_DATA . 'avatars', 0700);
+ }
+ \file_put_contents(
+ $sFile . \SnappyMail\File\MimeType::toExtension($aResult[0]),
+ $aResult[1]
+ );
+ }
+
if (!$aResult) {
$aServices = [
"services/{$sDomain}",
diff --git a/plugins/avatars/style.css b/plugins/avatars/style.css
index dc7eeb2e3..c9fadcd86 100644
--- a/plugins/avatars/style.css
+++ b/plugins/avatars/style.css
@@ -3,9 +3,22 @@
float: left;
width: 50px;
height: 50px;
- padding: 2px;
margin: 0 5px 0 0;
- background: #fff;
- border: 1px solid #ccc;
+ border: 1px solid var(--border-color, #ddd);
border-radius: 6px;
}
+/*
+.checkboxMessage {
+ background: #000 no-repeat center / contain;
+ background: #000 no-repeat right / 32px;
+ width:68px
+}
+*/
+#rl-app .messageCheckbox {
+ padding-right:0;
+ white-space:nowrap;
+}
+.messageCheckbox .fromPic {
+ margin: -0.6em 0 -0.6em 0.4em;
+ height: 2em;
+}