properly handle NewPeer event as per qt ui

This commit is contained in:
Dan Ballard 2021-02-05 16:31:03 -08:00
parent da1058f1a8
commit 8cb17c5ec1
6 changed files with 569 additions and 10 deletions

23
constants/attributes.go Normal file
View File

@ -0,0 +1,23 @@
package constants
const SchemaVersion = "schemaVersion"
const Name = "name"
const LastRead = "last-read"
const Picture = "picture"
const ShowBlocked = "show-blocked"
const ProfileTypeV1DefaultPassword = "v1-defaultPassword"
const ProfileTypeV1Password = "v1-userPassword"
// PeerOnline stores state on if the peer believes it is online
const PeerOnline = "peer-online"
const StateProfilePane = "state-profile-pane"
const StateSelectedConversation = "state-selected-conversation"
const StateSelectedProfileTime = "state-selected-profile-time"
// Settings
const BlockUnknownPeersSetting = "blockunknownpeers"
const LocaleSetting = "locale"
const ZoomSetting = "zoom"

View File

@ -0,0 +1,26 @@
package constants
import "cwtch.im/cwtch/event"
// The server manager defines its own events, most should be self-explanatory:
const (
NewServer = event.Type("NewServer")
// Force a UI update
ListServers = event.Type("ListServers")
// Takes an Onion, used to toggle off/on Server availability
StartServer = event.Type("StartServer")
StopServer = event.Type("StopServer")
// Takes an Onion and a AutoStartEnabled boolean
AutoStart = event.Type("AutoStart")
// Get the status of a particular server (takes an Onion)
CheckServerStatus = event.Type("CheckServerStatus")
ServerStatusUpdate = event.Type("ServerStatusUpdate")
)
const (
AutoStartEnabled = event.Field("AutoStartEnabled")
)

79
lib.go
View File

