diff options
Diffstat (limited to 'lisp/image/gravatar.el')
| -rw-r--r-- | lisp/image/gravatar.el | 111 |
1 files changed, 89 insertions, 22 deletions
diff --git a/lisp/image/gravatar.el b/lisp/image/gravatar.el index d1091e57cb5..3543be6de91 100644 --- a/lisp/image/gravatar.el +++ b/lisp/image/gravatar.el | |||
| @@ -39,6 +39,7 @@ | |||
| 39 | "Whether to cache retrieved gravatars." | 39 | "Whether to cache retrieved gravatars." |
| 40 | :type 'boolean | 40 | :type 'boolean |
| 41 | :group 'gravatar) | 41 | :group 'gravatar) |
| 42 | (make-obsolete-variable 'gravatar-automatic-caching nil "28.1") | ||
| 42 | 43 | ||
| 43 | (defcustom gravatar-cache-ttl 2592000 | 44 | (defcustom gravatar-cache-ttl 2592000 |
| 44 | "Time to live in seconds for gravatar cache entries. | 45 | "Time to live in seconds for gravatar cache entries. |
| @@ -48,6 +49,7 @@ is retrieved anew. The default value is 30 days." | |||
| 48 | ;; Restricted :type to number of seconds. | 49 | ;; Restricted :type to number of seconds. |
| 49 | :version "27.1" | 50 | :version "27.1" |
| 50 | :group 'gravatar) | 51 | :group 'gravatar) |
| 52 | (make-obsolete-variable 'gravatar-cache-ttl nil "28.1") | ||
| 51 | 53 | ||
| 52 | (defcustom gravatar-rating "g" | 54 | (defcustom gravatar-rating "g" |
| 53 | "Most explicit Gravatar rating level to allow. | 55 | "Most explicit Gravatar rating level to allow. |
| @@ -156,18 +158,58 @@ to track whether you're reading a specific mail." | |||
| 156 | (setq func | 158 | (setq func |
| 157 | (lambda (result) | 159 | (lambda (result) |
| 158 | (cond | 160 | (cond |
| 159 | (result | 161 | ((and |
| 160 | (funcall callback (format "%s://%s/avatar" | 162 | result ;there is a result |
| 161 | (cdar records) result))) | 163 | (let* ((data (mapcar (lambda (record) |
| 162 | ((> (length records) 1) | 164 | (dns-get 'data (cdr record))) |
| 163 | (pop records) | 165 | (dns-get 'answers result))) |
| 166 | (priorities (mapcar (lambda (r) | ||
| 167 | (dns-get 'priority r)) | ||
| 168 | data)) | ||
| 169 | (max-priority (if priorities | ||
| 170 | (apply #'max priorities) | ||
| 171 | 0)) | ||
| 172 | (sum 0) top) | ||
| 173 | ;; Attempt to find all records with the same maximal | ||
| 174 | ;; priority, and calculate the sum of their weights. | ||
| 175 | (dolist (ent data) | ||
| 176 | (when (= max-priority (dns-get 'priority ent)) | ||
| 177 | (setq sum (+ sum (dns-get 'weight ent))) | ||
| 178 | (push ent top))) | ||
| 179 | ;; In case there is more than one maximal priority | ||
| 180 | ;; record, choose one at random, while taking the | ||
| 181 | ;; individual record weights into consideration. | ||
| 182 | (catch 'done | ||
| 183 | (dolist (ent top) | ||
| 184 | (when (and (or (= 0 sum) | ||
| 185 | (<= 0 (random sum) | ||
| 186 | (dns-get 'weight ent))) | ||
| 187 | ;; Ensure that port and domain data are | ||
| 188 | ;; valid. In case non of the results | ||
| 189 | ;; were valid, `catch' will evaluate to | ||
| 190 | ;; nil, and the next cond clause will be | ||
| 191 | ;; tested. | ||
| 192 | (<= 1 (dns-get 'port ent) 65535) | ||
| 193 | (string-match-p "\\`[-.0-9A-Za-z]+\\'" | ||
| 194 | (dns-get 'target ent))) | ||
| 195 | (funcall callback | ||
| 196 | (url-normalize-url | ||
| 197 | (format "%s://%s:%s/avatar" | ||
| 198 | (cdar records) | ||
| 199 | (dns-get 'target ent) | ||
| 200 | (dns-get 'port ent)))) | ||
| 201 | (throw 'done t)) | ||
| 202 | (setq sum (- sum (dns-get 'weight ent)))))))) | ||
| 203 | ((setq records (cdr records)) | ||
| 204 | ;; In case there are at least two methods. | ||
| 164 | (dns-query-asynchronous | 205 | (dns-query-asynchronous |
| 165 | (concat (caar records) "._tcp." domain) | 206 | (concat (caar records) "._tcp." domain) |
| 166 | func 'SRV)) | 207 | func 'SRV)) |
| 167 | (t | 208 | (t ;fallback |
| 168 | (funcall callback "https://seccdn.libravatar.org/avatar"))))) | 209 | (funcall callback "https://seccdn.libravatar.org/avatar"))))) |
| 169 | (dns-query-asynchronous | 210 | (dns-query-asynchronous |
| 170 | (concat (caar records) "._tcp." domain) func 'SRV))))) | 211 | (concat (caar records) "._tcp." domain) |
| 212 | func 'SRV t))))) | ||
| 171 | 213 | ||
| 172 | (defun gravatar-hash (mail-address) | 214 | (defun gravatar-hash (mail-address) |
| 173 | "Return the Gravatar hash for MAIL-ADDRESS." | 215 | "Return the Gravatar hash for MAIL-ADDRESS." |
| @@ -206,19 +248,50 @@ to track whether you're reading a specific mail." | |||
| 206 | (search-forward "\n\n" nil t) | 248 | (search-forward "\n\n" nil t) |
| 207 | (buffer-substring (point) (point-max))))) | 249 | (buffer-substring (point) (point-max))))) |
| 208 | 250 | ||
| 251 | (defvar gravatar--cache (make-hash-table :test 'equal) | ||
| 252 | "Cache for gravatars.") | ||
| 253 | |||
| 209 | ;;;###autoload | 254 | ;;;###autoload |
| 210 | (defun gravatar-retrieve (mail-address callback &optional cbargs) | 255 | (defun gravatar-retrieve (mail-address callback &optional cbargs) |
| 211 | "Asynchronously retrieve a gravatar for MAIL-ADDRESS. | 256 | "Asynchronously retrieve a gravatar for MAIL-ADDRESS. |
| 212 | When finished, call CALLBACK as (apply CALLBACK GRAVATAR CBARGS), | 257 | When finished, call CALLBACK as (apply CALLBACK GRAVATAR CBARGS), |
| 213 | where GRAVATAR is either an image descriptor, or the symbol | 258 | where GRAVATAR is either an image descriptor, or the symbol |
| 214 | `error' if the retrieval failed." | 259 | `error' if the retrieval failed." |
| 215 | (gravatar-build-url | 260 | (let ((cached (gethash mail-address gravatar--cache))) |
| 216 | mail-address | 261 | (gravatar--prune-cache) |
| 217 | (lambda (url) | 262 | (if cached |
| 218 | (if (url-cache-expired url gravatar-cache-ttl) | 263 | (apply callback (cdr cached) cbargs) |
| 219 | (url-retrieve url #'gravatar-retrieved (list callback cbargs) t) | 264 | ;; Nothing in the cache, fetch it. |
| 220 | (with-current-buffer (url-fetch-from-cache url) | 265 | (gravatar-build-url |
| 221 | (gravatar-retrieved () callback cbargs)))))) | 266 | mail-address |
| 267 | (lambda (url) | ||
| 268 | (url-retrieve | ||
| 269 | url | ||
| 270 | (lambda (status) | ||
| 271 | (let* ((data (and (not (plist-get status :error)) | ||
| 272 | (gravatar-get-data))) | ||
| 273 | (image (and data (create-image data nil t)))) | ||
| 274 | ;; Store the image in the cache. | ||
| 275 | (when image | ||
| 276 | (setf (gethash mail-address gravatar--cache) | ||
| 277 | (cons (time-convert (current-time) 'integer) | ||
| 278 | image))) | ||
| 279 | (prog1 | ||
| 280 | (apply callback (if data image 'error) cbargs) | ||
| 281 | (kill-buffer)))) | ||
| 282 | nil t)))))) | ||
| 283 | |||
| 284 | (defun gravatar--prune-cache () | ||
| 285 | (let ((expired nil) | ||
| 286 | (time (- (time-convert (current-time) 'integer) | ||
| 287 | ;; Twelve hours. | ||
| 288 | (* 12 60 60)))) | ||
| 289 | (maphash (lambda (key val) | ||
| 290 | (when (< (car val) time) | ||
| 291 | (push key expired))) | ||
| 292 | gravatar--cache) | ||
| 293 | (dolist (key expired) | ||
| 294 | (remhash key gravatar--cache)))) | ||
| 222 | 295 | ||
| 223 | ;;;###autoload | 296 | ;;;###autoload |
| 224 | (defun gravatar-retrieve-synchronously (mail-address) | 297 | (defun gravatar-retrieve-synchronously (mail-address) |
| @@ -229,10 +302,8 @@ retrieval failed." | |||
| 229 | (gravatar-build-url mail-address (lambda (u) (setq url u))) | 302 | (gravatar-build-url mail-address (lambda (u) (setq url u))) |
| 230 | (while (not url) | 303 | (while (not url) |
| 231 | (sleep-for 0.01)) | 304 | (sleep-for 0.01)) |
| 232 | (with-current-buffer (if (url-cache-expired url gravatar-cache-ttl) | 305 | (with-current-buffer (url-retrieve-synchronously url t) |
| 233 | (url-retrieve-synchronously url t) | 306 | (gravatar-retrieved nil #'identity)))) |
| 234 | (url-fetch-from-cache url)) | ||
| 235 | (gravatar-retrieved () #'identity)))) | ||
| 236 | 307 | ||
| 237 | (defun gravatar-retrieved (status cb &optional cbargs) | 308 | (defun gravatar-retrieved (status cb &optional cbargs) |
| 238 | "Handle Gravatar response data in current buffer. | 309 | "Handle Gravatar response data in current buffer. |
| @@ -241,10 +312,6 @@ an image descriptor, or the symbol `error' on failure. | |||
| 241 | This function is intended as a callback for `url-retrieve'." | 312 | This function is intended as a callback for `url-retrieve'." |
| 242 | (let ((data (unless (plist-get status :error) | 313 | (let ((data (unless (plist-get status :error) |
| 243 | (gravatar-get-data)))) | 314 | (gravatar-get-data)))) |
| 244 | (and data ; Only cache on success. | ||
| 245 | url-current-object ; Only cache if not already cached. | ||
| 246 | gravatar-automatic-caching | ||
| 247 | (url-store-in-cache)) | ||
| 248 | (prog1 (apply cb (if data (create-image data nil t) 'error) cbargs) | 315 | (prog1 (apply cb (if data (create-image data nil t) 'error) cbargs) |
| 249 | (kill-buffer)))) | 316 | (kill-buffer)))) |
| 250 | 317 | ||