2021-02-06 00:31:03 +00:00
package utils
import (
"cwtch.im/cwtch/model"
"cwtch.im/cwtch/model/attr"
"cwtch.im/cwtch/peer"
2021-03-04 23:57:48 +00:00
"cwtch.im/cwtch/protocol/connections"
"errors"
2021-02-06 00:31:03 +00:00
"git.openprivacy.ca/flutter/libcwtch-go/constants"
"git.openprivacy.ca/openprivacy/log"
2021-03-04 23:57:48 +00:00
"strconv"
2021-02-06 00:31:03 +00:00
"strings"
"time"
)
type PeerHelper struct {
peer peer . CwtchPeer
}
func NewPeerHelper ( profile peer . CwtchPeer ) * PeerHelper {
return & PeerHelper { profile }
}
2021-03-04 02:05:22 +00:00
func ( p * PeerHelper ) IsGroup ( id string ) bool {
2021-02-06 00:31:03 +00:00
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 :
2021-03-16 20:16:45 +00:00
log . Errorf ( "Unhandled profile picture type of %v\n" , pic . T )
2021-02-06 00:31:03 +00:00
return ""
}
}
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
}
2021-03-04 02:05:22 +00:00
2021-03-04 23:57:48 +00:00
func getLastMessageTime ( tl * model . Timeline ) int {
if len ( tl . Messages ) == 0 {
return 0
}
return int ( tl . Messages [ len ( tl . Messages ) - 1 ] . Timestamp . Unix ( ) )
}
2021-02-06 00:31:03 +00:00
/ *
2021-03-16 20:16:45 +00:00
// AddProfile adds a new profile to the UI
2021-02-06 00:31:03 +00:00
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 )
2021-03-12 12:24:37 +00:00
gcd . AddProfile ( handle , nick , picPath , tag , online == Event . True )
2021-02-06 00:31:03 +00:00
}
} * /
/ *
type manager struct {
gcd * GrandCentralDispatcher
2021-03-16 20:16:45 +00:00
profile string
2021-02-06 00:31:03 +00:00
}
2021-03-12 12:24:37 +00:00
// Manager is a middleware helper for entities like peer Event listeners wishing to trigger ui changes (via the gcd)
2021-03-16 20:16:45 +00:00
// each manager is for one profile/peer
2021-02-06 00:31:03 +00:00
// 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
2021-03-16 20:16:45 +00:00
// 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
2021-02-06 00:31:03 +00:00
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 )
}
2021-03-16 20:16:45 +00:00
// 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 }
2021-02-06 00:31:03 +00:00
}
2021-03-04 23:57:48 +00:00
* /
// EnrichNewPeer populates required data for use by frontend
// uiManager.AddContact(onion)
// (handle string, displayName string, image string, badge int, status int, authorization string, loading bool, lastMsgTime int)
func EnrichNewPeer ( handle string , ph * PeerHelper , ev * EventProfileEnvelope ) error {
if ph . IsGroup ( handle ) {
group := ph . peer . GetGroup ( handle )
if group != nil {
lastRead := ph . InitLastReadTime ( group . GroupID )
2021-03-12 12:24:37 +00:00
ev . Event . Data [ "unread" ] = strconv . Itoa ( ph . CountUnread ( group . Timeline . GetMessages ( ) , lastRead ) )
ev . Event . Data [ "picture" ] = ph . GetProfilePic ( handle )
ev . Event . Data [ "nick" ] = ph . GetNick ( handle )
ev . Event . Data [ "status" ] = strconv . Itoa ( int ( connections . ConnectionStateToType [ group . State ] ) )
ev . Event . Data [ "authorization" ] = string ( model . AuthApproved )
ev . Event . Data [ "loading" ] = "false"
ev . Event . Data [ "lastMsgTime" ] = strconv . Itoa ( getLastMessageTime ( & group . Timeline ) )
2021-02-06 00:31:03 +00:00
}
2021-03-04 23:57:48 +00:00
} else if ph . IsPeer ( handle ) {
contact := ph . peer . GetContact ( handle )
2021-02-06 00:31:03 +00:00
if contact != nil {
2021-03-04 23:57:48 +00:00
lastRead := ph . InitLastReadTime ( contact . Onion )
2021-03-12 12:24:37 +00:00
ev . Event . Data [ "unread" ] = strconv . Itoa ( ph . CountUnread ( contact . Timeline . GetMessages ( ) , lastRead ) )
ev . Event . Data [ "picture" ] = ph . GetProfilePic ( handle )
2021-02-06 00:31:03 +00:00
2021-03-12 12:24:37 +00:00
ev . Event . Data [ "nick" ] = ph . GetNick ( handle )
ev . Event . Data [ "status" ] = strconv . Itoa ( int ( connections . ConnectionStateToType [ contact . State ] ) )
2021-03-04 23:57:48 +00:00
2021-03-12 12:24:37 +00:00
ev . Event . Data [ "authorization" ] = string ( contact . Authorization )
ev . Event . Data [ "loading" ] = "false"
ev . Event . Data [ "lastMsgTime" ] = strconv . Itoa ( getLastMessageTime ( & contact . Timeline ) )
2021-02-06 00:31:03 +00:00
}
2021-03-04 23:57:48 +00:00
} else {
// could be a server?
log . Errorf ( "sorry, unable to handle AddContact(%v)" , handle )
return errors . New ( "Not a peer or group" )
}
return nil
2021-02-06 00:31:03 +00:00
}
2021-03-04 23:57:48 +00:00
/ *
2021-02-06 00:31:03 +00:00
// 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 ) {
2021-03-16 20:16:45 +00:00
this . gcd . DoIfProfile ( this . profile , func ( ) {
2021-02-06 00:31:03 +00:00
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 ( )
2021-03-04 23:57:48 +00:00
} * /
2021-02-06 00:31:03 +00:00
2021-03-04 23:57:48 +00:00
/ *
2021-02-06 00:31:03 +00:00
// 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 ) {
2021-03-16 20:16:45 +00:00
this . gcd . DoIfProfile ( this . profile , func ( ) {
2021-02-06 00:31:03 +00:00
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 ) {
2021-03-16 20:16:45 +00:00
this . gcd . DoIfProfile ( this . profile , func ( ) {
2021-02-06 00:31:03 +00:00
this . gcd . UpdateContactDisplayName ( handle , GetNick ( handle ) )
} )
}
// UpdateContactPicture updates a contact's picture in the contact list and conversations
func ( this * manager ) UpdateContactPicture ( handle string ) {
2021-03-16 20:16:45 +00:00
this . gcd . DoIfProfile ( this . profile , func ( ) {
2021-02-06 00:31:03 +00:00
this . gcd . UpdateContactPicture ( handle , GetProfilePic ( handle ) )
} )
}
// UpdateContactAttribute update's a contacts attribute in the ui
func ( this * manager ) UpdateContactAttribute ( handle , key , value string ) {
2021-03-16 20:16:45 +00:00
this . gcd . DoIfProfile ( this . profile , func ( ) {
2021-02-06 00:31:03 +00:00
this . gcd . UpdateContactAttribute ( handle , key , value )
} )
}
func ( this * manager ) ChangePasswordResponse ( error bool ) {
this . gcd . ChangePasswordResponse ( error )
}
func ( this * manager ) UpdateNetworkStatus ( online bool ) {
2021-03-16 20:16:45 +00:00
this . gcd . UpdateProfileNetworkStatus ( this . profile , online )
2021-02-06 00:31:03 +00:00
}
2021-03-04 02:05:22 +00:00
* /