package main import ( "citrons.xyz/talk/client/client" "citrons.xyz/talk/client/object" "citrons.xyz/talk/client/window" "citrons.xyz/talk/proto" ) type application struct { client.Client quit bool connected bool reconnecting bool authenticated bool uid string cache object.ObjCache windowCache window.WindowCache currentWindow window.Location windowHist []window.Location channelList channelList cmdWindow cmdWindow prompts []window.Prompt activePaste <-chan string } func newApplication(serverAddress string) *application { var app application app.Client = client.New(serverAddress) app.goTo(app.cmdWindow.Location()) app.cache = object.NewCache(&app) app.windowCache = window.NewCache() app.cmdWindow.info("welcome! type /help for help. try: /join talk") app.cmdWindow.info("connecting to %s", app.Client.Address) return &app } func (a *application) OnConnect() { u := a.cache.Get(a.uid) var username string if u != nil { username = u.Fields[""] } a.pushPrompt(newLoginPrompt(username)) a.connected = true a.reconnecting = false a.cache = object.NewCache(a) a.cmdWindow.info("connected to %s", a.Client.Address) } func (a *application) OnDisconnect(err error) { a.connected = false a.authenticated = false a.uid = "" a.prompts = nil a.windowCache = window.NewCache() if !a.reconnecting { a.cmdWindow.err( "disconnected from %s: %s\nreconnecting...", a.Client.Address, err, ) a.reconnecting = true } } func (a *application) OnEvent(cmd proto.Command) { switch cmd.Kind { case "p": cw := a.windowCache.Get(channelLocation {cmd.Target}).(*channelWindow) if cw != nil && len(cmd.Args) > 0 { cw.put(cmd.Args[0]) } case "update": if len(cmd.Args) > 0 { a.onUpdate(cmd.Target, cmd.Args[0]) } case "delete": a.cache.Gone(cmd.Target) cl := channelLocation {cmd.Target} if a.windowCache.Get(cl) != nil { a.windowCache.Evict(cl) a.removeFromHistory(cl) } } } func (a *application) OnResponse(requestId string, cmd proto.Command) { switch cmd.Kind { case "you-are": if len(cmd.Args) > 0 { a.cmdWindow.info("your name is: %s", cmd.Args[0].Fields[""]) } } } func (a *application) onUpdate(target string, update proto.Object) { if target == a.uid { a.logUserUpdate(target, update) } a.cache.Update(target, update) } func (a *application) Sub(id string) { a.Request(proto.NewCmd("s", id), func(cmd proto.Command) { if cmd.Kind == "i" && len(cmd.Args) > 0 { a.cache.Update(id, cmd.Args[0]) } }) } func (a *application) Unsub(id string) { a.Request(proto.NewCmd("u", id), nil) } func (a *application) GetInfo(id string, callback func(proto.Command)) { a.Request(proto.NewCmd("i", id), callback) } func (a *application) auth(name string, authCallback func(success bool)) { callback := func(response proto.Command) { switch response.Kind { case "you-are": if len(response.Args) == 0 { authCallback(false) break } me := response.Args[0] a.authenticated = true a.uid = me.Id a.cache.Watch(a.uid) a.onAuth() if authCallback != nil { authCallback(true) } case "fail": if len(response.Args) != 0 { a.cmdWindow.err(proto.Strfail(response.Args[0])) } if authCallback != nil { authCallback(false) } } } a.Request(proto.NewCmd("auth", "", proto.Object { "anonymous", "", map[string]string {"": name}, }), callback) } func (a *application) onAuth() { a.Request(proto.NewCmd("channels", ""), func(response proto.Command) { if response.Kind == "channels" { previousChannels := make(channelList, len(a.channelList)) copy(previousChannels, a.channelList) a.channelList.setChannels(response.Args) u := a.cache.Get(a.uid) if u.Fields["anonymous"] == "yes" { for _, c := range previousChannels { cb := func(response proto.Command) { if response.Kind == "ok" { a.channelList = append(a.channelList, c) } w := a.windowCache.Get(c.location) switch w.(type) { case *channelWindow: w.(*channelWindow).endOfHistory = false } } a.Request(proto.NewCmd("join", c.location.id), cb) } } } }) } func (a *application) sendUpdate(o proto.Object, cb func(proto.Command)) { a.Request(proto.NewCmd("update", o.Id, o), cb) } func (a *application) lookup( name string, kind string, callback func(*proto.Object, *proto.Fail)) { cb := func(response proto.Command) { switch response.Kind { case "i": if len(response.Args) > 0 { callback(&response.Args[0], nil) } case "fail": if len(response.Args) > 0 { f := proto.Fail(response.Args[0]) callback(nil, &f) } } } o := proto.Object {kind, "", map[string]string {"": name}} a.Request(proto.NewCmd("lookup", "", o), cb) } func (a *application) join(channelName string) { a.lookup(channelName, "channel", func(ch *proto.Object, f *proto.Fail) { if f != nil { a.cmdWindow.err(f.Error()) return } a.Request(proto.NewCmd("join", ch.Id), func(response proto.Command) { switch response.Kind { case "ok": a.channelList.add(channelName, channelLocation {id: ch.Id}) a.goTo(channelLocation {id: ch.Id}) w := a.windowCache.Get(channelLocation {id: ch.Id}) switch w.(type) { case *channelWindow: w.(*channelWindow).endOfHistory = false } case "fail": if len(response.Args) > 0 { f := proto.Fail(response.Args[0]) a.cmdWindow.err(f.Error()) } } }) }) } func (a *application) createChannel(name string) { ch := proto.Object {"channel", "", map[string]string {"": name}} a.Request(proto.NewCmd("create", "", ch), func(response proto.Command) { switch response.Kind { case "create": if len(response.Args) > 0 { ch := response.Args[0] a.channelList.add(ch.Fields[""], channelLocation {id: ch.Id}) a.goTo(channelLocation {id: ch.Id}) } case "fail": if len(response.Args) > 0 { f := proto.Fail(response.Args[0]) a.cmdWindow.err(f.Error()) } } }) } func (a *application) getNick() string { if !a.authenticated { return "" } u := a.cache.Get(a.uid) if u != nil { return u.Fields[""] } return "" } func (a *application) setNick(newName string) { if !a.authenticated { return } callback := func(response proto.Command) { switch response.Kind { case "fail": if len(response.Args) != 0 { a.cmdWindow.err(proto.Strfail(response.Args[0])) } } } a.sendUpdate( proto.Object {"u", a.uid, map[string]string {"": newName}}, callback, ) }