@ -1,5 +1,5 @@
package cwtch
//package main
//package cwtch
package main
import "C"
import (
@ -7,8 +7,13 @@ import (
"cwtch.im/cwtch/app"
"cwtch.im/cwtch/event"
"cwtch.im/cwtch/peer"
"cwtch.im/cwtch/model/attr"
"cwtch.im/cwtch/app/plugins"
"encoding/json"
"fmt"
"git.openprivacy.ca/flutter/libcwtch-go/constants"
"git.openprivacy.ca/flutter/libcwtch-go/utils"
"encoding/base64"
"git.openprivacy.ca/openprivacy/connectivity/tor"
@ -22,6 +27,7 @@ import (
var application app.Application
var appBusQueue event.Queue
var profileRepaintQueue event.Queue
var acnQueue event.Queue
var contactEventsQueue event.Queue
@ -81,6 +87,10 @@ func StartCwtch(appDir string, torPath string) {
event.NewGetValMessageFromPeer,
event.PeerStateChange,
}
profileRepaintQueue = event.NewQueue()
newApp.GetPrimaryBus().Subscribe(event.NewPeer, profileRepaintQueue)
newApp.LoadProfiles("be gay do crime")
newApp.LaunchPeers()
application = newApp
@ -101,8 +111,8 @@ func ACNEvents() string {
}
}
//export c_AppBusEvent
func c_AppBusEvent() *C.char {
//export c_GetAppBusEvent
func c_GetAppBusEvent() *C.char {
return C.CString(GetAppBusEvent())
}
@ -111,13 +121,62 @@ func c_AppBusEvent() *C.char {
func GetAppBusEvent() string {
e := appBusQueue.Next()
if e.EventType == event.NewPeer {
e.Data[event.ProfileName] = "orb Quen"
e.Data[event.Path] = "profiles/044-witch.png"
//e.Data[event.ProfileName] = "orb Quen"
//e.Data[event.Path] = "profiles/044-witch.png"
onion := e.Data[event.Identity]
profile := application.GetPeer(e.Data[event.Identity])
if e.Data[event.Created] == event.True {
profile.SetAttribute(attr.GetPublicScope(constants.Name), profile.GetName())
profile.SetAttribute(attr.GetPublicScope(constants.Picture), utils.ImageToString(utils.NewImage(utils.RandomProfileImage(onion), utils.TypeImageDistro)))
}
if e.Data[event.Status] != event.StorageRunning || e.Data[event.Created] == event.True {
profile.SetAttribute(attr.GetLocalScope(constants.PeerOnline), event.False)
application.AddPeerPlugin(onion, plugins.CONNECTIONRETRY)
application.AddPeerPlugin(onion, plugins.NETWORKCHECK)
}
nick, exists := profile.GetAttribute(attr.GetPublicScope(constants.Name))
if !exists {
nick = onion
}
picVal, ok := profile.GetAttribute(attr.GetPublicScope(constants.Picture))
if !ok {
picVal = utils.ImageToString(utils.NewImage(utils.RandomProfileImage(onion), utils.TypeImageDistro))
}
pic, err := utils.StringToImage(picVal)
if err != nil {
pic = utils.NewImage(utils.RandomProfileImage(onion), utils.TypeImageDistro)
}
picPath := utils.GetPicturePath(pic)
//tag, _ := profile.GetAttribute(app.AttributeTag)
online, _ := profile.GetAttribute(attr.GetLocalScope(constants.PeerOnline))
e.Data[constants.Name] = nick
e.Data[constants.Picture] = picPath
e.Data["Online"] = online
}
ba, _ := json.Marshal(e)
return string(ba)
}
//export c_GetProfileRepaintEvent
func c_GetProfileRepaintEvent() int8 {
if GetProfileRepaintEvent() {
return 1
} else {
return 0
}
}
func GetProfileRepaintEvent() bool {
<- acnQueue.OutChan()
return true
}
type Profile struct {
Name string `json:"name"`
Onion string `json:"onion"`
@ -157,7 +216,7 @@ func c_GetContacts(onion_ptr *C.char, onion_len C.int) *C.char {
return C.CString(GetContacts(C.GoStringN(onion_ptr, onion_len)))
}
func GetContacts(onion string)string {
func GetContacts(onion string) string {
log.Infof("Get Contacts for %v", onion)
mypeer := application.GetPeer(onion)
@ -220,10 +279,10 @@ func ContactEvents() string {
}
//export c_NumMessages
func c_NumMessages(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int) (n C.int) {
func c_NumMessages(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int) (n int) {
profile := C.GoStringN(profile_ptr, profile_len)
handle := C.GoStringN(handle_ptr, handle_len)
return C.int(NumMessages(profile, handle))
return (NumMessages(profile, handle))
}
func NumMessages(profile, handle string) (n int) {
@ -261,4 +320,4 @@ func GetMessages(profile, handle string, start, end int) string {
}
// Leave as is, needed by ffi
//func main() {}
func main() {}

34
utils/imageType.go Normal file
View File

@ -0,0 +1,34 @@
package utils
import "encoding/json"
// Image types we support
const (
// TypeImageDistro is a reletive path to any of the distributed images in cwtch/ui in the assets folder
TypeImageDistro = "distro"
// TypeImageComposition will be an face image composed of a recipe of parts like faceType, eyeType, etc
TypeImageComposition = "composition"
)
type image struct {
Val string
T string
}
func NewImage(val, t string) *image {
return &image{val, t}
}
func StringToImage(str string) (*image, error) {
var img image
err := json.Unmarshal([]byte(str), &img)
if err != nil {
return nil, err
}
return &img, nil
}
func ImageToString(img *image) string {
bytes, _ := json.Marshal(img)
return string(bytes)
}

388
utils/manager.go Normal file
View File

@ -0,0 +1,388 @@
package utils
import (
"cwtch.im/cwtch/model"
"cwtch.im/cwtch/model/attr"
"cwtch.im/cwtch/peer"
"git.openprivacy.ca/flutter/libcwtch-go/constants"
"git.openprivacy.ca/openprivacy/log"
"strings"
"time"
)
type PeerHelper struct {
peer peer.CwtchPeer
}
func NewPeerHelper(profile peer.CwtchPeer) *PeerHelper {
return &PeerHelper{profile}
}
func (p *PeerHelper) IsGroup( id string) bool {
return len(id) == 32 && !p.IsServer(id)
}
func (p *PeerHelper) IsPeer(id string) bool {
return len(id) == 56 && !p.IsServer(id)
}
// Check if the id is associated with a contact with a KeyTypeServerOnion attribute (which indicates that this
// is a server, not a regular contact or a group
func (p *PeerHelper) IsServer(id string) bool {
_, ok := p.peer.GetContactAttribute(id, string(model.KeyTypeServerOnion))
return ok
}
/*
func getOrDefault(id, key string, defaultVal string) string {
var val string
var ok bool
if IsGroup(id) {
val, ok = the.Peer.GetGroupAttribute(id, key)
} else {
val, ok = the.Peer.GetContactAttribute(id, key)
}
if ok {
return val
} else {
return defaultVal
}
}*/
func (p *PeerHelper) GetWithSetDefault(id string, key string, defaultVal string) string {
var val string
var ok bool
if p.IsGroup(id) {
val, ok = p.peer.GetGroupAttribute(id, key)
} else {
val, ok = p.peer.GetContactAttribute(id, key)
}
if !ok {
val = defaultVal
if p.IsGroup(id) {
p.peer.SetGroupAttribute(id, key, defaultVal)
} else {
p.peer.SetContactAttribute(id, key, defaultVal)
}
}
return val
}
func (p *PeerHelper) GetNick(id string) string {
if p.IsGroup(id) {
nick, exists := p.peer.GetGroupAttribute(id, attr.GetLocalScope(constants.Name))
if !exists || nick == "" || nick == id {
nick, exists = p.peer.GetGroupAttribute(id, attr.GetPeerScope(constants.Name))
if !exists {
nick = "[" + id + "]"
}
}
return nick
} else {
nick, exists := p.peer.GetContactAttribute(id, attr.GetLocalScope(constants.Name))
if !exists || nick == "" || nick == id {
nick, exists = p.peer.GetContactAttribute(id, attr.GetPeerScope(constants.Name))
if !exists {
nick = "[" + id + "]"
}
}
return nick
}
}
// InitLastReadTime checks and gets the Attributable's LastRead time or sets it to now
func (p *PeerHelper) InitLastReadTime(id string) time.Time {
nowStr, _ := time.Now().MarshalText()
lastReadAttr := p.GetWithSetDefault(id, attr.GetLocalScope(constants.LastRead), string(nowStr))
var lastRead time.Time
lastRead.UnmarshalText([]byte(lastReadAttr))
return lastRead
}
// GetProfilePic returns a string path to an image to display for hte given peer/group id
func (p *PeerHelper) GetProfilePic(id string) string {
if p.IsGroup(id) {
if picVal, exists := p.peer.GetGroupAttribute(id, attr.GetLocalScope(constants.Picture)); exists {
pic, err := StringToImage(picVal)
if err == nil {
return GetPicturePath(pic)
}
}
if picVal, exists := p.peer.GetGroupAttribute(id, attr.GetPeerScope(constants.Picture)); exists {
pic, err := StringToImage(picVal)
if err == nil {
return GetPicturePath(pic)
}
}
return GetPicturePath(NewImage(RandomGroupImage(id), TypeImageDistro))
} else {
if picVal, exists := p.peer.GetContactAttribute(id, attr.GetLocalScope(constants.Picture)); exists {
pic, err := StringToImage(picVal)
if err == nil {
return GetPicturePath(pic)
}
}
if picVal, exists := p.peer.GetContactAttribute(id, attr.GetPeerScope(constants.Picture)); exists {
pic, err := StringToImage(picVal)
if err == nil {
return GetPicturePath(pic)
}
}
return RandomProfileImage(id)
}
}
// a lot of pics were stored full path + uri. remove all this to the relative path in images/
// fix for storing full paths introduced 2019.12
func profilePicRelativize(filename string) string {
parts := strings.Split(filename, "qml/images")
return parts[len(parts)-1]
}
func GetPicturePath(pic *image) string {
switch pic.T {
case TypeImageDistro:
return profilePicRelativize(pic.Val)
default:
log.Errorf("Unhandled profile picture type of %v\n", pic.T)
return ""
}
}
func (p *PeerHelper) updateLastReadTime(id string) {
lastRead, _ := time.Now().MarshalText()
if p.IsGroup(id) {
p.peer.SetGroupAttribute(id, attr.GetLocalScope(constants.LastRead), string(lastRead))
} else {
p.peer.SetContactAttribute(id, attr.GetLocalScope(constants.LastRead), string(lastRead))
}
}
func (p *PeerHelper) CountUnread(messages []model.Message, lastRead time.Time) int {
count := 0
for i := len(messages) - 1; i >= 0; i-- {
if messages[i].Timestamp.After(lastRead) || messages[i].Timestamp.Equal(lastRead) {
count++
} else {
break
}
}
return count
}
/*
// AddProfile adds a new profile to the UI
func AddProfile(gcd *GrandCentralDispatcher, handle string) {
p := the.CwtchApp.GetPeer(handle)
if p != nil {
nick, exists := p.GetAttribute(attr.GetPublicScope(constants.Name))
if !exists {
nick = handle
}
picVal, ok := p.GetAttribute(attr.GetPublicScope(constants.Picture))
if !ok {
picVal = ImageToString(NewImage(RandomProfileImage(handle), TypeImageDistro))
}
pic, err := StringToImage(picVal)
if err != nil {
pic = NewImage(RandomProfileImage(handle), TypeImageDistro)
}
picPath := getPicturePath(pic)
tag, _ := p.GetAttribute(app.AttributeTag)
online, _ := p.GetAttribute(attr.GetLocalScope(constants.PeerOnline))
log.Debugf("AddProfile %v %v %v %v %v\n", handle, nick, picPath, tag, online)
gcd.AddProfile(handle, nick, picPath, tag, online == event.True)
}
}*/
/*
type manager struct {
gcd *GrandCentralDispatcher
profile string
}
// Manager is a middleware helper for entities like peer event listeners wishing to trigger ui changes (via the gcd)
// each manager is for one profile/peer
// manager takes minimal arguments and builds the full struct of data (usually pulled from a cwtch peer) required to call the GCD to perform the ui action
// manager also performs call filtering based on UI state: users of manager can safely always call it on events and not have to worry about weather the relevant ui is active
// ie: you can always safely call AddMessage even if in the ui a different profile is selected. manager will check with gcd, and if the correct conditions are not met, it will not call on gcd to update the ui incorrectly
type Manager interface {
Acknowledge(handle, mID string)
AddContact(Handle string)
AddSendMessageError(peer string, signature string, err string)
AddMessage(handle string, from string, message string, fromMe bool, messageID string, timestamp time.Time, Acknowledged bool)
ReloadProfiles()
UpdateContactDisplayName(handle string)
UpdateContactPicture(handle string)
UpdateContactStatus(handle string, status int, loading bool)
UpdateContactAttribute(handle, key, value string)
ChangePasswordResponse(error bool)
AboutToAddMessage()
MessageJustAdded()
StoreAndNotify(peer.CwtchPeer, string, string, time.Time, string)
UpdateNetworkStatus(online bool)
}
// NewManager returns a new Manager interface for a profile to the gcd
func NewManager(profile string, gcd *GrandCentralDispatcher) Manager {
return &manager{gcd: gcd, profile: profile}
}
// Acknowledge acknowledges the given message id in the UI
func (this *manager) Acknowledge(handle, mID string) {
this.gcd.DoIfProfile(this.profile, func() {
this.gcd.DoIfConversation(handle, func() {
this.gcd.PeerAckAlert(mID)
})
})
}
func getLastMessageTime(tl *model.Timeline) int {
if len(tl.Messages) == 0 {
return 0
}
return int(tl.Messages[len(tl.Messages)-1].Timestamp.Unix())
}
// AddContact adds a new contact to the ui for this manager's profile
func (this *manager) AddContact(handle string) {
this.gcd.DoIfProfile(this.profile, func() {
if IsGroup(handle) {
group := the.Peer.GetGroup(handle)
if group != nil {
lastRead := InitLastReadTime(group.GroupID)
unread := CountUnread(group.Timeline.GetMessages(), lastRead)
picture := GetProfilePic(handle)
this.gcd.AddContact(handle, GetNick(handle), picture, unread, int(connections.ConnectionStateToType[group.State]), string(model.AuthApproved), false, getLastMessageTime(&group.Timeline))
}
return
} else if !IsPeer(handle) {
log.Errorf("sorry, unable to handle AddContact(%v)", handle)
debug.PrintStack()
return
}
contact := the.Peer.GetContact(handle)
if contact != nil {
lastRead := InitLastReadTime(contact.Onion)
unread := CountUnread(contact.Timeline.GetMessages(), lastRead)
picture := GetProfilePic(handle)
this.gcd.AddContact(handle, GetNick(handle), picture, unread, int(connections.ConnectionStateToType[contact.State]), string(contact.Authorization), false, getLastMessageTime(&contact.Timeline))
}
})
}
// AddSendMessageError adds an error not and icon to a message in a conversation in the ui for the message identified by the peer/sig combo
func (this *manager) AddSendMessageError(peer string, signature string, err string) {
this.gcd.DoIfProfile(this.profile, func() {
this.gcd.DoIfConversation(peer, func() {
log.Debugf("Received Error Sending Message: %v", err)
// FIXME: Sometimes, for the first Peer message we send our error beats our message to the UI
time.Sleep(time.Second * 1)
this.gcd.GroupSendError(signature, err)
})
})
}
func (this *manager) AboutToAddMessage() {
this.gcd.TimelineInterface.AddMessage(this.gcd.TimelineInterface.num())
}
func (this *manager) MessageJustAdded() {
this.gcd.TimelineInterface.RequestEIR()
}
func (this *manager) StoreAndNotify(pere peer.CwtchPeer, onion string, messageTxt string, sent time.Time, profileOnion string) {
// Send a New Message from Peer Notification
this.gcd.AndroidCwtchActivity.SetChannel(onion)
this.gcd.AndroidCwtchActivity.NotificationChanged("New Message from Peer")
this.gcd.DoIfProfileElse(this.profile, func() {
this.gcd.DoIfConversationElse(onion, func() {
this.gcd.TimelineInterface.AddMessage(this.gcd.TimelineInterface.num())
pere.StoreMessage(onion, messageTxt, sent)
this.gcd.TimelineInterface.RequestEIR()
updateLastReadTime(onion)
}, func() {
pere.StoreMessage(onion, messageTxt, sent)
})
this.gcd.IncContactUnreadCount(onion)
}, func() {
the.CwtchApp.GetPeer(profileOnion).StoreMessage(onion, messageTxt, sent)
})
this.gcd.Notify(onion)
}
// AddMessage adds a message to the message pane for the supplied conversation if it is active
func (this *manager) AddMessage(handle string, from string, message string, fromMe bool, messageID string, timestamp time.Time, Acknowledged bool) {
this.gcd.DoIfProfile(this.profile, func() {
this.gcd.DoIfConversation(handle, func() {
updateLastReadTime(handle)
// If the message is not from the user then add it, otherwise, just acknowledge.
if !fromMe || !Acknowledged {
this.gcd.TimelineInterface.AddMessage(this.gcd.TimelineInterface.num() - 1)
this.gcd.TimelineInterface.RequestEIR()
} else {
this.gcd.Acknowledged(messageID)
}
})
this.gcd.IncContactUnreadCount(handle)
})
if !fromMe {
this.gcd.Notify(handle)
}
}
func (this *manager) ReloadProfiles() {
this.gcd.reloadProfileList()
}
// UpdateContactDisplayName updates a contact's display name in the contact list and conversations
func (this *manager) UpdateContactDisplayName(handle string) {
this.gcd.DoIfProfile(this.profile, func() {
this.gcd.UpdateContactDisplayName(handle, GetNick(handle))
})
}
// UpdateContactPicture updates a contact's picture in the contact list and conversations
func (this *manager) UpdateContactPicture(handle string) {
this.gcd.DoIfProfile(this.profile, func() {
this.gcd.UpdateContactPicture(handle, GetProfilePic(handle))
})
}
// UpdateContactStatus updates a contact's status in the ui
func (this *manager) UpdateContactStatus(handle string, status int, loading bool) {
this.gcd.DoIfProfile(this.profile, func() {
this.gcd.UpdateContactStatus(handle, status, loading)
})
}
// UpdateContactAttribute update's a contacts attribute in the ui
func (this *manager) UpdateContactAttribute(handle, key, value string) {
this.gcd.DoIfProfile(this.profile, func() {
this.gcd.UpdateContactAttribute(handle, key, value)
})
}
func (this *manager) ChangePasswordResponse(error bool) {
this.gcd.ChangePasswordResponse(error)
}
func (this *manager) UpdateNetworkStatus(online bool) {
this.gcd.UpdateProfileNetworkStatus(this.profile, online)
}
*/

29
utils/utils.go Normal file
View File

@ -0,0 +1,29 @@
package utils
import (
"encoding/base32"
"encoding/hex"
"git.openprivacy.ca/openprivacy/log"
"strings"
)
// temporary until we do real picture selection
func RandomProfileImage(onion string) string {
choices := []string{"001-centaur", "002-kraken", "003-dinosaur", "004-tree-1", "005-hand", "006-echidna", "007-robot", "008-mushroom", "009-harpy", "010-phoenix", "011-dragon-1", "012-devil", "013-troll", "014-alien", "015-minotaur", "016-madre-monte", "017-satyr", "018-karakasakozou", "019-pirate", "020-werewolf", "021-scarecrow", "022-valkyrie", "023-curupira", "024-loch-ness-monster", "025-tree", "026-cerberus", "027-gryphon", "028-mermaid", "029-vampire", "030-goblin", "031-yeti", "032-leprechaun", "033-medusa", "034-chimera", "035-elf", "036-hydra", "037-cyclops", "038-pegasus", "039-narwhal", "040-woodcutter", "041-zombie", "042-dragon", "043-frankenstein", "044-witch", "045-fairy", "046-genie", "047-pinocchio", "048-ghost", "049-wizard", "050-unicorn"}
barr, err := base32.StdEncoding.DecodeString(strings.ToUpper(onion))
if err != nil || len(barr) != 35 {
log.Errorf("error: %v %v %v\n", onion, err, barr)
return "extra/openprivacy.png"
}
return "profiles/" + choices[int(barr[33])%len(choices)] + ".png"
}
func RandomGroupImage(handle string) string {
choices := []string{"001-borobudur", "002-opera-house", "003-burj-al-arab", "004-chrysler", "005-acropolis", "006-empire-state-building", "007-temple", "008-indonesia-1", "009-new-zealand", "010-notre-dame", "011-space-needle", "012-seoul", "013-mosque", "014-milan", "015-statue", "016-pyramid", "017-cologne", "018-brandenburg-gate", "019-berlin-cathedral", "020-hungarian-parliament", "021-buckingham", "022-thailand", "023-independence", "024-angkor-wat", "025-vaticano", "026-christ-the-redeemer", "027-colosseum", "028-golden-gate-bridge", "029-sphinx", "030-statue-of-liberty", "031-cradle-of-humankind", "032-istanbul", "033-london-eye", "034-sagrada-familia", "035-tower-bridge", "036-burj-khalifa", "037-washington", "038-big-ben", "039-stonehenge", "040-white-house", "041-ahu-tongariki", "042-capitol", "043-eiffel-tower", "044-church-of-the-savior-on-spilled-blood", "045-arc-de-triomphe", "046-windmill", "047-louvre", "048-torii-gate", "049-petronas", "050-matsumoto-castle", "051-fuji", "052-temple-of-heaven", "053-pagoda", "054-chichen-itza", "055-forbidden-city", "056-merlion", "057-great-wall-of-china", "058-taj-mahal", "059-pisa", "060-indonesia"}
barr, err := hex.DecodeString(handle)
if err != nil || len(barr) == 0 {
log.Errorf("error: %v %v %v\n", handle, err, barr)
return "extra/openprivacy.png"
}
return "servers/" + choices[int(barr[0])%len(choices)] + ".png"
}