Merge.
authorChris Jones <chris@cjones.org>
Wed, 06 Nov 2013 20:39:36 -0700
changeset 176 52c100897e9d
parent 175 fc8702a8572e (current diff)
parent 174 25b9d58daa13 (diff)
child 177 63f33bb8fa33
Merge.
TODO.txt
xmpp/xmpp.go
--- a/README	Wed Nov 06 20:38:03 2013 -0700
+++ b/README	Wed Nov 06 20:39:36 2013 -0700
@@ -4,4 +4,11 @@
 
 The core of the protocol is handled by xmpp.go, structs.go, and
 stream.go. Everything else is an extension, though some of the
-provided "extensions" are mandatory pieces of the protocol.
+provided "extensions" are mandatory pieces of the protocol. Many of
+the XEPs at http://xmpp.org/xmpp-protocols/xmpp-extensions/ can be
+supported by this library, though at present only base protocol
+support is here.
+
+An simple client using this library is in the example directory. A
+more interesting example can be found at
+https://cjones.org/hg/foosfiend.
--- a/TODO.txt	Wed Nov 06 20:38:03 2013 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-Review all the *Client receiver methods in layer3.go. They should
-probably either all be receivers, or none.
-
-Add a Reconnect() function.
-
-Eliminate as many uses of Generic as possible.
--- a/xmpp/layer1.go	Wed Nov 06 20:38:03 2013 -0700
+++ b/xmpp/layer1.go	Wed Nov 06 20:39:36 2013 -0700
@@ -64,7 +64,7 @@
 	for {
 		select {
 		case stat := <-status:
-			if stat.fatal() {
+			if stat.Fatal() {
 				return
 			}
 
--- a/xmpp/status.go	Wed Nov 06 20:38:03 2013 -0700
+++ b/xmpp/status.go	Wed Nov 06 20:39:36 2013 -0700
@@ -42,7 +42,9 @@
 	StatusError Status = statusError
 )
 
-func (s Status) fatal() bool {
+// Does the status value indicate that the client is or has
+// disconnected?
+func (s Status) Fatal() bool {
 	switch s {
 	default:
 		return false
@@ -135,7 +137,7 @@
 		if current == waitFor {
 			return nil
 		}
-		if current.fatal() {
+		if current.Fatal() {
 			break
 		}
 		if current > waitFor {
--- a/xmpp/structs.go	Wed Nov 06 20:38:03 2013 -0700
+++ b/xmpp/structs.go	Wed Nov 06 20:39:36 2013 -0700
@@ -98,9 +98,9 @@
 type Message struct {
 	XMLName xml.Name `xml:"jabber:client message"`
 	Header
-	Subject *Generic `xml:"jabber:client subject"`
-	Body    *Generic `xml:"jabber:client body"`
-	Thread  *Generic `xml:"jabber:client thread"`
+	Subject []Text   `xml:"jabber:client subject"`
+	Body    []Text   `xml:"jabber:client body"`
+	Thread  *Data  `xml:"jabber:client thread"`
 }
 
 var _ Stanza = &Message{}
@@ -109,9 +109,9 @@
 type Presence struct {
 	XMLName xml.Name `xml:"presence"`
 	Header
-	Show     *Generic `xml:"jabber:client show"`
-	Status   *Generic `xml:"jabber:client status"`
-	Priority *Generic `xml:"jabber:client priority"`
+	Show     *Data   `xml:"jabber:client show"`
+	Status   []Text  `xml:"jabber:client status"`
+	Priority *Data   `xml:"jabber:client priority"`
 }
 
 var _ Stanza = &Presence{}
@@ -142,6 +142,22 @@
 	Jid      *string  `xml:"jid"`
 }
 
+// Holds human-readable text, with an optional language
+// specification. Generally multiple instances of these can be found
+// together, allowing the software to choose which language to present
+// to the user.
+type Text struct {
+	XMLName xml.Name
+	Lang     string `xml:"http://www.w3.org/XML/1998/namespace lang,attr,omitempty"`
+	Chardata string `xml:",chardata"`
+}
+
+// Non-human-readable content of some sort, used by the protocol.
+type Data struct {
+	XMLName xml.Name
+	Chardata string `xml:",chardata"`
+}
+
 // Holds an XML element not described by the more specific types.
 type Generic struct {
 	XMLName  xml.Name
@@ -162,8 +178,7 @@
 	return result
 }
 
-// Set implements flag.Value. It returns true if it successfully
-// parses the string.
+// Set implements flag.Value.
 func (jid *JID) Set(val string) error {
 	r := regexp.MustCompile("^(([^@/]+)@)?([^@/]+)(/([^@/]+))?$")
 	parts := r.FindStringSubmatch(val)
--- a/xmpp/structs_test.go	Wed Nov 06 20:38:03 2013 -0700
+++ b/xmpp/structs_test.go	Wed Nov 06 20:39:36 2013 -0700
@@ -108,8 +108,8 @@
 }
 
 func TestMarshalEscaping(t *testing.T) {
-	msg := &Message{Body: &Generic{XMLName: xml.Name{Local: "body"},
-		Chardata: `&<!-- "`}}
+	msg := &Message{Body: []Text{Text{XMLName: xml.Name{Local: "body"},
+		Chardata: `&<!-- "`}}}
 	exp := `<message xmlns="jabber:client"><body>&amp;&lt;!-- &#34;</body></message>`
 	assertMarshal(t, exp, msg)
 }
@@ -123,8 +123,8 @@
 	obs := <-ch
 	exp := &Message{XMLName: xml.Name{Local: "message", Space: "jabber:client"},
 		Header: Header{To: "a@b.c", Innerxml: "<body>foo!</body>"},
-		Body: &Generic{XMLName: xml.Name{Local: "body", Space: "jabber:client"},
-			Chardata: "foo!"}}
+		Body: []Text{Text{XMLName: xml.Name{Local: "body", Space: "jabber:client"},
+			Chardata: "foo!"}}}
 	if !reflect.DeepEqual(obs, exp) {
 		t.Errorf("read %s\ngot:  %#v\nwant: %#v\n", str, obs, exp)
 	}
--- a/xmpp/xmpp.go	Wed Nov 06 20:38:03 2013 -0700
+++ b/xmpp/xmpp.go	Wed Nov 06 20:39:36 2013 -0700
@@ -13,6 +13,7 @@
 	"io"
 	"net"
 	"reflect"
+	"sync"
 )
 
 const (
@@ -66,7 +67,8 @@
 	// set up the XMPP stream will not appear here.
 	Recv <-chan Stanza
 	// Outgoing XMPP stanzas to the server should be sent to this
-	// channel.
+	// channel. The application should not close this channel;
+	// rather, call Close().
 	Send    chan<- Stanza
 	sendRaw chan<- interface{}
 	statmgr *statmgr
@@ -80,6 +82,7 @@
 	tlsConfig                    tls.Config
 	layer1                       *layer1
 	error                        chan error
+	shutdownOnce                 sync.Once
 }
 
 // Creates an XMPP client identified by the given JID, authenticating
@@ -256,8 +259,9 @@
 func (cl *Client) Close() {
 	// Shuts down the receivers:
 	cl.setStatus(StatusShutdown)
+
 	// Shuts down the senders:
-	close(cl.Send)
+	cl.shutdownOnce.Do(func() { close(cl.Send) })
 }
 
 // If there's a buffered error in the channel, return it. Otherwise,