hunk.code_window: 	PostIndexShards             *int
   	AggregatePostsAfterDays     *int
   	PostsAggregatorJobStartTime *string
   }
   type DataRetentionSettings struct {
  
hunk.labels: keep keep add keep keep keep
hunk.after_edit: 	IndexPrefix                 *string
  
hunk.file_path: model/config.go
hunk.type: add
file: // 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.
  
  package main
  
  /*
  TODO:
   - Del of main window should move to other window.
   - Editing main window should update status on \n or something like that.
   - Make use of full names from roster
  */
  
  import (
  	"bytes"
  	"flag"
  	"fmt"
  	"io/ioutil"
  	"log"
  	"os"
  	"regexp"
  	"strings"
  	"time"
  
  	"code.google.com/p/goplan9/plan9/acme"
  	"github.com/mattermost/rsc/google"
  	"github.com/mattermost/rsc/xmpp"
  )
  
  var acmeDebug = flag.Bool("acmedebug", false, "print acme debugging")
  
  type Window struct {
  	*acme.Win          // acme window
  	*acme.Event        // most recent event received
  	err         error  // error reading event
  	typ         string // kind of window "main", "chat"
  	name        string // acme window title
  	remote      string // for typ=="chat", remote address
  	dirty       bool   // window is dirty
  	blinky      bool   // window's dirty box is blinking
  
  	lastTime time.Time
  }
  
  type Msg struct {
  	w          *Window // window where message belongs
  	*xmpp.Chat         // recently received chat
  	err        error   // error reading chat message
  }
  
  var (
  	client       *xmpp.Client   // current xmpp client (can reconnect)
  	acct         google.Account // google acct info
  	statusCache  = make(map[string][]*xmpp.Presence)
  	active       = make(map[string]*Window) // active windows
  	acmeChan     = make(chan *Window)       // acme events
  	msgChan      = make(chan *Msg)          // chat events
  	mainWin      *Window
  	status       = xmpp.Available
  	statusMsg    = ""
  	lastActivity time.Time
  )
  
  const (
  	awayTime         = 10 * time.Minute
  	extendedAwayTime = 30 * time.Minute
  )
  
  func usage() {
  	fmt.Fprintf(os.Stderr, "usage: Chat [-a acct] name...\n")
  	flag.PrintDefaults()
  	os.Exit(2)
  }
  
  var acctName = flag.String("a", "", "account to use")
  
  func main() {
  	flag.Usage = usage
  	flag.Parse()
  
  	acct = google.Acct(*acctName)
  
  	aw, err := acme.New()
  	if err != nil {
  		log.Fatal(err)
  	}
  	aw.Name("Chat/" + acct.Nick + "/")
  
  	client, err = xmpp.NewClient("talk.google.com:443", acct.Email, acct.Password)
  	if err != nil {
  		log.Fatal(err)
  	}
  
  	w := &Window{Win: aw, typ: "main", name: "Chat/" + acct.Nick + "/"}
  	data, err := ioutil.ReadFile(google.Dir() + "/chat." + acct.Nick)
  	if err != nil {
  		log.Fatal(err)
  	}
  	if err == nil {
  		w.Write("body", data)
  	}
  	mainWin = w
  	active[w.name] = w
  	go w.readAcme()
  	client.Roster()
  	setStatus(status)
  	go w.readChat()
  	lastActivity = time.Now()
  
  	tick := time.Tick(0.5e9)
  Loop:
  	for len(active) > 0 {
  		select {
  		case w := <-acmeChan:
  			if w == nil {
  				// Sync with reader.
  				continue
  			}
  			if w.err != nil {
  				if active[w.name] == nil {
  					continue
  				}
  				log.Fatal(w.err)
  			}
  			if *acmeDebug {
  				fmt.Fprintf(os.Stderr, "%s %c%c %d,%d %q\n", w.name, w.C1, w.C2, w.Q0, w.Q1, w.Text)
  			}
  			if w.C1 == 'M' || w.C1 == 'K' {
  				lastActivity = time.Now()
  				if status != xmpp.Available {
  					setStatus(xmpp.Available)
  				}
  			}
  			if (w.C2 == 'x' || w.C2 == 'X') && string(w.Text) == "Del" {
  				// TODO: Hangup connection for w.typ == "acct"?
  				delete(active, w.name)
  				w.Del(true)
  				continue Loop
  			}
  
  			switch w.typ {
  			case "main":
  				switch w.C2 {
  				case 'L': // Button 3 in body: load chat window for contact.
  					w.expand()
  					fallthrough
  				case 'l': // Button 3 in tag
  					arg := string(w.Text)
  					showContact(arg)
  					continue Loop
  				}
  			case "chat":
  				if w.C1 == 'F' && w.C2 == 'I' {
  					continue Loop
  				}
  				if w.C1 != 'M' && w.C1 != 'K' {
  					break
  				}
  				if w.blinky {
  					w.blinky = false
  					w.Fprintf("ctl", "dirty\n")
  				}
  				switch w.C2 {
  				case 'X', 'x':
  					if string(w.Text) == "Ack" {
  						w.Fprintf("ctl", "clean\n")
  					}
  				case 'I':
  					w.sendMsg()
  					continue Loop
  				}
  			}
  			w.WriteEvent(w.Event)
  
  		case msg := <-msgChan:
  			w := msg.w
  			if msg.err != nil {
  				w.Fprintf("body", "ERROR: %s\n", msg.err)
  				continue Loop
  			}
  			you := msg.Remote
  			if i := strings.Index(you, "/"); i >= 0 {
  				you = you[:i]
  			}
  			switch msg.Type {
  			case "chat":
  				w := showContact(you)
  				text := strings.TrimSpace(msg.Text)
  				if text == "" {
  					// Probably a composing notification.
  					continue
  				}
  				w.message("> %s\n", text)
  				w.blinky = true
  				w.dirty = true
  
  			case "presence":
  				pr := msg.Presence
  				pr, new := savePresence(pr, you)
  				if !new {
  					continue
  				}
  				w := lookContact(you)
  				if w != nil {
  					w.status(pr)
  				}
  				mainStatus(pr, you)
  			}
  
  		case t := <-tick:
  			switch status {
  			case xmpp.Available:
  				if t.Sub(lastActivity) > awayTime {
  					setStatus(xmpp.Away)
  				}
  			case xmpp.Away:
  				if t.Sub(lastActivity) > extendedAwayTime {
  					setStatus(xmpp.ExtendedAway)
  				}
  			}
  			for _, w := range active {
  				if w.blinky {
  					w.dirty = !w.dirty
  					if w.dirty {
  						w.Fprintf("ctl", "dirty\n")
  					} else {
  						w.Fprintf("ctl", "clean\n")
  					}
  				}
  			}
  		}
  	}
  }
  
  func setStatus(st xmpp.Status) {
  	status = st
  	client.Status(status, statusMsg)
  	mainWin.statusTag(status, statusMsg)
  }
  
  func savePresence(pr *xmpp.Presence, you string) (pr1 *xmpp.Presence, new bool) {
  	old := cachedPresence(you)
  
  	pr.StatusMsg = strings.TrimSpace(pr.StatusMsg)
  	c := statusCache[you]
  	for i, p := range c {
  		if p.Remote == pr.Remote {
  			c[i] = pr
  			c[0], c[i] = c[i], c[0]
  			goto Best
  		}
  	}
  	c = append(c, pr)
  	c[0], c[len(c)-1] = c[len(c)-1], c[0]
  	statusCache[you] = c
  
  Best:
  	best := cachedPresence(you)
  	return best, old == nil || old.Status != best.Status || old.StatusMsg != best.StatusMsg
  }
  
  func cachedPresence(you string) *xmpp.Presence {
  	c := statusCache[you]
  	if len(c) == 0 {
  		return nil
  	}
  	best := c[0]
  	for _, p := range c {
  		if p.Status > best.Status {
  			best = p
  		}
  	}
  	return best
  }
  
  func short(st xmpp.Status) string {
  	switch st {
  	case xmpp.Unavailable:
  		return "?"
  	case xmpp.ExtendedAway:
  		return "x"
  	case xmpp.Away:
  		return "-"
  	case xmpp.Available:
  		return "+"
  	case xmpp.DoNotDisturb:
  		return "!"
  	}
  	return st.String()
  }
  
  func long(st xmpp.Status) string {
  	switch st {
  	case xmpp.Unavailable:
  		return "unavailable"
  	case xmpp.ExtendedAway:
  		return "offline"
  	case xmpp.Away:
  		return "away"
  	case xmpp.Available:
  		return "available"
  	case xmpp.DoNotDisturb:
  		return "busy"
  	}
  	return st.String()
  }
  
  func (w *Window) time() string {
  	/*
  		Auto-date chat windows:
  
  		 	Show date and time on first message.
  		 	Show time if minute is different from last message.
  		 	Show date if day is different from last message.
  
  		 	Oct 10 12:01 > hi
  		 	12:03 hello there
  		 	12:05 > what's up?
  
  			12:10 [Away]
  	*/
  	now := time.Now()
  	m1, d1, y1 := w.lastTime.Date()
  	m2, d2, y2 := now.Date()
  	w.lastTime = now
  
  	if m1 != m2 || d1 != d2 || y1 != y2 {
  		return now.Format("Jan 2 15:04 ")
  	}
  	return now.Format("15:04 ")
  }
  
  func (w *Window) status(pr *xmpp.Presence) {
  	msg := ""
  	if pr.StatusMsg != "" {
  		msg = ": " + pr.StatusMsg
  	}
  	w.message("[%s%s]\n", long(pr.Status), msg)
  
  	w.statusTag(pr.Status, pr.StatusMsg)
  }
  
  func (w *Window) statusTag(status xmpp.Status, statusMsg string) {
  	data, err := w.ReadAll("tag")
  	if err != nil {
  		log.Printf("read tag: %v", err)
  		return
  	}
  	//log.Printf("tag1: %s\n", data)
  	i := bytes.IndexByte(data, '|')
  	if i >= 0 {
  		data = data[i+1:]
  	} else {
  		data = nil
  	}
  	//log.Printf("tag2: %s\n", data)
  	j := bytes.IndexByte(data, '|')
  	if j >= 0 {
  		data = data[j+1:]
  	}
  	//log.Printf("tag3: %s\n", data)
  
  	msg := ""
  	if statusMsg != "" {
  		msg = " " + statusMsg
  	}
  	w.Ctl("cleartag\n")
  	w.Write("tag", []byte(" "+short(status)+msg+" |"+string(data)))
  }
  
  func mainStatus(pr *xmpp.Presence, you string) {
  	w := mainWin
  	if err := w.Addr("#0/^(.[ \t]+)?" + regexp.QuoteMeta(you) + "([ \t]*|$)/"); err != nil {
  		return
  	}
  	q0, q1, err := w.ReadAddr()
  	if err != nil {
  		log.Printf("ReadAddr: %s\n", err)
  		return
  	}
  	if err := w.Addr("#%d/"+regexp.QuoteMeta(you)+"/", q0); err != nil {
  		log.Printf("Addr2: %s\n", err)
  	}
  	q2, q3, err := w.ReadAddr()
  	if err != nil {
  		log.Printf("ReadAddr2: %s\n", err)
  		return
  	}
  
  	space := " "
  	if q1 > q3 || pr.StatusMsg == "" { // already have or don't need space
  		space = ""
  	}
  	if err := w.Addr("#%d/.*/", q1); err != nil {
  		log.Printf("Addr3: %s\n", err)
  	}
  	w.Fprintf("data", "%s%s", space, pr.StatusMsg)
  
  	space = ""
  	if q0 == q2 {
  		w.Addr("#%d,#%d", q0, q0)
  		space = " "
  	} else {
  		w.Addr("#%d,#%d", q0, q0+1)
  	}
  	w.Fprintf("data", "%s%s", short(pr.Status), space)
  }
  
  func (w *Window) expand() {
  	// Use selection if any.
  	w.Fprintf("ctl", "addr=dot\n")
  	q0, q1, err := w.ReadAddr()
  	if err == nil && q0 <= w.Q0 && w.Q0 <= q1 {
  		goto Read
  	}
  	if err = w.Addr("#%d-/[a-zA-Z0-9_@.\\-]*/,#%d+/[a-zA-Z0-9_@.\\-]*/", w.Q0, w.Q1); err != nil {
  		log.Printf("expand: %v", err)
  		return
  	}
  	q0, q1, err = w.ReadAddr()
  	if err != nil {
  		log.Printf("expand: %v", err)
  		return
  	}
  
  Read:
  	data, err := w.ReadAll("xdata")
  	if err != nil {
  		log.Printf("read: %v", err)
  		return
  	}
  	w.Text = data
  	w.Q0 = q0
  	w.Q1 = q1
  	return
  }
  
  // Invariant: in chat windows, the acme addr corresponds to the
  // empty string just before the input being typed.  Text before addr
  // is the chat history (usually ending in a blank line).
  
  func (w *Window) message(format string, args ...interface{}) {
  	if *acmeDebug {
  		q0, q1, _ := w.ReadAddr()
  		log.Printf("message; addr=%d,%d", q0, q1)
  	}
  	if err := w.Addr(".-/\\n?\\n?/"); err != nil && *acmeDebug {
  		log.Printf("set addr: %s", err)
  	}
  	q0, _, _ := w.ReadAddr()
  	nl := ""
  	if q0 > 0 {
  		nl = "\n"
  	}
  	if *acmeDebug {
  		q0, q1, _ := w.ReadAddr()
  		log.Printf("inserting; addr=%d,%d", q0, q1)
  	}
  	w.Fprintf("data", nl+w.time()+format+"\n", args...)
  	if *acmeDebug {
  		q0, q1, _ := w.ReadAddr()
  		log.Printf("wrote; addr=%d,%d", q0, q1)
  	}
  }
  
  func (w *Window) sendMsg() {
  	if *acmeDebug {
  		q0, q1, _ := w.ReadAddr()
  		log.Printf("sendMsg; addr=%d,%d", q0, q1)
  	}
  	if err := w.Addr(`.,./(.|\n)*\n/`); err != nil {
  		if *acmeDebug {
  			q0, q1, _ := w.ReadAddr()
  			log.Printf("no text (%s); addr=%d,%d", err, q0, q1)
  		}
  		return
  	}
  	q0, q1, _ := w.ReadAddr()
  	if *acmeDebug {
  		log.Printf("found msg; addr=%d,%d", q0, q1)
  	}
  	line, _ := w.ReadAll("xdata")
  	trim := string(bytes.TrimSpace(line))
  	if len(trim) > 0 {
  		err := client.Send(xmpp.Chat{Remote: w.remote, Type: "chat", Text: trim})
  
  		// Select blank line before input (if any) and input.
  		w.Addr("#%d-/\\n?\\n?/,#%d", q0, q1)
  		if *acmeDebug {
  			q0, q1, _ := w.ReadAddr()
  			log.Printf("selected text; addr=%d,%d", q0, q1)
  		}
  		q0, _, _ := w.ReadAddr()
  
  		// Overwrite with \nmsg\n\n.
  		// Leaves addr after final \n, which is where we want it.
  		nl := ""
  		if q0 > 0 {
  			nl = "\n"
  		}
  		errstr := ""
  		if err != nil {
  			errstr = fmt.Sprintf("\n%s", errstr)
  		}
  		w.Fprintf("data", "%s%s%s%s\n\n", nl, w.time(), trim, errstr)
  		if *acmeDebug {
  			q0, q1, _ := w.ReadAddr()
  			log.Printf("wrote; addr=%d,%d", q0, q1)
  		}
  		w.Fprintf("ctl", "clean\n")
  	}
  }
  
  func (w *Window) readAcme() {
  	for {
  		e, err := w.ReadEvent()
  		if err != nil {
  			w.err = err
  			acmeChan <- w
  			break
  		}
  		//fmt.Printf("%c%c %d,%d %d,%d %#x %#q %#q %#q\n", e.C1, e.C2, e.Q0, e.Q1, e.OrigQ0, e.OrigQ1, e.Flag, e.Text, e.Arg, e.Loc)
  		w.Event = e
  		acmeChan <- w
  		acmeChan <- nil
  	}
  }
  
  func (w *Window) readChat() {
  	for {
  		msg, err := client.Recv()
  		if err != nil {
  			msgChan <- &Msg{w: w, err: err}
  			break
  		}
  		//fmt.Printf("%s\n", *msg)
  		msgChan <- &Msg{w: w, Chat: &msg}
  	}
  }
  
  func lookContact(you string) *Window {
  	return active["Chat/"+acct.Nick+"/"+you]
  }
  
  func showContact(you string) *Window {
  	w := lookContact(you)
  	if w != nil {
  		w.Ctl("show\n")
  		return w
  	}
  
  	ww, err := acme.New()
  	if err != nil {
  		log.Fatal(err)
  	}
  
  	name := "Chat/" + acct.Nick + "/" + you
  	ww.Name(name)
  	w = &Window{Win: ww, typ: "chat", name: name, remote: you}
  	w.Fprintf("body", "\n")
  	w.Addr("#1")
  	w.OpenEvent()
  	w.Fprintf("ctl", "cleartag\n")
  	w.Fprintf("tag", " Ack")
  	if p := cachedPresence(you); p != nil {
  		w.status(p)
  	}
  	active[name] = w
  	go w.readAcme()
  	return w
  }
  
  func randid() string {
  	return fmt.Sprint(time.Now())
  }
  