939 lines
32 KiB
Go
939 lines
32 KiB
Go
package ui
|
|
|
|
import (
|
|
"cwtch.im/cwtch/app"
|
|
"cwtch.im/cwtch/event"
|
|
"cwtch.im/cwtch/model"
|
|
"cwtch.im/cwtch/model/attr"
|
|
"cwtch.im/cwtch/protocol/connections"
|
|
"cwtch.im/ui/go/constants"
|
|
"cwtch.im/ui/go/features/groups"
|
|
"cwtch.im/ui/go/ui/android"
|
|
"encoding/json"
|
|
"github.com/therecipe/qt/qml"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"cwtch.im/ui/go/the"
|
|
"encoding/base32"
|
|
"git.openprivacy.ca/openprivacy/log"
|
|
"github.com/therecipe/qt/core"
|
|
"strings"
|
|
)
|
|
|
|
type GrandCentralDispatcher struct {
|
|
core.QObject
|
|
|
|
AndroidCwtchActivity *android.CwtchActivity
|
|
QMLEngine *qml.QQmlApplicationEngine
|
|
Translator, OpaqueTranslator *core.QTranslator
|
|
|
|
uIManagers map[string]Manager // profile-onion : Manager
|
|
TimelineInterface *MessageModel
|
|
|
|
GlobalSettings *GlobalSettings
|
|
|
|
profileLock sync.Mutex
|
|
conversationLock sync.Mutex
|
|
|
|
m_selectedProfile string
|
|
|
|
_ int `property:"torStatus"`
|
|
_ string `property:"os"`
|
|
_ bool `property:"firstTime"`
|
|
_ int `property:"scaleFactor,auto,changed"`
|
|
_ string `property:"theme,auto,changed"`
|
|
_ string `property:"locale,auto,changed"`
|
|
_ string `property:"version"`
|
|
_ string `property:"torVersion"`
|
|
_ string `property:"buildDate"`
|
|
_ string `property:"assetPath"`
|
|
_ string `property:"selectedProfile,auto"`
|
|
_ string `property:"selectedConversation,auto,changed"`
|
|
_ bool `property:"experimentsEnabled,auto,changed"`
|
|
_ map[string]bool `property:"experiments,auto,changed"`
|
|
|
|
// general ui
|
|
_ func(pant int) `signal:"ChangeRootPane"`
|
|
_ func(pane int) `signal:"ChangeProfilePane"`
|
|
// profile management stuff
|
|
_ func() `signal:"Loaded"`
|
|
_ func(handle, displayname, image, tag string, online bool) `signal:"AddProfile"`
|
|
_ func() `signal:"ErrorLoaded0"`
|
|
_ func() `signal:"ResetProfile"`
|
|
_ func() `signal:"ResetProfileList"`
|
|
_ func(failed bool) `signal:"ChangePasswordResponse"`
|
|
_ func(onion string, online bool) `signal:"UpdateProfileNetworkStatus"`
|
|
_ func(onion string) `signal:"Notify"`
|
|
|
|
// server management
|
|
_ func(handle, displayname, image string, status int, autostart bool, bundle string, messages int, key_types []string, keys []string) `signal:"AddServer"`
|
|
_ func() `signal:"requestServers,auto"`
|
|
_ func() `signal:"newServer,auto"`
|
|
_ func(server string) `signal:"startServer,auto"`
|
|
_ func(server string) `signal:"stopServer,auto"`
|
|
_ func(server string) `signal:"checkServer,auto"`
|
|
_ func(server string, enabled bool) `signal:"autostartServer,auto"`
|
|
|
|
// contact list stuff
|
|
_ func(handle, displayName, image string, badge, status int, authorization string, loading bool, lastMsgTime int) `signal:"AddContact"`
|
|
_ func(handle, displayName string) `signal:"UpdateContactDisplayName"`
|
|
_ func(handle, image string) `signal:"UpdateContactPicture"`
|
|
_ func(handle string, status int, loading bool) `signal:"UpdateContactStatus"`
|
|
_ func(handle string) `signal:"IncContactUnreadCount"`
|
|
_ func(handle string) `signal:"RemoveContact"`
|
|
_ func(handle, key, value string) `signal:"UpdateContactAttribute"`
|
|
|
|
// messages pane stuff
|
|
_ 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"`
|
|
|
|
// profile-area stuff
|
|
_ func(name, onion, image, tag, showBlocked string, online bool) `signal:"UpdateMyProfile"`
|
|
|
|
// settings helpers
|
|
_ func(str string) `signal:"InvokePopup"`
|
|
_ func(locale string, scale int, theme string) `signal:"SupplySettings"`
|
|
_ func(groupID, name, server, invitation string, accepted bool, addrbooknames, addrbookaddrs []string) `signal:"SupplyGroupSettings"`
|
|
_ func(onion, nick string, authorization string, storage string) `signal:"SupplyPeerSettings"`
|
|
_ func(server string, key_types []string, keys []string) `signal:"SupplyServerSettings"`
|
|
|
|
// signals emitted from the ui (and implemented in go, below)
|
|
// ui
|
|
_ func() `signal:"onActivate,auto"`
|
|
_ func(pane int) `signal:"setRootPaneState,auto"`
|
|
_ func(pane int) `signal:"setProfilePaneState,auto"`
|
|
// profile managemenet
|
|
_ func(onion, nick string) `signal:"updateNick,auto"`
|
|
_ func(handle string) `signal:"loadProfile,auto"`
|
|
_ func(nick string, defaultPass bool, password string) `signal:"createProfile,auto"`
|
|
_ func(password string) `signal:"unlockProfiles,auto"`
|
|
_ func() `signal:"reloadProfileList,auto"`
|
|
_ func(onion string) `signal:"deleteProfile,auto"`
|
|
_ func(onion, currentPassword, newPassword string, defaultPass bool) `signal:"changePassword,auto"`
|
|
_ func(key, val string) `signal:"storeSetting,auto"`
|
|
// operating a profile
|
|
_ func(message string) `signal:"sendMessage,auto"`
|
|
_ func(onion string, auth string) `signal:"setPeerAuthorization,auto"`
|
|
_ func(onion string) `signal:"loadMessagesPane,auto"`
|
|
_ func(signal string) `signal:"broadcast,auto"` // convenience relay signal
|
|
_ func(name, address string) `signal:"addPeer,auto"`
|
|
_ func(address string) `signal:"addGroup,auto"`
|
|
_ func(str string) `signal:"createContact,auto"`
|
|
_ func(str string) `signal:"popup,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(groupID string) `signal:"requestGroupSettings,auto"`
|
|
_ func(groupID, nick string) `signal:"saveGroupSettings,auto"`
|
|
_ func(handle string) `signal:"requestPeerSettings,auto"`
|
|
_ func(onion, nick string) `signal:"savePeerSettings,auto"`
|
|
_ func(onion, groupID string) `signal:"inviteToGroup,auto"`
|
|
_ func(onion string) `signal:"deleteContact,auto"`
|
|
_ func() `signal:"allowUnknownPeers,auto"`
|
|
_ func() `signal:"blockUnknownPeers,auto"`
|
|
_ func(onion string) `signal:"storeHistoryForPeer,auto"`
|
|
_ func(onion string) `signal:"deleteHistoryForPeer,auto"`
|
|
// chat
|
|
_ func(mID string) `slot:"peerAckAlert,auto"`
|
|
|
|
_ func(handle string) `signal:"requestServerSettings,auto"`
|
|
|
|
_ func() `constructor:"init"`
|
|
|
|
// legacy overlay model support
|
|
_ func(onion string) `signal:"legacyLoadOverlay,auto"`
|
|
_ func(handle, from, displayName, message, image string, mID string, fromMe bool, ts int64, ackd bool, error bool) `signal:"AppendMessage"`
|
|
_ func(handle, from, displayName, message, image string, mID string, fromMe bool, ts int64, ackd bool, error bool) `signal:"PrependMessage"`
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) init() {
|
|
this.uIManagers = make(map[string]Manager)
|
|
firstTime := false
|
|
this.GlobalSettings, firstTime = ReadGlobalSettings()
|
|
this.SetFirstTime(firstTime)
|
|
this.SetScaleFactor(this.GlobalSettings.Scale)
|
|
this.SetTheme(this.GlobalSettings.Theme)
|
|
this.SetExperimentsEnabled(this.GlobalSettings.ExperimentsEnabled)
|
|
this.SetExperiments(this.GlobalSettings.Experiments)
|
|
this.AndroidCwtchActivity = android.NewCwtchActivity(nil)
|
|
|
|
// Per main.qml
|
|
// managementPane:0 settingsPane:1 addEditProfilePane:2 profilePane:3 addEditServerPane:4
|
|
// We can't support addEditProfile(2) or addEditServer(4) as we don't store the target id for those yet: TODO
|
|
// We don't switch here to profilePane(3) as we need to wait for appHandler to identify and set the selectedProfile
|
|
// managementPane(0) is a NOP as it's default pane
|
|
if this.GlobalSettings.StateRootPane == 1 {
|
|
this.ChangeRootPane(this.GlobalSettings.StateRootPane)
|
|
}
|
|
}
|
|
|
|
// GetUiManager gets (and creates if required) a ui Manager for the supplied profile id
|
|
func (this *GrandCentralDispatcher) GetUiManager(profile string) Manager {
|
|
this.profileLock.Lock()
|
|
defer this.profileLock.Unlock()
|
|
|
|
if manager, exists := this.uIManagers[profile]; exists {
|
|
return manager
|
|
} else {
|
|
this.uIManagers[profile] = NewManager(profile, this)
|
|
return this.uIManagers[profile]
|
|
}
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) selectedProfile() string {
|
|
this.profileLock.Lock()
|
|
defer this.profileLock.Unlock()
|
|
|
|
return this.m_selectedProfile
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) setSelectedProfile(onion string) {
|
|
this.profileLock.Lock()
|
|
defer this.profileLock.Unlock()
|
|
|
|
p := the.CwtchApp.GetPeer(onion)
|
|
if p != nil {
|
|
p.SetAttribute(attr.GetSettingsScope(constants.StateSelectedProfileTime), strconv.FormatInt(time.Now().Unix(), 10))
|
|
}
|
|
|
|
this.m_selectedProfile = onion
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) selectedProfileChanged(onion string) {
|
|
this.SelectedProfileChanged(onion)
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) setRootPaneState(pane int) {
|
|
this.GlobalSettings.StateRootPane = pane
|
|
WriteGlobalSettings(this.GlobalSettings)
|
|
}
|
|
|
|
// DoIfProfile performs a gcd action for a profile IF it is the currently selected profile in the UI
|
|
// otherwise it does nothing. it also locks profile switching for the duration of the action
|
|
func (this *GrandCentralDispatcher) DoIfProfile(profile string, fn func()) {
|
|
this.profileLock.Lock()
|
|
defer this.profileLock.Unlock()
|
|
|
|
if this.m_selectedProfile == profile {
|
|
fn()
|
|
}
|
|
}
|
|
|
|
// Like DoIfProfile() but runs elseFn() if profile isn't the currently selected one in the UI
|
|
func (this *GrandCentralDispatcher) DoIfProfileElse(profile string, fn func(), elseFn func()) {
|
|
this.profileLock.Lock()
|
|
defer this.profileLock.Unlock()
|
|
|
|
if this.m_selectedProfile == profile {
|
|
fn()
|
|
} else {
|
|
elseFn()
|
|
}
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) setProfilePaneState(pane int) {
|
|
this.profileLock.Lock()
|
|
defer this.profileLock.Unlock()
|
|
|
|
the.Peer.SetAttribute(attr.GetSettingsScope(constants.StateProfilePane), strconv.Itoa(pane))
|
|
}
|
|
|
|
/*func (this *GrandCentralDispatcher) selectedConversation() string {
|
|
this.conversationLock.Lock()
|
|
defer this.conversationLock.Unlock()
|
|
|
|
return this.m_selectedConversation
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) setSelectedConversation(handle string) {
|
|
this.conversationLock.Lock()
|
|
defer this.conversationLock.Unlock()
|
|
|
|
the.Peer.SetAttribute(attr.GetSettingsScope(constants.StateSelectedConversation), handle)
|
|
|
|
this.m_selectedConversation = handle
|
|
}*/
|
|
|
|
func (this *GrandCentralDispatcher) selectedConversationChanged(handle string) {
|
|
the.Peer.SetAttribute(attr.GetSettingsScope(constants.StateSelectedConversation), handle)
|
|
}
|
|
|
|
// DoIfConversation performs a gcd action for a conversation IF it is the currently selected conversation in the UI
|
|
// otherwise it does nothing. it also locks conversation switching for the duration of the action
|
|
func (this *GrandCentralDispatcher) DoIfConversation(conversation string, fn func()) {
|
|
this.conversationLock.Lock()
|
|
defer this.conversationLock.Unlock()
|
|
|
|
if this.SelectedConversation() == conversation {
|
|
fn()
|
|
}
|
|
}
|
|
|
|
// like DoIfConversation() but
|
|
func (this *GrandCentralDispatcher) DoIfConversationElse(conversation string, fn func(), elseFn func()) {
|
|
this.conversationLock.Lock()
|
|
defer this.conversationLock.Unlock()
|
|
|
|
if this.SelectedConversation() == conversation {
|
|
fn()
|
|
} else {
|
|
elseFn()
|
|
}
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) sendMessage(message string) {
|
|
if len(message) > 65530 {
|
|
this.InvokePopup("message is too long")
|
|
return
|
|
}
|
|
|
|
if this.SelectedConversation() == "" {
|
|
this.InvokePopup("ui error")
|
|
return
|
|
}
|
|
|
|
if isGroup(this.SelectedConversation()) {
|
|
if gf, err := groups.ExperimentGate(this.GlobalSettings.Experiments); err == nil {
|
|
groupHandle := this.SelectedConversation()
|
|
this.TimelineInterface.AddMessage(this.TimelineInterface.num())
|
|
err := gf.SendMessage(groupHandle, message)
|
|
this.TimelineInterface.RequestEIR()
|
|
|
|
if err != nil {
|
|
this.InvokePopup("failed to send message " + err.Error())
|
|
return
|
|
}
|
|
} else {
|
|
this.InvokePopup("Groups are currently disabled by an experiment gate. turn it on in Settings")
|
|
return
|
|
}
|
|
} else {
|
|
this.TimelineInterface.AddMessage(this.TimelineInterface.num())
|
|
the.Peer.SendMessageToPeer(this.SelectedConversation(), message)
|
|
this.TimelineInterface.RequestEIR()
|
|
}
|
|
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) loadMessagesPane(handle string) {
|
|
go this.loadMessagesPaneHelper(handle)
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) loadMessagesPaneHelper(handle string) {
|
|
if handle == the.Peer.GetOnion() {
|
|
return
|
|
}
|
|
this.ClearMessages()
|
|
this.SetSelectedConversation(handle)
|
|
|
|
if isGroup(handle) { // LOAD GROUP
|
|
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)
|
|
this.requestGroupSettings(handle)
|
|
|
|
nick := GetNick(handle)
|
|
updateLastReadTime(group.GroupID)
|
|
if nick == "" {
|
|
this.SetToolbarTitle(handle)
|
|
} else {
|
|
this.SetToolbarTitle(nick)
|
|
}
|
|
|
|
this.legacyLoadOverlay(handle)
|
|
|
|
return
|
|
} // ELSE LOAD CONTACT
|
|
|
|
contact := the.Peer.GetContact(handle)
|
|
|
|
this.UpdateContactStatus(handle, int(connections.ConnectionStateToType[contact.State]), false)
|
|
this.requestPeerSettings(handle)
|
|
|
|
var nick string
|
|
if contact != nil {
|
|
nick = GetNick(contact.Onion)
|
|
}
|
|
updateLastReadTime(contact.Onion)
|
|
this.SetToolbarTitle(nick)
|
|
|
|
this.legacyLoadOverlay(handle)
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) legacyLoadOverlay(handle string) {
|
|
// only do this for overlays 2 (bulletin) and 4 (lists)
|
|
go this.legacyLoadOverlay_helper(handle, []int{2,4})
|
|
}
|
|
|
|
func contains(arr []int, x int) bool {
|
|
for _, v := range arr {
|
|
if v == x {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) legacyLoadOverlay_helper(handle string, overlays []int) {
|
|
if isGroup(handle) {
|
|
group := the.CwtchApp.GetPeer(this.selectedProfile()).GetGroup(handle)
|
|
tl := group.GetTimeline()
|
|
|
|
for i := len(tl) - 1; i >= 0; i-- {
|
|
if tl[i].PeerID == this.selectedProfile() {
|
|
handle = "me"
|
|
} else {
|
|
handle = tl[i].PeerID
|
|
}
|
|
|
|
name := GetNick(tl[i].PeerID)
|
|
image := GetProfilePic(tl[i].PeerID)
|
|
|
|
obj := &OverlayJSONObject{}
|
|
err := json.Unmarshal([]byte(tl[i].Message), obj)
|
|
if err == nil && contains(overlays, obj.Overlay) {
|
|
this.PrependMessage(
|
|
handle,
|
|
tl[i].PeerID,
|
|
name,
|
|
tl[i].Message,
|
|
image,
|
|
string(tl[i].Signature),
|
|
tl[i].PeerID == this.selectedProfile(),
|
|
tl[i].Timestamp.Unix(),
|
|
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,
|
|
)
|
|
}
|
|
}
|
|
|
|
} else {// !isGroup
|
|
messages := the.CwtchApp.GetPeer(this.selectedProfile()).GetContact(handle).Timeline.GetMessages()
|
|
for i := len(messages) - 1; i >= 0; i-- {
|
|
from := messages[i].PeerID
|
|
fromMe := messages[i].PeerID == the.Peer.GetOnion()
|
|
if fromMe {
|
|
from = "me"
|
|
}
|
|
|
|
displayname := GetNick(messages[i].PeerID)
|
|
image := GetProfilePic(messages[i].PeerID)
|
|
|
|
obj := &OverlayJSONObject{}
|
|
err := json.Unmarshal([]byte(messages[i].Message), obj)
|
|
if err == nil && contains(overlays, obj.Overlay) {
|
|
this.PrependMessage(
|
|
from,
|
|
messages[i].PeerID,
|
|
displayname,
|
|
messages[i].Message,
|
|
image,
|
|
string(messages[i].Signature),
|
|
fromMe,
|
|
messages[i].Timestamp.Unix(),
|
|
messages[i].Acknowledged,
|
|
messages[i].Error != "",
|
|
)
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) requestSettings() {
|
|
this.SupplySettings(this.GlobalSettings.Locale, this.GlobalSettings.Scale, this.GlobalSettings.Theme)
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) saveSettings(zoom, locale string) {
|
|
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) requestPeerSettings(handle string) {
|
|
contact := the.Peer.GetContact(handle)
|
|
if contact == nil {
|
|
log.Errorf("error: requested settings for unknown contact %v?", handle)
|
|
this.SupplyPeerSettings(this.SelectedConversation(), this.SelectedConversation(), string(model.AuthUnknown), "")
|
|
return
|
|
}
|
|
|
|
name := GetNick(contact.Onion)
|
|
|
|
// Todo: Move to profile settings
|
|
//blockunkownpeers, _ := the.Peer.GetAttribute(attr.GetPeerScope(constants.BlockUnknownPeersSetting))
|
|
|
|
// Whether Cwtch should save the history of the peer
|
|
saveHistory, exists := contact.GetAttribute(event.SaveHistoryKey)
|
|
if !exists {
|
|
saveHistory = event.DeleteHistoryDefault
|
|
}
|
|
|
|
this.SupplyPeerSettings(contact.Onion, name, string(contact.Authorization), saveHistory)
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) savePeerSettings(onion, nick string) {
|
|
the.Peer.SetContactAttribute(onion, attr.GetLocalScope(constants.Name), nick)
|
|
newNick := GetNick(onion)
|
|
this.UpdateContactDisplayName(onion, newNick)
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) storeHistoryForPeer(onion string) {
|
|
the.Peer.SetContactAttribute(onion, event.SaveHistoryKey, event.SaveHistoryConfirmed)
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) deleteHistoryForPeer(onion string) {
|
|
the.Peer.SetContactAttribute(onion, event.SaveHistoryKey, event.DeleteHistoryConfirmed)
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) requestServerSettings(groupID string) {
|
|
group := the.Peer.GetGroup(groupID)
|
|
|
|
if group == nil {
|
|
log.Errorf("couldn't find group %v", groupID)
|
|
return
|
|
}
|
|
|
|
serverInfo := the.Peer.GetContact(group.GroupServer)
|
|
|
|
if serverInfo == nil {
|
|
// This should never happen...there is a bug....
|
|
log.Errorf("No server info found for ", group.GroupServer)
|
|
return
|
|
}
|
|
|
|
key_types := []model.KeyType{model.KeyTypeServerOnion, model.KeyTypeTokenOnion, model.KeyTypePrivacyPass}
|
|
var keyNames []string
|
|
var keys []string
|
|
|
|
for _, key_type := range key_types {
|
|
log.Debugf("Looking up %v %v", key_type, keyNames)
|
|
if key, has := serverInfo.GetAttribute(string(key_type)); has {
|
|
keyNames = append(keyNames, string(key_type))
|
|
keys = append(keys, key)
|
|
}
|
|
}
|
|
|
|
this.SupplyServerSettings(group.GroupServer, keyNames, keys)
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) requestServers() {
|
|
the.AppBus.Publish(event.NewEvent(constants.ListServers, map[event.Field]string{}))
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) newServer() {
|
|
the.AppBus.Publish(event.NewEvent(constants.NewServer, map[event.Field]string{}))
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) startServer(onion string) {
|
|
log.Debugf("Requesting Start Server: %v", onion)
|
|
the.AppBus.Publish(event.NewEvent(constants.StartServer, map[event.Field]string{
|
|
event.Onion: onion,
|
|
}))
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) stopServer(onion string) {
|
|
log.Debugf("Requesting Stop Server: %v", onion)
|
|
the.AppBus.Publish(event.NewEvent(constants.StopServer, map[event.Field]string{
|
|
event.Onion: onion,
|
|
}))
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) checkServer(onion string) {
|
|
log.Debugf("Requesting Stop Server: %v", onion)
|
|
the.AppBus.Publish(event.NewEvent(constants.CheckServerStatus, map[event.Field]string{
|
|
event.Onion: onion,
|
|
}))
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) autostartServer(onion string, enabled bool) {
|
|
log.Debugf("Requesting Autostart Toggle: %v %v", onion, enabled)
|
|
value := event.False
|
|
if enabled {
|
|
value = event.True
|
|
}
|
|
the.AppBus.Publish(event.NewEvent(constants.AutoStart, map[event.Field]string{
|
|
event.Onion: onion,
|
|
constants.AutoStartEnabled: value,
|
|
}))
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) requestGroupSettings(groupID string) {
|
|
group := the.Peer.GetGroup(groupID)
|
|
|
|
if group == nil {
|
|
log.Errorf("couldn't find group %v", groupID)
|
|
return
|
|
}
|
|
|
|
nick := GetNick(groupID)
|
|
invite, _ := the.Peer.ExportGroup(groupID)
|
|
|
|
contactaddrs := the.Peer.GetContacts()
|
|
contactnames := make([]string, len(contactaddrs))
|
|
for i, contact := range contactaddrs {
|
|
contactnames[i] = GetNick(contact)
|
|
}
|
|
this.SupplyGroupSettings(group.GroupID, nick, group.GroupServer, invite, group.Accepted, contactnames, contactaddrs)
|
|
status := connections.ConnectionStateToType[group.State]
|
|
log.Debugf("Sending New Group Status: %v %v", group.GroupServer, status)
|
|
this.UpdateContactStatus(group.GroupServer, int(status), false)
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) saveGroupSettings(groupID, nick string) {
|
|
the.Peer.SetGroupAttribute(groupID, attr.GetLocalScope(constants.Name), nick)
|
|
this.UpdateContactDisplayName(groupID, nick)
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) broadcast(signal string) {
|
|
switch signal {
|
|
default:
|
|
log.Debugf("unhandled broadcast signal: %v", signal)
|
|
case "ResetMessagePane":
|
|
this.ResetMessagePane()
|
|
case "ResetProfile":
|
|
this.ResetProfile()
|
|
}
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) createContact(onion string) {
|
|
if contact := the.Peer.GetContact(onion); contact != nil {
|
|
return
|
|
}
|
|
the.Peer.AddContact(onion, onion, model.AuthApproved)
|
|
the.Peer.PeerWithOnion(onion)
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) addGroup(address string) {
|
|
log.Debugf("importing group: %s\n", address)
|
|
address = strings.TrimSpace(address)
|
|
|
|
if gf, err := groups.ExperimentGate(this.GlobalSettings.Experiments); err == nil {
|
|
if gf.ValidPrefix(address) {
|
|
err = gf.HandleImportString(address)
|
|
if err == nil {
|
|
// TODO: We need a better way of signaling the success of "invisible" actions like adding server bundles
|
|
this.InvokePopup("successfully imported")
|
|
return
|
|
}
|
|
this.InvokePopup("failed import: " + err.Error())
|
|
return
|
|
}
|
|
// drop through to peer import strings
|
|
}
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) addPeer(name, address string) {
|
|
|
|
log.Debugf("importing peer: %s\n", address)
|
|
name = strings.TrimSpace(name)
|
|
address = strings.TrimSpace(address)
|
|
|
|
if len(address) != 56 {
|
|
this.InvokePopup("invalid peer onion format")
|
|
return
|
|
}
|
|
|
|
_, err := base32.StdEncoding.DecodeString(strings.ToUpper(address[:56]))
|
|
if err != nil {
|
|
log.Debugln(err)
|
|
this.InvokePopup("bad format. missing handlers?")
|
|
return
|
|
}
|
|
|
|
checkc := the.Peer.GetContact(address)
|
|
if checkc != nil {
|
|
this.InvokePopup("already have this contact")
|
|
return //TODO: bring them to the duplicate
|
|
} else {
|
|
the.Peer.AddContact(name, address, model.AuthApproved)
|
|
if name != "" {
|
|
the.Peer.SetContactAttribute(address, attr.GetLocalScope(constants.Name), name)
|
|
}
|
|
the.Peer.PeerWithOnion(address)
|
|
}
|
|
|
|
this.GetUiManager(this.selectedProfile()).AddContact(address)
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) popup(str string) {
|
|
this.InvokePopup(str)
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) updateNick(onion, nick string) {
|
|
p := the.CwtchApp.GetPeer(onion)
|
|
if p != nil {
|
|
p.SetName(nick)
|
|
p.SetAttribute(attr.GetPublicScope(constants.Name), nick)
|
|
the.CwtchApp.GetEventBus(onion).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.GetUiManager(this.selectedProfile()).AddContact(groupID)
|
|
|
|
the.Peer.SetGroupAttribute(groupID, attr.GetLocalScope(constants.Name), groupName)
|
|
|
|
the.Peer.JoinServer(server)
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) setPeerAuthorization(onion string, authorization string) {
|
|
log.Debugf("Setting peer auth level to %v for %v\n", authorization, onion)
|
|
err := the.Peer.SetContactAuthorization(onion, model.Authorization(authorization))
|
|
if err != nil {
|
|
log.Errorf("Could not set peer authorization %v to %v\n", onion, authorization)
|
|
return
|
|
}
|
|
this.RemoveContact(onion)
|
|
this.GetUiManager(this.selectedProfile()).AddContact(onion)
|
|
if model.Authorization(authorization) == model.AuthApproved {
|
|
the.Peer.PeerWithOnion(onion)
|
|
}
|
|
}
|
|
|
|
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) blockUnknownPeers() {
|
|
the.Peer.SetAttribute(attr.GetSettingsScope(constants.BlockUnknownPeersSetting), "true")
|
|
the.EventBus.Publish(event.NewEvent(event.BlockUnknownPeers, map[event.Field]string{}))
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) allowUnknownPeers() {
|
|
the.Peer.SetAttribute(attr.GetSettingsScope(constants.BlockUnknownPeersSetting), "false")
|
|
the.EventBus.Publish(event.NewEvent(event.AllowUnknownPeers, map[event.Field]string{}))
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) localeChanged(locale string) {
|
|
this.GlobalSettings.Locale = locale
|
|
WriteGlobalSettings(this.GlobalSettings)
|
|
this.setLocaleHelper(locale)
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) setLocaleHelper(locale string) {
|
|
log.Debugf("Loading translators for '%v'\n", locale)
|
|
newTranslator := core.NewQTranslator(nil)
|
|
success := newTranslator.Load("translation_"+locale, ":/i18n/", "", "")
|
|
if success {
|
|
core.QCoreApplication_RemoveTranslator(this.Translator)
|
|
this.Translator = newTranslator
|
|
core.QCoreApplication_InstallTranslator(this.Translator)
|
|
} else {
|
|
log.Errorf("Could not load translator for '%v'\n", locale)
|
|
}
|
|
|
|
newOpaqueTranslator := core.NewQTranslator(nil)
|
|
success = newOpaqueTranslator.Load("translation_"+locale, ":/qml/opaque/i18n/", "", "")
|
|
if success {
|
|
core.QCoreApplication_RemoveTranslator(this.OpaqueTranslator)
|
|
this.OpaqueTranslator = newOpaqueTranslator
|
|
core.QCoreApplication_InstallTranslator(this.OpaqueTranslator)
|
|
} else {
|
|
log.Errorf("Could not load opaque translator for '%v'\n", locale)
|
|
}
|
|
|
|
this.QMLEngine.Retranslate()
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) scaleFactorChanged(newScale int) {
|
|
this.GlobalSettings.Scale = newScale
|
|
WriteGlobalSettings(this.GlobalSettings)
|
|
}
|
|
|
|
// Turn on/off global experiments
|
|
func (this *GrandCentralDispatcher) experimentsEnabledChanged(enabled bool) {
|
|
this.GlobalSettings.ExperimentsEnabled = enabled
|
|
log.Debugf("Experiments Enabled: %v %v", enabled, this.GlobalSettings.ExperimentsEnabled)
|
|
WriteGlobalSettings(this.GlobalSettings)
|
|
}
|
|
|
|
// Turn on/off global experiments
|
|
func (this *GrandCentralDispatcher) experimentsChanged(experiments map[string]bool) {
|
|
for k, v := range experiments {
|
|
this.GlobalSettings.Experiments[k] = v
|
|
}
|
|
log.Debugf("Experiments: %v", experiments)
|
|
WriteGlobalSettings(this.GlobalSettings)
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) themeChanged(newTheme string) {
|
|
this.GlobalSettings.Theme = newTheme
|
|
WriteGlobalSettings(this.GlobalSettings)
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) onActivate() {
|
|
log.Debugln("onActivate")
|
|
if the.CwtchApp != nil {
|
|
go the.CwtchApp.QueryACNStatus()
|
|
}
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) unlockProfiles(password string) {
|
|
the.CwtchApp.LoadProfiles(password)
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) loadProfile(onion string) {
|
|
the.Peer = the.CwtchApp.GetPeer(onion)
|
|
if the.Peer == nil {
|
|
return
|
|
}
|
|
the.EventBus = the.CwtchApp.GetEventBus(onion)
|
|
|
|
picVal, exists := the.Peer.GetAttribute(attr.GetPublicScope(constants.Picture))
|
|
if !exists {
|
|
picVal = ImageToString(NewImage(RandomProfileImage(onion), TypeImageDistro))
|
|
the.Peer.SetAttribute(attr.GetPublicScope(constants.Picture), picVal)
|
|
}
|
|
pic, err := StringToImage(picVal)
|
|
if err != nil {
|
|
pic = NewImage(RandomProfileImage(onion), TypeImageDistro)
|
|
the.Peer.SetAttribute(attr.GetPublicScope(constants.Picture), ImageToString(pic))
|
|
}
|
|
tag, _ := the.Peer.GetAttribute(app.AttributeTag)
|
|
showBlocked, exists := the.Peer.GetAttribute(attr.GetSettingsScope(constants.ShowBlocked))
|
|
if !exists {
|
|
showBlocked = "false"
|
|
the.Peer.SetAttribute(attr.GetSettingsScope(constants.ShowBlocked), showBlocked)
|
|
}
|
|
|
|
online, _ := the.Peer.GetAttribute(attr.GetLocalScope(constants.PeerOnline))
|
|
|
|
this.UpdateMyProfile(the.Peer.GetName(), the.Peer.GetOnion(), getPicturePath(pic), tag, showBlocked, online == event.True)
|
|
|
|
contacts := the.Peer.GetContacts()
|
|
for i := range contacts {
|
|
if the.Peer.GetContact(contacts[i]).IsServer() == false {
|
|
this.GetUiManager(this.selectedProfile()).AddContact(contacts[i])
|
|
}
|
|
}
|
|
|
|
// Groups Gating
|
|
if _, err := groups.ExperimentGate(this.GlobalSettings.Experiments); err == nil {
|
|
the.Peer.StartServerConnections()
|
|
groups := the.Peer.GetGroups()
|
|
for i := range groups {
|
|
// Only join servers for active and explicitly accepted groups.
|
|
this.GetUiManager(this.selectedProfile()).AddContact(groups[i])
|
|
}
|
|
} else {
|
|
// Leave all active groups for this Peer.
|
|
groups := the.Peer.GetGroups()
|
|
for i := range groups {
|
|
group := the.Peer.GetGroup(groups[i])
|
|
the.EventBus.Publish(event.NewEvent(event.LeaveServer, map[event.Field]string{
|
|
event.GroupServer: group.GroupServer,
|
|
}))
|
|
}
|
|
}
|
|
|
|
selectedPane, pok := the.Peer.GetAttribute(attr.GetSettingsScope(constants.StateProfilePane))
|
|
if pok {
|
|
selectedPaneId, err := strconv.Atoi(selectedPane)
|
|
if err == nil {
|
|
// emptyPane:0 addPeerGroupPane:4 main.qml
|
|
if selectedPaneId != 0 && selectedPaneId != 4 {
|
|
selectedContact, cok := the.Peer.GetAttribute(attr.GetSettingsScope(constants.StateSelectedConversation))
|
|
if cok {
|
|
this.Broadcast("ResetMessagePane")
|
|
this.SetSelectedConversation(selectedContact)
|
|
|
|
this.TimelineInterface.handle = selectedContact
|
|
this.loadMessagesPane(selectedContact)
|
|
|
|
if isPeer(selectedContact) {
|
|
this.requestPeerSettings(selectedContact)
|
|
} else {
|
|
this.requestGroupSettings(selectedContact)
|
|
}
|
|
this.ChangeProfilePane(selectedPaneId)
|
|
}
|
|
} else {
|
|
this.ChangeProfilePane(selectedPaneId)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) createProfile(nick string, defaultPass bool, password string) {
|
|
if defaultPass {
|
|
the.CwtchApp.CreateTaggedPeer(nick, the.AppPassword, constants.ProfileTypeV1DefaultPassword)
|
|
} else {
|
|
the.CwtchApp.CreateTaggedPeer(nick, password, constants.ProfileTypeV1Password)
|
|
}
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) changePassword(onion, currentPassword, newPassword string, defaultPass bool) {
|
|
tag, _ := the.CwtchApp.GetPeer(onion).GetAttribute(app.AttributeTag)
|
|
|
|
if tag == constants.ProfileTypeV1DefaultPassword {
|
|
currentPassword = the.AppPassword
|
|
}
|
|
|
|
if defaultPass {
|
|
newPassword = the.AppPassword
|
|
}
|
|
|
|
the.CwtchApp.ChangePeerPassword(onion, currentPassword, newPassword)
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) storeSetting(key, val string) {
|
|
the.Peer.SetAttribute(attr.GetSettingsScope(key), val)
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) reloadProfileList() {
|
|
this.ResetProfileList()
|
|
|
|
for onion := range the.CwtchApp.ListPeers() {
|
|
AddProfile(this, onion)
|
|
}
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) deleteProfile(onion string) {
|
|
log.Infof("deleteProfile %v\n", onion)
|
|
the.CwtchApp.DeletePeer(onion)
|
|
}
|
|
|
|
func (this *GrandCentralDispatcher) peerAckAlert(mID string) {
|
|
idx, _ := strconv.Atoi(mID)
|
|
this.TimelineInterface.EditMessage(idx)
|
|
}
|