/*
* avatar.c -- Pep avatar events
*
* Copyrigth (C) 2009 Myhailo Danylenko <isbear@ukrpost.net>
*
* This program 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 of the License, or (at
* your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
#define PNG_USER_MEM_SUPPORTED
#include <glib.h>
#include <gmodule.h>
#include <loudmouth/loudmouth.h>
#include <aalib.h>
#include <png.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <mcabber/compl.h>
#include <mcabber/commands.h>
#include <mcabber/utils.h>
#include <mcabber/xmpp.h>
#include <mcabber/screen.h>
#include <mcabber/hbuf.h>
#include <mcabber/hooks.h>
#include <mcabber/settings.h>
#include <mcabber/pep.h>
#include "config.h"
#define NS_AVATAR_DATA ( "urn:xmpp:avatar:data" )
#define NS_AVATAR_METADATA ( "urn:xmpp:avatar:metadata" )
#define NS_AVATAR_METADATA_NOTIFY ( "urn:xmpp:avatar:metadata+notify" )
static guint avatar_cid = 0;
static GSList *reply_handlers = NULL;
static LmMessageHandler *avatar_metadata_reply_handler = NULL;
static gboolean publish_delayed = FALSE;
static gsize publish_len = 0;
static gchar *publish_data = NULL;
// predeclarations
static LmHandlerResult avatar_retrieve_data_reply_handler (LmMessageHandler *handler, LmConnection *connection, LmMessage *message, gpointer userdata);
// png stuff
static png_voidp png_glib_malloc (png_structp ignore, png_size_t size)
{
return g_malloc (size);
}
static void png_glib_free (png_structp ignore, png_voidp chunk)
{
g_free (chunk);
}
// reads file and returns array of pointers to image rows in
// grayscale 8-bit without alpha-channel format (i.e. one byte per pixel)
// after use just do g_free of this pointer - all data are in one memory chunk
static png_bytep *png_read_file (const char *file, int *rheight, int *rwidth, png_uint_16 background)
{
FILE *fd = fopen (file, "rb");
png_infop info_ptr;
png_structp png_ptr;
int rowbytes;
png_bytep *row_pointers;
int width;
int height;
if (!fd)
return NULL;
{ // check signature
char header[8];
fread (header, 1, 8, fd);
if (png_sig_cmp (header, 0, 8)) {
fclose (fd);
return NULL;
}
}
// initialize reader
png_ptr = png_create_read_struct_2 (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL, NULL, png_glib_malloc, png_glib_free);
if (!png_ptr) {
fclose (fd);
return NULL;
}
info_ptr = png_create_info_struct (png_ptr);
if (!info_ptr) {
png_destroy_read_struct (&png_ptr, NULL, NULL);
fclose (fd);
return NULL;
}
if (setjmp (png_jmpbuf (png_ptr))) {
png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
fclose (fd);
return NULL;
}
png_init_io (png_ptr, fd);
png_set_sig_bytes (png_ptr, 8);
// get information
png_read_info (png_ptr, info_ptr);
{ // set up transformations
png_byte color_type = info_ptr->color_type;
png_byte bit_depth = info_ptr->bit_depth;
// trying to convert anything to grayscale 8-bit without alpha channel
if (color_type == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb (png_ptr);
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
png_set_expand_gray_1_2_4_to_8 (png_ptr);
if (bit_depth == 16)
png_set_strip_16 (png_ptr);
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
png_set_tRNS_to_alpha (png_ptr);
if (color_type & PNG_COLOR_MASK_COLOR)
png_set_rgb_to_gray_fixed (png_ptr, 1, -1, -1);
if (color_type & PNG_COLOR_MASK_ALPHA) {
png_color_16 my_background = {
.red = background,
.green = background,
.blue = background,
.gray = background,
};
png_set_background (png_ptr, &my_background, PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
}
// renew information in info structure
png_read_update_info (png_ptr, info_ptr);
}
width = info_ptr->width;
height = info_ptr->height;
rowbytes = info_ptr->rowbytes;
{ // allocate buffer for image data
png_bytep chunk = g_malloc ((sizeof(png_bytep) + rowbytes) * height);
png_bytep start = chunk + sizeof(png_bytep) * height;
int y;
row_pointers = (png_bytep *) chunk;
for (y = 0; y < height; ++y)
row_pointers[y] = start + y * rowbytes;
}
// read image
if (setjmp (png_jmpbuf (png_ptr))) {
g_free (row_pointers);
png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
fclose (fd);
return NULL;
}
png_read_image (png_ptr, row_pointers);
png_read_end (png_ptr, NULL);
// free resources
png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
fclose (fd);
*rheight = height;
*rwidth = width;
return row_pointers;
}
// aa stuff
// converts image in format, returned by png_read_file into ascii,
// scaling it to fit specified max values. If font parameters are
// set properly, should result in image on screen with original png
// aspect ratio.
static gchar *aa_convert_image (png_bytep *row_pointers, int height, int width, int maxcharh, int maxcharw, int fonth, int fontw)
{
gchar *result;
struct aa_hardware_params hwparams = {
.font = NULL,
.supported = AA_NORMAL_MASK,
.width = 0,
.height = 0,
};
// params for zooming
int finalcharh;
int finalcharw;
int finalh;
int finalw;
int starty;
int startx;
int finalratioh;
int finalratiow;
// process aa parameters from environment variables
aa_parseoptions (&hwparams, NULL, NULL, NULL);
{ // calculate parameters for image zooming into aa surface
// this always makes me a hard time, thus such a mess :/
// I hope, compiler will optimize out all these variables...
// font size in font pixels (real?)
//int fonth = 16;
//int fontw = 8;
float fontaspect = (float) fontw / fonth;
// aa pixels per character
const int aappch = 2;
const int aappcw = 2;
const float aaaspect = (float) aappcw / aappch;
// font pixels per aa pixel
int aappph = (float) fonth / aappch;
int aapppw = (float) fontw / aappcw;
float aapixelaspect = (float) aapppw / aappph;
// original image size in real pixels
int originh = height;
int originw = width;
float originaspect = (float) originw / originh;
// maximal resulting image size in chars
//int maxcharh = res_height;
//int maxcharw = res_width;
// maximal resulting image size in aa pixels
int maxh = maxcharh * aappch;
int maxw = maxcharw * aappcw;
// maximal resulting image size in real pixels
int maxh_rp;
int maxw_rp;
if (aapixelaspect > 1) {
maxh_rp = maxh;
maxw_rp = (float) maxw * aapixelaspect;
} else {
maxh_rp = (float) maxh / aapixelaspect;
maxw_rp = maxw;
}
float maxaspect_rp = (float) maxw_rp / maxh_rp;
// resulting image size in real pixels
float ratio_rp;
int resh_rp;
int resw_rp;
if (originh > maxh_rp || originw > maxw_rp) {
// boundaries crossed, will zoom
if (originaspect > maxaspect_rp) {
// width is bigger
ratio_rp = (float) maxw_rp / originw;
resh_rp = (float) originh * ratio_rp;
resw_rp = maxw_rp;
} else {
// height is bigger
ratio_rp = (float) maxh_rp / originh;
resh_rp = maxh_rp;
resw_rp = (float) originw * ratio_rp;
}
} else {
// image fits, no zooming
ratio_rp = 1;
resh_rp = originh;
resw_rp = originw;
}
// resulting image size in aa pixels
int resh;
int resw;
if (aapixelaspect > 1) {
resh = resh_rp;
resw = (float) resw_rp / aapixelaspect;
} else {
resh = (float) resh_rp * aapixelaspect;
resw = resw_rp;
}
// number of original image pixels per one resulting image pixel
int ratioh = (float) originh / resh;
int ratiow = (float) originw / resw;
// resulting image size in chars
int rescharh = (float) resh / aappch;
int rescharw = (float) resw / aappcw;
// re-calculate parameters, as they may change due to rounding (?)
finalcharh = rescharh;
finalcharw = rescharw;
finalh = rescharh * aappch;
finalw = rescharw * aappcw;
int finalh_rp;
int finalw_rp;
if (aapixelaspect > 1) {
finalh_rp = finalh;
finalw_rp = (float) finalw * aapixelaspect;
} else {
finalh_rp = (float) finalh / aapixelaspect;
finalw_rp = finalw;
}
finalratioh = (float) originh / finalh;
finalratiow = (float) originw / finalw;
// center image part actually used
int areah = (float) finalh * finalratioh;
int areaw = (float) finalw * finalratiow;
int losth = originh - areah;
int lostw = originw - areaw;
starty = losth / 2;
startx = lostw / 2;
}
// create aa image
hwparams.height = finalcharh;
hwparams.width = finalcharw;
aa_context *context = aa_init (&mem_d, &hwparams, NULL);
if (context == NULL)
return NULL;
{ // fill aa image from png buffer with scaling
int height, width;
int y;
height = aa_imgheight (context);
width = aa_imgwidth (context);
if (height > finalh)
height = finalh;
if (width > finalw)
width = finalw;
for (y = 0; y < height; ++y) {
int x;
for (x = 0; x < width; ++x) {
png_bytep *row_pointer = row_pointers + starty + y * finalratioh;
unsigned int color = 0;
unsigned int cy;
// just arithmetic average
for (cy = 0; cy < finalratioh; ++cy) {
png_bytep row = row_pointer[cy] + startx + x * finalratiow;
int cx;
for (cx = 0; cx < finalratiow; ++cx)
color += row[cx];
}
color /= finalratioh * finalratiow;
aa_putpixel (context, x, y, color);
}
}
}
// render ascii image
aa_render (context, &aa_defrenderparams, 0, 0, aa_scrwidth (context), aa_scrheight (context));
aa_flush (context);
{ // format the result
char *text = aa_text (context);
int width = aa_scrwidth (context);
int height = aa_scrheight (context);
GString *avatar = g_string_new (NULL);
char *end = text + width * height;
while (text < end) {
g_string_append_len (avatar, text, width);
g_string_append_c (avatar, '\n');
text += width;
}
// strip last '\n'
g_string_truncate (avatar, avatar->len - 1);
result = g_string_free (avatar, FALSE);
}
// free resources
aa_close (context);
return result;
}
// avatar stuff
// returns filename to save this avatar. NULL on error. g_free result.
static gchar *avatar_id_filename (const gchar *id)
{
gchar *escid = g_strdup (id);
gchar *dir = (gchar *) settings_opt_get ("avatar_directory");
gchar *file;
if (!dir)
return NULL;
{ // neutralize id
const gchar *valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
g_strcanon (escid, valid, '_');
}
dir = expand_filename (dir);
file = g_strdup_printf ("%s/%s.png", dir, escid);
g_free (dir);
g_free (escid);
return file;
}
// returns name of symlink to avatar image file for this jid
static gchar *avatar_jid_filename (const gchar *jid)
{
gchar *dir = (gchar *) settings_opt_get ("avatar_directory");
gchar *file = NULL;
gchar *bjid;
if (!dir)
return NULL;
// if jid validity alone is enough for mcabber to use as
// names for history logs, it's fine for us too.
if (check_jid_syntax (jid))
return NULL;
bjid = jidtodisp (jid);
mc_strtolower (bjid);
dir = expand_filename (dir);
file = g_strdup_printf ("%s/%s", dir, bjid);
g_free (bjid);
return file;
}
// symlinks specified jid to specified file. performs jid
// checks, but not file checks.
static void set_jid_avatar (const char *jid, const char *file)
{
gchar *jfile;
if (!jid || !file)
return;
jfile = avatar_jid_filename (jid);
if (!jfile)
return;
unlink (jfile);
if (symlink (file, jfile) == -1)
scr_LogPrint (LPRINT_LOGNORM, "avatar: Cannot symlink jid-file to avatar: %s.", strerror (errno));
}
// reply handler for metadata publish request
// just prints error message if unsuccessful
static LmHandlerResult avatar_publish_metadata_reply_handler (LmMessageHandler *handler, LmConnection *connection, LmMessage *message, gpointer userdata)
{
switch (lm_message_get_sub_type (message)) {
case LM_MESSAGE_SUB_TYPE_RESULT:
break;
case LM_MESSAGE_SUB_TYPE_ERROR:
{
LmMessageNode *node = lm_message_get_node (message);
const gchar *type;
const gchar *reason;
node = lm_message_node_get_child (node, "error");
type = lm_message_node_get_attribute (node, "type");
if (node->children)
reason = node->children->name;
else
reason = "undefined";
scr_LogPrint (LPRINT_LOGNORM, "avatar: Metadata publishing failed: %s - %s.", type, reason);
}
break;
default:
return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
break;
}
return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}
// reply handler for data publish request
// sends metadata update request or prints error message
static LmHandlerResult avatar_publish_data_reply_handler (LmMessageHandler *handler, LmConnection *connection, LmMessage *message, gpointer userdata)
{
LmMessage *request = (LmMessage *) userdata;
reply_handlers = g_slist_remove (reply_handlers, handler);
switch (lm_message_get_sub_type (message)) {
case LM_MESSAGE_SUB_TYPE_RESULT:
{ // send
GError *error = NULL;
lm_connection_send_with_reply (connection, request, avatar_metadata_reply_handler, &error);
if (error) {
scr_LogPrint (LPRINT_DEBUG, "avatar: Metadata publication error: %s.", error -> message);
g_error_free (error);
}
lm_message_unref (request);
}
break;
case LM_MESSAGE_SUB_TYPE_ERROR:
{
LmMessageNode *node = lm_message_get_node (message);
const gchar *type;
const gchar *reason;
node = lm_message_node_get_child (node, "error");
type = lm_message_node_get_attribute (node, "type");
if (node->children)
reason = node->children->name;
else
reason = "undefined";
scr_LogPrint (LPRINT_LOGNORM, "avatar: Data publishing failed: %s - %s.", type, reason);
}
break;
default:
return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
break;
}
return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}
// prints avatar (if available) for jid into jid's buffer
// id can be omitted
static gboolean print_avatar (const gchar *file, const gchar *jid, const gchar *id)
{
png_bytep *row_pointers;
gchar *avatar;
int width;
int height;
if (!file)
return FALSE;
{ // check if file exists to not trigger unnecessary error messages
struct stat buf;
if (stat (file, &buf) == -1) {
gchar *bjid;
LmMessage *request;
LmMessageNode *node;
if (!xmpp_is_online ()) {
scr_LogPrint (LPRINT_NORMAL, "avatar: You are not online, request not sent.");
return FALSE;
}
bjid = jidtodisp (jid);
scr_WriteIncomingMessage (bjid, "No avatar for this buddy yet, sending request.", 0, HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); // NO conversion from utf-8
// create data request
request = lm_message_new_with_sub_type (bjid, LM_MESSAGE_TYPE_IQ, LM_MESSAGE_SUB_TYPE_GET);
node = lm_message_get_node (request);
lm_message_node_set_attribute (node, "from", lm_connection_get_jid (lconnection));
node = lm_message_node_add_child (node, "pubsub", NULL);
lm_message_node_set_attribute (node, "xmlns", NS_PUBSUB);
node = lm_message_node_add_child (node, "items", NULL);
lm_message_node_set_attribute (node, "node", NS_AVATAR_METADATA);
{ // send, result will be handled by pep
GError *error = NULL;
lm_connection_send (lconnection, request, &error);
if (error) {
scr_LogPrint (LPRINT_DEBUG, "avatar: Request sending error: %s.", error -> message);
g_error_free (error);
}
}
lm_message_unref (request);
g_free (bjid);
return TRUE;
}
}
{
png_uint_16 background = settings_opt_get_int ("avatar_background");
row_pointers = png_read_file (file, &width, &height, background);
}
if (!row_pointers) {
scr_LogPrint (LPRINT_LOGNORM, "avatar: Cannot decode png data from file %s.", file);
return FALSE;
}
{ // convert to ascii
int maxcharh = settings_opt_get_int ("avatar_max_height");
int maxcharw = settings_opt_get_int ("avatar_max_width");
int fonth = settings_opt_get_int ("avatar_font_height");
int fontw = settings_opt_get_int ("avatar_font_width");
// if not set explicitly, try to calculate available space
if (!maxcharh) {
char *var = getenv ("LINES");
if (var)
maxcharh = atoi (var);
else
maxcharh = 25;
maxcharh -= settings_opt_get_int ("log_win_height") + 3 + 1; // +1 due to avatar header line
}
if (!maxcharw) {
char *var = getenv ("COLUMNS");
if (var)
maxcharw = atoi (var);
else
maxcharw = 80;
maxcharw -= settings_opt_get_int ("roster_width") + scr_getprefixwidth ();
}
if (!fonth)
fonth = 16;
if (!fontw)
fontw = 8;
avatar = aa_convert_image (row_pointers, width, height, maxcharh, maxcharw, fonth, fontw);
}
g_free (row_pointers);
if (!avatar) {
scr_LogPrint (LPRINT_LOGNORM, "avatar: Error converting image to ascii.");
return FALSE;
}
{ // print out avatar
gchar *bjid = jidtodisp (jid);
gchar *mesg;
if (id)
mesg = g_strdup_printf ("Avatar [%s]:\n%s", id, avatar);
else
mesg = g_strdup_printf ("Avatar:\n%s", avatar);
scr_WriteIncomingMessage (bjid, mesg, 0, HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); // NO conversion from utf-8
g_free (bjid);
g_free (mesg);
}
g_free (avatar);
return TRUE;
}
// reply handler for image/png data request
// saves image and prints it to buddy buffer
static LmHandlerResult avatar_retrieve_data_reply_handler (LmMessageHandler *handler, LmConnection *connection, LmMessage *message, gpointer userdata)
{
gchar *from = (gchar *) userdata;
reply_handlers = g_slist_remove (reply_handlers, handler);
switch (lm_message_get_sub_type (message)) {
case LM_MESSAGE_SUB_TYPE_RESULT:
{
LmMessageNode *node = lm_message_get_node (message);
node = lm_message_node_get_child (node, "pubsub");
if (!node)
return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
node = lm_message_node_get_child (node, "items");
if (!node)
return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
{
LmMessageNode *item;
for (item = node->children; item; item = item->next) {
if (!strcmp (item->name, "item")) {
LmMessageNode *data = lm_message_node_get_child (item, "data");
const gchar *id = lm_message_node_get_attribute (item, "id");
if (!data)
continue;
{ // save to file and display in ascii
const gchar *base64 = lm_message_node_get_value (data);
gchar *png;
gsize len;
gchar *file;
if (!base64)
continue;
png = g_base64_decode (base64, &len);
if (!png)
continue;
file = avatar_id_filename (id);
if (!file) {
g_free (png);
continue;
}
{ // write image to file
int fd = open (file, O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
if (fd == -1) {
scr_LogPrint (LPRINT_LOGNORM, "avatar: Cannot create file: %s.", strerror (errno));
g_free (png);
g_free (file);
continue;
}
write (fd, png, len);
close (fd);
}
// create jid symlink to avatar image
set_jid_avatar (from, file);
g_free (png);
// read it back :/
print_avatar (file, from, id);
g_free (file);
}
}
}
}
}
break;
case LM_MESSAGE_SUB_TYPE_ERROR:
{
LmMessageNode *node = lm_message_get_node (message);
const gchar *type;
const gchar *reason;
node = lm_message_node_get_child (node, "error");
type = lm_message_node_get_attribute (node, "type");
if (node->children)
reason = node->children->name;
else
reason = "undefined";
scr_LogPrint (LPRINT_LOGNORM, "avatar: Obtaining avatar data for %s failed: %s - %s.", from, type, reason);
}
break;
default:
return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
break;
}
return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}
// data is image/png data of length len
// call with len = 0 to publish empty avatar
static void avatar_publish (const gchar *data, gsize len)
{
LmMessage *datarequest;
LmMessageHandler *handler;
gchar *id;
LmMessage *request;
LmMessageNode *node;
GError *error = NULL;
if (!xmpp_is_online ()) {
scr_LogPrint (LPRINT_DEBUG, "avatar: Not connected, delaying publish.");
g_free (publish_data);
publish_data = g_strndup (data, len);
publish_len = len;
publish_delayed = TRUE;
return;
}
request = lm_message_new_with_sub_type (NULL, LM_MESSAGE_TYPE_IQ, LM_MESSAGE_SUB_TYPE_SET);
node = lm_message_get_node (request);
if (len) {
// create data publish request
lm_message_node_set_attribute (node, "from", lm_connection_get_jid (lconnection));
node = lm_message_node_add_child (node, "pubsub", NULL);
lm_message_node_set_attribute (node, "xmlns", NS_PUBSUB);
node = lm_message_node_add_child (node, "publish", NULL);
lm_message_node_set_attribute (node, "node", NS_AVATAR_DATA);
node = lm_message_node_add_child (node, "item", NULL);
id = g_compute_checksum_for_data (G_CHECKSUM_SHA1, data, len);
lm_message_node_set_attribute (node, "id", id);
{
gchar *base64 = g_base64_encode (data, len);
node = lm_message_node_add_child (node, "data", base64);
lm_message_node_set_attribute (node, "xmlns", NS_AVATAR_DATA);
g_free (base64);
}
// then create metadata publish request to be supplied to reply handler as userdata
datarequest = lm_message_new_with_sub_type (NULL, LM_MESSAGE_TYPE_IQ, LM_MESSAGE_SUB_TYPE_SET);
node = lm_message_get_node (datarequest);
}
lm_message_node_set_attribute (node, "from", lm_connection_get_jid (lconnection));
node = lm_message_node_add_child (node, "pubsub", NULL);
lm_message_node_set_attribute (node, "xmlns", NS_PUBSUB);
node = lm_message_node_add_child (node, "publish", NULL);
lm_message_node_set_attribute (node, "node", NS_AVATAR_METADATA);
node = lm_message_node_add_child (node, "item", NULL);
if (len)
lm_message_node_set_attribute (node, "id", id);
node = lm_message_node_add_child (node, "metadata", NULL);
lm_message_node_set_attribute (node, "xmlns", NS_AVATAR_METADATA);
if (len) {
gchar *bytes = g_strdup_printf ("%d", len);
node = lm_message_node_add_child (node, "info", NULL);
lm_message_node_set_attributes (node, "bytes", bytes, "id", id, "type", "image/png", NULL);
g_free (bytes);
// create handler
LmMessageHandler *handler = lm_message_handler_new (avatar_publish_data_reply_handler, (gpointer) datarequest, (GDestroyNotify) lm_message_unref /* custom, remove from queue? */);
reply_handlers = g_slist_append (reply_handlers, handler);
// send
lm_connection_send_with_reply (lconnection, request, handler, &error);
lm_message_handler_unref (handler);
} else
// send
lm_connection_send_with_reply (lconnection, request, avatar_metadata_reply_handler, &error);
if (error) {
scr_LogPrint (LPRINT_DEBUG, "avatar: Publish request sending error: %s.", error -> message);
g_error_free (error);
}
lm_message_unref (request);
}
// pep node handler
// if avatar is not yet saved, sends data request, else updates jid's symlink
static void avatar_handler (const gchar *from, const gchar *enode, LmMessageNode *n, const gchar *id, gpointer ignore)
{
LmMessageNode *node;
// well, anyway we now do not store information about jid-avatar relationship,
// thus "no avatar" publishes are meaningless to us
for (node = n->children; node; node = node->next) {
const gchar *name = node->name;
if (!strcmp (name, "info")) {
gchar *jid = jidtodisp (from);
const gchar *url = lm_message_node_get_attribute (node, "url");
const gchar *type = lm_message_node_get_attribute (node, "type");
const gchar *id = lm_message_node_get_attribute (node, "id");
const gchar *bytes = lm_message_node_get_attribute (node, "bytes");
const gchar *height = lm_message_node_get_attribute (node, "height");
const gchar *width = lm_message_node_get_attribute (node, "width");
{ // print to buddy's buffer
GString *mesg = g_string_new ("Avatar:");
gchar *text;
// [id] type w x h (# bytes) url
g_string_append_printf (mesg, " [%s]", id ? id : "(none)");
if (type) {
g_string_append_c (mesg, ' ');
g_string_append (mesg, type);
}
if (width && height)
g_string_append_printf (mesg, " %s x %s", width, height);
if (bytes)
g_string_append_printf (mesg, " (%s bytes)", bytes);
if (url) {
g_string_append_c (mesg, ' ');
g_string_append (mesg, url);
}
text = g_string_free (mesg, FALSE);
scr_WriteIncomingMessage (jid, text, 0, HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); // NO conversion from utf-8
g_free (text);
}
if (!url) {
// it is server-published png, go ahead and request data
LmMessage *request;
LmMessageNode *node;
LmMessageHandler *dhandler;
{ // check, if file already exists
gchar *file = avatar_id_filename (id);
struct stat buf;
if (!file) {
scr_LogPrint (LPRINT_LOGNORM, "avatar: Cannot obtain filename to save file, probably avatar_directory is not set.");
g_free (jid);
continue;
}
if (stat (file, &buf) != -1) {
scr_WriteIncomingMessage (jid, "Avatar file exists, will not download.", 0, HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
// link jid to this file
set_jid_avatar (jid, file);
g_free (file);
g_free (jid);
continue;
}
g_free (file);
}
// create data request
request = lm_message_new_with_sub_type (jid, LM_MESSAGE_TYPE_IQ, LM_MESSAGE_SUB_TYPE_GET);
node = lm_message_get_node (request);
lm_message_node_set_attribute (node, "from", lm_connection_get_jid (lconnection));
node = lm_message_node_add_child (node, "pubsub", NULL);
lm_message_node_set_attribute (node, "xmlns", NS_PUBSUB);
node = lm_message_node_add_child (node, "items", NULL);
lm_message_node_set_attribute (node, "node", NS_AVATAR_DATA);
node = lm_message_node_add_child (node, "item", NULL);
lm_message_node_set_attribute (node, "id", id);
// create handler
dhandler = lm_message_handler_new (avatar_retrieve_data_reply_handler, jid, g_free);
reply_handlers = g_slist_append (reply_handlers, dhandler);
{ // send
GError *error = NULL;
lm_connection_send_with_reply (lconnection, request, dhandler, &error);
if (error) {
scr_LogPrint (LPRINT_DEBUG, "avatar: Data request sending error: %s.", error -> message);
g_error_free (error);
}
}
lm_message_handler_unref (dhandler);
lm_message_unref (request);
// NOT free jid here - it will be freed on handler destruction
} else
g_free (jid);
}
}
}
// /AVATAR [filename|-]
static void do_avatar (char *arg)
{
gchar *fname;
gchar *data;
gsize len;
if (!*arg) { // print current byddy's avatar
const gchar *jid = CURRENT_JID;
if (!jid) {
scr_LogPrint (LPRINT_NORMAL, "This item cannot have avatar.");
return;
}
fname = avatar_jid_filename (jid);
print_avatar (fname, jid, NULL);
g_free (fname);
return;
}
// publish
if (arg[0] == '-' && arg[1] == '\0') {
// no avatar
avatar_publish (NULL, 0);
return;
}
fname = expand_filename (arg);
{ // read file
char buffer[1024];
GString *datastring;
int fd = open (fname, O_RDONLY);
if (!fd) {
scr_LogPrint (LPRINT_NORMAL, "Cannot open file '%s': %s.", fname, strerror (errno));
g_free (fname);
return;
}
datastring = g_string_new (NULL);
{ // read data
int ret;
while ((ret = read (fd, buffer, 1024)) > 0)
g_string_append_len (datastring, buffer, ret);
}
close (fd);
len = datastring->len;
data = g_string_free (datastring, FALSE);
}
g_free (fname);
avatar_publish (data, len);
g_free (data);
}
static void avatar_free_reply_handlers (void)
{
GSList *hel;
for (hel = reply_handlers; hel; hel = hel->next) {
LmMessageHandler *handler = (LmMessageHandler *) hel->data;
lm_message_handler_invalidate (handler);
#ifdef HAVE_LM_CONNECTION_UNREGISTER_REPLY_HANDLER
if (lconnection)
lm_connection_unregister_reply_handler (lconnection, handler);
#endif
}
g_slist_free (reply_handlers);
reply_handlers = NULL;
#ifdef HAVE_LM_CONNECTION_UNREGISTER_REPLY_HANDLERS
if (lconnection)
lm_connection_unregister_reply_handler (lconnection, avatar_metadata_reply_handler);
#endif
}
// release handlers before reconnect
static void avatar_hh (guint32 hid, hk_arg_t *args, gpointer userdata)
{
#ifndef HOOK_POST_CONNECT
hk_arg_t *arg;
for (arg = args; arg->name; arg++) {
if (!strcmp (arg->name, "hook")) {
if (!strcmp (arg->value, "hook-pre-disconnect")) {
#else
if (hid == HOOK_PRE_DISCONNECT) {
#endif
avatar_free_reply_handlers ();
#ifndef HOOK_POST_CONNECT
} else if (publish_delayed && !strcmp (arg -> value, "hook-post-connect")) {
#else
} else if (hid == HOOK_POST_CONNECT && publish_delayed) {
#endif
char *tmp_data = publish_data;
scr_LogPrint (LPRINT_DEBUG, "avatar: Publishing delayed data.");
publish_data = NULL;
publish_delayed = FALSE;
avatar_publish (publish_data, publish_len);
g_free (tmp_data);
#ifndef HOOK_POST_CONNECT
}
return;
}
#endif
}
}
const gchar *g_module_check_init (GModule *module)
{
pep_register_xmlns_handler (NS_AVATAR_METADATA, avatar_handler, NULL, NULL);
avatar_metadata_reply_handler = lm_message_handler_new (avatar_publish_metadata_reply_handler, NULL, NULL);
cmd_add ("avatar", "", COMPL_FILENAME, 0, do_avatar, NULL);
#ifdef HOOK_PRE_DISCONNECT
hk_add_handler (avatar_hh, HOOK_PRE_DISCONNECT | HOOK_POST_CONNECT, NULL);
#else
hk_add_handler (avatar_hh, HOOK_INTERNAL, NULL);
#endif
xmpp_add_feature (NS_AVATAR_METADATA);
xmpp_add_feature (NS_AVATAR_METADATA_NOTIFY);
return NULL;
}
void g_module_unload (GModule *module)
{
xmpp_del_feature (NS_AVATAR_METADATA);
xmpp_del_feature (NS_AVATAR_METADATA_NOTIFY);
hk_del_handler (avatar_hh, NULL);
cmd_del ("avatar");
pep_unregister_xmlns_handler (NS_AVATAR_METADATA);
avatar_free_reply_handlers ();
if (avatar_metadata_reply_handler) {
lm_message_handler_invalidate (avatar_metadata_reply_handler);
lm_message_handler_unref (avatar_metadata_reply_handler);
}
g_free (publish_data);
}
/* vim: se ts=4 sw=4: */