# vcard.tcl --
#
# This file is part of the jabberlib.
# It handles vcard stuff and provides cache for it as well.
#
# Copyright (c) 2005-2006 Mats Bengtsson
#
# This file is distributed under BSD style license.
#
# $Id: vcard.tcl,v 1.14 2007-11-10 15:44:59 matben Exp $
#
############################# USAGE ############################################
#
# NAME
# vcard - convenience command library for the vcard extension.
#
# SYNOPSIS
# jlib::vcard::init jlibName ?-opt value ...?
#
# INSTANCE COMMANDS
# jlibname vcard send_get jid callbackProc
# jlibname vcard send_set jid callbackProc
# jlibname vcard get_async jid callbackProc
# jlibname vcard has_cache jid
# jlibname vcard get_cache jid
#
################################################################################
package require jlib
package provide jlib::vcard 0.1
namespace eval jlib::vcard {
# Note: jlib::ensamble_register is last in this file!
}
# jlib::vcard::init --
#
# Creates a new instance of a vcard object.
#
# Arguments:
# jlibname: name of existing jabberlib instance
# args:
#
# Results:
# namespaced instance command
proc jlib::vcard::init {jlibname args} {
variable xmlns
set xmlns(vcard) "vcard-temp"
# Instance specific arrays.
namespace eval ${jlibname}::vcard {
variable state
}
upvar ${jlibname}::vcard::state state
set state(cache) 1
return
}
# jlib::vcard::cmdproc --
#
# Just dispatches the command to the right procedure.
#
# Arguments:
# jlibname: name of existing jabberlib instance
# cmd:
# args: all args to the cmd procedure.
#
# Results:
# none.
proc jlib::vcard::cmdproc {jlibname cmd args} {
# Which command? Just dispatch the command to the right procedure.
return [eval {$cmd $jlibname} $args]
}
# jlib::vcard::send_get --
#
# It implements the 'jabber:iq:vcard-temp' get method.
#
# Arguments:
# jlibname: the instance of this jlib.
# jid: bare JID for other users, full jid for ourself.
# cmd: client command to be executed at the iq "result" element.
#
# Results:
# none.
proc jlib::vcard::send_get {jlibname jid cmd} {
variable xmlns
upvar ${jlibname}::vcard::state state
set mjid [jlib::jidmap $jid]
set state(pending,$mjid) 1
set attrlist [list xmlns $xmlns(vcard)]
set xmllist [wrapper::createtag "vCard" -attrlist $attrlist]
jlib::send_iq $jlibname "get" [list $xmllist] -to $jid -command \
[list [namespace current]::send_get_cb $jlibname $jid $cmd]
return
}
# jlib::vcard::send_get_cb --
#
# Cache vcard info from above and call up.
proc jlib::vcard::send_get_cb {jlibname jid cmd type subiq} {
upvar ${jlibname}::vcard::state state
set mjid [jlib::jidmap $jid]
unset -nocomplain state(pending,$mjid)
if {$state(cache)} {
set state(cache,$mjid) $subiq
}
InvokeStacked $jlibname $jid $type $subiq
uplevel #0 $cmd [list $jlibname $type $subiq]
}
# jlib::vcard::get_async --
#
# Get vcard async using 'cmd' callback.
# If cached it is returned directly using 'cmd', if pending the cmd
# is invoked when getting result, else we do a send_get.
proc jlib::vcard::get_async {jlibname jid cmd} {
upvar ${jlibname}::vcard::state state
set mjid [jlib::jidmap $jid]
if {[info exists state(cache,$mjid)]} {
uplevel #0 $cmd [list $jlibname result $state(cache,$mjid)]
} elseif {[info exists state(pending,$mjid)]} {
lappend state(invoke,$mjid) $cmd
} else {
send_get $jlibname $jid $cmd
}
return
}
proc jlib::vcard::InvokeStacked {jlibname jid type subiq} {
upvar ${jlibname}::vcard::state state
set mjid [jlib::jidmap $jid]
if {[info exists state(invoke,$mjid)]} {
foreach cmd $state(invoke,$mjid) {
uplevel #0 $cmd [list $jlibname $type $subiq]
}
unset -nocomplain state(invoke,$mjid)
}
}
# jlib::vcard::get_own_async --
#
# Getting and setting owns vcard is special since lacks to attribute.
proc jlib::vcard::get_own_async {jlibname cmd} {
upvar ${jlibname}::vcard::state state
set jid [$jlibname myjid2]
set mjid [jlib::jidmap $jid]
if {[info exists state(cache,$mjid)]} {
uplevel #0 $cmd [list $jlibname result $state(cache,$mjid)]
} elseif {[info exists state(pending,$mjid)]} {
lappend state(invoke,$mjid) $cmd
} else {
send_get_own $jlibname $cmd
}
return
}
proc jlib::vcard::send_get_own {jlibname cmd} {
variable xmlns
# A user may retrieve his or her own vCard by sending XML of the
# following form to his or her own JID (the 'to' attribute SHOULD NOT
# be included).
set attrlist [list xmlns $xmlns(vcard)]
set xmllist [wrapper::createtag "vCard" -attrlist $attrlist]
jlib::send_iq $jlibname "get" [list $xmllist] -command \
[list [namespace current]::send_get_own_cb $jlibname $cmd]
}
proc jlib::vcard::send_get_own_cb {jlibname cmd type subiq} {
upvar ${jlibname}::vcard::state state
set jid [$jlibname myjid2]
set mjid [jlib::jidmap $jid]
unset -nocomplain state(pending,$mjid)
if {$state(cache)} {
set state(cache,$mjid) $subiq
}
InvokeStacked $jlibname $jid $type $subiq
uplevel #0 $cmd [list $jlibname $type $subiq]
}
# jlib::vcard::set_my_photo --
#
# A utility to set our vCard photo.
# If photo empty then remove photo from vCard.
#
# @@@ TODO: Perhaps we should use a cached vCard instead of getting it
# each time? The cache would only need one request and then
# set each time we set our usual vCard.
proc jlib::vcard::set_my_photo {jlibname photo mime cmd} {
send_get_own $jlibname \
[list [namespace current]::get_my_photo_cb $photo $mime $cmd]
}
proc jlib::vcard::get_my_photo_cb {photo mime cmd jlibname type subiq} {
variable xmlns
# Replace or set an element:
#
#
# image/jpeg
# Base64-encoded-avatar-file-here!
#
if {$type eq "result"} {
if {[string length $photo]} {
set newphoto 1
set vcardE $subiq
# Replace or add photo. But only if different.
set photoE [wrapper::getfirstchildwithtag $vcardE "PHOTO"]
if {[llength $photoE]} {
set binE [wrapper::getfirstchildwithtag $photoE "BINVAL"]
if {[llength $binE]} {
set sphoto [wrapper::getcdata $binE]
# Base64 code can contain undefined spaces: decode!
set sdata [::base64::decode $sphoto]
set data [::base64::decode $photo]
if {[string equal $sdata $data]} {
set newphoto 0
}
}
}
if {$newphoto} {
lappend subElems [wrapper::createtag "TYPE" -chdata $mime]
lappend subElems [wrapper::createtag "BINVAL" -chdata $photo]
set photoE [wrapper::createtag "PHOTO" -subtags $subElems]
if {$vcardE eq {}} {
set xmllist [wrapper::createtag "vCard" \
-attrlist [list xmlns $xmlns(vcard)] \
-subtags [list $photoE]]
} else {
set xmllist [wrapper::setchildwithtag $vcardE $photoE]
}
jlib::send_iq $jlibname "set" [list $xmllist] -command \
[list [namespace current]::set_my_photo_cb $jlibname $cmd]
}
} else {
# Remove any photo. If there is no PHOTO no need to set.
set photoE [wrapper::getfirstchildwithtag $subiq "PHOTO"]
if {[llength $photoE]} {
set xmllist [wrapper::deletechildswithtag $subiq "PHOTO"]
jlib::send_iq $jlibname "set" [list $xmllist] -command \
[list [namespace current]::set_my_photo_cb $jlibname $cmd]
}
}
} else {
uplevel #0 $cmd [list $jlibname $type $subiq]
}
}
proc jlib::vcard::set_my_photo_cb {jlibname cmd type subiq} {
uplevel #0 $cmd [list $jlibname $type $subiq]
}
proc jlib::vcard::has_cache {jlibname jid} {
upvar ${jlibname}::vcard::state state
set mjid [jlib::jidmap $jid]
return [info exists state(cache,$mjid)]
}
proc jlib::vcard::get_cache {jlibname jid} {
upvar ${jlibname}::vcard::state state
set mjid [jlib::jidmap $jid]
if {[info exists state(cache,$mjid)]} {
return $state(cache,$mjid)
} else {
return
}
}
# jlib::vcard::send_set, createvcard --
#
# Sends our vCard to the server. Internally we use all lower case
# but the spec (XEP-0054) says that all tags be all upper case.
#
# Arguments:
# jlibname: the instance of this jlib.
# cmd: client command to be executed at the iq "result" element.
# args: All keys are named so that the element hierarchy becomes
# vcardElement_subElement_subsubElement ... and so on;
# all lower case.
#
# Results:
# none.
proc jlib::vcard::send_set {jlibname cmd args} {
upvar ${jlibname}::vcard::state state
set jid [$jlibname myjid2]
set xmllist [eval {create $jlibname} $args]
set state(cache,$jid) $xmllist
jlib::send_iq $jlibname "set" [list $xmllist] -command \
[list [namespace current]::send_set_cb $jlibname $cmd]
return
}
proc jlib::vcard::create {jlibname args} {
variable xmlns
set attrlist [list xmlns $xmlns(vcard)]
# Form all the sub elements by inspecting the -key.
array set arr $args
set subE [list]
# All "sub" elements with no children.
foreach tag {fn nickname bday url title role desc} {
if {[info exists arr(-$tag)]} {
lappend subE [wrapper::createtag [string toupper $tag] \
-chdata $arr(-$tag)]
}
}
if {[info exists arr(-email_internet_pref)]} {
set elem [list]
lappend elem [wrapper::createtag "INTERNET"]
lappend elem [wrapper::createtag "PREF"]
lappend subE [wrapper::createtag "EMAIL" \
-chdata $arr(-email_internet_pref) -subtags $elem]
}
if {[info exists arr(-email_internet)]} {
foreach email $arr(-email_internet) {
set elem [list]
lappend elem [wrapper::createtag "INTERNET"]
lappend subE [wrapper::createtag "EMAIL" \
-chdata $email -subtags $elem]
}
}
# All "subsub" elements.
foreach tag {n org} {
set elem [list]
foreach key [array names arr "-${tag}_*"] {
regexp -- "-${tag}_(.+)" $key match sub
lappend elem [wrapper::createtag [string toupper $sub] \
-chdata $arr($key)]
}
# Insert subsub elements where they belong.
if {[llength $elem]} {
lappend subE [wrapper::createtag [string toupper $tag] \
-subtags $elem]
}
}
# The , sub elements.
foreach tag {adr_home adr_work} {
regexp -- {([^_]+)_(.+)} $tag match head sub
set elem [list [wrapper::createtag [string toupper $sub]]]
set haveThisTag 0
foreach key [array names arr "-${tag}_*"] {
set haveThisTag 1
regexp -- "-${tag}_(.+)" $key match sub
lappend elem [wrapper::createtag [string toupper $sub] \
-chdata $arr($key)]
}
if {$haveThisTag} {
lappend subE [wrapper::createtag [string toupper $head] \
-subtags $elem]
}
}
# The sub elements.
foreach tag [array names arr "-tel_*"] {
if {[regexp -- {-tel_([^_]+)_([^_]+)} $tag match second third]} {
set elem {}
lappend elem [wrapper::createtag [string toupper $second]]
lappend elem [wrapper::createtag [string toupper $third]]
lappend subE [wrapper::createtag "TEL" -chdata $arr($tag) \
-subtags $elem]
}
}
# The sub elements.
if {[info exists arr(-photo_binval)]} {
set elem {}
lappend elem [wrapper::createtag "BINVAL" -chdata $arr(-photo_binval)]
if {[info exists arr(-photo_type)]} {
lappend elem [wrapper::createtag "TYPE" -chdata $arr(-photo_type)]
}
lappend subE [wrapper::createtag "PHOTO" -subtags $elem]
}
return [wrapper::createtag "vCard" -attrlist $attrlist -subtags $subE]
}
proc jlib::vcard::send_set_cb {jlibname cmd type subiq args} {
uplevel #0 $cmd [list $jlibname $type $subiq]
}
proc jlib::vcard::cache {jlibname args} {
upvar ${jlibname}::vcard::state state
if {[llength $args] == 1} {
set state(cache) [lindex $args 0]
}
return $state(cache)
}
proc jlib::vcard::clear {jlibname {jid ""}} {
upvar ${jlibname}::vcard::state state
if {$jid eq ""} {
array unset state "cache,*"
} else {
set mjid [jlib::jidmap $jid]
array unset state "cache,[jlib::ESC $mjid]"
}
}
# We have to do it here since need the initProc before doing this.
namespace eval jlib::vcard {
jlib::ensamble_register vcard \
[namespace current]::init \
[namespace current]::cmdproc
}