2021-06-24 22:30:46 +00:00
package utils
import (
2021-12-14 21:23:32 +00:00
"encoding/json"
2022-02-03 23:14:39 +00:00
"fmt"
2022-02-08 20:43:57 +00:00
"os"
2021-12-14 21:23:32 +00:00
"strconv"
2021-06-24 22:30:46 +00:00
"cwtch.im/cwtch/app"
"cwtch.im/cwtch/app/plugins"
"cwtch.im/cwtch/model"
"cwtch.im/cwtch/model/attr"
2021-10-15 20:09:36 +00:00
"cwtch.im/cwtch/model/constants"
2021-12-19 01:16:21 +00:00
"cwtch.im/cwtch/peer"
2021-06-24 22:30:46 +00:00
"cwtch.im/cwtch/protocol/connections"
2021-10-15 20:09:36 +00:00
constants2 "git.openprivacy.ca/cwtch.im/libcwtch-go/constants"
2021-08-05 23:29:20 +00:00
"git.openprivacy.ca/cwtch.im/libcwtch-go/features/groups"
2021-10-29 23:14:08 +00:00
"git.openprivacy.ca/cwtch.im/libcwtch-go/features/servers"
2021-06-24 22:30:46 +00:00
"git.openprivacy.ca/openprivacy/log"
2021-12-17 00:43:23 +00:00
"time"
2021-12-14 21:23:32 +00:00
"cwtch.im/cwtch/event"
"cwtch.im/cwtch/functionality/filesharing"
2021-06-24 22:30:46 +00:00
)
type EventProfileEnvelope struct {
Event event . Event
Profile string
}
type EventHandler struct {
app app . Application
appBusQueue event . Queue
profileEvents chan EventProfileEnvelope
}
2022-04-21 00:18:45 +00:00
// We should be reading from profile events pretty quickly, but we make this buffer fairly large...
const profileEventsBufferSize = 512
2021-06-24 22:30:46 +00:00
func NewEventHandler ( ) * EventHandler {
2022-04-21 00:18:45 +00:00
eh := & EventHandler { app : nil , appBusQueue : event . NewQueue ( ) , profileEvents : make ( chan EventProfileEnvelope , profileEventsBufferSize ) }
2021-06-24 22:30:46 +00:00
return eh
}
func ( eh * EventHandler ) HandleApp ( application app . Application ) {
eh . app = application
application . GetPrimaryBus ( ) . Subscribe ( event . NewPeer , eh . appBusQueue )
application . GetPrimaryBus ( ) . Subscribe ( event . PeerError , eh . appBusQueue )
application . GetPrimaryBus ( ) . Subscribe ( event . PeerDeleted , eh . appBusQueue )
application . GetPrimaryBus ( ) . Subscribe ( event . Shutdown , eh . appBusQueue )
application . GetPrimaryBus ( ) . Subscribe ( event . AppError , eh . appBusQueue )
application . GetPrimaryBus ( ) . Subscribe ( event . ACNStatus , eh . appBusQueue )
application . GetPrimaryBus ( ) . Subscribe ( event . ACNVersion , eh . appBusQueue )
application . GetPrimaryBus ( ) . Subscribe ( UpdateGlobalSettings , eh . appBusQueue )
application . GetPrimaryBus ( ) . Subscribe ( CwtchStarted , eh . appBusQueue )
2021-10-29 23:14:08 +00:00
application . GetPrimaryBus ( ) . Subscribe ( servers . NewServer , eh . appBusQueue )
2021-11-02 01:48:00 +00:00
application . GetPrimaryBus ( ) . Subscribe ( servers . ServerIntentUpdate , eh . appBusQueue )
2021-11-04 01:19:30 +00:00
application . GetPrimaryBus ( ) . Subscribe ( servers . ServerDeleted , eh . appBusQueue )
2021-11-26 01:59:00 +00:00
application . GetPrimaryBus ( ) . Subscribe ( servers . ServerStatsUpdate , eh . appBusQueue )
2021-12-18 00:56:38 +00:00
application . GetPrimaryBus ( ) . Subscribe ( event . StartingStorageMiragtion , eh . appBusQueue )
application . GetPrimaryBus ( ) . Subscribe ( event . DoneStorageMigration , eh . appBusQueue )
2021-06-24 22:30:46 +00:00
}
func ( eh * EventHandler ) GetNextEvent ( ) string {
appChan := eh . appBusQueue . OutChan ( )
select {
case e := <- appChan :
return eh . handleAppBusEvent ( & e )
2021-11-17 20:33:51 +00:00
default :
select {
case e := <- appChan :
return eh . handleAppBusEvent ( & e )
case ev := <- eh . profileEvents :
return eh . handleProfileEvent ( & ev )
}
2021-06-24 22:30:46 +00:00
}
}
// handleAppBusEvent enriches AppBus events so they are usable with out further data fetches
func ( eh * EventHandler ) handleAppBusEvent ( e * event . Event ) string {
if eh . app != nil {
switch e . EventType {
case event . ACNStatus :
if e . Data [ event . Progress ] == "100" {
2021-10-15 20:09:36 +00:00
for _ , onion := range eh . app . ListProfiles ( ) {
2021-06-24 22:30:46 +00:00
// launch a listen thread (internally this does a check that the protocol engine is not listening)
// and as such is safe to call.
eh . app . GetPeer ( onion ) . Listen ( )
}
}
case event . NewPeer :
onion := e . Data [ event . Identity ]
profile := eh . app . GetPeer ( e . Data [ event . Identity ] )
2022-01-13 22:42:31 +00:00
if profile == nil {
log . Errorf ( "NewPeer: skipping profile initialization. this should only happen when the app is rapidly opened+closed (eg during testing)" )
break
}
2021-06-24 22:30:46 +00:00
log . Debug ( "New Peer Event: %v" , e )
if e . Data [ "Reload" ] != event . True {
eh . startHandlingPeer ( onion )
}
2021-10-15 20:09:36 +00:00
// CwtchPeer will always set this now...
2021-10-15 20:19:14 +00:00
tag , _ := profile . GetScopedZonedAttribute ( attr . LocalScope , attr . ProfileZone , constants . Tag )
2021-10-15 20:09:36 +00:00
e . Data [ constants . Tag ] = tag
2021-06-24 22:30:46 +00:00
if e . Data [ event . Created ] == event . True {
2021-10-15 20:09:36 +00:00
profile . SetScopedZonedAttribute ( attr . PublicScope , attr . ProfileZone , constants2 . Picture , ImageToString ( NewImage ( RandomProfileImage ( onion ) , TypeImageDistro ) ) )
2021-06-24 22:30:46 +00:00
}
2021-12-18 01:20:17 +00:00
profile . SetScopedZonedAttribute ( attr . LocalScope , attr . ProfileZone , constants2 . PeerOnline , event . False )
eh . app . AddPeerPlugin ( onion , plugins . CONNECTIONRETRY )
eh . app . AddPeerPlugin ( onion , plugins . NETWORKCHECK )
// If the user has chosen to block unknown profiles
// then explicitly configure the protocol engine to do so..
2022-02-08 20:43:57 +00:00
settings := ReadGlobalSettings ( )
if settings . BlockUnknownConnections {
2021-12-18 01:20:17 +00:00
profile . BlockUnknownConnections ( )
} else {
// For completeness
profile . AllowUnknownConnections ( )
}
// Start up the Profile
profile . Listen ( )
profile . StartPeersConnections ( )
if _ , err := groups . ExperimentGate ( ReadGlobalSettings ( ) . Experiments ) ; err == nil {
profile . StartServerConnections ( )
2021-06-24 22:30:46 +00:00
}
2021-10-15 20:09:36 +00:00
online , _ := profile . GetScopedZonedAttribute ( attr . LocalScope , attr . ProfileZone , constants2 . PeerOnline )
2021-10-15 20:48:05 +00:00
// Name always exists
e . Data [ constants . Name ] , _ = profile . GetScopedZonedAttribute ( attr . PublicScope , attr . ProfileZone , constants . Name )
2022-02-07 22:21:52 +00:00
e . Data [ constants2 . DefaultProfilePicture ] = RandomProfileImage ( onion )
2022-02-03 23:14:39 +00:00
// if a custom profile image exists then default to it.
key , exists := profile . GetScopedZonedAttribute ( attr . PublicScope , attr . ProfileZone , constants . CustomProfileImageKey )
if ! exists {
e . Data [ constants2 . Picture ] = RandomProfileImage ( onion )
} else {
e . Data [ constants2 . Picture ] , _ = profile . GetScopedZonedAttribute ( attr . LocalScope , attr . FilesharingZone , fmt . Sprintf ( "%s.path" , key ) )
serializedManifest , _ := profile . GetScopedZonedAttribute ( attr . ConversationScope , attr . FilesharingZone , fmt . Sprintf ( "%s.manifest" , key ) )
profile . ShareFile ( key , serializedManifest )
2022-02-04 21:33:28 +00:00
log . Debugf ( "Custom Profile Image: %v %s" , e . Data [ constants2 . Picture ] , serializedManifest )
2022-02-03 23:14:39 +00:00
}
2021-11-26 23:07:20 +00:00
// Resolve the profile image of the profile.
2022-02-03 23:14:39 +00:00
2021-06-24 22:30:46 +00:00
e . Data [ "Online" ] = online
2021-11-26 23:07:20 +00:00
// Construct our conversations and our srever lists
2021-06-24 22:30:46 +00:00
var contacts [ ] Contact
var servers [ ] groups . Server
2021-11-17 20:33:51 +00:00
conversations , err := profile . FetchConversations ( )
2021-11-26 23:07:20 +00:00
if err == nil {
// We have conversations attached to this profile...
for _ , conversationInfo := range conversations {
// Only compile the server info if we have enabled the experiment...
// Note that this means that this info can become stale if when first loaded the experiment
// has been disabled and then is later re-enabled. As such we need to ensure that this list is
// re-fetched when the group experiment is enabled via a dedicated ListServerInfo event...
if conversationInfo . IsServer ( ) {
groupHandler , err := groups . ExperimentGate ( ReadGlobalSettings ( ) . Experiments )
if err == nil {
servers = append ( servers , groupHandler . GetServerInfo ( conversationInfo . Handle , profile ) )
}
continue
2021-06-24 22:30:46 +00:00
}
2021-11-26 23:07:20 +00:00
// Prefer local override to public name...
name , exists := conversationInfo . GetAttribute ( attr . LocalScope , attr . ProfileZone , constants . Name )
2021-11-18 23:44:21 +00:00
if ! exists {
2021-11-26 23:07:20 +00:00
name , exists = conversationInfo . GetAttribute ( attr . PublicScope , attr . ProfileZone , constants . Name )
if ! exists {
name = conversationInfo . Handle
}
2021-11-18 23:44:21 +00:00
}
2021-06-24 22:30:46 +00:00
2021-11-26 23:07:20 +00:00
// Resolve the profile image of the contact
var cpicPath string
2022-03-22 20:02:20 +00:00
var defaultPath string
2021-11-26 23:07:20 +00:00
if conversationInfo . IsGroup ( ) {
cpicPath = RandomGroupImage ( conversationInfo . Handle )
2022-03-22 20:02:20 +00:00
defaultPath = RandomGroupImage ( conversationInfo . Handle )
2021-11-26 23:07:20 +00:00
} else {
2022-02-08 20:43:57 +00:00
cpicPath = GetProfileImage ( profile , conversationInfo , settings . DownloadPath )
2022-03-22 20:02:20 +00:00
defaultPath = RandomProfileImage ( conversationInfo . Handle )
2021-11-26 23:07:20 +00:00
}
2021-06-24 22:30:46 +00:00
2021-11-26 23:07:20 +00:00
// Resolve Save History Setting
saveHistory , set := conversationInfo . GetAttribute ( attr . LocalScope , attr . ProfileZone , event . SaveHistoryKey )
if ! set {
saveHistory = event . DeleteHistoryDefault
}
2021-06-24 22:30:46 +00:00
2021-11-26 23:07:20 +00:00
// Resolve Archived Setting
isArchived , set := conversationInfo . GetAttribute ( attr . LocalScope , attr . ProfileZone , constants2 . Archived )
if ! set {
isArchived = event . False
}
2021-06-24 22:30:46 +00:00
2022-04-01 22:52:14 +00:00
unread := 0
2022-04-19 23:51:31 +00:00
lastSeenMessageId := - 1
2022-04-01 22:52:14 +00:00
lastSeenTimeStr , set := conversationInfo . GetAttribute ( attr . LocalScope , attr . ProfileZone , constants2 . LastSeenTime )
if set {
lastSeenTime , err := time . Parse ( constants2 . DartIso8601 , lastSeenTimeStr )
if err == nil {
// get last 100 messages and count how many are after the lastSeenTime (100 cus hte ui just shows 99+ after)
messages , err := profile . GetMostRecentMessages ( conversationInfo . ID , 0 , 0 , 100 )
if err == nil {
for _ , message := range messages {
msgTime , err := time . Parse ( time . RFC3339Nano , message . Attr [ constants . AttrSentTimestamp ] )
if err == nil {
if msgTime . UTC ( ) . After ( lastSeenTime . UTC ( ) ) {
unread ++
} else {
2022-04-19 23:51:31 +00:00
lastSeenMessageId = message . ID
2022-04-01 22:52:14 +00:00
break
}
}
}
}
}
}
2022-01-07 16:32:46 +00:00
groupServer , _ := conversationInfo . GetAttribute ( attr . LocalScope , attr . LegacyGroupZone , constants . GroupServer )
stateHandle := conversationInfo . Handle
if conversationInfo . IsGroup ( ) {
stateHandle = groupServer
}
state := profile . GetPeerState ( stateHandle )
2021-11-26 23:07:20 +00:00
if ! set {
state = connections . DISCONNECTED
}
2021-11-17 20:33:51 +00:00
2022-01-06 21:31:21 +00:00
blocked := false
if conversationInfo . ACL [ conversationInfo . Handle ] . Blocked {
blocked = true
2021-11-26 23:07:20 +00:00
}
// Fetch the message count, and the time of the most recent message
count , err := profile . GetChannelMessageCount ( conversationInfo . ID , 0 )
if err != nil {
log . Errorf ( "error fetching channel message count %v %v" , conversationInfo . ID , err )
}
lastMessage , _ := profile . GetMostRecentMessages ( conversationInfo . ID , 0 , 0 , 1 )
2022-02-08 02:12:57 +00:00
notificationPolicy := constants2 . ConversationNotificationPolicyDefault
if notificationPolicyAttr , exists := conversationInfo . GetAttribute ( attr . LocalScope , attr . ProfileZone , constants2 . ConversationNotificationPolicy ) ; exists {
notificationPolicy = notificationPolicyAttr
2022-02-04 21:25:06 +00:00
}
2021-11-26 23:07:20 +00:00
contacts = append ( contacts , Contact {
2022-02-05 00:17:39 +00:00
Name : name ,
Identifier : conversationInfo . ID ,
Onion : conversationInfo . Handle ,
Status : connections . ConnectionStateName [ state ] ,
Picture : cpicPath ,
2022-03-22 20:02:20 +00:00
DefaultPicture : defaultPath ,
2022-02-05 00:17:39 +00:00
Accepted : conversationInfo . Accepted ,
2022-04-06 23:07:59 +00:00
AccessControlList : conversationInfo . ACL ,
2022-02-05 00:17:39 +00:00
Blocked : blocked ,
SaveHistory : saveHistory ,
Messages : count ,
2022-04-01 22:52:14 +00:00
Unread : unread ,
2022-04-19 23:51:31 +00:00
LastSeenMessageId : lastSeenMessageId ,
2022-02-05 00:17:39 +00:00
LastMessage : strconv . Itoa ( getLastMessageTime ( lastMessage ) ) ,
IsGroup : conversationInfo . IsGroup ( ) ,
GroupServer : groupServer ,
IsArchived : isArchived == event . True ,
2022-02-08 02:12:57 +00:00
NotificationPolicy : notificationPolicy ,
2021-11-26 23:07:20 +00:00
} )
2021-08-27 20:25:41 +00:00
}
2021-06-24 22:30:46 +00:00
}
bytes , _ := json . Marshal ( contacts )
e . Data [ "ContactsJson" ] = string ( bytes )
// Marshal the server list into the new peer event...
serversListBytes , _ := json . Marshal ( servers )
e . Data [ groups . ServerList ] = string ( serversListBytes )
log . Debugf ( "contactsJson %v" , e . Data [ "ContactsJson" ] )
}
}
json , _ := json . Marshal ( e )
return string ( json )
}
2022-02-08 20:43:57 +00:00
func GetProfileImage ( profile peer . CwtchPeer , conversationInfo * model . Conversation , basepath string ) string {
2022-02-03 23:14:39 +00:00
fileKey , err := profile . GetConversationAttribute ( conversationInfo . ID , attr . PublicScope . ConstructScopedZonedPath ( attr . ProfileZone . ConstructZonedPath ( constants . CustomProfileImageKey ) ) )
if err == nil {
2022-02-08 21:13:33 +00:00
if value , exists := profile . GetScopedZonedAttribute ( attr . LocalScope , attr . FilesharingZone , fmt . Sprintf ( "%s.complete" , fileKey ) ) ; exists && value == event . True {
2022-02-08 20:43:57 +00:00
fp , _ := filesharing . GenerateDownloadPath ( basepath , fileKey , true )
// check if the file exists...if it does then set the path...
if _ , err := os . Stat ( fp ) ; err == nil {
image , _ := profile . GetScopedZonedAttribute ( attr . LocalScope , attr . FilesharingZone , fmt . Sprintf ( "%s.path" , fileKey ) )
return image
}
2022-02-03 23:14:39 +00:00
}
}
return RandomProfileImage ( conversationInfo . Handle )
}
2021-06-24 22:30:46 +00:00
// handleProfileEvent enriches Profile events so they are usable with out further data fetches
func ( eh * EventHandler ) handleProfileEvent ( ev * EventProfileEnvelope ) string {
2022-01-06 21:31:21 +00:00
// cache of contact states to use to filter out events repeating known states
var contactStateCache = make ( map [ string ] connections . ConnectionState )
2021-06-24 22:30:46 +00:00
if eh . app == nil {
log . Errorf ( "eh.app == nil in handleProfileEvent... this shouldnt happen?" )
} else {
2021-11-17 20:33:51 +00:00
profile := eh . app . GetPeer ( ev . Profile )
2021-06-24 22:30:46 +00:00
log . Debugf ( "New Profile Event to Handle: %v" , ev )
switch ev . Event . EventType {
case event . NewMessageFromPeer : //event.TimestampReceived, event.RemotePeer, event.Data
// only needs contact nickname and picture, for displaying on popup notifications
2021-11-17 20:33:51 +00:00
ci , err := profile . FetchConversationInfo ( ev . Event . Data [ "RemotePeer" ] )
2022-02-03 23:14:39 +00:00
ev . Event . Data [ constants2 . Picture ] = RandomProfileImage ( ev . Event . Data [ "RemotePeer" ] )
2021-11-17 20:33:51 +00:00
if ci != nil && err == nil {
ev . Event . Data [ event . ConversationID ] = strconv . Itoa ( ci . ID )
profile . SetConversationAttribute ( ci . ID , attr . LocalScope . ConstructScopedZonedPath ( attr . ProfileZone . ConstructZonedPath ( constants2 . Archived ) ) , event . False )
2022-02-08 20:43:57 +00:00
ev . Event . Data [ constants2 . Picture ] = GetProfileImage ( profile , ci , ReadGlobalSettings ( ) . DownloadPath )
2021-11-17 22:34:35 +00:00
} else {
// TODO This Conversation May Not Exist Yet...But we are not in charge of creating it...
2021-11-18 23:44:21 +00:00
log . Errorf ( "todo wait for contact to be added before processing this event..." )
2021-12-01 12:16:15 +00:00
return ""
2021-11-17 20:33:51 +00:00
}
2021-11-26 23:07:20 +00:00
var exists bool
ev . Event . Data [ "Nick" ] , exists = ci . GetAttribute ( attr . LocalScope , attr . ProfileZone , constants . Name )
if ! exists {
2021-11-30 21:31:21 +00:00
ev . Event . Data [ "Nick" ] , exists = ci . GetAttribute ( attr . PublicScope , attr . ProfileZone , constants . Name )
if ! exists {
ev . Event . Data [ "Nick" ] = ev . Event . Data [ "RemotePeer" ]
2021-12-19 18:52:10 +00:00
// If we dont have a name val for a peer, but they have sent us a message, we might be approved now, re-ask
profile . SendScopedZonedGetValToContact ( ci . ID , attr . PublicScope , attr . ProfileZone , constants . Name )
2022-02-03 23:14:39 +00:00
profile . SendScopedZonedGetValToContact ( ci . ID , attr . PublicScope , attr . ProfileZone , constants . CustomProfileImageKey )
2021-11-30 21:31:21 +00:00
}
2021-11-26 23:07:20 +00:00
}
2021-12-14 21:23:32 +00:00
2021-12-19 01:16:21 +00:00
if ci . Accepted {
handleImagePreviews ( profile , & ev . Event , ci . ID , ci . ID )
2021-12-14 21:23:32 +00:00
}
2022-02-04 21:25:06 +00:00
2022-02-05 00:17:39 +00:00
ev . Event . Data [ "notification" ] = string ( determineNotification ( ci ) )
2021-06-24 22:30:46 +00:00
case event . NewMessageFromGroup :
// only needs contact nickname and picture, for displaying on popup notifications
2021-11-17 20:33:51 +00:00
ci , err := profile . FetchConversationInfo ( ev . Event . Data [ "RemotePeer" ] )
2022-02-03 23:14:39 +00:00
ev . Event . Data [ constants2 . Picture ] = RandomProfileImage ( ev . Event . Data [ "RemotePeer" ] )
2021-11-17 20:33:51 +00:00
if ci != nil && err == nil {
2021-11-26 23:07:20 +00:00
var exists bool
ev . Event . Data [ "Nick" ] , exists = ci . GetAttribute ( attr . LocalScope , attr . ProfileZone , constants . Name )
if ! exists {
2021-11-30 21:31:21 +00:00
ev . Event . Data [ "Nick" ] , exists = ci . GetAttribute ( attr . PublicScope , attr . ProfileZone , constants . Name )
if ! exists {
ev . Event . Data [ "Nick" ] = ev . Event . Data [ "RemotePeer" ]
}
2021-11-26 23:07:20 +00:00
}
2022-02-08 20:43:57 +00:00
ev . Event . Data [ constants2 . Picture ] = GetProfileImage ( profile , ci , ReadGlobalSettings ( ) . DownloadPath )
2021-11-17 20:33:51 +00:00
}
2022-02-03 23:14:39 +00:00
2021-11-18 23:44:21 +00:00
conversationID , _ := strconv . Atoi ( ev . Event . Data [ event . ConversationID ] )
2021-11-17 20:33:51 +00:00
profile . SetConversationAttribute ( conversationID , attr . LocalScope . ConstructScopedZonedPath ( attr . ProfileZone . ConstructZonedPath ( constants2 . Archived ) ) , event . False )
2021-12-17 00:43:23 +00:00
2021-12-19 01:16:21 +00:00
if ci != nil && ci . Accepted {
handleImagePreviews ( profile , & ev . Event , conversationID , ci . ID )
2021-12-17 00:43:23 +00:00
}
2022-03-04 00:34:39 +00:00
gci , _ := profile . GetConversationInfo ( conversationID )
2022-02-09 21:17:51 +00:00
ev . Event . Data [ "notification" ] = string ( determineNotification ( gci ) )
2021-06-24 22:30:46 +00:00
case event . PeerAcknowledgement :
2021-11-17 20:33:51 +00:00
ci , err := profile . FetchConversationInfo ( ev . Event . Data [ "RemotePeer" ] )
if ci != nil && err == nil {
ev . Event . Data [ event . ConversationID ] = strconv . Itoa ( ci . ID )
}
2021-11-17 22:34:35 +00:00
case event . ContactCreated :
2021-11-18 23:44:21 +00:00
conversationID , _ := strconv . Atoi ( ev . Event . Data [ event . ConversationID ] )
2021-11-17 22:34:35 +00:00
count , err := profile . GetChannelMessageCount ( conversationID , 0 )
2021-06-24 22:30:46 +00:00
if err != nil {
2021-11-17 22:34:35 +00:00
log . Errorf ( "error fetching channel message count %v %v" , conversationID , err )
2021-06-24 22:30:46 +00:00
}
2021-11-17 22:34:35 +00:00
2021-12-01 12:16:15 +00:00
conversationInfo , err := profile . GetConversationInfo ( conversationID )
if err != nil {
log . Errorf ( "error fetching conversation info for %v %v" , conversationID , err )
2021-06-24 22:30:46 +00:00
}
2021-12-01 12:16:15 +00:00
2022-01-06 21:31:21 +00:00
blocked := constants . False
if conversationInfo . ACL [ conversationInfo . Handle ] . Blocked {
blocked = constants . True
2021-12-01 12:16:15 +00:00
}
2022-01-06 21:31:21 +00:00
accepted := constants . False
if conversationInfo . Accepted {
accepted = constants . True
2021-12-01 12:16:15 +00:00
}
2022-04-06 23:07:59 +00:00
acl , _ := json . Marshal ( conversationInfo . ACL )
2021-11-18 23:44:21 +00:00
lastMessage , _ := profile . GetMostRecentMessages ( conversationID , 0 , 0 , 1 )
2021-12-01 12:16:15 +00:00
ev . Event . Data [ "unread" ] = strconv . Itoa ( count ) // if this is a new contact with messages attached then by-definition these are unread...
2022-02-03 23:14:39 +00:00
ev . Event . Data [ constants2 . Picture ] = RandomProfileImage ( conversationInfo . Handle )
2022-02-07 22:21:52 +00:00
ev . Event . Data [ constants2 . DefaultProfilePicture ] = RandomProfileImage ( conversationInfo . Handle )
2021-11-17 22:34:35 +00:00
ev . Event . Data [ "numMessages" ] = strconv . Itoa ( count )
2021-12-01 12:16:15 +00:00
ev . Event . Data [ "nick" ] = conversationInfo . Handle
ev . Event . Data [ "status" ] = connections . ConnectionStateName [ profile . GetPeerState ( conversationInfo . Handle ) ]
2022-01-06 21:31:21 +00:00
ev . Event . Data [ "accepted" ] = accepted
2022-04-06 23:07:59 +00:00
ev . Event . Data [ "accessControlList" ] = string ( acl )
2022-01-06 21:31:21 +00:00
ev . Event . Data [ "blocked" ] = blocked
2021-11-17 22:34:35 +00:00
ev . Event . Data [ "loading" ] = "false"
ev . Event . Data [ "lastMsgTime" ] = strconv . Itoa ( getLastMessageTime ( lastMessage ) )
2021-06-24 22:30:46 +00:00
case event . GroupCreated :
// This event should only happen after we have validated the invite, as such the error
// condition *should* never happen.
2021-11-17 20:33:51 +00:00
groupPic := RandomGroupImage ( ev . Event . Data [ event . GroupID ] )
2022-02-03 23:14:39 +00:00
ev . Event . Data [ constants2 . Picture ] = groupPic
2022-04-06 23:07:59 +00:00
conversationID , _ := strconv . Atoi ( ev . Event . Data [ event . ConversationID ] )
conversationInfo , _ := profile . GetConversationInfo ( conversationID )
acl , _ := json . Marshal ( conversationInfo . ACL )
ev . Event . Data [ "accessControlList" ] = string ( acl )
2021-06-24 22:30:46 +00:00
case event . NewGroup :
// This event should only happen after we have validated the invite, as such the error
// condition *should* never happen.
serializedInvite := ev . Event . Data [ event . GroupInvite ]
if invite , err := model . ValidateInvite ( serializedInvite ) ; err == nil {
2021-11-17 20:33:51 +00:00
groupPic := RandomGroupImage ( invite . GroupID )
2022-02-03 23:14:39 +00:00
ev . Event . Data [ constants2 . Picture ] = groupPic
2022-04-06 23:07:59 +00:00
conversationID , _ := strconv . Atoi ( ev . Event . Data [ event . ConversationID ] )
conversationInfo , _ := profile . GetConversationInfo ( conversationID )
acl , _ := json . Marshal ( conversationInfo . ACL )
ev . Event . Data [ "accessControlList" ] = string ( acl )
2021-06-24 22:30:46 +00:00
} else {
2022-04-06 23:07:59 +00:00
log . Errorf ( "received a new group event which contained an invalid invite %v. this should never happen and likely means there is a bug in cwtch. Please file a ticket @ https://git.openprivacy.ca/cwtch.im/cwtch" , err )
2021-06-24 22:30:46 +00:00
return ""
}
case event . PeerStateChange :
cxnState := connections . ConnectionStateToType ( ) [ ev . Event . Data [ event . ConnectionState ] ]
2022-01-06 21:31:21 +00:00
// skip events the UI doesn't act on
if cxnState == connections . CONNECTING || cxnState == connections . CONNECTED {
return ""
}
2022-02-03 23:14:39 +00:00
contact , err := profile . FetchConversationInfo ( ev . Event . Data [ event . RemotePeer ] )
2021-06-24 22:30:46 +00:00
2021-11-26 22:26:26 +00:00
if ev . Event . Data [ event . RemotePeer ] == profile . GetOnion ( ) {
return "" // suppress events from our own profile...
}
2021-12-01 12:16:15 +00:00
// We do not know who this is...don't send any event until we see a message from them
// (at that point the conversation will have been created...)
2022-02-03 23:14:39 +00:00
if contact == nil || err != nil || contact . ID == 0 {
2021-06-24 22:30:46 +00:00
return ""
}
2022-01-06 21:31:21 +00:00
// if we already know this state, suppress
if knownState , exists := contactStateCache [ ev . Event . Data [ event . RemotePeer ] ] ; exists && cxnState == knownState {
return ""
}
contactStateCache [ ev . Event . Data [ event . RemotePeer ] ] = cxnState
2021-06-24 22:30:46 +00:00
if contact != nil {
// No enrichment needed
if cxnState == connections . AUTHENTICATED {
// if known and authed, get vars
2021-11-17 22:34:35 +00:00
profile . SendScopedZonedGetValToContact ( contact . ID , attr . PublicScope , attr . ProfileZone , constants . Name )
2022-02-03 23:14:39 +00:00
profile . SendScopedZonedGetValToContact ( contact . ID , attr . PublicScope , attr . ProfileZone , constants . CustomProfileImageKey )
2021-06-24 22:30:46 +00:00
}
}
2022-01-06 21:31:21 +00:00
case event . ServerStateChange :
cxnState := connections . ConnectionStateToType ( ) [ ev . Event . Data [ event . ConnectionState ] ]
// skip events the UI doesn't act on
if cxnState == connections . CONNECTING || cxnState == connections . CONNECTED {
return ""
}
// if we already know this state, suppress
if knownState , exists := contactStateCache [ ev . Event . Data [ event . RemotePeer ] ] ; exists && cxnState == knownState {
return ""
}
contactStateCache [ ev . Event . Data [ event . RemotePeer ] ] = cxnState
2021-06-24 22:30:46 +00:00
case event . NewRetValMessageFromPeer :
// auto handled event means the setting is already done, we're just deciding if we need to tell the UI
2022-02-03 23:14:39 +00:00
onion := ev . Event . Data [ event . RemotePeer ]
2021-06-24 22:30:46 +00:00
scope := ev . Event . Data [ event . Scope ]
path := ev . Event . Data [ event . Path ]
2022-02-03 23:14:39 +00:00
val := ev . Event . Data [ event . Data ]
2021-06-24 22:30:46 +00:00
exists , _ := strconv . ParseBool ( ev . Event . Data [ event . Exists ] )
2022-02-03 23:14:39 +00:00
conversation , err := profile . FetchConversationInfo ( onion )
if err == nil {
if exists && attr . IntoScope ( scope ) == attr . PublicScope {
zone , path := attr . ParseZone ( path )
// auto download profile images from contacts...
settings := ReadGlobalSettings ( )
if settings . ExperimentsEnabled && zone == attr . ProfileZone && path == constants . CustomProfileImageKey {
fileKey := val
fsf , err := filesharing . FunctionalityGate ( settings . Experiments )
imagePreviewsEnabled := settings . Experiments [ "filesharing-images" ]
2022-02-08 20:43:57 +00:00
if err == nil && imagePreviewsEnabled && conversation . Accepted {
2022-02-08 20:59:33 +00:00
2022-02-03 23:14:39 +00:00
basepath := settings . DownloadPath
fp , mp := filesharing . GenerateDownloadPath ( basepath , fileKey , true )
2022-02-08 20:59:33 +00:00
2022-02-08 21:09:04 +00:00
if value , exists := profile . GetScopedZonedAttribute ( attr . LocalScope , attr . FilesharingZone , fmt . Sprintf ( "%s.complete" , fileKey ) ) ; exists && value == event . True {
2022-02-08 20:43:57 +00:00
if _ , err := os . Stat ( fp ) ; err == nil {
// file is marked as completed downloaded and exists...
return ""
2022-02-08 21:09:04 +00:00
} else {
// the user probably deleted the file, mark completed as false...
profile . SetScopedZonedAttribute ( attr . LocalScope , attr . FilesharingZone , fmt . Sprintf ( "%s.complete" , fileKey ) , event . False )
2022-02-08 20:43:57 +00:00
}
}
2022-02-08 20:59:33 +00:00
2022-02-03 23:14:39 +00:00
log . Debugf ( "Downloading Profile Image %v %v %v" , fp , mp , fileKey )
ev . Event . Data [ event . FilePath ] = fp
2022-02-08 20:43:57 +00:00
fsf . DownloadFile ( profile , conversation . ID , fp , mp , val , constants . ImagePreviewMaxSizeInBytes )
2022-02-03 23:14:39 +00:00
} else {
2022-02-08 20:43:57 +00:00
// if image previews are disabled then ignore this event...
2022-02-03 23:14:39 +00:00
return ""
}
}
2022-04-18 16:47:39 +00:00
if val , err := profile . GetConversationAttribute ( conversation . ID , attr . LocalScope . ConstructScopedZonedPath ( zone . ConstructZonedPath ( path ) ) ) ; err == nil || val != "" {
2022-02-03 23:14:39 +00:00
// we have a locally set override, don't pass this remote set public scope update to UI
return ""
}
2021-06-24 22:30:46 +00:00
}
}
}
}
json , _ := json . Marshal ( unwrap ( ev ) )
return string ( json )
}
func unwrap ( original * EventProfileEnvelope ) * event . Event {
unwrapped := & original . Event
unwrapped . Data [ "ProfileOnion" ] = original . Profile
return unwrapped
}
func ( eh * EventHandler ) startHandlingPeer ( onion string ) {
eventBus := eh . app . GetEventBus ( onion )
q := event . NewQueue ( )
2021-11-26 22:26:26 +00:00
2022-03-22 20:02:20 +00:00
eventBus . Subscribe ( event . NetworkStatus , q )
2022-01-17 20:37:34 +00:00
eventBus . Subscribe ( event . ACNInfo , q )
2021-06-24 22:30:46 +00:00
eventBus . Subscribe ( event . NewMessageFromPeer , q )
2021-11-26 22:26:26 +00:00
eventBus . Subscribe ( event . UpdatedProfileAttribute , q )
2021-06-24 22:30:46 +00:00
eventBus . Subscribe ( event . PeerAcknowledgement , q )
eventBus . Subscribe ( event . DeleteContact , q )
eventBus . Subscribe ( event . AppError , q )
eventBus . Subscribe ( event . IndexedAcknowledgement , q )
eventBus . Subscribe ( event . IndexedFailure , q )
2021-11-19 19:50:37 +00:00
eventBus . Subscribe ( event . ContactCreated , q )
2021-06-24 22:30:46 +00:00
eventBus . Subscribe ( event . NewMessageFromGroup , q )
2021-06-29 23:38:12 +00:00
eventBus . Subscribe ( event . MessageCounterResync , q )
2021-06-24 22:30:46 +00:00
eventBus . Subscribe ( event . GroupCreated , q )
eventBus . Subscribe ( event . NewGroup , q )
eventBus . Subscribe ( event . DeleteGroup , q )
eventBus . Subscribe ( event . ServerStateChange , q )
eventBus . Subscribe ( event . PeerStateChange , q )
eventBus . Subscribe ( event . ChangePasswordSuccess , q )
eventBus . Subscribe ( event . ChangePasswordError , q )
eventBus . Subscribe ( event . NewRetValMessageFromPeer , q )
2021-09-30 17:21:36 +00:00
eventBus . Subscribe ( event . ShareManifest , q )
eventBus . Subscribe ( event . ManifestSizeReceived , q )
eventBus . Subscribe ( event . ManifestError , q )
eventBus . Subscribe ( event . ManifestReceived , q )
eventBus . Subscribe ( event . ManifestSaved , q )
eventBus . Subscribe ( event . FileDownloadProgressUpdate , q )
eventBus . Subscribe ( event . FileDownloaded , q )
2021-06-24 22:30:46 +00:00
go eh . forwardProfileMessages ( onion , q )
}
func ( eh * EventHandler ) forwardProfileMessages ( onion string , q event . Queue ) {
2021-06-25 05:16:47 +00:00
log . Infof ( "Launching Forwarding Goroutine" )
2021-06-24 22:30:46 +00:00
// TODO: graceful shutdown, via an injected event of special QUIT type exiting loop/go routine
for {
e := q . Next ( )
ev := EventProfileEnvelope { Event : e , Profile : onion }
eh . profileEvents <- ev
if ev . Event . EventType == event . Shutdown {
return
}
}
}
2021-11-26 01:59:00 +00:00
// Push pushes an event onto the app event bus
// It is also a way for libCwtch-go to publish an event for consumption by a UI before a Cwtch app has been initialized
// use: to signal an error before a cwtch app could be created
2021-06-24 22:30:46 +00:00
func ( eh * EventHandler ) Push ( newEvent event . Event ) {
eh . appBusQueue . Publish ( newEvent )
}
2021-11-17 20:33:51 +00:00
func getLastMessageTime ( conversationMessages [ ] model . ConversationMessage ) int {
if len ( conversationMessages ) == 0 {
return 0
}
time , err := time . Parse ( time . RFC3339Nano , conversationMessages [ 0 ] . Attr [ constants . AttrSentTimestamp ] )
if err != nil {
return 0
}
return int ( time . Unix ( ) )
2021-11-18 23:44:21 +00:00
}
2021-12-19 01:16:21 +00:00
// handleImagePreviews checks settings and, if appropriate, auto-downloads any images
func handleImagePreviews ( profile peer . CwtchPeer , ev * event . Event , conversationID , senderID int ) {
settings := ReadGlobalSettings ( )
fh , err := filesharing . PreviewFunctionalityGate ( settings . Experiments )
2022-02-03 23:14:39 +00:00
imagePreviewsEnabled := settings . Experiments [ "filesharing-images" ]
if err == nil && imagePreviewsEnabled {
2021-12-19 01:16:21 +00:00
var cm model . MessageWrapper
err := json . Unmarshal ( [ ] byte ( ev . Data [ event . Data ] ) , & cm )
if err == nil && cm . Overlay == model . OverlayFileSharing {
var fm filesharing . OverlayMessage
err = json . Unmarshal ( [ ] byte ( cm . Data ) , & fm )
if err == nil {
if fm . ShouldAutoDL ( ) {
basepath := settings . DownloadPath
2022-02-03 23:14:39 +00:00
fp , mp := filesharing . GenerateDownloadPath ( basepath , fm . Name , false )
2021-12-19 01:16:21 +00:00
log . Debugf ( "autodownloading file!" )
ev . Data [ "Auto" ] = constants . True
mID , _ := strconv . Atoi ( ev . Data [ "Index" ] )
profile . UpdateMessageAttribute ( conversationID , 0 , mID , constants . AttrDownloaded , constants . True )
2022-02-04 20:48:39 +00:00
fh . DownloadFile ( profile , senderID , fp , mp , fm . FileKey ( ) , constants . ImagePreviewMaxSizeInBytes )
2021-12-19 01:16:21 +00:00
}
}
}
}
}