summaryrefslogtreecommitdiff
path: root/jabber-avatar.el
diff options
context:
space:
mode:
Diffstat (limited to 'jabber-avatar.el')
-rw-r--r--jabber-avatar.el234
1 files changed, 234 insertions, 0 deletions
diff --git a/jabber-avatar.el b/jabber-avatar.el
new file mode 100644
index 0000000..ac02523
--- /dev/null
+++ b/jabber-avatar.el
@@ -0,0 +1,234 @@
+;;; jabber-avatar.el --- generic functions for avatars
+
+;; Copyright (C) 2006, 2007, 2008 Magnus Henoch
+
+;; Author: Magnus Henoch <mange@freemail.hu>
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to
+;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
+
+;;; Commentary:
+
+;; There are several methods for transporting avatars in Jabber
+;; (JEP-0008, JEP-0084, JEP-0153). They all have in common that they
+;; identify avatars by their SHA1 checksum, and (at least partially)
+;; use Base64-encoded image data. Thus this library of support
+;; functions for interpreting and caching avatars.
+
+;; A contact with an avatar has the image in the avatar property of
+;; the JID symbol. Use `jabber-avatar-set' to set it.
+
+;;; Code:
+
+(require 'mailcap)
+(eval-when-compile (require 'cl))
+
+;;;; Variables
+
+(defgroup jabber-avatar nil
+ "Avatar related settings"
+ :group 'jabber)
+
+(defcustom jabber-avatar-cache-directory
+ (locate-user-emacs-file "jabber-avatar-cache" ".jabber-avatars")
+ "Directory to use for cached avatars"
+ :group 'jabber-avatar
+ :type 'directory)
+
+(defcustom jabber-avatar-verbose nil
+ "Display messages about irregularities with other people's avatars."
+ :group 'jabber-avatar
+ :type 'boolean)
+
+(defcustom jabber-avatar-max-width 96
+ "Maximum width of avatars."
+ :group 'jabber-avatar
+ :type 'integer)
+
+(defcustom jabber-avatar-max-height 96
+ "Maximum height of avatars."
+ :group 'jabber-avatar
+ :type 'integer)
+
+;;;; Avatar data handling
+
+(defstruct avatar sha1-sum mime-type url base64-data height width bytes)
+
+(defun jabber-avatar-from-url (url)
+ "Construct an avatar structure from the given URL.
+Retrieves the image to find info about it."
+ (with-current-buffer (let ((coding-system-for-read 'binary))
+ (url-retrieve-synchronously url))
+ (let* ((case-fold-search t)
+ (mime-type (ignore-errors
+ (search-forward-regexp "^content-type:[ \t]*\\(.*\\)$")
+ (match-string 1)))
+ (data (progn
+ (search-forward "\n\n")
+ (buffer-substring (point) (point-max)))))
+ (prog1
+ (jabber-avatar-from-data data nil mime-type)
+ (kill-buffer nil)))))
+
+(defun jabber-avatar-from-file (filename)
+ "Construct an avatar structure from FILENAME."
+ (require 'mailcap)
+ (let ((data (with-temp-buffer
+ (insert-file-contents-literally filename)
+ (buffer-string)))
+ (mime-type (when (string-match "\\.[^.]+$" filename)
+ (mailcap-extension-to-mime (match-string 0 filename)))))
+ (jabber-avatar-from-data data nil mime-type)))
+
+(defun jabber-avatar-from-base64-string (base64-string &optional mime-type)
+ "Construct an avatar stucture from BASE64-STRING.
+If MIME-TYPE is not specified, try to find it from the image data."
+ (jabber-avatar-from-data nil base64-string mime-type))
+
+(defun jabber-avatar-from-data (raw-data base64-string &optional mime-type)
+ "Construct an avatar structure from RAW-DATA and/or BASE64-STRING.
+If either is not provided, it is computed.
+If MIME-TYPE is not specified, try to find it from the image data."
+ (let* ((data (or raw-data (base64-decode-string base64-string)))
+ (bytes (length data))
+ (sha1-sum (sha1 data))
+ (base64-data (or base64-string (base64-encode-string raw-data)))
+ (type (or mime-type
+ (cdr (assq (get :type (cdr (condition-case nil
+ (jabber-create-image data nil t)
+ (error nil))))
+ '((png "image/png")
+ (jpeg "image/jpeg")
+ (gif "image/gif")))))))
+ (jabber-avatar-compute-size
+ (make-avatar :mime-type mime-type :sha1-sum sha1-sum :base64-data base64-data :bytes bytes))))
+
+;; XXX: This function is based on an outdated version of JEP-0084.
+;; (defun jabber-avatar-from-data-node (data-node)
+;; "Construct an avatar structure from the given <data/> node."
+;; (jabber-xml-let-attributes
+;; (content-type id bytes height width) data-node
+;; (let ((base64-data (car (jabber-xml-node-children data-node))))
+;; (make-avatar :mime-type content-type :sha1-sum id :bytes bytes
+;; :height height :width width :base64-data base64-data))))
+
+(defun jabber-avatar-image (avatar)
+ "Create an image from AVATAR.
+Return nil if images of this type are not supported."
+ (condition-case nil
+ (jabber-create-image (with-temp-buffer
+ (set-buffer-multibyte nil)
+ (insert (avatar-base64-data avatar))
+ (base64-decode-region (point-min) (point-max))
+ (buffer-string))
+ nil
+ t)
+ (error nil)))
+
+(defun jabber-avatar-compute-size (avatar)
+ "Compute and set the width and height fields of AVATAR.
+Return AVATAR."
+ ;; image-size only works when there is a window system.
+ ;; But display-graphic-p doesn't exist on XEmacs...
+ (let ((size (and (fboundp 'display-graphic-p)
+ (display-graphic-p)
+ (let ((image (jabber-avatar-image avatar)))
+ (and image
+ (image-size image t))))))
+ (when size
+ (setf (avatar-width avatar) (car size))
+ (setf (avatar-height avatar) (cdr size)))
+ avatar))
+
+;;;; Avatar cache
+
+(defun jabber-avatar-find-cached (sha1-sum)
+ "Return file name of cached image for avatar identified by SHA1-SUM.
+If there is no cached image, return nil."
+ (let ((filename (expand-file-name sha1-sum jabber-avatar-cache-directory)))
+ (if (file-exists-p filename)
+ filename
+ nil)))
+
+(defun jabber-avatar-cache (avatar)
+ "Cache the AVATAR."
+ (let* ((id (avatar-sha1-sum avatar))
+ (base64-data (avatar-base64-data avatar))
+ (mime-type (avatar-mime-type avatar))
+ (filename (expand-file-name id jabber-avatar-cache-directory)))
+ (unless (file-directory-p jabber-avatar-cache-directory)
+ (make-directory jabber-avatar-cache-directory t))
+
+ (if (file-exists-p filename)
+ (when jabber-avatar-verbose
+ (message "Caching avatar, but %s already exists" filename))
+ (with-temp-buffer
+ (let ((require-final-newline nil)
+ (coding-system-for-write 'binary))
+ (if (fboundp 'set-buffer-multibyte)
+ (set-buffer-multibyte nil))
+ (insert base64-data)
+ (base64-decode-region (point-min) (point-max))
+ (write-region (point-min) (point-max) filename nil 'silent))))))
+
+;;;; Set avatar for contact
+
+(defun jabber-avatar-set (jid avatar)
+ "Set the avatar of JID to be AVATAR.
+JID is a string containing a bare JID.
+AVATAR may be one of:
+* An avatar structure.
+* The SHA1 sum of a cached avatar.
+* nil, meaning no avatar."
+ ;; We want to optimize for the case of same avatar.
+ ;; Loading an image is expensive, so do it lazily.
+ (let ((jid-symbol (jabber-jid-symbol jid))
+ image hash)
+ (cond
+ ((avatar-p avatar)
+ (setq hash (avatar-sha1-sum avatar))
+ (setq image (lambda () (jabber-avatar-image avatar))))
+ ((stringp avatar)
+ (setq hash avatar)
+ (setq image (lambda ()
+ (condition-case nil
+ (jabber-create-image (jabber-avatar-find-cached avatar))
+ (error nil)))))
+ (t
+ (setq hash nil)
+ (setq image #'ignore)))
+
+ (unless (string= hash (get jid-symbol 'avatar-hash))
+ (put jid-symbol 'avatar (funcall image))
+ (put jid-symbol 'avatar-hash hash)
+ (jabber-presence-update-roster jid-symbol))))
+
+(defun jabber-create-image (file-or-data &optional type data-p)
+ "Create image, scaled down to jabber-avatar-max-width/height,
+if width/height exceeds either of those, and ImageMagick is
+available."
+ (let* ((image (create-image file-or-data type data-p))
+ (size (image-size image t))
+ (spec (cdr image)))
+ (when (and (functionp 'imagemagick-types)
+ (or (> (car size) jabber-avatar-max-width)
+ (> (cdr size) jabber-avatar-max-height)))
+ (plist-put spec :type 'imagemagick)
+ (plist-put spec :width jabber-avatar-max-width)
+ (plist-put spec :height jabber-avatar-max-height))
+ image))
+
+(provide 'jabber-avatar)
+;; arch-tag: 2405c3f8-8eaa-11da-826c-000a95c2fcd0