ui/go/gothings/gcd.go

482 lines
14 KiB
Go

package gothings
import (
"cwtch.im/cwtch/event"
"cwtch.im/ui/go/constants"
"cwtch.im/ui/go/cwutil"
"cwtch.im/ui/go/gobjects"
"cwtch.im/ui/go/the"
"encoding/base32"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"github.com/therecipe/qt/core"
"strings"
"time"
)
type GrandCentralDispatcher struct {
core.QObject
OutgoingMessages chan gobjects.Letter
UIState InterfaceState
_ string `property:"currentOpenConversation"`
_ float32 `property:"themeScale"`
// contact list stuff
_ func(handle, displayName, image, server string, badge, status int, trusted bool) `signal:"AddContact"`
_ func(handle, displayName, image, server string, badge, status int, trusted bool) `signal:"UpdateContact"`
_ func(handle, key, value string) `signal:"UpdateContactAttribute"`
// messages pane stuff
_ func(handle, from, displayName, message, image string, mID string, fromMe bool, ts string, ackd bool, error bool) `signal:"AppendMessage"`
_ func() `signal:"ClearMessages"`
_ func() `signal:"ResetMessagePane"`
_ func(mID string) `signal:"Acknowledged"`
_ func(title string) `signal:"SetToolbarTitle"`
_ func(signature string, err string) `signal:"GroupSendError"`
// profile-area stuff
_ func(name, onion, image string) `signal:"UpdateMyProfile"`
_ func(status int, str string) `signal:"TorStatus"`
// settings helpers
_ func(str string) `signal:"InvokePopup"`
_ func(groupID, name, server, invitation string, accepted bool, addrbooknames, addrbookaddrs []string) `signal:"SupplyGroupSettings"`
_ func(onion, nick string) `signal:"SupplyPeerSettings"`
// signals emitted from the ui (written in go, below)
_ func(message string, mid string) `signal:"sendMessage,auto"`
_ func(onion string) `signal:"loadMessagesPane,auto"`
_ func(signal string) `signal:"broadcast,auto"` // convenience relay signal
_ func(str string) `signal:"importString,auto"`
_ func(str string) `signal:"popup,auto"`
_ func(nick string) `signal:"updateNick,auto"`
_ func(server, groupName string) `signal:"createGroup,auto"`
_ func(groupID string) `signal:"leaveGroup,auto"`
_ func(groupID string) `signal:"acceptGroup,auto"`
_ func(groupID string) `signal:"requestGroupSettings,auto"`
_ func(groupID, nick string) `signal:"saveGroupSettings,auto"`
_ func() `signal:"requestPeerSettings,auto"`
_ func(onion, nick string) `signal:"savePeerSettings,auto"`
_ func(onion, groupID string) `signal:"inviteToGroup,auto"`
_ func(onion, key, nick string) `signal:"setAttribute,auto"`
}
func (this *GrandCentralDispatcher) sendMessage(message string, mID string) {
if len(message) > 65530 {
this.InvokePopup("message is too long")
return
}
if this.CurrentOpenConversation() == "" {
this.InvokePopup("ui error")
return
}
if len(this.CurrentOpenConversation()) == 32 { // SEND TO GROUP
if !the.Peer.GetGroup(this.CurrentOpenConversation()).Accepted {
err := the.Peer.AcceptInvite(this.CurrentOpenConversation())
if err != nil {
log.Errorf("tried to mark a nonexistent group as existed. bad!")
return
}
c := this.UIState.GetContact(this.CurrentOpenConversation())
c.Trusted = true
this.UIState.UpdateContact(c.Handle)
}
var err error
mID,err = the.Peer.SendMessageToGroupTracked(this.CurrentOpenConversation(), message)
this.UIState.AddMessage(&gobjects.Message{
this.CurrentOpenConversation(),
"me",
"",
message,
"",
true,
mID,
time.Now(),
false,
false,
})
if err != nil {
this.InvokePopup("failed to send message " +err.Error())
return
}
} else {
// TODO: require explicit invite accept/reject instead of implicitly trusting on send
if !this.UIState.GetContact(this.CurrentOpenConversation()).Trusted {
this.UIState.GetContact(this.CurrentOpenConversation()).Trusted = true
this.UIState.UpdateContact(this.CurrentOpenConversation())
}
to := this.CurrentOpenConversation();
the.Peer.PeerWithOnion(to)
mID = the.Peer.SendMessageToPeer(to, message)
this.UIState.AddMessage(&gobjects.Message{
to,
"me",
"",
message,
"",
true,
mID,
time.Now(),
false,
false,
})
ackID := new(the.AckId)
ackID.ID = mID
ackID.Ack = false
the.AcknowledgementIDs[to] = append(the.AcknowledgementIDs[to], ackID)
}
}
func (this *GrandCentralDispatcher) loadMessagesPane(handle string) {
go this.loadMessagesPaneHelper(handle)
}
func (this *GrandCentralDispatcher) loadMessagesPaneHelper(handle string) {
this.ClearMessages()
this.SetCurrentOpenConversation(handle)
c := this.UIState.GetContact(handle)
if c == nil {
this.UIState.AddContact(&gobjects.Contact{
handle,
handle,
cwutil.RandomProfileImage(handle),
"",
0,
0,
false,
})
} else {
c.Badge = 0
this.UIState.UpdateContact(handle)
}
if len(handle) == 32 { // LOAD GROUP
log.Debugf("LOADING GROUP %s", handle)
group := the.Peer.GetGroup(handle)
tl := group.GetTimeline()
nick, _ := group.GetAttribute("nick")
if nick == "" {
this.SetToolbarTitle(handle)
} else {
this.SetToolbarTitle(nick)
}
log.Debugf("messages: %d", len(tl))
for i := range tl {
if tl[i].PeerID == the.Peer.GetProfile().Onion {
handle = "me"
} else {
handle = tl[i].PeerID
}
var name string
var exists bool
ctc := the.Peer.GetContact(tl[i].PeerID)
if ctc != nil {
name, exists = ctc.GetAttribute("nick")
if !exists || name == "" {
name = tl[i].PeerID[:16] + "..."
}
}
this.AppendMessage(
handle,
tl[i].PeerID,
name,
tl[i].Message,
cwutil.RandomProfileImage(tl[i].PeerID),
string(tl[i].Signature),
tl[i].PeerID == the.Peer.GetProfile().Onion,
tl[i].Timestamp.Format(constants.TIME_FORMAT),
tl[i].Received.Equal(time.Unix(0,0)) == false, // If the received timestamp is epoch, we have not yet received this message through an active server
false,
)
}
return
} // ELSE LOAD CONTACT
contact, _ := the.Peer.GetProfile().GetContact(handle)
if contact != nil {
nick, _ := contact.GetAttribute("nick")
if nick == "" {
this.SetToolbarTitle(handle)
} else {
this.SetToolbarTitle(nick)
}
}
messages := this.UIState.GetMessages(handle)
for i := range messages {
from := messages[i].From
if messages[i].FromMe {
from = "me"
}
this.AppendMessage(
messages[i].Handle,
from,
messages[i].DisplayName,
messages[i].Message,
cwutil.RandomProfileImage(handle),
messages[i].MessageID,
messages[i].FromMe,
messages[i].Timestamp.Format(constants.TIME_FORMAT),
false,
messages[i].Error,
)
for _,id := range the.AcknowledgementIDs[messages[i].Handle] {
if id.ID == messages[i].MessageID && id.Ack && !id.Error {
this.Acknowledged(id.ID)
}
}
}
}
func (this *GrandCentralDispatcher) requestPeerSettings() {
contact := the.Peer.GetContact(this.CurrentOpenConversation())
if contact == nil {
log.Errorf("error: requested settings for unknown contact %v?", this.CurrentOpenConversation())
this.SupplyPeerSettings(this.CurrentOpenConversation(), this.CurrentOpenConversation())
return
}
name, exists := contact.GetAttribute("nick")
if !exists {
log.Errorf("error: couldn't find contact %v", this.CurrentOpenConversation())
this.SupplyPeerSettings(this.CurrentOpenConversation(), this.CurrentOpenConversation())
return
}
this.SupplyPeerSettings(contact.Onion, name)
}
func (this *GrandCentralDispatcher) savePeerSettings(onion, nick string) {
contact := the.Peer.GetContact(onion)
if contact == nil {
log.Errorf("error: tried to save settings for unknown peer %v", onion)
return
}
contact.SetAttribute("nick", nick)
the.CwtchApp.EventBus().Publish(event.NewEvent(event.SetPeerAttribute, map[event.Field]string{
event.RemotePeer: onion,
event.Key: "nick",
event.Data: nick,
}))
this.UIState.contacts[onion].DisplayName = nick
this.UIState.UpdateContact(onion)
}
func (this *GrandCentralDispatcher) requestGroupSettings(groupID string) {
group := the.Peer.GetGroup(groupID)
if group == nil {
log.Errorf("couldn't find group %v", groupID)
return
}
nick, _ := group.GetAttribute("nick")
invite, _ := the.Peer.ExportGroup(groupID)
contactaddrs := the.Peer.GetContacts()
contactnames := make([]string, len(contactaddrs))
for i, contact := range contactaddrs {
name, hasname := the.Peer.GetContact(contact).GetAttribute("nick")
if hasname {
contactnames[i] = name
} else {
contactnames[i] = contact
}
}
this.SupplyGroupSettings(group.GroupID, nick, group.GroupServer, invite, group.Accepted, contactnames, contactaddrs)
}
func (this *GrandCentralDispatcher) saveGroupSettings(groupID, nick string) {
group := the.Peer.GetGroup(groupID)
if group == nil {
log.Errorf("couldn't find group %v", groupID)
return
}
group.SetAttribute("nick", nick)
the.CwtchApp.EventBus().Publish(event.NewEvent(event.SetGroupAttribute, map[event.Field]string{
event.GroupID: groupID,
event.Key: "nick",
event.Data: nick,
}))
this.UIState.contacts[groupID].DisplayName = nick
this.UIState.UpdateContact(groupID)
}
func (this *GrandCentralDispatcher) broadcast(signal string) {
switch signal {
default:
log.Debugf("unhandled broadcast signal: %v", signal)
case "ResetMessagePane":
this.ResetMessagePane()
}
}
func (this *GrandCentralDispatcher) importString(str string) {
if len(str) < 5 {
log.Debugf("ignoring short string")
return
}
log.Debugf("importing: %s\n", str)
onion := str
name := onion
str = strings.TrimSpace(str)
//eg: torv3JFDWkXExBsZLkjvfkkuAxHsiLGZBk0bvoeJID9ItYnU=EsEBCiBhOWJhZDU1OTQ0NWI3YmM2N2YxYTM5YjkzMTNmNTczNRIgpHeNaG+6jy750eDhwLO39UX4f2xs0irK/M3P6mDSYQIaOTJjM2ttb29ibnlnaGoyenc2cHd2N2Q1N3l6bGQ3NTNhdW8zdWdhdWV6enB2ZmFrM2FoYzRiZHlkCiJAdVSSVgsksceIfHe41OJu9ZFHO8Kwv3G6F5OK3Hw4qZ6hn6SiZjtmJlJezoBH0voZlCahOU7jCOg+dsENndZxAA==
if str[0:5] == "torv3" { // GROUP INVITE
_, err := the.Peer.ImportGroup(str)
if err != nil {
this.InvokePopup("not a valid group invite")
return
}
return
}
if strings.Contains(str, " ") { // usually people prepend spaces and we don't want it going into the name (use ~ for that)
parts := strings.Split(strings.TrimSpace(str), " ")
str = parts[len(parts)-1]
}
if strings.Contains(str, "~") {
parts := strings.Split(str, "~")
onion = parts[len(parts)-1]
name = strings.Join(parts[:len(parts)-1], " ")
}
if len(onion) != 56 {
this.InvokePopup("invalid format")
return
}
name = strings.TrimSpace(name)
if name == "" {
this.InvokePopup("empty name")
return
}
if len(name) > 32 {
name = name[:32] //TODO: better strategy for long names?
}
_, err := base32.StdEncoding.DecodeString(strings.ToUpper(onion[:56]))
if err != nil {
log.Debugln(err)
this.InvokePopup("bad format. missing characters?")
return
}
checkc := the.Peer.GetContact(onion)
if checkc != nil {
deleted,_ := checkc.GetAttribute("deleted")
if deleted != "deleted" {
this.InvokePopup("already have this contact")
return //TODO: bring them to the duplicate
}
this.SetAttribute(onion, "deleted", "")
}
this.UIState.AddContact(&gobjects.Contact{
Handle: onion,
DisplayName: name,
Image: cwutil.RandomProfileImage(onion),
Trusted: true,
})
}
func (this *GrandCentralDispatcher) popup(str string) {
this.InvokePopup(str)
}
func (this *GrandCentralDispatcher) updateNick(nick string) {
the.Peer.GetProfile().Name = nick
the.CwtchApp.EventBus().Publish(event.NewEvent(event.SetProfileName, map[event.Field]string{
event.ProfileName: nick,
}))
}
func (this *GrandCentralDispatcher) createGroup(server, groupName string) {
groupID, _, err := the.Peer.StartGroup(server)
if err != nil {
this.popup("group creation failed :(")
return
}
this.UIState.AddContact(&gobjects.Contact{
Handle: groupID,
DisplayName: groupName,
Image: cwutil.RandomGroupImage(groupID),
Server: server,
Trusted: true,
})
group := the.Peer.GetGroup(groupID)
group.SetAttribute("nick", groupName)
the.CwtchApp.EventBus().Publish(event.NewEvent(event.SetGroupAttribute, map[event.Field]string{
event.GroupID: groupID,
event.Key: "nick",
event.Data: groupName,
}))
the.Peer.JoinServer(server)
}
func (this *GrandCentralDispatcher) inviteToGroup(onion, groupID string) {
err := the.Peer.InviteOnionToGroup(onion, groupID)
if err != nil {
log.Errorf("inviting %v to %v: %v", onion, groupID, err)
}
}
func (this *GrandCentralDispatcher) leaveGroup(groupID string) {
the.CwtchApp.EventBus().Publish(event.NewEvent(event.SetGroupAttribute, map[event.Field]string{
event.GroupID: groupID,
event.Key: "deleted",
event.Data: "deleted",
}))
this.UIState.UpdateContactAttribute(groupID, "deleted", "deleted")
}
func (this *GrandCentralDispatcher) acceptGroup(groupID string) {
if the.Peer.GetGroup(groupID) != nil {
the.Peer.AcceptInvite(groupID)
the.Peer.JoinServer(the.Peer.GetGroup(groupID).GroupServer)
this.UIState.UpdateContact(groupID)
}
}
func (this *GrandCentralDispatcher) setAttribute(onion, key, value string) {
pp,_ := the.Peer.GetProfile().GetContact(onion)
if pp != nil {
pp.SetAttribute(key, value)
the.CwtchApp.EventBus().Publish(event.NewEvent(event.SetPeerAttribute, map[event.Field]string{
event.RemotePeer: onion,
event.Key: key,
event.Data: value,
}))
this.UIState.UpdateContactAttribute(onion, key, value)
}
}