This repository has been archived on 2021-06-24. You can view files and clone it, but cannot push or open issues or pull requests.
ui/go/ui/gcd.go

524 lines
18 KiB
Go
Raw Normal View History

package ui
2018-11-22 00:01:17 +00:00
import (
2019-01-21 21:41:47 +00:00
"cwtch.im/cwtch/event"
"cwtch.im/cwtch/protocol/connections"
2018-11-28 22:14:02 +00:00
"cwtch.im/ui/go/constants"
2019-03-18 23:52:46 +00:00
"github.com/therecipe/qt/qml"
2018-11-22 00:01:17 +00:00
2018-11-28 22:14:02 +00:00
"cwtch.im/ui/go/the"
2018-11-22 00:01:17 +00:00
"encoding/base32"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"github.com/therecipe/qt/core"
2018-11-22 00:01:17 +00:00
"strings"
"time"
)
type GrandCentralDispatcher struct {
core.QObject
UIManager Manager
QMLEngine *qml.QQmlApplicationEngine
Translator *core.QTranslator
2018-11-22 00:01:17 +00:00
_ string `property:"os"`
2018-11-22 00:01:17 +00:00
_ string `property:"currentOpenConversation"`
_ float32 `property:"themeScale"`
_ string `property:"version"`
_ string `property:"buildDate"`
2018-11-22 00:01:17 +00:00
// profile management stuff
_ func() `signal:"Loaded"`
_ func(handle, displayname, image string) `signal:"AddProfile"`
_ func() `signal:"ErrorLoaded0"`
_ func() `signal:"ResetProfile"`
2018-11-22 00:01:17 +00:00
// contact list stuff
_ func(handle, displayName, image, server string, badge, status int, blocked bool, loading bool, lastMsgTime int) `signal:"AddContact"`
_ func(handle, displayName string) `signal:"UpdateContactDisplayName"`
_ func(handle string, status int, loading bool) `signal:"UpdateContactStatus"`
_ func(handle string, blocked bool) `signal:"UpdateContactBlocked"`
_ func(handle string) `signal:"IncContactUnreadCount"`
_ func(handle string) `signal:"RemoveContact"`
_ func(handle, key, value string) `signal:"UpdateContactAttribute"`
2018-11-22 00:01:17 +00:00
// messages pane stuff
_ func(handle, from, displayName, message, image string, mID string, fromMe bool, ts string, ackd bool, error bool) `signal:"AppendMessage"`
2019-04-08 20:28:36 +00:00
_ func(handle, from, displayName, message, image string, mID string, fromMe bool, ts string, ackd bool, error bool) `signal:"PrependMessage"`
_ func() `signal:"ClearMessages"`
_ func() `signal:"ResetMessagePane"`
_ func(mID string) `signal:"Acknowledged"`
_ func(title string) `signal:"SetToolbarTitle"`
_ func(signature string, err string) `signal:"GroupSendError"`
_ func(loading bool) `signal:"SetLoadingState"`
2018-11-22 00:01:17 +00:00
// profile-area stuff
_ func(name, onion, image string) `signal:"UpdateMyProfile"`
_ func(status int, str string) `signal:"TorStatus"`
// settings helpers
_ func(str string) `signal:"InvokePopup"`
2019-08-21 20:55:08 +00:00
_ func(zoom, locale string, blockunknownpeers bool) `signal:"SupplySettings"`
_ func(groupID, name, server, invitation string, accepted bool, addrbooknames, addrbookaddrs []string) `signal:"SupplyGroupSettings"`
_ func(onion, nick string, blocked bool) `signal:"SupplyPeerSettings"`
2018-11-22 00:01:17 +00:00
// signals emitted from the ui (written in go, below)
_ func(message string, mid string) `signal:"sendMessage,auto"`
_ func(onion string) `signal:"blockPeer,auto"`
2019-08-08 21:42:51 +00:00
_ func(onion string) `signal:"unblockPeer,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:"createContact,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() `signal:"requestSettings,auto"`
_ func(zoom, locale string) `signal:"saveSettings,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(onion string) `signal:"deleteContact,auto"`
_ func(locale string) `signal:"setLocale,auto"`
2019-08-21 20:55:08 +00:00
_ func() `signal:"allowUnknownPeers,auto"`
_ func() `signal:"blockUnknownPeers,auto"`
_ func() `signal:"onActivate,auto"`
_ func(password string) `signal:"unlockProfiles,auto"`
_ func(handle string) `signal:"loadProfile,auto"`
2018-11-22 00:01:17 +00:00
}
func (this *GrandCentralDispatcher) sendMessage(message string, mID string) {
2018-11-22 00:01:17 +00:00
if len(message) > 65530 {
this.InvokePopup("message is too long")
return
}
if this.CurrentOpenConversation() == "" {
this.InvokePopup("ui error")
return
}
if isGroup(this.CurrentOpenConversation()) {
2018-11-22 00:01:17 +00:00
if !the.Peer.GetGroup(this.CurrentOpenConversation()).Accepted {
2019-01-28 19:57:44 +00:00
err := the.Peer.AcceptInvite(this.CurrentOpenConversation())
if err != nil {
log.Errorf("tried to mark a nonexistent group as existed. bad!")
return
}
2018-11-22 00:01:17 +00:00
}
var err error
mID, err = the.Peer.SendMessageToGroupTracked(this.CurrentOpenConversation(), message)
2019-03-04 22:02:11 +00:00
this.UIManager.AddMessage(this.CurrentOpenConversation(), "me", message, true, mID, time.Now(), false)
2019-03-04 22:02:11 +00:00
if err != nil {
this.InvokePopup("failed to send message " + err.Error())
return
}
} else {
to := this.CurrentOpenConversation()
2019-03-04 22:02:11 +00:00
mID = the.Peer.SendMessageToPeer(to, message)
this.UIManager.AddMessage(to, "me", message, true, mID, time.Now(), false)
2018-11-22 00:01:17 +00:00
}
}
func (this *GrandCentralDispatcher) loadMessagesPane(handle string) {
2019-02-03 04:52:29 +00:00
go this.loadMessagesPaneHelper(handle)
}
func (this *GrandCentralDispatcher) loadMessagesPaneHelper(handle string) {
if handle == the.Peer.GetProfile().Onion {
return
}
2018-11-22 00:01:17 +00:00
this.ClearMessages()
this.SetCurrentOpenConversation(handle)
if isGroup(handle) { // LOAD GROUP
2019-02-02 00:27:17 +00:00
group := the.Peer.GetGroup(handle)
loading := false
state := connections.ConnectionStateToType[group.State]
if state == connections.AUTHENTICATED {
loading = true
}
this.UpdateContactStatus(group.GroupID, int(state), loading)
2019-02-02 00:27:17 +00:00
tl := group.GetTimeline()
nick, _ := group.GetAttribute(constants.Nick)
updateLastReadTime(group.GroupID)
2019-02-02 00:27:17 +00:00
if nick == "" {
this.SetToolbarTitle(handle)
} else {
this.SetToolbarTitle(nick)
}
2019-04-16 19:40:19 +00:00
2019-04-08 20:28:36 +00:00
for i := len(tl) - 1; i >= 0; i-- {
2018-11-22 00:01:17 +00:00
if tl[i].PeerID == the.Peer.GetProfile().Onion {
handle = "me"
} else {
handle = tl[i].PeerID
}
name := getOrDefault(tl[i].PeerID, constants.Nick, tl[i].PeerID)
image := getProfilePic(tl[i].PeerID)
2019-04-17 21:03:50 +00:00
2019-04-08 20:28:36 +00:00
this.PrependMessage(
2018-11-22 00:01:17 +00:00
handle,
tl[i].PeerID,
name,
tl[i].Message,
image,
string(tl[i].Signature),
2018-11-22 00:01:17 +00:00
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,
)
2018-11-22 00:01:17 +00:00
}
return
} // ELSE LOAD CONTACT
2019-02-03 00:04:41 +00:00
contact, _ := the.Peer.GetProfile().GetContact(handle)
this.UpdateContactStatus(handle, int(connections.ConnectionStateToType[contact.State]), false)
2019-10-21 19:19:18 +00:00
var nick string
2019-02-13 03:08:23 +00:00
if contact != nil {
nick, _ = contact.GetAttribute(constants.Nick)
2019-02-13 03:08:23 +00:00
if nick == "" {
2019-10-21 19:19:18 +00:00
nick = handle
2019-02-13 03:08:23 +00:00
}
2019-02-02 00:27:17 +00:00
}
updateLastReadTime(contact.Onion)
2019-10-21 19:19:18 +00:00
this.SetToolbarTitle(nick)
2019-02-02 00:27:17 +00:00
2019-10-21 19:19:18 +00:00
peer := the.Peer.GetContact(handle)
messages := peer.Timeline.GetMessages()
2018-11-22 00:01:17 +00:00
for i := range messages {
2019-10-21 19:19:18 +00:00
from := messages[i].PeerID
fromMe := messages[i].PeerID == the.Peer.GetProfile().Onion
if fromMe {
2018-11-22 00:01:17 +00:00
from = "me"
}
displayname := getOrDefault(messages[i].PeerID, constants.Nick, messages[i].PeerID)
image := getProfilePic(messages[i].PeerID)
2019-07-31 18:59:43 +00:00
2018-11-22 00:01:17 +00:00
this.AppendMessage(
from,
2019-10-21 19:19:18 +00:00
messages[i].PeerID,
displayname,
2018-11-22 00:01:17 +00:00
messages[i].Message,
image,
2019-10-21 19:19:18 +00:00
string(messages[i].Signature),
fromMe,
2018-11-22 00:01:17 +00:00
messages[i].Timestamp.Format(constants.TIME_FORMAT),
2019-10-21 19:19:18 +00:00
messages[i].Acknowledged,
messages[i].Error != "",
2018-11-22 00:01:17 +00:00
)
}
}
2019-03-18 23:52:46 +00:00
func (this *GrandCentralDispatcher) requestSettings() {
2019-08-21 20:55:08 +00:00
zoom, _ := the.Peer.GetProfile().GetAttribute(constants.ZoomSetting)
locale, _ := the.Peer.GetProfile().GetAttribute(constants.LocaleSetting)
blockunkownpeers, _ := the.Peer.GetProfile().GetAttribute(constants.BlockUnknownPeersSetting)
this.SupplySettings(zoom, locale, blockunkownpeers == "true")
2019-03-18 23:52:46 +00:00
}
func (this *GrandCentralDispatcher) saveSettings(zoom, locale string) {
// saveSettings accidentally gets called once when the app first starts but before the app has been prepared
// so let's just ignore that one
if the.CwtchApp == nil {
return
}
the.Peer.SetAttribute(constants.ZoomSetting, zoom)
2019-03-18 23:52:46 +00:00
}
func (this *GrandCentralDispatcher) requestPeerSettings() {
contact := the.Peer.GetContact(this.CurrentOpenConversation())
if contact == nil {
log.Errorf("error: requested settings for unknown contact %v?", this.CurrentOpenConversation())
2019-08-08 21:42:51 +00:00
this.SupplyPeerSettings(this.CurrentOpenConversation(), this.CurrentOpenConversation(), false)
return
}
name, exists := contact.GetAttribute(constants.Nick)
if !exists {
log.Errorf("error: couldn't find contact %v", this.CurrentOpenConversation())
2019-08-08 21:42:51 +00:00
this.SupplyPeerSettings(this.CurrentOpenConversation(), this.CurrentOpenConversation(), contact.Blocked)
return
}
2019-08-08 21:42:51 +00:00
this.SupplyPeerSettings(contact.Onion, name, contact.Blocked)
}
func (this *GrandCentralDispatcher) savePeerSettings(onion, nick string) {
the.Peer.SetContactAttribute(onion, constants.Nick, nick)
this.UpdateContactDisplayName(onion, nick)
}
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(constants.Nick)
invite, _ := the.Peer.ExportGroup(groupID)
2019-02-11 20:23:31 +00:00
contactaddrs := the.Peer.GetContacts()
contactnames := make([]string, len(contactaddrs))
for i, contact := range contactaddrs {
name, hasname := the.Peer.GetContact(contact).GetAttribute(constants.Nick)
2019-02-11 20:23:31 +00:00
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) {
the.Peer.SetGroupAttribute(groupID, constants.Nick, nick)
this.UpdateContactDisplayName(groupID, nick)
2018-11-22 00:01:17 +00:00
}
func (this *GrandCentralDispatcher) broadcast(signal string) {
switch signal {
default:
log.Debugf("unhandled broadcast signal: %v", signal)
2018-11-22 00:01:17 +00:00
case "ResetMessagePane":
this.ResetMessagePane()
case "ResetProfile":
this.ResetProfile()
2018-11-22 00:01:17 +00:00
}
}
func (this *GrandCentralDispatcher) createContact(onion string) {
if contact := the.Peer.GetContact(onion); contact != nil {
return
}
the.Peer.AddContact(onion, onion, false)
the.Peer.PeerWithOnion(onion)
}
2018-11-22 00:01:17 +00:00
func (this *GrandCentralDispatcher) importString(str string) {
if len(str) < 5 {
log.Debugf("ignoring short string")
2018-11-22 00:01:17 +00:00
return
}
log.Debugf("importing: %s\n", str)
2018-11-22 00:01:17 +00:00
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)
2018-11-22 00:01:17 +00:00
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 handlers?")
2018-11-22 00:01:17 +00:00
return
}
checkc := the.Peer.GetContact(onion)
if checkc != nil {
this.InvokePopup("already have this contact")
return //TODO: bring them to the duplicate
} else {
the.Peer.AddContact(name, onion, false)
the.Peer.PeerWithOnion(onion)
2018-11-22 00:01:17 +00:00
}
this.UIManager.AddContact(onion)
2018-11-22 00:01:17 +00:00
}
func (this *GrandCentralDispatcher) popup(str string) {
this.InvokePopup(str)
}
func (this *GrandCentralDispatcher) updateNick(nick string) {
the.Peer.GetProfile().Name = nick
the.EventBus.Publish(event.NewEvent(event.SetProfileName, map[event.Field]string{
2019-01-28 19:57:44 +00:00
event.ProfileName: nick,
}))
2018-11-22 00:01:17 +00:00
}
func (this *GrandCentralDispatcher) createGroup(server, groupName string) {
groupID, _, err := the.Peer.StartGroup(server)
if err != nil {
this.popup("group creation failed :(")
return
}
this.UIManager.AddContact(groupID)
2018-11-22 00:01:17 +00:00
the.Peer.SetGroupAttribute(groupID, constants.Nick, groupName)
2018-11-22 00:01:17 +00:00
the.Peer.JoinServer(server)
2018-11-28 22:14:02 +00:00
}
2019-02-11 20:23:31 +00:00
func (this *GrandCentralDispatcher) blockPeer(onion string) {
err := the.Peer.BlockPeer(onion)
if err != nil {
this.InvokePopup("Error Blocking Peer: " + err.Error())
}
this.UpdateContactBlocked(onion, true)
}
2019-08-08 21:42:51 +00:00
func (this *GrandCentralDispatcher) unblockPeer(onion string) {
err := the.Peer.UnblockPeer(onion)
if err != nil {
this.InvokePopup("Error Unblocking Peer: " + err.Error())
}
the.Peer.PeerWithOnion(onion)
this.UpdateContactBlocked(onion, false)
2019-08-08 21:42:51 +00:00
}
2019-02-11 20:23:31 +00:00
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.Peer.DeleteGroup(groupID)
this.RemoveContact(groupID)
}
func (this *GrandCentralDispatcher) deleteContact(onion string) {
the.Peer.DeleteContact(onion)
this.RemoveContact(onion)
}
func (this *GrandCentralDispatcher) acceptGroup(groupID string) {
if the.Peer.GetGroup(groupID) != nil {
the.Peer.AcceptInvite(groupID)
}
}
func (this *GrandCentralDispatcher) setAttribute(onion, key, value string) {
the.Peer.SetContactAttribute(onion, key, value)
this.UIManager.UpdateContactAttribute(onion, key, value)
2019-03-18 23:52:46 +00:00
}
2019-08-21 20:55:08 +00:00
func (this *GrandCentralDispatcher) blockUnknownPeers() {
the.Peer.SetAttribute(constants.BlockUnknownPeersSetting, "true")
2019-08-21 20:55:08 +00:00
the.EventBus.Publish(event.NewEvent(event.BlockUnknownPeers, map[event.Field]string{}))
}
func (this *GrandCentralDispatcher) allowUnknownPeers() {
the.Peer.SetAttribute(constants.BlockUnknownPeersSetting, "false")
2019-08-21 20:55:08 +00:00
the.EventBus.Publish(event.NewEvent(event.AllowUnknownPeers, map[event.Field]string{}))
}
2019-03-18 23:52:46 +00:00
func (this *GrandCentralDispatcher) setLocale(locale string) {
this.SetLocale_helper(locale)
the.Peer.SetAttribute(constants.LocaleSetting, locale)
2019-03-18 23:52:46 +00:00
2019-08-21 20:55:08 +00:00
zoom, _ := the.Peer.GetProfile().GetAttribute(constants.ZoomSetting)
blockunkownpeers, _ := the.Peer.GetProfile().GetAttribute(constants.BlockUnknownPeersSetting)
this.SupplySettings(zoom, locale, blockunkownpeers == "true")
2019-03-18 23:52:46 +00:00
}
func (this *GrandCentralDispatcher) onActivate() {
log.Debugln("onActivate")
if the.CwtchApp != nil {
the.CwtchApp.QueryACNStatus()
}
}
2019-03-18 23:52:46 +00:00
func (this *GrandCentralDispatcher) SetLocale_helper(locale string) {
core.QCoreApplication_RemoveTranslator(this.Translator)
this.Translator = core.NewQTranslator(nil)
this.Translator.Load("translation_"+locale, ":/i18n/", "", "")
core.QCoreApplication_InstallTranslator(this.Translator)
this.QMLEngine.Retranslate()
}
func (this *GrandCentralDispatcher) unlockProfiles(password string) {
the.CwtchApp.LoadProfiles(password)
}
func (this *GrandCentralDispatcher) loadProfile(onion string) {
the.Peer = the.CwtchApp.GetPeer(onion)
the.EventBus = the.CwtchApp.GetEventBus(onion)
pic, exists := the.Peer.GetAttribute(constants.Picture)
if !exists {
pic = RandomProfileImage(the.Peer.GetProfile().Onion)
the.Peer.SetAttribute(constants.Picture, pic)
}
this.UpdateMyProfile(the.Peer.GetProfile().Name, the.Peer.GetProfile().Onion, pic)
contacts := the.Peer.GetContacts()
for i := range contacts {
this.UIManager.AddContact(contacts[i])
}
groups := the.Peer.GetGroups()
for i := range groups {
// Only join servers for active and explicitly accepted groups.
this.UIManager.AddContact(groups[i])
}
// load ui preferences
this.RequestSettings()
locale, exists := the.Peer.GetProfile().GetAttribute(constants.LocaleSetting)
if exists {
this.SetLocale_helper(locale)
}
}