Compare commits
No commits in common. "trunk" and "v1.4.1" have entirely different histories.
47
.drone.yml
47
.drone.yml
|
@ -5,36 +5,35 @@ name: linux-android-windows-test
|
|||
|
||||
steps:
|
||||
- name: fetch
|
||||
image: golang:1.19.1
|
||||
image: golang
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /go
|
||||
commands:
|
||||
- go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||
- wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/master/tor/tor
|
||||
- wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/master/tor/torrc
|
||||
- chmod a+x tor
|
||||
- go get -u golang.org/x/lint/golint
|
||||
- git fetch --tags
|
||||
#- export GO111MODULE=on
|
||||
#- go mod vendor
|
||||
- go mod download
|
||||
# mobile is... special
|
||||
# go get golang.org/x/mobile/bind
|
||||
- go get
|
||||
# TODO: upgrade to go1.16, remove mod/vendor, add go install for 1.16
|
||||
- echo `git describe --tags` > VERSION
|
||||
- echo `date +%G-%m-%d-%H-%M` > BUILDDATE
|
||||
|
||||
- name: quality
|
||||
image: golang:1.19.1
|
||||
image: golang
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /go
|
||||
commands:
|
||||
- staticcheck ./...
|
||||
- go list ./... | xargs go vet
|
||||
- go list ./... | xargs golint
|
||||
#Todo: fix all the lint errors and add `-set_exit_status` above to enforce linting
|
||||
|
||||
- name: build-linux
|
||||
image: golang:1.19.1
|
||||
image: golang
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /go
|
||||
|
@ -42,7 +41,7 @@ steps:
|
|||
- make linux
|
||||
|
||||
- name: build-android
|
||||
image: openpriv/android-go-mobile:2023.02
|
||||
image: openpriv/android-go-mobile:2021.03
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /go
|
||||
|
@ -52,7 +51,7 @@ steps:
|
|||
- make android
|
||||
|
||||
- name: build-windows
|
||||
image: openpriv/mingw-go:2023.01
|
||||
image: openpriv/mingw-go:2021.03
|
||||
environment:
|
||||
GOPATH: /go
|
||||
volumes:
|
||||
|
@ -63,7 +62,6 @@ steps:
|
|||
|
||||
- name: deploy-buildfiles
|
||||
image: kroniak/ssh-client
|
||||
pull: if-not-exists
|
||||
environment:
|
||||
BUILDFILES_KEY:
|
||||
from_secret: buildfiles_key
|
||||
|
@ -88,7 +86,6 @@ steps:
|
|||
|
||||
- name: gitea-release
|
||||
image: plugins/gitea-release
|
||||
pull: if-not-exists
|
||||
when:
|
||||
event: tag
|
||||
settings:
|
||||
|
@ -105,9 +102,18 @@ steps:
|
|||
- sha256
|
||||
- sha512
|
||||
|
||||
- name: notify-email
|
||||
image: drillster/drone-email
|
||||
settings:
|
||||
host: build.openprivacy.ca
|
||||
port: 25
|
||||
skip_verify: true
|
||||
from: drone@openprivacy.ca
|
||||
when:
|
||||
status: [ failure ]
|
||||
|
||||
- name: notify-gogs
|
||||
image: openpriv/drone-gogs
|
||||
pull: if-not-exists
|
||||
when:
|
||||
event: pull_request
|
||||
status: [ success, changed, failure ]
|
||||
|
@ -148,18 +154,10 @@ steps:
|
|||
# TODO: upgrade to go1.16, remove mod/vendor, add go install for 1.16
|
||||
- echo `git describe --tags` > VERSION
|
||||
- echo `date +%G-%m-%d-%H-%M` > BUILDDATE
|
||||
- name: build-macos-x64
|
||||
- name: build-macos
|
||||
commands:
|
||||
- export PATH=$PATH:/usr/local/go/bin/
|
||||
- make libCwtch.x64.dylib
|
||||
- name: build-macos-arm64
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
status: [ success ]
|
||||
commands:
|
||||
- export PATH=$PATH:/usr/local/go/bin/
|
||||
- make libCwtch.arm64.dylib
|
||||
- make macos
|
||||
- name: deploy-buildfiles
|
||||
environment:
|
||||
BUILDFILES_KEY:
|
||||
|
@ -174,8 +172,7 @@ steps:
|
|||
- chmod 400 ~/id_rsa
|
||||
- export DIR=libCwtch-go-macos-`cat BUILDDATE`-`cat VERSION`
|
||||
- mkdir $DIR
|
||||
- mv libCwtch.x64.dylib $DIR/
|
||||
- mv libCwtch.arm64.dylib $DIR/
|
||||
- mv libCwtch.dylib $DIR/
|
||||
- cd $DIR
|
||||
- find . -type f -exec shasum -a 512 {} \; > ./../sha512s.txt
|
||||
- mv ./../sha512s.txt .
|
||||
|
|
20
Makefile
20
Makefile
|
@ -8,7 +8,7 @@ all: linux android windows
|
|||
|
||||
linux: libCwtch.so
|
||||
|
||||
macos: libCwtch.x64.dylib libCwtch.arm64.dylib
|
||||
macos: libCwtch.dylib
|
||||
|
||||
android: cwtch.aar
|
||||
|
||||
|
@ -16,27 +16,19 @@ windows: libCwtch.dll
|
|||
|
||||
libCwtch.so: lib.go
|
||||
./switch-ffi.sh
|
||||
go build -trimpath -ldflags "-buildid=$(shell git describe --tags) -X main.buildVer=$(shell git describe --tags) -X main.buildDate=$(shell git log -1 --format=%cd --date=format:%G-%m-%d-%H-%M)" -buildmode c-shared -o libCwtch.so
|
||||
go build -buildmode c-shared -o libCwtch.so
|
||||
|
||||
libCwtch.x64.dylib: lib.go
|
||||
libCwtch.dylib: lib.go
|
||||
./switch-ffi.sh
|
||||
go build -trimpath -ldflags "-buildid=$(shell git describe --tags) -X main.buildVer=$(shell git describe --tags) -X main.buildDate=$(shell git log -1 --format=%cd --date=format:%G-%m-%d-%H-%M)" -buildmode c-shared -o libCwtch.x64.dylib
|
||||
|
||||
libCwtch.arm64.dylib: lib.go
|
||||
./switch-ffi.sh
|
||||
env GOARCH=arm64 GOOS=darwin CGO_ENABLED=1 go build -trimpath -ldflags "-buildid=$(shell git describe --tags) -X main.buildVer=$(shell git describe --tags) -X main.buildDate=$(shell git log -1 --format=%cd --date=format:%G-%m-%d-%H-%M)" -buildmode c-shared -o libCwtch.arm64.dylib
|
||||
go build -buildmode c-shared -o libCwtch.dylib
|
||||
|
||||
cwtch.aar: lib.go
|
||||
./switch-gomobile.sh
|
||||
gomobile bind -trimpath -target android/arm,android/arm64,android/amd64 -ldflags="-buildid=$(shell git describe --tags) -X cwtch.buildVer=$(shell git describe --tags) -X cwtch.buildDate=$(shell git log -1 --format=%cd --date=format:%G-%m-%d-%H-%M)"
|
||||
gomobile bind -target android
|
||||
|
||||
libCwtch.dll: lib.go
|
||||
./switch-ffi.sh
|
||||
# '-Xlinker --no-insert-timestamp` sets the output dll PE timestamp header to all zeros, instead of the actual time
|
||||
# this is necessary for reproducible builds (see: https://wiki.debian.org/ReproducibleBuilds/TimestampsInPEBinaries for additional information)
|
||||
# note: the above documentation also references an ability to set an optional timestamp - this behaviour seems to no longer be supported in more recent versions of mingw32-gcc (the help docs no longer reference that functionality)
|
||||
# these flags have to be passed through to the underlying gcc process using the -extldflags option in the underlying go linker, note that the whole flag is quoted...this is necessary.
|
||||
GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc-win32 go build -trimpath -ldflags "-buildid=$(shell git describe --tags) -X main.buildVer=$(shell git describe --tags) -X main.buildDate=$(shell git log -1 --format=%cd --date=format:%G-%m-%d-%H-%M) '-extldflags=-Xlinker --no-insert-timestamp'" -buildmode c-shared -o libCwtch.dll
|
||||
GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc-win32 go build -buildmode c-shared -o libCwtch.dll
|
||||
|
||||
clean:
|
||||
rm -f cwtch.aar cwtch_go.apk libCwtch.h libCwtch.so cwtch-sources.jar libCwtch.dll libCwtch.dylib
|
||||
|
|
29
README.md
29
README.md
|
@ -1,5 +1,3 @@
|
|||
# NOTE: libcwtch-go has been deprecated in favour of [autobindings](https://git.openprivacy.ca/cwtch.im/autobindings). This repository has been archived and is no longer maintained.
|
||||
|
||||
# libcwtch-go
|
||||
|
||||
C-bindings for the Go Cwtch Library.
|
||||
|
@ -8,35 +6,12 @@ C-bindings for the Go Cwtch Library.
|
|||
make linux
|
||||
make android
|
||||
make windows
|
||||
make macos
|
||||
|
||||
## Android Build Notes
|
||||
|
||||
Our build infrastructure is using Go 1.15.10, NDK 21.0.6113669,
|
||||
and gomobile commit bdb1ca9a1e083af5929a8214e8a056d638ebbf2d (2021 07 16)
|
||||
|
||||
Go 1.17.4, NDK 22.1.7171670, and gomobile 4e6c2922fdeed32d3596616518aaee7b0d79ce55 (2021 12 07) appear to compile as well.
|
||||
|
||||
Other version combinations untested and some definitely do not work.
|
||||
|
||||
## Windows
|
||||
|
||||
Cwtch relies on sqlite which in turn requires the use of CGO. As per [this issue](https://github.com/golang/go/issues/12029)
|
||||
that means [TDM-GCC](https://jmeubank.github.io/tdm-gcc/download/) is required to be installed and used to compile on Windows.
|
||||
Install it and add it to your path and `make windows` should then work.
|
||||
|
||||
## Experimental iOS support
|
||||
make ios
|
||||
|
||||
# Using
|
||||
|
||||
## General Environment Variables
|
||||
|
||||
- `LOG_FILE` if defined will mean all go logging will go to a file instead of stdout
|
||||
- `LOG_LEVEL` if set to `debug` will cause debug logging to be included in log output
|
||||
- `CWTCH_PROFILE` if set to `1` will cause a memory profile to be written to `mem.prof` and all active goroutines
|
||||
written to `stdout` when `DebugInfo()` is called.
|
||||
|
||||
## Linux Desktop:
|
||||
|
||||
- `LD_LIBRARY_PATH` set to point to `libCwtch.so`
|
||||
|
@ -49,7 +24,3 @@ written to `stdout` when `DebugInfo()` is called.
|
|||
## Windows
|
||||
|
||||
- copy libCwtch.dll into the directory of the `.exe` using it
|
||||
|
||||
## MacOS
|
||||
|
||||
- copy libCwtch.x64.dylib and libCwtch.arm.dylib into the directory you are executing from
|
||||
|
|
|
@ -5,10 +5,8 @@ const SchemaVersion = "schemaVersion"
|
|||
const Name = "name"
|
||||
const LastRead = "last-read"
|
||||
const Picture = "picture"
|
||||
const DefaultProfilePicture = "defaultPicture"
|
||||
const ShowBlocked = "show-blocked"
|
||||
const Archived = "archived"
|
||||
const LastSeenTime = "lastMessageSeenTime"
|
||||
|
||||
const ProfileTypeV1DefaultPassword = "v1-defaultPassword"
|
||||
const ProfileTypeV1Password = "v1-userPassword"
|
||||
|
@ -16,14 +14,6 @@ const ProfileTypeV1Password = "v1-userPassword"
|
|||
// PeerOnline stores state on if the peer believes it is online
|
||||
const PeerOnline = "peer-online"
|
||||
|
||||
const PeerAutostart = "autostart"
|
||||
|
||||
// Description is used on server contacts,
|
||||
const Description = "description"
|
||||
|
||||
// ConversationNotificationPolicy is the attribute label for conversations. When App NotificationPolicy is OptIn a true value here opts in
|
||||
const ConversationNotificationPolicy = "notification-policy"
|
||||
|
||||
const StateProfilePane = "state-profile-pane"
|
||||
const StateSelectedConversation = "state-selected-conversation"
|
||||
const StateSelectedProfileTime = "state-selected-profile-time"
|
||||
|
@ -32,6 +22,3 @@ const StateSelectedProfileTime = "state-selected-profile-time"
|
|||
const BlockUnknownPeersSetting = "blockunknownpeers"
|
||||
const LocaleSetting = "locale"
|
||||
const ZoomSetting = "zoom"
|
||||
|
||||
// App Experiments
|
||||
const MessageFormattingExperiment = "message-formatting"
|
||||
|
|
|
@ -12,24 +12,3 @@ const (
|
|||
StatusError = "error"
|
||||
)
|
||||
|
||||
type NotificationType string
|
||||
|
||||
const (
|
||||
// NotificationNone enum for message["notification"] that means no notification
|
||||
NotificationNone = NotificationType("None")
|
||||
// NotificationEvent enum for message["notification"] that means emit a notification that a message event happened only
|
||||
NotificationEvent = NotificationType("SimpleEvent")
|
||||
// NotificationConversation enum for message["notification"] that means emit a notification event with Conversation handle included
|
||||
NotificationConversation = NotificationType("ContactInfo")
|
||||
)
|
||||
|
||||
const (
|
||||
// ConversationNotificationPolicyDefault enum for conversations indicating to use global notification policy
|
||||
ConversationNotificationPolicyDefault = "ConversationNotificationPolicy.Default"
|
||||
// ConversationNotificationPolicyOptIn enum for conversation indicating to opt in to nofitications when allowed
|
||||
ConversationNotificationPolicyOptIn = "ConversationNotificationPolicy.OptIn"
|
||||
// ConversationNotificationPolicyNever enum for conversation indicating to opt in to never do notifications
|
||||
ConversationNotificationPolicyNever = "ConversationNotificationPolicy.Never"
|
||||
)
|
||||
|
||||
const DartIso8601 = "2006-01-02T15:04:05.999Z"
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package contact
|
||||
|
||||
import (
|
||||
"cwtch.im/cwtch/model"
|
||||
"cwtch.im/cwtch/peer"
|
||||
"git.openprivacy.ca/cwtch.im/libcwtch-go/features"
|
||||
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||
)
|
||||
|
||||
// Functionality groups some common UI triggered functions for contacts...
|
||||
type Functionality struct {
|
||||
}
|
||||
|
||||
const addContactPrefix = "addcontact"
|
||||
|
||||
const sendMessagePrefix = "sendmessage"
|
||||
|
||||
// FunctionalityGate returns contact.Functionality always
|
||||
func FunctionalityGate(experimentMap map[string]bool) (*Functionality, error) {
|
||||
return new(Functionality), nil
|
||||
}
|
||||
|
||||
// SendMessage handles sending messages to contacts
|
||||
func (pf *Functionality) SendMessage(peer peer.SendMessages, handle string, message string) features.Response {
|
||||
err := peer.SendMessage(handle, message)
|
||||
if err == nil {
|
||||
return features.ConstructResponse(sendMessagePrefix, "success")
|
||||
}
|
||||
return features.ConstructResponse(sendMessagePrefix, err.Error())
|
||||
}
|
||||
|
||||
// HandleImportString handles contact import strings
|
||||
func (pf *Functionality) HandleImportString(peer peer.ModifyContactsAndPeers, importString string) features.Response {
|
||||
if tor.IsValidHostname(importString) {
|
||||
if peer.GetContact(importString) == nil {
|
||||
peer.AddContact(importString, importString, model.AuthApproved)
|
||||
// Implicit Peer Attempt
|
||||
peer.PeerWithOnion(importString)
|
||||
return features.ConstructResponse(addContactPrefix, "success")
|
||||
}
|
||||
return features.ConstructResponse(addContactPrefix, "contact_already_exists")
|
||||
}
|
||||
return features.ConstructResponse(addContactPrefix, "invalid_import_string")
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
package contact
|
||||
|
||||
import (
|
||||
"cwtch.im/cwtch/model"
|
||||
"git.openprivacy.ca/cwtch.im/libcwtch-go/features"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const ValidHostname = "openpravyvc6spbd4flzn4g2iqu4sxzsizbtb5aqec25t76dnoo5w7yd"
|
||||
|
||||
type MockPeer struct {
|
||||
hasContact bool
|
||||
addContact bool
|
||||
peerRequest bool
|
||||
}
|
||||
|
||||
func (m MockPeer) BlockUnknownConnections() {
|
||||
panic("should never be called")
|
||||
}
|
||||
|
||||
func (m MockPeer) AllowUnknownConnections() {
|
||||
panic("should never be called")
|
||||
}
|
||||
|
||||
func (m MockPeer) GetContacts() []string {
|
||||
panic("should never be called")
|
||||
}
|
||||
|
||||
func (m MockPeer) GetContact(s string) *model.PublicProfile {
|
||||
if m.hasContact {
|
||||
return &(model.GenerateNewProfile("").PublicProfile)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m MockPeer) GetContactAttribute(s string, s2 string) (string, bool) {
|
||||
panic("should never be called")
|
||||
}
|
||||
|
||||
func (m *MockPeer) AddContact(nick, onion string, authorization model.Authorization) {
|
||||
m.addContact = true
|
||||
}
|
||||
|
||||
func (m MockPeer) SetContactAuthorization(s string, authorization model.Authorization) error {
|
||||
panic("should never be called")
|
||||
}
|
||||
|
||||
func (m MockPeer) SetContactAttribute(s string, s2 string, s3 string) {
|
||||
panic("should never be called")
|
||||
}
|
||||
|
||||
func (m MockPeer) DeleteContact(s string) {
|
||||
panic("should never be called")
|
||||
}
|
||||
|
||||
func (m *MockPeer) PeerWithOnion(s string) {
|
||||
m.peerRequest = true
|
||||
}
|
||||
|
||||
func (m MockPeer) JoinServer(s string) error {
|
||||
panic("should never be called")
|
||||
}
|
||||
|
||||
func TestContactFunctionality_InValidHostname(t *testing.T) {
|
||||
cf, _ := FunctionalityGate(map[string]bool{})
|
||||
|
||||
peer := &MockPeer{
|
||||
hasContact: false,
|
||||
addContact: false,
|
||||
peerRequest: false,
|
||||
}
|
||||
|
||||
response := cf.HandleImportString(peer, "")
|
||||
|
||||
if peer.addContact || peer.peerRequest {
|
||||
t.Fatalf("HandleImportString for a malformed import string should have no resulted in addContact or a peerRequest: %v", peer)
|
||||
}
|
||||
|
||||
if response.Error() != features.ConstructResponse(addContactPrefix, "invalid_import_string").Error() {
|
||||
t.Fatalf("Response to a successful import is malformed: %v", response)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestContactFunctionality_ValidHostnameExistingContact(t *testing.T) {
|
||||
cf, _ := FunctionalityGate(map[string]bool{})
|
||||
|
||||
peer := &MockPeer{
|
||||
hasContact: true,
|
||||
addContact: false,
|
||||
peerRequest: false,
|
||||
}
|
||||
|
||||
response := cf.HandleImportString(peer, ValidHostname)
|
||||
|
||||
if peer.addContact || peer.peerRequest {
|
||||
t.Fatalf("HandleImportString for a valid string should not call addContact or a peerRequest when the contact already exists: %v", peer)
|
||||
}
|
||||
|
||||
if response.Error() != features.ConstructResponse(addContactPrefix, "contact_already_exists").Error() {
|
||||
t.Fatalf("Response to a successful import is malformed: %v", response)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestContactFunctionality_ValidHostnameUnknownContact(t *testing.T) {
|
||||
cf, _ := FunctionalityGate(map[string]bool{})
|
||||
|
||||
peer := &MockPeer{
|
||||
hasContact: false,
|
||||
addContact: false,
|
||||
peerRequest: false,
|
||||
}
|
||||
|
||||
response := cf.HandleImportString(peer, ValidHostname)
|
||||
|
||||
if peer.addContact && peer.peerRequest {
|
||||
if response.Error() != features.ConstructResponse(addContactPrefix, "success").Error() {
|
||||
t.Fatalf("Response to a successful import is malformed: %v", response)
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("HandleImportString for a valid import string should have resulted in addContact or a peerRequest: %v", peer)
|
||||
}
|
||||
}
|
|
@ -3,16 +3,21 @@ package groups
|
|||
import (
|
||||
"cwtch.im/cwtch/event"
|
||||
"cwtch.im/cwtch/model"
|
||||
"cwtch.im/cwtch/model/attr"
|
||||
constants2 "cwtch.im/cwtch/model/constants"
|
||||
"cwtch.im/cwtch/peer"
|
||||
"cwtch.im/cwtch/protocol/connections"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"git.openprivacy.ca/cwtch.im/libcwtch-go/constants"
|
||||
"git.openprivacy.ca/cwtch.im/libcwtch-go/features"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const serverPrefix = "server:"
|
||||
const tofuBundlePrefix = "tofubundle:"
|
||||
const groupPrefix = "torv3"
|
||||
const groupExperiment = "tapir-groups-experiment"
|
||||
|
||||
const importBundlePrefix = "importBundle"
|
||||
|
||||
const (
|
||||
// ServerList is a json encoded list of servers
|
||||
ServerList = event.Field("ServerList")
|
||||
|
@ -23,6 +28,12 @@ const (
|
|||
UpdateServerInfo = event.Type("UpdateServerInfo")
|
||||
)
|
||||
|
||||
// ReadServerInfo is a meta-interface for reading information about servers..
|
||||
type ReadServerInfo interface {
|
||||
peer.ReadContacts
|
||||
peer.ReadServers
|
||||
}
|
||||
|
||||
// GroupFunctionality provides experiment gated server functionality
|
||||
type GroupFunctionality struct {
|
||||
}
|
||||
|
@ -35,8 +46,27 @@ func ExperimentGate(experimentMap map[string]bool) (*GroupFunctionality, error)
|
|||
return nil, fmt.Errorf("gated by %v", groupExperiment)
|
||||
}
|
||||
|
||||
// SendMessage is a deprecated api
|
||||
func (gf *GroupFunctionality) SendMessage(peer peer.CwtchPeer, handle string, message string) (string, error) {
|
||||
// TODO this auto accepting behaviour needs some thinking through
|
||||
if !peer.GetGroup(handle).Accepted {
|
||||
err := peer.AcceptInvite(handle)
|
||||
if err != nil {
|
||||
log.Errorf("tried to mark a nonexistent group as existed. bad!")
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return "", peer.SendMessage(handle, message)
|
||||
}
|
||||
|
||||
// ValidPrefix returns true if an import string contains a prefix that indicates it contains information about a
|
||||
// server or a group
|
||||
func (gf *GroupFunctionality) ValidPrefix(importString string) bool {
|
||||
return strings.HasPrefix(importString, tofuBundlePrefix) || strings.HasPrefix(importString, serverPrefix) || strings.HasPrefix(importString, groupPrefix)
|
||||
}
|
||||
|
||||
// GetServerInfoList compiles all the information the UI might need regarding all servers..
|
||||
func (gf *GroupFunctionality) GetServerInfoList(profile peer.CwtchPeer) []Server {
|
||||
func (gf *GroupFunctionality) GetServerInfoList(profile ReadServerInfo) []Server {
|
||||
var servers []Server
|
||||
for _, server := range profile.GetServers() {
|
||||
servers = append(servers, gf.GetServerInfo(server, profile))
|
||||
|
@ -46,21 +76,54 @@ func (gf *GroupFunctionality) GetServerInfoList(profile peer.CwtchPeer) []Server
|
|||
|
||||
// GetServerInfo compiles all the information the UI might need regarding a particular server including any verified
|
||||
// cryptographic keys
|
||||
func (gf *GroupFunctionality) GetServerInfo(serverOnion string, profile peer.CwtchPeer) Server {
|
||||
serverInfo, _ := profile.FetchConversationInfo(serverOnion)
|
||||
func (gf *GroupFunctionality) GetServerInfo(serverOnion string, profile peer.ReadContacts) Server {
|
||||
serverInfo := profile.GetContact(serverOnion)
|
||||
keyTypes := []model.KeyType{model.KeyTypeServerOnion, model.KeyTypeTokenOnion, model.KeyTypePrivacyPass}
|
||||
var serverKeys []ServerKey
|
||||
|
||||
for _, keyType := range keyTypes {
|
||||
if key, has := serverInfo.GetAttribute(attr.PublicScope, attr.ServerKeyZone, string(keyType)); has {
|
||||
if key, has := serverInfo.GetAttribute(string(keyType)); has {
|
||||
serverKeys = append(serverKeys, ServerKey{Type: string(keyType), Key: key})
|
||||
}
|
||||
}
|
||||
|
||||
description, _ := serverInfo.GetAttribute(attr.LocalScope, attr.ServerZone, constants.Description)
|
||||
startTimeStr := serverInfo.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants2.SyncPreLastMessageTime)).ToString()]
|
||||
recentTimeStr := serverInfo.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants2.SyncMostRecentMessageTime)).ToString()]
|
||||
syncStatus := SyncStatus{startTimeStr, recentTimeStr}
|
||||
|
||||
return Server{Onion: serverOnion, Identifier: serverInfo.ID, Status: connections.ConnectionStateName[profile.GetPeerState(serverInfo.Handle)], Keys: serverKeys, Description: description, SyncProgress: syncStatus}
|
||||
return Server{Onion: serverOnion, Status: serverInfo.State, Keys: serverKeys}
|
||||
}
|
||||
|
||||
// HandleImportString handles import strings for groups and servers
|
||||
func (gf *GroupFunctionality) HandleImportString(peer peer.CwtchPeer, importString string) error {
|
||||
if strings.HasPrefix(importString, tofuBundlePrefix) {
|
||||
bundle := strings.Split(importString, "||")
|
||||
if len(bundle) == 2 {
|
||||
err := gf.HandleImportString(peer, bundle[0][len(tofuBundlePrefix):])
|
||||
// if the server import failed then abort the whole process..
|
||||
if err != nil && !strings.HasSuffix(err.Error(), "success") {
|
||||
return features.ConstructResponse(importBundlePrefix, err.Error())
|
||||
}
|
||||
return gf.HandleImportString(peer, bundle[1])
|
||||
}
|
||||
} else if strings.HasPrefix(importString, serverPrefix) {
|
||||
// Server Key Bundles are prefixed with
|
||||
bundle, err := base64.StdEncoding.DecodeString(importString[len(serverPrefix):])
|
||||
if err == nil {
|
||||
serverOnion, err := peer.AddServer(string(bundle))
|
||||
if err != nil {
|
||||
return features.ConstructResponse(importBundlePrefix, err.Error())
|
||||
}
|
||||
peer.JoinServer(serverOnion)
|
||||
return features.ConstructResponse(importBundlePrefix, "success")
|
||||
}
|
||||
return features.ConstructResponse(importBundlePrefix, err.Error())
|
||||
} else if strings.HasPrefix(importString, groupPrefix) {
|
||||
//eg: torv3JFDWkXExBsZLkjvfkkuAxHsiLGZBk0bvoeJID9ItYnU=EsEBCiBhOWJhZDU1OTQ0NWI3YmM2N2YxYTM5YjkzMTNmNTczNRIgpHeNaG+6jy750eDhwLO39UX4f2xs0irK/M3P6mDSYQIaOTJjM2ttb29ibnlnaGoyenc2cHd2N2Q1N3l6bGQ3NTNhdW8zdWdhdWV6enB2ZmFrM2FoYzRiZHlkCiJAdVSSVgsksceIfHe41OJu9ZFHO8Kwv3G6F5OK3Hw4qZ6hn6SiZjtmJlJezoBH0voZlCahOU7jCOg+dsENndZxAA==
|
||||
if gid, err := peer.ImportGroup(importString); err != nil {
|
||||
return features.ConstructResponse(importBundlePrefix, err.Error())
|
||||
} else {
|
||||
// Auto accept the group here.
|
||||
if peer.AcceptInvite(gid) != nil {
|
||||
log.Errorf("Error accepting invite: %v", err)
|
||||
}
|
||||
return features.ConstructResponse(importBundlePrefix, "success")
|
||||
}
|
||||
}
|
||||
return features.ConstructResponse(importBundlePrefix, "invalid_group_invite_prefix")
|
||||
}
|
||||
|
|
|
@ -2,6 +2,22 @@ package groups
|
|||
|
||||
import "testing"
|
||||
|
||||
func TestGroupFunctionality_ValidPrefix(t *testing.T) {
|
||||
gf, _ := ExperimentGate(map[string]bool{groupExperiment: true})
|
||||
if gf.ValidPrefix("torv3blahblahblah") == false {
|
||||
t.Fatalf("torv3 should be a valid prefix")
|
||||
}
|
||||
if gf.ValidPrefix("tofubundle:32432423||3242342") == false {
|
||||
t.Fatalf("tofubundle should be a valid prefix")
|
||||
}
|
||||
if gf.ValidPrefix("server:23541233t") == false {
|
||||
t.Fatalf("server should be a valid prefix")
|
||||
}
|
||||
if gf.ValidPrefix("alice!24234") == true {
|
||||
t.Fatalf("alice should be an invalid predix")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupFunctionality_IsEnabled(t *testing.T) {
|
||||
|
||||
_, err := ExperimentGate(map[string]bool{})
|
||||
|
|
|
@ -5,16 +5,8 @@ type ServerKey struct {
|
|||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type SyncStatus struct {
|
||||
StartTime string `json:"startTime"`
|
||||
LastMessageTime string `json:"lastMessageTime"`
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
Onion string `json:"onion"`
|
||||
Identifier int `json:"identifier"`
|
||||
Status string `json:"status"`
|
||||
Description string `json:"description"`
|
||||
Keys []ServerKey `json:"keys"`
|
||||
SyncProgress SyncStatus `json:"syncProgress"`
|
||||
Onion string `json:"onion"`
|
||||
Status string `json:"status"`
|
||||
Keys []ServerKey `json:"keys"`
|
||||
}
|
||||
|
|
|
@ -3,30 +3,26 @@ package servers
|
|||
import (
|
||||
"cwtch.im/cwtch/event"
|
||||
"fmt"
|
||||
"git.openprivacy.ca/cwtch.im/libcwtch-go/constants"
|
||||
"git.openprivacy.ca/cwtch.im/server"
|
||||
"git.openprivacy.ca/openprivacy/connectivity"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const serversExperiment = "servers-experiment"
|
||||
|
||||
const (
|
||||
ZeroServersLoaded = event.Type("ZeroServersLoaded")
|
||||
NewServer = event.Type("NewServer")
|
||||
ZeroServersLoaded = event.Type("ZeroServersLoaded")
|
||||
NewServer = event.Type("NewServer")
|
||||
ServerIntentUpdate = event.Type("ServerIntentUpdate")
|
||||
ServerDeleted = event.Type("ServerDeleted")
|
||||
ServerStatsUpdate = event.Type("ServerStatsUpdate")
|
||||
ServerDeleted = event.Type("ServerDeleted")
|
||||
)
|
||||
|
||||
const (
|
||||
Intent = event.Field("Intent")
|
||||
TotalMessages = event.Field("TotalMessages")
|
||||
Connections = event.Field("Connections")
|
||||
Intent = event.Field("Intent")
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -34,26 +30,19 @@ const (
|
|||
IntentStopped = "stopped"
|
||||
)
|
||||
|
||||
// TODO: move into Cwtch model/attr
|
||||
|
||||
type ServerInfo struct {
|
||||
Onion string
|
||||
Onion string
|
||||
ServerBundle string
|
||||
Autostart bool
|
||||
Running bool
|
||||
Description string
|
||||
StorageType string
|
||||
Autostart bool
|
||||
Running bool
|
||||
Description string
|
||||
StorageType string
|
||||
}
|
||||
|
||||
type PublishFn func(event.Event)
|
||||
|
||||
var lock sync.Mutex
|
||||
var appServers server.Servers
|
||||
var publishFn PublishFn
|
||||
var killStatsUpdate chan bool = make(chan bool, 1)
|
||||
var enabled bool = false
|
||||
|
||||
func InitServers(acn connectivity.ACN, appdir string, pfn PublishFn) {
|
||||
func InitServers(acn connectivity.ACN, appdir string) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
if appServers == nil {
|
||||
|
@ -63,26 +52,16 @@ func InitServers(acn connectivity.ACN, appdir string, pfn PublishFn) {
|
|||
log.Errorf("Could not init servers directory: %s", err)
|
||||
}
|
||||
appServers = server.NewServers(acn, serversDir)
|
||||
publishFn = pfn
|
||||
appServers.LoadServers(constants.DefactoPasswordForUnencryptedProfiles)
|
||||
}
|
||||
}
|
||||
|
||||
func Disable() {
|
||||
func DeactivateServers() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
if appServers != nil {
|
||||
appServers.Stop()
|
||||
}
|
||||
if enabled {
|
||||
enabled = false
|
||||
killStatsUpdate <- true
|
||||
}
|
||||
}
|
||||
|
||||
func Enabled() bool {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
return enabled
|
||||
}
|
||||
|
||||
// ServersFunctionality provides experiment gated server functionality
|
||||
|
@ -93,29 +72,14 @@ type ServersFunctionality struct {
|
|||
func ExperimentGate(experimentMap map[string]bool) (*ServersFunctionality, error) {
|
||||
if experimentMap[serversExperiment] {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
defer lock.Unlock()
|
||||
return &ServersFunctionality{}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("gated by %v", serversExperiment)
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) Enable() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
if appServers != nil && !enabled {
|
||||
enabled = true
|
||||
go cacheForwardServerMetricUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) LoadServers(password string) ([]string, error) {
|
||||
servers, err := appServers.LoadServers(password)
|
||||
// server:1.3/libcwtch-go:1.4 accidentally enabled monitor logging by default. make sure it's turned off
|
||||
for _, onion := range servers {
|
||||
server := appServers.GetServer(onion)
|
||||
server.SetMonitorLogging(false)
|
||||
}
|
||||
return servers, err
|
||||
return appServers.LoadServers(password)
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) CreateServer(password string) (server.Server, error) {
|
||||
|
@ -126,14 +90,6 @@ func (sf *ServersFunctionality) GetServer(onion string) server.Server {
|
|||
return appServers.GetServer(onion)
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) GetServerStatistics(onion string) server.Statistics {
|
||||
s := appServers.GetServer(onion)
|
||||
if s != nil {
|
||||
return s.GetStatistics()
|
||||
}
|
||||
return server.Statistics{}
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) ListServers() []string {
|
||||
return appServers.ListServers()
|
||||
}
|
||||
|
@ -144,11 +100,6 @@ func (sf *ServersFunctionality) DeleteServer(onion string, currentPassword strin
|
|||
|
||||
func (sf *ServersFunctionality) LaunchServer(onion string) {
|
||||
appServers.LaunchServer(onion)
|
||||
server := appServers.GetServer(onion)
|
||||
if server != nil {
|
||||
newStats := server.GetStatistics()
|
||||
publishFn(event.NewEventList(ServerStatsUpdate, event.Identity, onion, TotalMessages, strconv.Itoa(newStats.TotalMessages), Connections, strconv.Itoa(newStats.TotalConnections)))
|
||||
}
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) StopServer(onion string) {
|
||||
|
@ -188,33 +139,3 @@ func (si *ServerInfo) EnrichEvent(e *event.Event) {
|
|||
e.Data["Running"] = "false"
|
||||
}
|
||||
}
|
||||
|
||||
// cacheForwardServerMetricUpdates every minute gets metrics for all servers, and if they have changed, sends events to the UI
|
||||
func cacheForwardServerMetricUpdates() {
|
||||
var cache map[string]server.Statistics = make(map[string]server.Statistics)
|
||||
duration := time.Second // allow first load
|
||||
for {
|
||||
select {
|
||||
case <-time.After(duration):
|
||||
duration = time.Minute
|
||||
serverList := appServers.ListServers()
|
||||
for _, serverOnion := range serverList {
|
||||
server := appServers.GetServer(serverOnion)
|
||||
if running, err := server.CheckStatus(); running && err == nil {
|
||||
newStats := server.GetStatistics()
|
||||
if stats, ok := cache[serverOnion]; !ok || stats.TotalConnections != newStats.TotalConnections || stats.TotalMessages != newStats.TotalMessages {
|
||||
cache[serverOnion] = newStats
|
||||
publishFn(event.NewEventList(ServerStatsUpdate, event.Identity, serverOnion, TotalMessages, strconv.Itoa(newStats.TotalMessages), Connections, strconv.Itoa(newStats.TotalConnections)))
|
||||
}
|
||||
}
|
||||
}
|
||||
case <-killStatsUpdate:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Shutdown() {
|
||||
Disable()
|
||||
appServers.Destroy()
|
||||
}
|
||||
|
|
32
go.mod
32
go.mod
|
@ -1,31 +1,13 @@
|
|||
module git.openprivacy.ca/cwtch.im/libcwtch-go
|
||||
|
||||
go 1.17
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
cwtch.im/cwtch v0.18.10
|
||||
git.openprivacy.ca/cwtch.im/server v1.4.5
|
||||
git.openprivacy.ca/openprivacy/connectivity v1.8.6
|
||||
cwtch.im/cwtch v0.13.3
|
||||
git.openprivacy.ca/cwtch.im/server v1.3.3
|
||||
git.openprivacy.ca/openprivacy/connectivity v1.5.0
|
||||
git.openprivacy.ca/openprivacy/log v1.0.3
|
||||
github.com/mutecomm/go-sqlcipher/v4 v4.4.2
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.0.0 // indirect
|
||||
git.openprivacy.ca/cwtch.im/tapir v0.6.0 // indirect
|
||||
git.openprivacy.ca/openprivacy/bine v0.0.4 // indirect
|
||||
github.com/gtank/merlin v0.1.1 // indirect
|
||||
github.com/gtank/ristretto255 v0.1.3-0.20210930101514-6bb39798585c // indirect
|
||||
github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b // indirect
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d // indirect
|
||||
// go mobile should stay pinned to golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08
|
||||
// until we intentionally upgrade it, requiring upgrading our docker container
|
||||
// android_go_mobile as well (matching version there), and possibly after upgrading past go 1.17
|
||||
golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect
|
||||
golang.org/x/tools v0.1.10 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
golang.org/x/mobile v0.0.0-20210716004757-34ab1303b554 // indirect
|
||||
golang.org/x/mod v0.5.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect
|
||||
)
|
206
go.sum
206
go.sum
|
@ -1,202 +1,108 @@
|
|||
cwtch.im/cwtch v0.18.0/go.mod h1:StheazFFY7PKqBbEyDVLhzWW6WOat41zV0ckC240c5Y=
|
||||
cwtch.im/cwtch v0.18.3 h1:3zBvC4buII6pWQ+OOVUR6WuAwQDKCxSrj0ZOYKEeB6I=
|
||||
cwtch.im/cwtch v0.18.3/go.mod h1:StheazFFY7PKqBbEyDVLhzWW6WOat41zV0ckC240c5Y=
|
||||
cwtch.im/cwtch v0.18.4 h1:Oht7rEDVJjVWDOKg0xqDgXvY/H059HMJlOPt/nBGqxk=
|
||||
cwtch.im/cwtch v0.18.4/go.mod h1:h8S7EgEM+8pE1k+XLB5jAFdIPlOzwoXEY0GH5mQye5A=
|
||||
cwtch.im/cwtch v0.18.5 h1:yqDns4flbowsbaWjMiUm7Em4IAlM8kkgm79CCcXV1GE=
|
||||
cwtch.im/cwtch v0.18.5/go.mod h1:h8S7EgEM+8pE1k+XLB5jAFdIPlOzwoXEY0GH5mQye5A=
|
||||
cwtch.im/cwtch v0.18.6 h1:CRwoZ/H7y1rAp6jrYh6YCIILU+Sw59hJUvHaWqPgBjg=
|
||||
cwtch.im/cwtch v0.18.6/go.mod h1:h8S7EgEM+8pE1k+XLB5jAFdIPlOzwoXEY0GH5mQye5A=
|
||||
cwtch.im/cwtch v0.18.7 h1:ysE1kjy4oTF+VaQrkNdwdEs6rklWGOe9Dp8rlu9VDKI=
|
||||
cwtch.im/cwtch v0.18.7/go.mod h1:h8S7EgEM+8pE1k+XLB5jAFdIPlOzwoXEY0GH5mQye5A=
|
||||
cwtch.im/cwtch v0.18.8 h1:D5mmsBkmHhE7jhRodZG2DtdaxmfvdvLG0W7CAPBf7eo=
|
||||
cwtch.im/cwtch v0.18.8/go.mod h1:h8S7EgEM+8pE1k+XLB5jAFdIPlOzwoXEY0GH5mQye5A=
|
||||
cwtch.im/cwtch v0.18.10 h1:iTzLzlms1mgn8kLfClU/yAWIVWVRRT8UmfbDNli9dzE=
|
||||
cwtch.im/cwtch v0.18.10/go.mod h1:h8S7EgEM+8pE1k+XLB5jAFdIPlOzwoXEY0GH5mQye5A=
|
||||
cwtch.im/cwtch v0.13.3 h1:YMqb18msXmBW7gHAn2X/QgljoDag/HgWe6qdz9OgwlY=
|
||||
cwtch.im/cwtch v0.13.3/go.mod h1:QpTkQK7MqNt0dQK9/pBk5VpkvFhy6xuoxJIn401B8fM=
|
||||
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
|
||||
filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
|
||||
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||
git.openprivacy.ca/cwtch.im/server v1.4.5 h1:QuNAIxld+aWeQfWuGHB2QYZXsqJMmTyl55Pcmdn8FQA=
|
||||
git.openprivacy.ca/cwtch.im/server v1.4.5/go.mod h1:dGB1bePUgDU9xwk7gGkioNeshrbNgGWhSH8zMQwIAUg=
|
||||
git.openprivacy.ca/cwtch.im/tapir v0.5.5 h1:km6UDrLYH/GCEn2s+S299/TiRHhxKCIAipYr9GbG3Hk=
|
||||
git.openprivacy.ca/cwtch.im/tapir v0.5.5/go.mod h1:bWWHrDYBtHvxMri59RwIB/w7Eg1aC0BrQ/ycKlnbB5k=
|
||||
git.openprivacy.ca/cwtch.im/tapir v0.6.0 h1:TtnKjxitkIDMM7Qn0n/u+mOHRLJzuQUYjYRu5n0/QFY=
|
||||
git.openprivacy.ca/cwtch.im/tapir v0.6.0/go.mod h1:iQIq4y7N+DuP3CxyG66WNEC/d6vzh+wXvvOmelB+KoY=
|
||||
git.openprivacy.ca/cwtch.im/server v1.3.1 h1:Kt4TnlGxGPk1KTjvs1cXtnFWDx+hYqu8w+2eIaqt+F4=
|
||||
git.openprivacy.ca/cwtch.im/server v1.3.1/go.mod h1:gps6glXDt/ra66Do491csrm0TwatAc2lMLOAKCkh5Vw=
|
||||
git.openprivacy.ca/cwtch.im/server v1.3.2 h1:dUNMM88IWER6cmgCfhekahkef7laH597hqTp9CE2tYg=
|
||||
git.openprivacy.ca/cwtch.im/server v1.3.2/go.mod h1:gps6glXDt/ra66Do491csrm0TwatAc2lMLOAKCkh5Vw=
|
||||
git.openprivacy.ca/cwtch.im/server v1.3.3 h1:LhHXlFAKzl2zM6Jd2Z3w3qMZM3UnRUA0qXWrlRBvKv8=
|
||||
git.openprivacy.ca/cwtch.im/server v1.3.3/go.mod h1:gps6glXDt/ra66Do491csrm0TwatAc2lMLOAKCkh5Vw=
|
||||
git.openprivacy.ca/cwtch.im/tapir v0.4.9 h1:LXonlztwvI1F1++0IyomIcDH1/Bxzo+oN8YjGonNvjM=
|
||||
git.openprivacy.ca/cwtch.im/tapir v0.4.9/go.mod h1:p4bHo3DAO8wwimU6JAeZXbfPQ4jnoA2bV+4YvknWTNQ=
|
||||
git.openprivacy.ca/openprivacy/bine v0.0.4 h1:CO7EkGyz+jegZ4ap8g5NWRuDHA/56KKvGySR6OBPW+c=
|
||||
git.openprivacy.ca/openprivacy/bine v0.0.4/go.mod h1:13ZqhKyqakDsN/ZkQkIGNULsmLyqtXc46XBcnuXm/mU=
|
||||
git.openprivacy.ca/openprivacy/connectivity v1.8.6 h1:g74PyDGvpMZ3+K0dXy3mlTJh+e0rcwNk0XF8owzkmOA=
|
||||
git.openprivacy.ca/openprivacy/connectivity v1.8.6/go.mod h1:Hn1gpOx/bRZp5wvCtPQVJPXrfeUH0EGiG/Aoa0vjGLg=
|
||||
git.openprivacy.ca/openprivacy/connectivity v1.5.0 h1:ZxsR/ZaVKXIkD2x6FlajZn62ciNQjamrI4i/5xIpdoQ=
|
||||
git.openprivacy.ca/openprivacy/connectivity v1.5.0/go.mod h1:UjQiGBnWbotmBzIw59B8H6efwDadjkKzm3RPT1UaIRw=
|
||||
git.openprivacy.ca/openprivacy/log v1.0.2/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw=
|
||||
git.openprivacy.ca/openprivacy/log v1.0.3 h1:E/PMm4LY+Q9s3aDpfySfEDq/vYQontlvNj/scrPaga0=
|
||||
git.openprivacy.ca/openprivacy/log v1.0.3/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is=
|
||||
github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s=
|
||||
github.com/gtank/ristretto255 v0.1.3-0.20210930101514-6bb39798585c h1:gkfmnY4Rlt3VINCo4uKdpvngiibQyoENVj5Q88sxXhE=
|
||||
github.com/gtank/ristretto255 v0.1.3-0.20210930101514-6bb39798585c/go.mod h1:tDPFhGdt3hJWqtKwx57i9baiB1Cj0yAg22VOPUqm5vY=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc=
|
||||
github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA=
|
||||
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 h1:hLDRPB66XQT/8+wG9WsDpiCvZf1yKO7sz7scAjSlBa0=
|
||||
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM=
|
||||
github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b h1:QrHweqAtyJ9EwCaGHBu1fghwxIPiopAHV06JlXrMHjk=
|
||||
github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b/go.mod h1:xxLb2ip6sSUts3g1irPVHyk/DGslwQsNOo9I7smJfNU=
|
||||
github.com/mutecomm/go-sqlcipher/v4 v4.4.2 h1:eM10bFtI4UvibIsKr10/QT7Yfz+NADfjZYh0GKrXUNc=
|
||||
github.com/mutecomm/go-sqlcipher/v4 v4.4.2/go.mod h1:mF2UmIpBnzFeBdu/ypTDb/LdbS0nk0dfSN1WUsWTjMA=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY=
|
||||
github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
|
||||
github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||
github.com/struCoder/pidusage v0.2.1 h1:dFiEgUDkubeIj0XA1NpQ6+8LQmKrLi7NiIQl86E6BoY=
|
||||
github.com/struCoder/pidusage v0.2.1/go.mod h1:bewtP2KUA1TBUyza5+/PCpSQ6sc/H6jJbIKAzqW86BA=
|
||||
github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
|
||||
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee h1:4yd7jl+vXjalO5ztz6Vc1VADv+S/80LGJmyl1ROJ2AI=
|
||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU=
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08 h1:h+GZ3ubjuWaQjGe8owMGcmMVCqs0xYJtRG5y2bpHaqU=
|
||||
golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
|
||||
golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105 h1:3vUV5x5+3LfQbgk7paCM6INOaJG9xXQbn79xoNkwfIk=
|
||||
golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
|
||||
golang.org/x/mobile v0.0.0-20210716004757-34ab1303b554 h1:3In5TnfvnuXTF/uflgpYxSCEGP2NdYT37KsPh3VjZYU=
|
||||
golang.org/x/mobile v0.0.0-20210716004757-34ab1303b554/go.mod h1:jFTmtFYCV0MFtXBU+J5V/+5AUeVS0ON/0WkE/KSrl6E=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/mod v0.5.0 h1:UG21uOlmZabA4fW5i7ZX6bjw1xELEGg/ZLgZq9auk/Q=
|
||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k=
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -9,7 +9,7 @@ go list ./... | xargs go vet
|
|||
echo ""
|
||||
echo "Linting:"
|
||||
|
||||
staticcheck ./...
|
||||
go list ./... | xargs golint
|
||||
|
||||
|
||||
echo "Time to format"
|
||||
|
@ -21,4 +21,4 @@ ineffassign .
|
|||
|
||||
# misspell (https://github.com/client9/misspell/cmd/misspell)
|
||||
echo "Checking for misspelled words..."
|
||||
misspell . | grep -v "testing/" | grep -v "vendor/" | grep -v "go.sum" | grep -v ".idea"
|
||||
misspell . | grep -v "vendor/" | grep -v "go.sum" | grep -v ".idea"
|
||||
|
|
|
@ -1,25 +1,16 @@
|
|||
package utils
|
||||
|
||||
import "cwtch.im/cwtch/model"
|
||||
|
||||
type Contact struct {
|
||||
Name string `json:"name"`
|
||||
Onion string `json:"onion"`
|
||||
Status string `json:"status"`
|
||||
Picture string `json:"picture"`
|
||||
DefaultPicture string `json:"defaultPicture"`
|
||||
Accepted bool `json:"accepted"`
|
||||
AccessControlList model.AccessControlList `json:"accessControlList"`
|
||||
Blocked bool `json:"blocked"`
|
||||
SaveHistory string `json:"saveConversationHistory"`
|
||||
Messages int `json:"numMessages"`
|
||||
Unread int `json:"numUnread"`
|
||||
LastSeenMessageId int `json:"lastSeenMessageId"`
|
||||
LastMessage string `json:"lastMsgTime"`
|
||||
IsGroup bool `json:"isGroup"`
|
||||
GroupServer string `json:"groupServer"`
|
||||
IsArchived bool `json:"isArchived"`
|
||||
Identifier int `json:"identifier"`
|
||||
NotificationPolicy string `json:"notificationPolicy"`
|
||||
Attributes map[string]string `json:"attributes"`
|
||||
Name string `json:"name"`
|
||||
Onion string `json:"onion"`
|
||||
Status string `json:"status"`
|
||||
Picture string `json:"picture"`
|
||||
Authorization string `json:"authorization"`
|
||||
SaveHistory string `json:"saveConversationHistory"`
|
||||
Messages int `json:"numMessages"`
|
||||
Unread int `json:"numUnread"`
|
||||
LastMessage string `json:"lastMsgTime"`
|
||||
IsGroup bool `json:"isGroup"`
|
||||
GroupServer string `json:"groupServer"`
|
||||
IsArchived bool `json:"isArchived"`
|
||||
}
|
||||
|
|
|
@ -1,54 +1,43 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.openprivacy.ca/cwtch.im/libcwtch-go/features/servers"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"cwtch.im/cwtch/app"
|
||||
"cwtch.im/cwtch/app/plugins"
|
||||
"cwtch.im/cwtch/model"
|
||||
"cwtch.im/cwtch/model/attr"
|
||||
"cwtch.im/cwtch/model/constants"
|
||||
"cwtch.im/cwtch/peer"
|
||||
"cwtch.im/cwtch/protocol/connections"
|
||||
"encoding/json"
|
||||
constants2 "git.openprivacy.ca/cwtch.im/libcwtch-go/constants"
|
||||
"git.openprivacy.ca/cwtch.im/libcwtch-go/features/groups"
|
||||
"git.openprivacy.ca/cwtch.im/libcwtch-go/features/servers"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
|
||||
"time"
|
||||
|
||||
"cwtch.im/cwtch/event"
|
||||
"cwtch.im/cwtch/functionality/filesharing"
|
||||
"strconv"
|
||||
)
|
||||
import "cwtch.im/cwtch/event"
|
||||
|
||||
type EventProfileEnvelope struct {
|
||||
Event event.Event
|
||||
Profile string
|
||||
}
|
||||
|
||||
type LCG_API_Handler struct {
|
||||
LaunchServers func()
|
||||
StopServers func()
|
||||
}
|
||||
|
||||
type EventHandler struct {
|
||||
app app.Application
|
||||
appBusQueue event.Queue
|
||||
profileEvents chan EventProfileEnvelope
|
||||
api LCG_API_Handler
|
||||
}
|
||||
|
||||
// We should be reading from profile events pretty quickly, but we make this buffer fairly large...
|
||||
const profileEventsBufferSize = 512
|
||||
|
||||
func NewEventHandler(api LCG_API_Handler) *EventHandler {
|
||||
eh := &EventHandler{app: nil, appBusQueue: event.NewQueue(), profileEvents: make(chan EventProfileEnvelope, profileEventsBufferSize), api: api}
|
||||
func NewEventHandler() *EventHandler {
|
||||
eh := &EventHandler{app: nil, appBusQueue: event.NewQueue(), profileEvents: make(chan EventProfileEnvelope)}
|
||||
return eh
|
||||
}
|
||||
|
||||
// PublishAppEvent is a way for libCwtch-go to publish an event for consumption by a UI before a Cwtch app has been initialized
|
||||
// Main use: to signal an error before a cwtch app could be created
|
||||
func (eh *EventHandler) PublishAppEvent(event event.Event) {
|
||||
eh.appBusQueue.Publish(event)
|
||||
}
|
||||
|
||||
func (eh *EventHandler) HandleApp(application app.Application) {
|
||||
eh.app = application
|
||||
application.GetPrimaryBus().Subscribe(event.NewPeer, eh.appBusQueue)
|
||||
|
@ -57,15 +46,13 @@ func (eh *EventHandler) HandleApp(application app.Application) {
|
|||
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.ReloadDone, eh.appBusQueue)
|
||||
application.GetPrimaryBus().Subscribe(event.ACNVersion, eh.appBusQueue)
|
||||
application.GetPrimaryBus().Subscribe(UpdateGlobalSettings, eh.appBusQueue)
|
||||
application.GetPrimaryBus().Subscribe(CwtchStarted, eh.appBusQueue)
|
||||
application.GetPrimaryBus().Subscribe(servers.NewServer, eh.appBusQueue)
|
||||
application.GetPrimaryBus().Subscribe(servers.ServerIntentUpdate, eh.appBusQueue)
|
||||
application.GetPrimaryBus().Subscribe(servers.ServerDeleted, eh.appBusQueue)
|
||||
application.GetPrimaryBus().Subscribe(servers.ServerStatsUpdate, eh.appBusQueue)
|
||||
application.GetPrimaryBus().Subscribe(event.StartingStorageMiragtion, eh.appBusQueue)
|
||||
application.GetPrimaryBus().Subscribe(event.DoneStorageMigration, eh.appBusQueue)
|
||||
}
|
||||
|
||||
func (eh *EventHandler) GetNextEvent() string {
|
||||
|
@ -74,63 +61,27 @@ func (eh *EventHandler) GetNextEvent() string {
|
|||
select {
|
||||
case e := <-appChan:
|
||||
return eh.handleAppBusEvent(&e)
|
||||
default:
|
||||
select {
|
||||
case e := <-appChan:
|
||||
return eh.handleAppBusEvent(&e)
|
||||
case ev := <-eh.profileEvents:
|
||||
return eh.handleProfileEvent(&ev)
|
||||
}
|
||||
case ev := <-eh.profileEvents:
|
||||
return eh.handleProfileEvent(&ev)
|
||||
}
|
||||
}
|
||||
|
||||
// track acnStatus across events
|
||||
var acnStatus = -1
|
||||
|
||||
// handleAppBusEvent enriches AppBus events so they are usable with out further data fetches
|
||||
func (eh *EventHandler) handleAppBusEvent(e *event.Event) string {
|
||||
|
||||
log.Debugf("New AppBus Event to Handle: %v", e)
|
||||
if eh.app != nil {
|
||||
switch e.EventType {
|
||||
case event.ACNStatus:
|
||||
newAcnStatus, err := strconv.Atoi(e.Data[event.Progress])
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if newAcnStatus == 100 {
|
||||
if acnStatus != 100 {
|
||||
// just came online
|
||||
doServers := false
|
||||
if _, err := groups.ExperimentGate(ReadGlobalSettings().Experiments); err == nil {
|
||||
doServers = true
|
||||
}
|
||||
|
||||
for _, onion := range eh.app.ListProfiles() {
|
||||
profile := eh.app.GetPeer(onion)
|
||||
autostart, exists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants2.PeerAutostart)
|
||||
if !exists || autostart == "true" {
|
||||
eh.app.ActivatePeerEngine(onion, true, true, doServers)
|
||||
}
|
||||
}
|
||||
eh.api.LaunchServers()
|
||||
}
|
||||
} else {
|
||||
if acnStatus == 100 {
|
||||
// just fell offline
|
||||
for _, onion := range eh.app.ListProfiles() {
|
||||
eh.app.DeactivatePeerEngine(onion)
|
||||
}
|
||||
eh.api.StopServers()
|
||||
if e.Data[event.Progress] == "100" {
|
||||
for _, onion := range eh.app.ListProfiles() {
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
acnStatus = newAcnStatus
|
||||
case event.NewPeer:
|
||||
onion := e.Data[event.Identity]
|
||||
profile := eh.app.GetPeer(e.Data[event.Identity])
|
||||
if profile == nil {
|
||||
log.Errorf("NewPeer: skipping profile initialization. this should only happen when the app is rapidly opened+closed (eg during testing)")
|
||||
break
|
||||
}
|
||||
log.Debug("New Peer Event: %v", e)
|
||||
|
||||
if e.Data["Reload"] != event.True {
|
||||
|
@ -144,182 +95,128 @@ func (eh *EventHandler) handleAppBusEvent(e *event.Event) string {
|
|||
if e.Data[event.Created] == event.True {
|
||||
profile.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants2.Picture, ImageToString(NewImage(RandomProfileImage(onion), TypeImageDistro)))
|
||||
}
|
||||
if e.Data[event.Status] != event.StorageRunning || e.Data[event.Created] == event.True {
|
||||
profile.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants2.PeerOnline, event.False)
|
||||
eh.app.AddPeerPlugin(onion, plugins.CONNECTIONRETRY)
|
||||
eh.app.AddPeerPlugin(onion, plugins.NETWORKCHECK)
|
||||
|
||||
profile.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants2.PeerOnline, event.False)
|
||||
// disabeling network check for connection attempt reservation, needs rework
|
||||
//eh.app.AddPeerPlugin(onion, plugins.NETWORKCHECK)
|
||||
eh.app.AddPeerPlugin(onion, plugins.ANTISPAM)
|
||||
// If the user has chosen to block unknown profiles
|
||||
// then explicitly configure the protocol engine to do so..
|
||||
if ReadGlobalSettings().BlockUnknownConnections {
|
||||
profile.BlockUnknownConnections()
|
||||
} else {
|
||||
// For completeness
|
||||
profile.AllowUnknownConnections()
|
||||
}
|
||||
|
||||
// If the user has chosen to block unknown profiles
|
||||
// then explicitly configure the protocol engine to do so..
|
||||
settings := ReadGlobalSettings()
|
||||
if settings.BlockUnknownConnections {
|
||||
profile.BlockUnknownConnections()
|
||||
} else {
|
||||
// For completeness
|
||||
profile.AllowUnknownConnections()
|
||||
}
|
||||
|
||||
// Start up the Profile
|
||||
if acnStatus == 100 {
|
||||
autostart, exists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants2.PeerAutostart)
|
||||
if !exists || autostart == "true" {
|
||||
doServers := false
|
||||
if _, err := groups.ExperimentGate(ReadGlobalSettings().Experiments); err == nil {
|
||||
doServers = true
|
||||
}
|
||||
eh.app.ActivatePeerEngine(onion, true, true, doServers)
|
||||
// Start up the Profile
|
||||
profile.Listen()
|
||||
profile.StartPeersConnections()
|
||||
if _, err := groups.ExperimentGate(ReadGlobalSettings().Experiments); err == nil {
|
||||
profile.StartServerConnections()
|
||||
}
|
||||
}
|
||||
|
||||
online, _ := profile.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants2.PeerOnline)
|
||||
e.Data["Online"] = online
|
||||
|
||||
autostart, exists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants2.PeerAutostart)
|
||||
// legacy profiles should autostart by default
|
||||
if !exists {
|
||||
autostart = "true"
|
||||
picVal, ok := profile.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants2.Picture)
|
||||
if !ok {
|
||||
picVal = ImageToString(NewImage(RandomProfileImage(onion), TypeImageDistro))
|
||||
}
|
||||
e.Data["autostart"] = autostart
|
||||
pic, err := StringToImage(picVal)
|
||||
if err != nil {
|
||||
pic = NewImage(RandomProfileImage(onion), TypeImageDistro)
|
||||
}
|
||||
picPath := GetPicturePath(pic)
|
||||
|
||||
// Set publicly scopes attributes
|
||||
profile.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants2.Picture, picPath)
|
||||
|
||||
online, _ := profile.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants2.PeerOnline)
|
||||
|
||||
// Name always exists
|
||||
e.Data[constants.Name], _ = profile.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name)
|
||||
e.Data[constants2.DefaultProfilePicture] = RandomProfileImage(onion)
|
||||
// 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))
|
||||
}
|
||||
// Construct our conversations and our srever lists
|
||||
e.Data[constants2.Picture] = picPath
|
||||
e.Data["Online"] = online
|
||||
|
||||
var contacts []Contact
|
||||
var servers []groups.Server
|
||||
for _, contact := range profile.GetContacts() {
|
||||
|
||||
conversations, err := profile.FetchConversations()
|
||||
|
||||
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
|
||||
// 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 profile.GetContact(contact).IsServer() {
|
||||
groupHandler, err := groups.ExperimentGate(ReadGlobalSettings().Experiments)
|
||||
if err == nil {
|
||||
servers = append(servers, groupHandler.GetServerInfo(contact, profile))
|
||||
}
|
||||
|
||||
// Prefer local override to public name...
|
||||
name, exists := conversationInfo.GetAttribute(attr.LocalScope, attr.ProfileZone, constants.Name)
|
||||
if !exists {
|
||||
name, exists = conversationInfo.GetAttribute(attr.PublicScope, attr.ProfileZone, constants.Name)
|
||||
if !exists {
|
||||
name = conversationInfo.Handle
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve the profile image of the contact
|
||||
var cpicPath string
|
||||
var defaultPath string
|
||||
if conversationInfo.IsGroup() {
|
||||
cpicPath = RandomGroupImage(conversationInfo.Handle)
|
||||
defaultPath = RandomGroupImage(conversationInfo.Handle)
|
||||
} else {
|
||||
cpicPath = GetProfileImage(profile, conversationInfo, settings.DownloadPath)
|
||||
defaultPath = RandomProfileImage(conversationInfo.Handle)
|
||||
}
|
||||
|
||||
// Resolve Save History Setting
|
||||
saveHistory, set := conversationInfo.GetAttribute(attr.LocalScope, attr.ProfileZone, event.SaveHistoryKey)
|
||||
if !set {
|
||||
saveHistory = event.DeleteHistoryDefault
|
||||
}
|
||||
|
||||
// Resolve Archived Setting
|
||||
isArchived, set := conversationInfo.GetAttribute(attr.LocalScope, attr.ProfileZone, constants2.Archived)
|
||||
if !set {
|
||||
isArchived = event.False
|
||||
}
|
||||
|
||||
unread := 0
|
||||
lastSeenMessageId := -1
|
||||
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 {
|
||||
lastSeenMessageId = message.ID
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
groupServer, _ := conversationInfo.GetAttribute(attr.LocalScope, attr.LegacyGroupZone, constants.GroupServer)
|
||||
|
||||
stateHandle := conversationInfo.Handle
|
||||
if conversationInfo.IsGroup() {
|
||||
stateHandle = groupServer
|
||||
}
|
||||
state := profile.GetPeerState(stateHandle)
|
||||
if !set {
|
||||
state = connections.DISCONNECTED
|
||||
}
|
||||
|
||||
blocked := false
|
||||
if conversationInfo.ACL[conversationInfo.Handle].Blocked {
|
||||
blocked = true
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
notificationPolicy := constants2.ConversationNotificationPolicyDefault
|
||||
if notificationPolicyAttr, exists := conversationInfo.GetAttribute(attr.LocalScope, attr.ProfileZone, constants2.ConversationNotificationPolicy); exists {
|
||||
notificationPolicy = notificationPolicyAttr
|
||||
}
|
||||
|
||||
contacts = append(contacts, Contact{
|
||||
Name: name,
|
||||
Identifier: conversationInfo.ID,
|
||||
Onion: conversationInfo.Handle,
|
||||
Status: connections.ConnectionStateName[state],
|
||||
Picture: cpicPath,
|
||||
DefaultPicture: defaultPath,
|
||||
Accepted: conversationInfo.Accepted,
|
||||
AccessControlList: conversationInfo.ACL,
|
||||
Blocked: blocked,
|
||||
SaveHistory: saveHistory,
|
||||
Messages: count,
|
||||
Unread: unread,
|
||||
LastSeenMessageId: lastSeenMessageId,
|
||||
LastMessage: strconv.Itoa(getLastMessageTime(lastMessage)),
|
||||
IsGroup: conversationInfo.IsGroup(),
|
||||
GroupServer: groupServer,
|
||||
IsArchived: isArchived == event.True,
|
||||
NotificationPolicy: notificationPolicy,
|
||||
Attributes: conversationInfo.Attributes,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
contactInfo := profile.GetContact(contact)
|
||||
ph := NewPeerHelper(profile)
|
||||
name := ph.GetNick(contact)
|
||||
cpicPath := ph.GetProfilePic(contact)
|
||||
saveHistory, set := contactInfo.GetAttribute(event.SaveHistoryKey)
|
||||
if !set {
|
||||
saveHistory = event.DeleteHistoryDefault
|
||||
}
|
||||
isArchived, set := contactInfo.GetAttribute(attr.GetLocalScope(constants2.Archived))
|
||||
if !set {
|
||||
isArchived = event.False
|
||||
}
|
||||
contacts = append(contacts, Contact{
|
||||
Name: name,
|
||||
Onion: contactInfo.Onion,
|
||||
Status: contactInfo.State,
|
||||
Picture: cpicPath,
|
||||
Authorization: string(contactInfo.Authorization),
|
||||
SaveHistory: saveHistory,
|
||||
Messages: contactInfo.Timeline.Len(),
|
||||
Unread: 0,
|
||||
LastMessage: strconv.Itoa(getLastMessageTime(&contactInfo.Timeline)),
|
||||
IsGroup: false,
|
||||
IsArchived: isArchived == event.True,
|
||||
})
|
||||
}
|
||||
|
||||
// We compile and send the groups regardless of the experiment flag, and hide them in the UI
|
||||
for _, groupId := range profile.GetGroups() {
|
||||
group := profile.GetGroup(groupId)
|
||||
|
||||
// Check that the group is cryptographically valid
|
||||
if !group.CheckGroup() {
|
||||
continue
|
||||
}
|
||||
|
||||
ph := NewPeerHelper(profile)
|
||||
cpicPath := ph.GetProfilePic(groupId)
|
||||
|
||||
authorization := model.AuthUnknown
|
||||
if group.Accepted {
|
||||
authorization = model.AuthApproved
|
||||
}
|
||||
isArchived, set := group.GetAttribute(attr.GetLocalScope(constants2.Archived))
|
||||
if !set {
|
||||
isArchived = event.False
|
||||
}
|
||||
// Use the server state when assessing group state
|
||||
state := profile.GetContact(group.GroupServer).State
|
||||
|
||||
contacts = append(contacts, Contact{
|
||||
Name: ph.GetNick(groupId),
|
||||
Onion: group.GroupID,
|
||||
Status: state,
|
||||
Picture: cpicPath,
|
||||
Authorization: string(authorization),
|
||||
SaveHistory: event.SaveHistoryConfirmed,
|
||||
Messages: group.Timeline.Len(),
|
||||
Unread: 0,
|
||||
LastMessage: strconv.Itoa(getLastMessageTime(&group.Timeline)),
|
||||
IsGroup: true,
|
||||
GroupServer: group.GroupServer,
|
||||
IsArchived: isArchived == event.True,
|
||||
})
|
||||
}
|
||||
|
||||
bytes, _ := json.Marshal(contacts)
|
||||
|
@ -337,302 +234,99 @@ func (eh *EventHandler) handleAppBusEvent(e *event.Event) string {
|
|||
return string(json)
|
||||
}
|
||||
|
||||
func GetProfileImage(profile peer.CwtchPeer, conversationInfo *model.Conversation, basepath string) string {
|
||||
fileKey, err := profile.GetConversationAttribute(conversationInfo.ID, attr.PublicScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.CustomProfileImageKey)))
|
||||
if err == nil {
|
||||
if value, exists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.complete", fileKey)); exists && value == event.True {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
return RandomProfileImage(conversationInfo.Handle)
|
||||
}
|
||||
|
||||
// handleProfileEvent enriches Profile events so they are usable with out further data fetches
|
||||
func (eh *EventHandler) handleProfileEvent(ev *EventProfileEnvelope) string {
|
||||
// cache of contact states to use to filter out events repeating known states
|
||||
var contactStateCache = make(map[string]connections.ConnectionState)
|
||||
|
||||
if eh.app == nil {
|
||||
log.Errorf("eh.app == nil in handleProfileEvent... this shouldnt happen?")
|
||||
} else {
|
||||
profile := eh.app.GetPeer(ev.Profile)
|
||||
peer := eh.app.GetPeer(ev.Profile)
|
||||
ph := NewPeerHelper(peer)
|
||||
log.Debugf("New Profile Event to Handle: %v", ev)
|
||||
switch ev.Event.EventType {
|
||||
|
||||
/*
|
||||
TODO: still handle this somewhere - network info from plugin Network check
|
||||
case event.NetworkStatus:
|
||||
online, _ := peer.GetAttribute(attr.GetLocalScope(constants.PeerOnline))
|
||||
if e.Data[event.Status] == plugins.NetworkCheckSuccess && online == event.False {
|
||||
peer.SetAttribute(attr.GetLocalScope(constants.PeerOnline), event.True)
|
||||
uiManager.UpdateNetworkStatus(true)
|
||||
// TODO we may have to reinitialize the peer
|
||||
} else if e.Data[event.Status] == plugins.NetworkCheckError && online == event.True {
|
||||
peer.SetAttribute(attr.GetLocalScope(constants.PeerOnline), event.False)
|
||||
uiManager.UpdateNetworkStatus(false)
|
||||
}*/
|
||||
|
||||
case event.NewMessageFromPeer: //event.TimestampReceived, event.RemotePeer, event.Data
|
||||
// only needs contact nickname and picture, for displaying on popup notifications
|
||||
ci, err := profile.FetchConversationInfo(ev.Event.Data["RemotePeer"])
|
||||
ev.Event.Data[constants2.Picture] = RandomProfileImage(ev.Event.Data["RemotePeer"])
|
||||
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)
|
||||
ev.Event.Data[constants2.Picture] = GetProfileImage(profile, ci, ReadGlobalSettings().DownloadPath)
|
||||
} else {
|
||||
// TODO This Conversation May Not Exist Yet...But we are not in charge of creating it...
|
||||
log.Errorf("todo wait for contact to be added before processing this event...")
|
||||
return ""
|
||||
}
|
||||
var exists bool
|
||||
ev.Event.Data["Nick"], exists = ci.GetAttribute(attr.LocalScope, attr.ProfileZone, constants.Name)
|
||||
if !exists {
|
||||
ev.Event.Data["Nick"], exists = ci.GetAttribute(attr.PublicScope, attr.ProfileZone, constants.Name)
|
||||
if !exists {
|
||||
ev.Event.Data["Nick"] = ev.Event.Data["RemotePeer"]
|
||||
// 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)
|
||||
profile.SendScopedZonedGetValToContact(ci.ID, attr.PublicScope, attr.ProfileZone, constants.CustomProfileImageKey)
|
||||
}
|
||||
}
|
||||
|
||||
if ci.Accepted {
|
||||
handleImagePreviews(profile, &ev.Event, ci.ID, ci.ID)
|
||||
}
|
||||
|
||||
ev.Event.Data["notification"] = string(determineNotification(ci))
|
||||
ev.Event.Data["Nick"] = ph.GetNick(ev.Event.Data["RemotePeer"])
|
||||
ev.Event.Data["Picture"] = ph.GetProfilePic(ev.Event.Data["RemotePeer"])
|
||||
peer.SetContactAttribute(ev.Event.Data["RemotePeer"], attr.GetLocalScope(constants2.Archived), event.False)
|
||||
case event.NewMessageFromGroup:
|
||||
// only needs contact nickname and picture, for displaying on popup notifications
|
||||
ci, err := profile.FetchConversationInfo(ev.Event.Data["RemotePeer"])
|
||||
ev.Event.Data[constants2.Picture] = RandomProfileImage(ev.Event.Data["RemotePeer"])
|
||||
if ci != nil && err == nil {
|
||||
var exists bool
|
||||
ev.Event.Data["Nick"], exists = ci.GetAttribute(attr.LocalScope, attr.ProfileZone, constants.Name)
|
||||
if !exists {
|
||||
ev.Event.Data["Nick"], exists = ci.GetAttribute(attr.PublicScope, attr.ProfileZone, constants.Name)
|
||||
if !exists {
|
||||
ev.Event.Data["Nick"] = ev.Event.Data["RemotePeer"]
|
||||
}
|
||||
}
|
||||
ev.Event.Data[constants2.Picture] = GetProfileImage(profile, ci, ReadGlobalSettings().DownloadPath)
|
||||
}
|
||||
|
||||
conversationID, _ := strconv.Atoi(ev.Event.Data[event.ConversationID])
|
||||
profile.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants2.Archived)), event.False)
|
||||
|
||||
if ci != nil && ci.Accepted {
|
||||
handleImagePreviews(profile, &ev.Event, conversationID, ci.ID)
|
||||
}
|
||||
|
||||
gci, _ := profile.GetConversationInfo(conversationID)
|
||||
groupServer := gci.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupServer)).ToString()]
|
||||
state := profile.GetPeerState(groupServer)
|
||||
// if syncing, don't flood with notifications
|
||||
if state == connections.SYNCED {
|
||||
ev.Event.Data["notification"] = string(determineNotification(gci))
|
||||
} else {
|
||||
ev.Event.Data["notification"] = string(constants2.NotificationNone)
|
||||
}
|
||||
ev.Event.Data["Nick"] = ph.GetNick(ev.Event.Data[event.GroupID])
|
||||
ev.Event.Data["Picture"] = ph.GetProfilePic(ev.Event.Data[event.GroupID])
|
||||
peer.SetGroupAttribute(ev.Event.Data[event.GroupID], attr.GetLocalScope(constants2.Archived), event.False)
|
||||
case event.PeerAcknowledgement:
|
||||
ci, err := profile.FetchConversationInfo(ev.Event.Data["RemotePeer"])
|
||||
if ci != nil && err == nil {
|
||||
ev.Event.Data[event.ConversationID] = strconv.Itoa(ci.ID)
|
||||
}
|
||||
case event.ContactCreated:
|
||||
conversationID, _ := strconv.Atoi(ev.Event.Data[event.ConversationID])
|
||||
count, err := profile.GetChannelMessageCount(conversationID, 0)
|
||||
// No enrichement required
|
||||
case event.PeerCreated:
|
||||
handle := ev.Event.Data[event.RemotePeer]
|
||||
err := EnrichNewPeer(handle, ph, ev)
|
||||
if err != nil {
|
||||
log.Errorf("error fetching channel message count %v %v", conversationID, err)
|
||||
return ""
|
||||
}
|
||||
|
||||
conversationInfo, err := profile.GetConversationInfo(conversationID)
|
||||
if err != nil {
|
||||
log.Errorf("error fetching conversation info for %v %v", conversationID, err)
|
||||
}
|
||||
|
||||
blocked := constants.False
|
||||
if conversationInfo.ACL[conversationInfo.Handle].Blocked {
|
||||
blocked = constants.True
|
||||
}
|
||||
|
||||
accepted := constants.False
|
||||
if conversationInfo.Accepted {
|
||||
accepted = constants.True
|
||||
}
|
||||
|
||||
acl, _ := json.Marshal(conversationInfo.ACL)
|
||||
|
||||
lastMessage, _ := profile.GetMostRecentMessages(conversationID, 0, 0, 1)
|
||||
ev.Event.Data["unread"] = strconv.Itoa(count) // if this is a new contact with messages attached then by-definition these are unread...
|
||||
ev.Event.Data[constants2.Picture] = RandomProfileImage(conversationInfo.Handle)
|
||||
ev.Event.Data[constants2.DefaultProfilePicture] = RandomProfileImage(conversationInfo.Handle)
|
||||
ev.Event.Data["numMessages"] = strconv.Itoa(count)
|
||||
ev.Event.Data["nick"] = conversationInfo.Handle
|
||||
ev.Event.Data["status"] = connections.ConnectionStateName[profile.GetPeerState(conversationInfo.Handle)]
|
||||
ev.Event.Data["accepted"] = accepted
|
||||
ev.Event.Data["accessControlList"] = string(acl)
|
||||
ev.Event.Data["blocked"] = blocked
|
||||
ev.Event.Data["loading"] = "false"
|
||||
ev.Event.Data["lastMsgTime"] = strconv.Itoa(getLastMessageTime(lastMessage))
|
||||
case event.GroupCreated:
|
||||
// This event should only happen after we have validated the invite, as such the error
|
||||
// condition *should* never happen.
|
||||
groupPic := RandomGroupImage(ev.Event.Data[event.GroupID])
|
||||
ev.Event.Data[constants2.Picture] = groupPic
|
||||
|
||||
conversationID, _ := strconv.Atoi(ev.Event.Data[event.ConversationID])
|
||||
conversationInfo, _ := profile.GetConversationInfo(conversationID)
|
||||
acl, _ := json.Marshal(conversationInfo.ACL)
|
||||
ev.Event.Data["accessControlList"] = string(acl)
|
||||
groupPic := ph.GetProfilePic(ev.Event.Data[event.GroupID])
|
||||
ev.Event.Data["PicturePath"] = groupPic
|
||||
ev.Event.Data["GroupName"] = ph.GetNick(ev.Event.Data[event.GroupID])
|
||||
|
||||
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 {
|
||||
groupPic := RandomGroupImage(invite.GroupID)
|
||||
ev.Event.Data[constants2.Picture] = groupPic
|
||||
|
||||
conversationID, _ := strconv.Atoi(ev.Event.Data[event.ConversationID])
|
||||
conversationInfo, _ := profile.GetConversationInfo(conversationID)
|
||||
acl, _ := json.Marshal(conversationInfo.ACL)
|
||||
ev.Event.Data["accessControlList"] = string(acl)
|
||||
groupPic := ph.GetProfilePic(invite.GroupID)
|
||||
ev.Event.Data["PicturePath"] = groupPic
|
||||
} else {
|
||||
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)
|
||||
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.openprivcy.ca/cwtch.im/cwtch", err)
|
||||
return ""
|
||||
}
|
||||
case event.PeerStateChange:
|
||||
cxnState := connections.ConnectionStateToType()[ev.Event.Data[event.ConnectionState]]
|
||||
contact := peer.GetContact(ev.Event.Data[event.RemotePeer])
|
||||
|
||||
// skip events the UI doesn't act on
|
||||
if cxnState == connections.CONNECTING || cxnState == connections.CONNECTED {
|
||||
if cxnState == connections.AUTHENTICATED && contact == nil {
|
||||
peer.AddContact(ev.Event.Data[event.RemotePeer], ev.Event.Data[event.RemotePeer], model.AuthUnknown)
|
||||
return ""
|
||||
}
|
||||
|
||||
contact, err := profile.FetchConversationInfo(ev.Event.Data[event.RemotePeer])
|
||||
|
||||
if ev.Event.Data[event.RemotePeer] == profile.GetOnion() {
|
||||
return "" // suppress events from our own profile...
|
||||
}
|
||||
|
||||
// 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...)
|
||||
if contact == nil || err != nil || contact.ID == 0 {
|
||||
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
|
||||
|
||||
if contact != nil {
|
||||
// No enrichment needed
|
||||
//uiManager.UpdateContactStatus(contact.Onion, int(cxnState), false)
|
||||
if cxnState == connections.AUTHENTICATED {
|
||||
// if known and authed, get vars
|
||||
profile.SendScopedZonedGetValToContact(contact.ID, attr.PublicScope, attr.ProfileZone, constants.Name)
|
||||
profile.SendScopedZonedGetValToContact(contact.ID, attr.PublicScope, attr.ProfileZone, constants.CustomProfileImageKey)
|
||||
peer.SendScopedZonedGetValToContact(ev.Event.Data[event.RemotePeer], attr.PublicScope, attr.ProfileZone, constants.Name)
|
||||
peer.SendScopedZonedGetValToContact(ev.Event.Data[event.RemotePeer], attr.PublicScope, attr.ProfileZone, constants2.Picture)
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
case event.NewRetValMessageFromPeer:
|
||||
// auto handled event means the setting is already done, we're just deciding if we need to tell the UI
|
||||
onion := ev.Event.Data[event.RemotePeer]
|
||||
scope := ev.Event.Data[event.Scope]
|
||||
path := ev.Event.Data[event.Path]
|
||||
val := ev.Event.Data[event.Data]
|
||||
//val := ev.Event.Data[event.Data]
|
||||
exists, _ := strconv.ParseBool(ev.Event.Data[event.Exists])
|
||||
|
||||
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"]
|
||||
if err == nil && imagePreviewsEnabled && conversation.Accepted {
|
||||
|
||||
basepath := settings.DownloadPath
|
||||
fp, mp := filesharing.GenerateDownloadPath(basepath, fileKey, true)
|
||||
|
||||
if value, exists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.complete", fileKey)); exists && value == event.True {
|
||||
if _, err := os.Stat(fp); err == nil {
|
||||
// file is marked as completed downloaded and exists...
|
||||
return ""
|
||||
} else {
|
||||
// the user probably deleted the file, mark completed as false...
|
||||
profile.SetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.complete", fileKey), event.False)
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Downloading Profile Image %v %v %v", fp, mp, fileKey)
|
||||
ev.Event.Data[event.FilePath] = fp
|
||||
fsf.DownloadFile(profile, conversation.ID, fp, mp, val, constants.ImagePreviewMaxSizeInBytes)
|
||||
} else {
|
||||
// if image previews are disabled then ignore this event...
|
||||
return ""
|
||||
}
|
||||
}
|
||||
if val, err := profile.GetConversationAttribute(conversation.ID, attr.LocalScope.ConstructScopedZonedPath(zone.ConstructZonedPath(path))); err == nil || val != "" {
|
||||
// we have a locally set override, don't pass this remote set public scope update to UI
|
||||
return ""
|
||||
}
|
||||
if exists && attr.IntoScope(scope) == attr.PublicScope {
|
||||
if _, exists := peer.GetContactAttribute(onion, attr.GetLocalScope(path)); exists {
|
||||
// we have a locally set override, don't pass this remote set public scope update to UI
|
||||
return ""
|
||||
}
|
||||
}
|
||||
case event.TokenManagerInfo:
|
||||
conversations, err := profile.FetchConversations()
|
||||
if err == nil {
|
||||
var associatedGroups []int
|
||||
for _, ci := range conversations {
|
||||
groupServer, groupServerExists := ci.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupServer)).ToString()]
|
||||
if groupServerExists {
|
||||
gci, err := profile.FetchConversationInfo(groupServer)
|
||||
if err == nil {
|
||||
tokenOnion, onionExists := gci.Attributes[attr.PublicScope.ConstructScopedZonedPath(attr.ServerKeyZone.ConstructZonedPath(string(model.KeyTypeTokenOnion))).ToString()]
|
||||
if onionExists && tokenOnion == ev.Event.Data[event.ServerTokenOnion] {
|
||||
associatedGroups = append(associatedGroups, ci.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
associatedGroupsJson, _ := json.Marshal(associatedGroups)
|
||||
ev.Event.Data[event.Data] = string(associatedGroupsJson)
|
||||
}
|
||||
case event.ProtocolEngineCreated:
|
||||
// TODO this code should be moved into Cwtch during the API officialization...
|
||||
settings := ReadGlobalSettings()
|
||||
|
||||
// ensure that protocol engine respects blocking settings...
|
||||
if settings.BlockUnknownConnections {
|
||||
profile.BlockUnknownConnections()
|
||||
} else {
|
||||
profile.AllowUnknownConnections()
|
||||
}
|
||||
|
||||
// Now that the Peer Engine is Activated, Share Files
|
||||
key, exists := profile.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.CustomProfileImageKey)
|
||||
if exists {
|
||||
serializedManifest, _ := profile.GetScopedZonedAttribute(attr.ConversationScope, attr.FilesharingZone, fmt.Sprintf("%s.manifest", key))
|
||||
// reset the share timestamp, currently file shares are hardcoded to expire after 30 days...
|
||||
// we reset the profile image here so that it is always available.
|
||||
profile.SetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.ts", key), strconv.FormatInt(time.Now().Unix(), 10))
|
||||
log.Debugf("Custom Profile Image: %v %s", key, serializedManifest)
|
||||
}
|
||||
// If file sharing is enabled then reshare all active files...
|
||||
fsf, err := filesharing.FunctionalityGate(settings.Experiments)
|
||||
if err == nil {
|
||||
fsf.ReShareFiles(profile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -649,23 +343,30 @@ func unwrap(original *EventProfileEnvelope) *event.Event {
|
|||
func (eh *EventHandler) startHandlingPeer(onion string) {
|
||||
eventBus := eh.app.GetEventBus(onion)
|
||||
q := event.NewQueue()
|
||||
|
||||
eventBus.Subscribe(event.NetworkStatus, q)
|
||||
eventBus.Subscribe(event.ACNInfo, q)
|
||||
eventBus.Subscribe(event.NewMessageFromPeer, q)
|
||||
eventBus.Subscribe(event.UpdatedProfileAttribute, q)
|
||||
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)
|
||||
eventBus.Subscribe(event.ContactCreated, q)
|
||||
eventBus.Subscribe(event.NewMessageFromGroup, q)
|
||||
eventBus.Subscribe(event.MessageCounterResync, q)
|
||||
eventBus.Subscribe(event.GroupCreated, q)
|
||||
eventBus.Subscribe(event.NewGroup, q)
|
||||
eventBus.Subscribe(event.AcceptGroupInvite, q)
|
||||
eventBus.Subscribe(event.SetPeerAttribute, q)
|
||||
eventBus.Subscribe(event.SetGroupAttribute, q)
|
||||
eventBus.Subscribe(event.DeleteGroup, q)
|
||||
eventBus.Subscribe(event.SendMessageToGroupError, q)
|
||||
eventBus.Subscribe(event.SendMessageToPeerError, q)
|
||||
eventBus.Subscribe(event.ServerStateChange, q)
|
||||
eventBus.Subscribe(event.PeerStateChange, q)
|
||||
eventBus.Subscribe(event.PeerCreated, q)
|
||||
eventBus.Subscribe(event.NetworkStatus, q)
|
||||
eventBus.Subscribe(event.ChangePasswordSuccess, q)
|
||||
eventBus.Subscribe(event.ChangePasswordError, q)
|
||||
eventBus.Subscribe(event.NewRetValMessageFromPeer, q)
|
||||
eventBus.Subscribe(event.SetAttribute, q)
|
||||
eventBus.Subscribe(event.ShareManifest, q)
|
||||
eventBus.Subscribe(event.ManifestSizeReceived, q)
|
||||
eventBus.Subscribe(event.ManifestError, q)
|
||||
|
@ -673,8 +374,7 @@ func (eh *EventHandler) startHandlingPeer(onion string) {
|
|||
eventBus.Subscribe(event.ManifestSaved, q)
|
||||
eventBus.Subscribe(event.FileDownloadProgressUpdate, q)
|
||||
eventBus.Subscribe(event.FileDownloaded, q)
|
||||
eventBus.Subscribe(event.TokenManagerInfo, q)
|
||||
eventBus.Subscribe(event.ProtocolEngineCreated, q)
|
||||
|
||||
go eh.forwardProfileMessages(onion, q)
|
||||
|
||||
}
|
||||
|
@ -692,65 +392,6 @@ func (eh *EventHandler) forwardProfileMessages(onion string, q event.Queue) {
|
|||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
func (eh *EventHandler) Push(newEvent event.Event) {
|
||||
eh.appBusQueue.Publish(newEvent)
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
// Short-circuit if file sharing is disabled
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Short-circuit failures
|
||||
// Don't autodownload images if the download path does not exist.
|
||||
if settings.DownloadPath == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Don't autodownload images if the download path does not exist.
|
||||
if _, err := os.Stat(settings.DownloadPath); os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
|
||||
// Now look at the image preview experiment
|
||||
imagePreviewsEnabled := settings.Experiments["filesharing-images"]
|
||||
if imagePreviewsEnabled {
|
||||
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
|
||||
fp, mp := filesharing.GenerateDownloadPath(basepath, fm.Name, false)
|
||||
log.Debugf("autodownloading file!")
|
||||
ev.Data["Auto"] = constants.True
|
||||
mID, _ := strconv.Atoi(ev.Data["Index"])
|
||||
profile.UpdateMessageAttribute(conversationID, 0, mID, constants.AttrDownloaded, constants.True)
|
||||
fh.DownloadFile(profile, senderID, fp, mp, fm.FileKey(), constants.ImagePreviewMaxSizeInBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,251 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"cwtch.im/cwtch/model"
|
||||
"cwtch.im/cwtch/model/attr"
|
||||
"cwtch.im/cwtch/peer"
|
||||
"cwtch.im/cwtch/protocol/connections"
|
||||
"errors"
|
||||
"git.openprivacy.ca/cwtch.im/libcwtch-go/constants"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"strconv"
|
||||
"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
|
||||
}
|
||||
|
||||
// GetTimeline returns a pointer to the timeline associated with the conversation handle or nil if the handle
|
||||
// does not exist (this can happen if the conversation has been deleted)
|
||||
func (p *PeerHelper) GetTimeline(handle string) *model.Timeline {
|
||||
if p.IsServer(handle) {
|
||||
// This should *never* happen
|
||||
log.Errorf("server accessed as contact when getting timeline...")
|
||||
return &model.Timeline{}
|
||||
}
|
||||
// We return a pointer to the timeline to avoid copying, accessing Timeline is thread-safe
|
||||
if p.IsGroup(handle) {
|
||||
group := p.peer.GetGroup(handle)
|
||||
if group == nil {
|
||||
return nil
|
||||
}
|
||||
return &group.Timeline
|
||||
}
|
||||
contact := p.peer.GetContact(handle)
|
||||
if contact == nil {
|
||||
return nil
|
||||
}
|
||||
return &contact.Timeline
|
||||
}
|
||||
|
||||
/*
|
||||
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 + "]"
|
||||
// we do not have a canonical nick for this contact.
|
||||
// re-request if authenticated
|
||||
// TODO: This check probably doesn't belong here...
|
||||
if contact := p.peer.GetContact(id); contact != nil && contact.State == connections.ConnectionStateName[connections.AUTHENTICATED] {
|
||||
p.peer.SendScopedZonedGetValToContact(id, attr.PublicScope, attr.ProfileZone, constants.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
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) 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
|
||||
}
|
||||
|
||||
func getLastMessageTime(tl *model.Timeline) int {
|
||||
if len(tl.Messages) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return int(tl.Messages[len(tl.Messages)-1].Timestamp.Unix())
|
||||
}
|
||||
|
||||
// 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)
|
||||
ev.Event.Data["unread"] = strconv.Itoa(ph.CountUnread(group.Timeline.GetMessages(), lastRead))
|
||||
ev.Event.Data["picture"] = ph.GetProfilePic(handle)
|
||||
ev.Event.Data["numMessages"] = strconv.Itoa(group.Timeline.Len())
|
||||
ev.Event.Data["nick"] = ph.GetNick(handle)
|
||||
ev.Event.Data["status"] = group.State
|
||||
ev.Event.Data["authorization"] = string(model.AuthApproved)
|
||||
ev.Event.Data["loading"] = "false"
|
||||
ev.Event.Data["lastMsgTime"] = strconv.Itoa(getLastMessageTime(&group.Timeline))
|
||||
}
|
||||
} else if ph.IsPeer(handle) {
|
||||
contact := ph.peer.GetContact(handle)
|
||||
if contact != nil {
|
||||
lastRead := ph.InitLastReadTime(contact.Onion)
|
||||
ev.Event.Data["unread"] = strconv.Itoa(ph.CountUnread(contact.Timeline.GetMessages(), lastRead))
|
||||
ev.Event.Data["numMessages"] = strconv.Itoa(contact.Timeline.Len())
|
||||
ev.Event.Data["picture"] = ph.GetProfilePic(handle)
|
||||
|
||||
ev.Event.Data["nick"] = ph.GetNick(handle)
|
||||
|
||||
// TODO Replace this if with a better flow that separates New Contacts and Peering Updates
|
||||
if contact.State == "" {
|
||||
// Will be disconnected to start
|
||||
ev.Event.Data["status"] = connections.ConnectionStateName[connections.DISCONNECTED]
|
||||
} else {
|
||||
ev.Event.Data["status"] = contact.State
|
||||
}
|
||||
ev.Event.Data["authorization"] = string(contact.Authorization)
|
||||
ev.Event.Data["loading"] = "false"
|
||||
ev.Event.Data["lastMsgTime"] = strconv.Itoa(getLastMessageTime(&contact.Timeline))
|
||||
} else {
|
||||
log.Errorf("Failed to find contact: %v", handle)
|
||||
}
|
||||
} else {
|
||||
// could be a server?
|
||||
log.Debugf("sorry, unable to handle AddContact(%v)", handle)
|
||||
return errors.New("not a peer or group")
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"cwtch.im/cwtch/model"
|
||||
"cwtch.im/cwtch/model/attr"
|
||||
"git.openprivacy.ca/cwtch.im/libcwtch-go/constants"
|
||||
)
|
||||
|
||||
func determineNotification(ci *model.Conversation) constants.NotificationType {
|
||||
settings := ReadGlobalSettings()
|
||||
switch settings.NotificationPolicy {
|
||||
case NotificationPolicyMute:
|
||||
return constants.NotificationNone
|
||||
case NotificationPolicyOptIn:
|
||||
if ci != nil {
|
||||
if policy, exists := ci.GetAttribute(attr.LocalScope, attr.ProfileZone, constants.ConversationNotificationPolicy); exists {
|
||||
switch policy {
|
||||
case constants.ConversationNotificationPolicyDefault:
|
||||
return constants.NotificationNone
|
||||
case constants.ConversationNotificationPolicyNever:
|
||||
return constants.NotificationNone
|
||||
case constants.ConversationNotificationPolicyOptIn:
|
||||
return notificationContentToNotificationType(settings.NotificationContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
return constants.NotificationNone
|
||||
case NotificationPolicyDefaultAll:
|
||||
if ci != nil {
|
||||
if policy, exists := ci.GetAttribute(attr.LocalScope, attr.ProfileZone, constants.ConversationNotificationPolicy); exists {
|
||||
switch policy {
|
||||
case constants.ConversationNotificationPolicyNever:
|
||||
return constants.NotificationNone
|
||||
case constants.ConversationNotificationPolicyDefault:
|
||||
fallthrough
|
||||
case constants.ConversationNotificationPolicyOptIn:
|
||||
return notificationContentToNotificationType(settings.NotificationContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
return notificationContentToNotificationType(settings.NotificationContent)
|
||||
}
|
||||
return constants.NotificationNone
|
||||
}
|
||||
|
||||
func notificationContentToNotificationType(notificationContent string) constants.NotificationType {
|
||||
if notificationContent == "NotificationContent.ContactInfo" {
|
||||
return constants.NotificationConversation
|
||||
}
|
||||
return constants.NotificationEvent
|
||||
}
|
|
@ -1,17 +1,15 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"git.openprivacy.ca/cwtch.im/libcwtch-go/constants"
|
||||
path "path/filepath"
|
||||
"sync"
|
||||
|
||||
"cwtch.im/cwtch/event"
|
||||
"cwtch.im/cwtch/storage/v1"
|
||||
"sync"
|
||||
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -26,38 +24,18 @@ var lock sync.Mutex
|
|||
const GlobalSettingsFilename = "ui.globals"
|
||||
const saltFile = "SALT"
|
||||
|
||||
type NotificationPolicy string
|
||||
|
||||
const (
|
||||
NotificationPolicyMute = NotificationPolicy("NotificationPolicy.Mute")
|
||||
NotificationPolicyOptIn = NotificationPolicy("NotificationPolicy.OptIn")
|
||||
NotificationPolicyDefaultAll = NotificationPolicy("NotificationPolicy.DefaultAll")
|
||||
)
|
||||
|
||||
type GlobalSettings struct {
|
||||
Locale string
|
||||
Theme string
|
||||
ThemeMode string
|
||||
PreviousPid int64
|
||||
ExperimentsEnabled bool
|
||||
Experiments map[string]bool
|
||||
BlockUnknownConnections bool
|
||||
NotificationPolicy NotificationPolicy
|
||||
NotificationContent string
|
||||
StreamerMode bool
|
||||
StateRootPane int
|
||||
FirstTime bool
|
||||
UIColumnModePortrait string
|
||||
UIColumnModeLandscape string
|
||||
DownloadPath string
|
||||
AllowAdvancedTorConfig bool
|
||||
CustomTorrc string
|
||||
UseCustomTorrc bool
|
||||
UseExternalTor bool
|
||||
CustomSocksPort int
|
||||
CustomControlPort int
|
||||
UseTorCache bool
|
||||
TorCacheDir string
|
||||
}
|
||||
|
||||
var DefaultGlobalSettings = GlobalSettings{
|
||||
|
@ -65,30 +43,20 @@ var DefaultGlobalSettings = GlobalSettings{
|
|||
Theme: "dark",
|
||||
PreviousPid: -1,
|
||||
ExperimentsEnabled: false,
|
||||
Experiments: map[string]bool{constants.MessageFormattingExperiment: true},
|
||||
Experiments: make(map[string]bool),
|
||||
StateRootPane: 0,
|
||||
FirstTime: true,
|
||||
BlockUnknownConnections: false,
|
||||
StreamerMode: false,
|
||||
UIColumnModePortrait: "DualpaneMode.Single",
|
||||
UIColumnModeLandscape: "DualpaneMode.CopyPortrait",
|
||||
NotificationPolicy: "NotificationPolicy.Mute",
|
||||
NotificationContent: "NotificationContent.SimpleEvent",
|
||||
DownloadPath: "",
|
||||
AllowAdvancedTorConfig: false,
|
||||
CustomTorrc: "",
|
||||
UseCustomTorrc: false,
|
||||
CustomSocksPort: -1,
|
||||
CustomControlPort: -1,
|
||||
UseTorCache: false,
|
||||
TorCacheDir: "",
|
||||
}
|
||||
|
||||
func InitGlobalSettingsFile(directory string, password string) error {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
var key [32]byte
|
||||
salt, err := os.ReadFile(path.Join(directory, saltFile))
|
||||
salt, err := ioutil.ReadFile(path.Join(directory, saltFile))
|
||||
if err != nil {
|
||||
log.Infof("Could not find salt file: %v (creating a new settings file)", err)
|
||||
var newSalt [128]byte
|
||||
|
@ -98,7 +66,7 @@ func InitGlobalSettingsFile(directory string, password string) error {
|
|||
return err
|
||||
}
|
||||
os.Mkdir(directory, 0700)
|
||||
err := os.WriteFile(path.Join(directory, saltFile), newSalt[:], 0600)
|
||||
err := ioutil.WriteFile(path.Join(directory, saltFile), newSalt[:], 0600)
|
||||
if err != nil {
|
||||
log.Errorf("Could not write salt file: %v", err)
|
||||
return err
|
||||
|
|
|
@ -12,18 +12,18 @@ 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 finding image for profile: %v %v %v\n", onion, err, barr)
|
||||
return "assets/extra/openprivacy.png"
|
||||
log.Errorf("error: %v %v %v\n", onion, err, barr)
|
||||
return "extra/openprivacy.png"
|
||||
}
|
||||
return "assets/profiles/" + choices[int(barr[33])%len(choices)] + ".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 finding image for group: %v %v %v\n", handle, err, barr)
|
||||
return "assets/extra/openprivacy.png"
|
||||
log.Errorf("error: %v %v %v\n", handle, err, barr)
|
||||
return "extra/openprivacy.png"
|
||||
}
|
||||
return "assets/servers/" + choices[int(barr[0])%len(choices)] + ".png"
|
||||
return "servers/" + choices[int(barr[0])%len(choices)] + ".png"
|
||||
}
|
||||
|
|
Reference in New Issue