// Copyright 2011 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.packagexmppimport("fmt""os""xml")// This file contains support for roster management, RFC 3921, Section 7.varrosterExtExtension=Extension{StanzaHandlers:map[string]func(*xml.Name)interface{}{NsRoster:newRosterQuery},Start:startRosterFilter}// Roster query/resulttypeRosterQuerystruct{XMLNamexml.Name`xml:"jabber:iq:roster query"`Item[]RosterItem}// See RFC 3921, Section 7.1.typeRosterItemstruct{XMLNamexml.Name`xml:"item"`Jidstring`xml:"attr"`Subscriptionstring`xml:"attr"`Namestring`xml:"attr"`Group[]string}typerosterClientstruct{rosterChan<-chan[]RosterItemrosterUpdatechan<-RosterItem}var(rosterClients=make(map[string]rosterClient))// Implicitly becomes part of NewClient's extStanza arg.funcnewRosterQuery(name*xml.Name)interface{}{return&RosterQuery{}}// Synchronously fetch this entity's roster from the server and cache// that information. This is called once from a fairly deep call stack// as part of XMPP negotiation.funcfetchRoster(client*Client)os.Error{rosterUpdate:=rosterClients[client.Uid].rosterUpdateiq:=&Iq{From:client.Jid.String(),Id:<-Id,Type:"get",Nested:[]interface{}{RosterQuery{}}}ch:=make(chanos.Error)f:=func(stStanza)bool{deferclose(ch)ifiq.Type=="error"{ch<-iq.Errorreturnfalse}varrq*RosterQueryfor_,ele:=range(st.GetNested()){ifq,ok:=ele.(*RosterQuery);ok{rq=qbreak}}ifrq==nil{ch<-os.NewError(fmt.Sprintf("Roster query result not query: %v",st))returnfalse}for_,item:=range(rq.Item){rosterUpdate<-item}ch<-nilreturnfalse}client.HandleStanza(iq.Id,f)client.Out<-iq// Wait for f to complete.return<-ch}// The roster filter updates the Client's representation of the// roster, but it lets the relevant stanzas through. This also starts// the roster feeder, which is the goroutine that provides data on// client.Roster.funcstartRosterFilter(client*Client){out:=make(chanStanza)in:=client.AddFilter(out)gofunc(in<-chanStanza,outchan<-Stanza){deferclose(out)forst:=range(in){maybeUpdateRoster(client,st)out<-st}}(in,out)rosterCh:=make(chan[]RosterItem)rosterUpdate:=make(chanRosterItem)rosterClients[client.Uid]=rosterClient{rosterChan:rosterCh,rosterUpdate:rosterUpdate}gofeedRoster(rosterCh,rosterUpdate)}funcmaybeUpdateRoster(client*Client,stStanza){rosterUpdate:=rosterClients[client.Uid].rosterUpdatevarrq*RosterQueryfor_,ele:=range(st.GetNested()){ifq,ok:=ele.(*RosterQuery);ok{rq=qbreak}}ifst.GetName()=="iq"&&st.GetType()=="set"&&rq!=nil{for_,item:=range(rq.Item){rosterUpdate<-item}// Send a reply.iq:=&Iq{To:st.GetFrom(),Id:st.GetId(),Type:"result"}client.Out<-iq}}funcfeedRoster(rosterChchan<-[]RosterItem,rosterUpdate<-chanRosterItem){roster:=make(map[string]RosterItem)snapshot:=[]RosterItem{}for{select{casenewIt:=<-rosterUpdate:ifnewIt.Subscription=="remove"{roster[newIt.Jid]=RosterItem{},false}else{roster[newIt.Jid]=newIt}caserosterCh<-snapshot:}snapshot=make([]RosterItem,0,len(roster))for_,v:=range(roster){snapshot=append(snapshot,v)}}}// Retrieve a snapshot of the roster for the given Client.funcRoster(client*Client)[]RosterItem{rosterChan:=rosterClients[client.Uid].rosterChanreturn<-rosterChan}