# 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 }