forked from cwtch.im/libcwtch-go
Compare commits
320 Commits
reportACNE
...
trunk
Author | SHA1 | Date |
---|---|---|
Sarah Jamie Lewis | 055e1d65a1 | |
Sarah Jamie Lewis | 8a7c647ea0 | |
Sarah Jamie Lewis | 5871a2c077 | |
Sarah Jamie Lewis | ae179fab72 | |
Sarah Jamie Lewis | dd411bd337 | |
Sarah Jamie Lewis | 1d3d2878b7 | |
Sarah Jamie Lewis | 28fe3b21fb | |
Sarah Jamie Lewis | 20897a9f8d | |
Sarah Jamie Lewis | 709d377bf4 | |
Sarah Jamie Lewis | 1baac147d7 | |
Sarah Jamie Lewis | f152b02230 | |
Sarah Jamie Lewis | bef3f11150 | |
Sarah Jamie Lewis | b29293334d | |
Sarah Jamie Lewis | 9da33c3083 | |
Sarah Jamie Lewis | 3d0a3a5a49 | |
Sarah Jamie Lewis | 1e4221c6bd | |
Dan Ballard | 973e73a308 | |
Sarah Jamie Lewis | ff20656e22 | |
Sarah Jamie Lewis | e12cb2c965 | |
Sarah Jamie Lewis | 54ba8c463a | |
Sarah Jamie Lewis | 137027d011 | |
Dan Ballard | 916ca279ea | |
Sarah Jamie Lewis | 0d05a0731c | |
Dan Ballard | e7e9a71515 | |
Dan Ballard | 6021daeaca | |
Sarah Jamie Lewis | 92ec4c6667 | |
Sarah Jamie Lewis | d28d0db77c | |
Dan Ballard | 18c6907dbe | |
Sarah Jamie Lewis | c88700b226 | |
Sarah Jamie Lewis | 1f3617cce6 | |
Sarah Jamie Lewis | 3a15d75011 | |
Sarah Jamie Lewis | 482cb54263 | |
Sarah Jamie Lewis | ec467116b1 | |
Dan Ballard | 397d264bd9 | |
Sarah Jamie Lewis | 1a25b45bcf | |
Sarah Jamie Lewis | 989cc25c93 | |
Dan Ballard | 9ef5cbc911 | |
Sarah Jamie Lewis | 39eaa5c8c3 | |
Sarah Jamie Lewis | f0ea513177 | |
Dan Ballard | e0d2c229c7 | |
Sarah Jamie Lewis | 2bd7a2658e | |
Sarah Jamie Lewis | 67e58b1613 | |
Sarah Jamie Lewis | 079578c172 | |
Sarah Jamie Lewis | 2bcadca55f | |
Dan Ballard | 2f188b3a46 | |
Dan Ballard | f75286cb97 | |
Dan Ballard | 61c7418252 | |
Dan Ballard | 99a51ef19a | |
Sarah Jamie Lewis | f66921b5ce | |
Sarah Jamie Lewis | 2058a51579 | |
Sarah Jamie Lewis | 085b414bd8 | |
Sarah Jamie Lewis | e83369d4cc | |
Sarah Jamie Lewis | f1de9b1951 | |
Dan Ballard | 251f757c5f | |
Sarah Jamie Lewis | b99e35ed03 | |
Dan Ballard | 10d9059d49 | |
Dan Ballard | 2d9a448b52 | |
Dan Ballard | 7b3e842715 | |
Sarah Jamie Lewis | f2ea1ec84d | |
Sarah Jamie Lewis | bf36df8de8 | |
Sarah Jamie Lewis | 44173471c8 | |
Sarah Jamie Lewis | 7e01cf4916 | |
Sarah Jamie Lewis | f119d316c5 | |
Sarah Jamie Lewis | a558ce67d6 | |
Sarah Jamie Lewis | 7069fab0b1 | |
Dan Ballard | 1efb0d076b | |
Sarah Jamie Lewis | 231e27d116 | |
Sarah Jamie Lewis | 1e4e2368f9 | |
Dan Ballard | b98162dc3c | |
Dan Ballard | 9895a37f2f | |
Sarah Jamie Lewis | e70e0f8f2f | |
Dan Ballard | 46b34f0a60 | |
Dan Ballard | 07b496fc48 | |
Dan Ballard | b3bb69829a | |
Dan Ballard | ff23465f84 | |
Sarah Jamie Lewis | 26f8f8fcfd | |
Dan Ballard | 02c7b0b962 | |
Sarah Jamie Lewis | 0474d5f327 | |
Sarah Jamie Lewis | e029076b20 | |
Sarah Jamie Lewis | 7c2cc61b62 | |
Sarah Jamie Lewis | d8ed0bf57c | |
Dan Ballard | 352e362111 | |
Dan Ballard | c32d5082e4 | |
Dan Ballard | 89f8ec0253 | |
Sarah Jamie Lewis | 9901e081e4 | |
Sarah Jamie Lewis | 58029ee79e | |
Dan Ballard | 3d8ef9f06b | |
Dan Ballard | 8d00248aa8 | |
Dan Ballard | 3ad0bc904c | |
Dan Ballard | cfd8fe21c8 | |
Dan Ballard | 97defdf965 | |
Dan Ballard | cd48642411 | |
Sarah Jamie Lewis | 1acae32030 | |
Dan Ballard | 98eae2e6ff | |
Dan Ballard | ca4897b191 | |
Sarah Jamie Lewis | 3f431d9f18 | |
Dan Ballard | 5b34715f8c | |
Sarah Jamie Lewis | 31dda072e5 | |
Sarah Jamie Lewis | 68c976107d | |
Sarah Jamie Lewis | c2874db3c0 | |
Dan Ballard | 7ace298d6e | |
Dan Ballard | 4b881b9a12 | |
Dan Ballard | f1c43f44ca | |
Sarah Jamie Lewis | e8b2def3a4 | |
Dan Ballard | 5af4317851 | |
Sarah Jamie Lewis | a6277fc998 | |
Dan Ballard | 11fbb17bfd | |
Sarah Jamie Lewis | d0d5300a95 | |
Dan Ballard | 419c39cc68 | |
erinn | 4467c40e17 | |
Sarah Jamie Lewis | db8e43cb05 | |
Sarah Jamie Lewis | 50b7a43466 | |
Sarah Jamie Lewis | 08774268a8 | |
Sarah Jamie Lewis | 8040385681 | |
Dan Ballard | 758af8dcaf | |
Dan Ballard | ce09ccdd6a | |
Dan Ballard | e5e4d21fa0 | |
erinn | 17acc3b8ef | |
erinn | 942b8b4709 | |
Sarah Jamie Lewis | 44e5c38aa6 | |
erinn | 4e4e3315dd | |
Sarah Jamie Lewis | a9563b615c | |
Sarah Jamie Lewis | fc9999f835 | |
Sarah Jamie Lewis | 2f3db01c2f | |
Sarah Jamie Lewis | d40900481a | |
Sarah Jamie Lewis | 2e9f9fb14f | |
Sarah Jamie Lewis | 2bef622860 | |
erinn | e1073be7d2 | |
erinn | d77d7bbc52 | |
Sarah Jamie Lewis | 240c5c232d | |
erinn | e0e1a4bf28 | |
Sarah Jamie Lewis | 5e271d0f3c | |
Dan Ballard | 6865ec1ec8 | |
Dan Ballard | 82a801678b | |
Dan Ballard | c58be05110 | |
Dan Ballard | 84d451fb46 | |
Sarah Jamie Lewis | 5e4e3d4083 | |
Sarah Jamie Lewis | 0db0610a23 | |
Dan Ballard | 2aea700ebd | |
Sarah Jamie Lewis | 85af1ad3ba | |
Sarah Jamie Lewis | 799fc6e621 | |
erinn | 4cf95d6507 | |
Sarah Jamie Lewis | dde5b2b1e8 | |
erinn | c098df5db3 | |
erinn | 1df0ba54f7 | |
erinn | af47036b4e | |
Sarah Jamie Lewis | 0eb7a91d26 | |
Sarah Jamie Lewis | 6ad7a41b35 | |
Sarah Jamie Lewis | 24bcd72e75 | |
Dan Ballard | f2067348ec | |
Dan Ballard | f1d2e6a310 | |
Dan Ballard | 4da1cb70df | |
Dan Ballard | 86dfe63a8f | |
Sarah Jamie Lewis | 9c32586068 | |
Dan Ballard | fa42ce1fb1 | |
Dan Ballard | a1e06a868f | |
Dan Ballard | 6fb5234724 | |
Dan Ballard | 8598802abd | |
Dan Ballard | 95c36703cc | |
Sarah Jamie Lewis | 9e575f6801 | |
Sarah Jamie Lewis | 32c3d93e4d | |
Sarah Jamie Lewis | 75a31fd096 | |
Dan Ballard | ba4b31f06b | |
Sarah Jamie Lewis | ab2375e57c | |
Sarah Jamie Lewis | 68c02502a2 | |
Sarah Jamie Lewis | 597b09dba9 | |
Sarah Jamie Lewis | d6c1b386a9 | |
Sarah Jamie Lewis | 212e71a1b9 | |
Sarah Jamie Lewis | 306c074e7e | |
Sarah Jamie Lewis | e4244aeb31 | |
erinn | 11bbc3aa78 | |
erinn | f4ff802460 | |
erinn | 8b38a8b159 | |
Sarah Jamie Lewis | a3707bcadb | |
Dan Ballard | 4903e9376b | |
Dan Ballard | d30052176b | |
Dan Ballard | 4c48fd603d | |
erinn | eb33f11127 | |
erinn | d59d54ab6a | |
erinn | cac25c0d67 | |
erinn | 1b0333d468 | |
Sarah Jamie Lewis | aa102bd169 | |
Dan Ballard | d2abed7583 | |
Sarah Jamie Lewis | 28a13aaca9 | |
Dan Ballard | 9eadc77afd | |
Dan Ballard | 69df789355 | |
Dan Ballard | 37eb9f681a | |
Sarah Jamie Lewis | 5d7f45e50d | |
Dan Ballard | 35abad7224 | |
Dan Ballard | 25d035267a | |
Sarah Jamie Lewis | 79a87b78e0 | |
Sarah Jamie Lewis | a43c225267 | |
Sarah Jamie Lewis | 8e80dbaf78 | |
Sarah Jamie Lewis | 1aea1c6e5f | |
Sarah Jamie Lewis | 62f3d8878e | |
Sarah Jamie Lewis | fee6c7e641 | |
Sarah Jamie Lewis | e31d31d76f | |
Sarah Jamie Lewis | b0f54bb169 | |
Sarah Jamie Lewis | bd176f89cd | |
Sarah Jamie Lewis | 92d2925622 | |
Sarah Jamie Lewis | edff87f77c | |
Sarah Jamie Lewis | 6723bdf558 | |
Sarah Jamie Lewis | 3cc56fb011 | |
Sarah Jamie Lewis | b2da9fc54d | |
Sarah Jamie Lewis | dcdbf382cb | |
Sarah Jamie Lewis | 7f5d466d49 | |
Dan Ballard | 3efc9543a4 | |
Sarah Jamie Lewis | 3d3e127520 | |
Dan Ballard | bbd6748d06 | |
Sarah Jamie Lewis | 86c5a51b22 | |
Dan Ballard | e966392233 | |
Dan Ballard | d519a4d0b2 | |
Dan Ballard | 85a90658b9 | |
Sarah Jamie Lewis | 81705ce48f | |
Sarah Jamie Lewis | 3386afd3b5 | |
Dan Ballard | 2b2adb01e3 | |
Sarah Jamie Lewis | 706be7fc38 | |
erinn | b56b821b19 | |
Dan Ballard | 2f1fe86d50 | |
erinn | 0bd369888f | |
erinn | 792e84d5f9 | |
Dan Ballard | 4adb501a89 | |
Dan Ballard | b3d3464e1e | |
Dan Ballard | b47b27008e | |
Sarah Jamie Lewis | 06d51fb0bb | |
Sarah Jamie Lewis | f3463d54d3 | |
Dan Ballard | 77a5cc3a86 | |
Sarah Jamie Lewis | 17ab80f09c | |
Dan Ballard | 9f7ffb50f9 | |
erinn | d5231b13db | |
erinn | 777d564ca4 | |
Sarah Jamie Lewis | baddf633b2 | |
Sarah Jamie Lewis | ac00e2195e | |
Dan Ballard | f9f074a65c | |
Dan Ballard | 63e96f32bf | |
Dan Ballard | f3b753827f | |
Dan Ballard | 063ad61e36 | |
Dan Ballard | c8f19b3e4c | |
Dan Ballard | 5b72edebad | |
Dan Ballard | a237314d5f | |
erinn | 4529984e45 | |
Sarah Jamie Lewis | 041bdcf97b | |
Sarah Jamie Lewis | a36e439589 | |
Dan Ballard | 9fdbd45bb2 | |
Sarah Jamie Lewis | a3e3c221ca | |
Sarah Jamie Lewis | 0f5d0389ca | |
Sarah Jamie Lewis | 2f14966051 | |
Sarah Jamie Lewis | d54567339d | |
Dan Ballard | fcc9d7148a | |
Sarah Jamie Lewis | 585f1eed46 | |
Sarah Jamie Lewis | 2f943d4b97 | |
Sarah Jamie Lewis | a378c8dfb5 | |
Sarah Jamie Lewis | 3782bc6471 | |
erinn | 7a16c29525 | |
Sarah Jamie Lewis | 62aaffeae3 | |
Sarah Jamie Lewis | 5cce57d469 | |
Sarah Jamie Lewis | 44efbfea0a | |
Dan Ballard | de5f1104d3 | |
Dan Ballard | 1d18559287 | |
Sarah Jamie Lewis | f82a7d4873 | |
erinn | 495c4390c7 | |
Dan Ballard | f8882c0c0a | |
Sarah Jamie Lewis | e53d91b09e | |
Sarah Jamie Lewis | a8e7bba3f5 | |
Dan Ballard | 5656478198 | |
Dan Ballard | f6fec4082a | |
Sarah Jamie Lewis | 58f9d882c4 | |
Sarah Jamie Lewis | 9f24031d97 | |
Sarah Jamie Lewis | dbcb671117 | |
Sarah Jamie Lewis | 81d22e08a2 | |
Sarah Jamie Lewis | 2909fa0f6d | |
Dan Ballard | 94f8f216b6 | |
Dan Ballard | fd4a03e34e | |
Sarah Jamie Lewis | d93cb45052 | |
Dan Ballard | 2ebba5329e | |
erinn | c6a3a4e994 | |
Sarah Jamie Lewis | 47015e9b08 | |
erinn | 300b68c93a | |
Sarah Jamie Lewis | 8f29d68860 | |
Dan Ballard | 7376218fac | |
Sarah Jamie Lewis | f86e670241 | |
Sarah Jamie Lewis | 23c15130c7 | |
Sarah Jamie Lewis | 0d06f802a4 | |
Dan Ballard | 73c57c60f2 | |
Sarah Jamie Lewis | adf77dc0cf | |
Sarah Jamie Lewis | e62ed455f1 | |
Sarah Jamie Lewis | 4dcc10c133 | |
Dan Ballard | 23b9888ad7 | |
Dan Ballard | 7d02f84174 | |
Dan Ballard | 127ad82b21 | |
Dan Ballard | b524f9e8ad | |
Dan Ballard | 64528d731f | |
Linus Gasser | c57942c693 | |
Linus Gasser | c395153ad8 | |
Linus Gasser | 3d8164c06d | |
Sarah Jamie Lewis | bda19b3197 | |
Dan Ballard | a8ee7047c7 | |
Dan Ballard | 41ae09d763 | |
Sarah Jamie Lewis | df6c3dd84a | |
Sarah Jamie Lewis | 4d218df85d | |
Dan Ballard | 3ddaedd227 | |
Dan Ballard | 801a805966 | |
Sarah Jamie Lewis | bf193e6542 | |
Sarah Jamie Lewis | db1f31fa71 | |
Sarah Jamie Lewis | 343c3bc578 | |
Dan Ballard | 028ad3441c | |
erinn | f8eedcae3f | |
erinn | 9ab9df09fc | |
erinn | 58460ca0ca | |
erinn | 98ebe3aacb | |
Sarah Jamie Lewis | 043774223a | |
Dan Ballard | 539a28444b | |
Sarah Jamie Lewis | 750106496f | |
Dan Ballard | 2e0b6ef4c8 | |
Sarah Jamie Lewis | b0d8bb96c3 | |
Sarah Jamie Lewis | cec0604224 | |
Trevor Bergeron | 17abf467c0 | |
Sarah Jamie Lewis | 3c465050d8 | |
Dan Ballard | 9c23d8cc33 | |
Sarah Jamie Lewis | 520d35ad2f |
97
.drone.yml
97
.drone.yml
|
@ -1,39 +1,40 @@
|
|||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: default
|
||||
name: linux-android-windows-test
|
||||
|
||||
steps:
|
||||
- name: fetch
|
||||
image: golang
|
||||
image: golang:1.19.1
|
||||
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 get
|
||||
- go mod download
|
||||
# mobile is... special
|
||||
# go get golang.org/x/mobile/bind
|
||||
# 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
|
||||
image: golang:1.19.1
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /go
|
||||
commands:
|
||||
- go list ./... | xargs go vet
|
||||
- go list ./... | xargs golint
|
||||
- staticcheck ./...
|
||||
#Todo: fix all the lint errors and add `-set_exit_status` above to enforce linting
|
||||
|
||||
- name: build-linux
|
||||
image: golang
|
||||
image: golang:1.19.1
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /go
|
||||
|
@ -41,7 +42,7 @@ steps:
|
|||
- make linux
|
||||
|
||||
- name: build-android
|
||||
image: openpriv/android-go-mobile:2021.03
|
||||
image: openpriv/android-go-mobile:2023.02
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /go
|
||||
|
@ -51,7 +52,7 @@ steps:
|
|||
- make android
|
||||
|
||||
- name: build-windows
|
||||
image: openpriv/mingw-go:2021.03
|
||||
image: openpriv/mingw-go:2023.01
|
||||
environment:
|
||||
GOPATH: /go
|
||||
volumes:
|
||||
|
@ -62,6 +63,7 @@ steps:
|
|||
|
||||
- name: deploy-buildfiles
|
||||
image: kroniak/ssh-client
|
||||
pull: if-not-exists
|
||||
environment:
|
||||
BUILDFILES_KEY:
|
||||
from_secret: buildfiles_key
|
||||
|
@ -75,17 +77,18 @@ steps:
|
|||
- echo $BUILDFILES_KEY > ~/id_rsab64
|
||||
- base64 -d ~/id_rsab64 > ~/id_rsa
|
||||
- chmod 400 ~/id_rsa
|
||||
- export DIR=libCwtch-go-`cat VERSION`-`cat BUILDDATE`
|
||||
- export DIR=libCwtch-go-`cat BUILDDATE`-`cat VERSION`
|
||||
- mkdir $DIR
|
||||
- mv libCwtch.so libCwtch.dll cwtch.aar cwtch-sources.jar libCwtch.h $DIR/
|
||||
- cd $DIR
|
||||
- find . -type f -exec sha256sum {} \; > ./../sha256s.txt
|
||||
- mv ./../sha256s.txt .
|
||||
- cd ..
|
||||
- scp -r -o StrictHostKeyChecking=no -i ~/id_rsa $DIR buildfiles@openprivacy.ca:/home/buildfiles/buildfiles/
|
||||
- scp -r -o StrictHostKeyChecking=no -i ~/id_rsa $DIR buildfiles@build.openprivacy.ca:/home/buildfiles/buildfiles/
|
||||
|
||||
- name: gitea-release
|
||||
image: plugins/gitea-release
|
||||
pull: if-not-exists
|
||||
when:
|
||||
event: tag
|
||||
settings:
|
||||
|
@ -102,18 +105,9 @@ 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 ]
|
||||
|
@ -135,3 +129,62 @@ trigger:
|
|||
- push
|
||||
- pull_request
|
||||
- tag
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: exec
|
||||
name: macos
|
||||
|
||||
platform:
|
||||
os: darwin
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: fetch
|
||||
commands:
|
||||
- export PATH=$PATH:/usr/local/go/bin/
|
||||
- git fetch --tags
|
||||
- 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: build-macos-x64
|
||||
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
|
||||
- name: deploy-buildfiles
|
||||
environment:
|
||||
BUILDFILES_KEY:
|
||||
from_secret: buildfiles_key
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
status: [ success ]
|
||||
commands:
|
||||
- echo $BUILDFILES_KEY > ~/id_rsab64
|
||||
- base64 -d ~/id_rsab64 > ~/id_rsa
|
||||
- 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/
|
||||
- cd $DIR
|
||||
- find . -type f -exec shasum -a 512 {} \; > ./../sha512s.txt
|
||||
- mv ./../sha512s.txt .
|
||||
- cd ..
|
||||
- scp -r -o StrictHostKeyChecking=no -i ~/id_rsa $DIR buildfiles@build.openprivacy.ca:/home/buildfiles/buildfiles/
|
||||
|
||||
trigger:
|
||||
repo: cwtch.im/libcwtch-go
|
||||
branch: trunk
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
|
@ -3,4 +3,6 @@ cwtch-sources.jar
|
|||
cwtch.aar
|
||||
libCwtch.h
|
||||
libCwtch.so
|
||||
libCwtch.dylib
|
||||
libCwtch.dll
|
||||
ios/
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
MIT License
|
||||
Copyright (c) 2021 Open Privacy Research Society
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# Memory Model
|
||||
|
||||
This document provides an overview of the memory model of libCwtch. Callers should consult this document to ensure
|
||||
that they are properly handling pointers passed to-and-from libCwtch.
|
||||
|
||||
## Pointer Parameters
|
||||
|
||||
All pointers **passed** into functions in this library, except `c_FreePointer`, are assumed to be owned by the caller.
|
||||
|
||||
libCwtch **will not** modifying the underlying memory, nor attempt to free or otherwise modify these pointers.
|
||||
|
||||
This is realized by copying the underlying memory using `C.GoStringN`. libCwtch guarantees that it will not depend
|
||||
on the underlying memory of these pointers being available after returning from this function.
|
||||
|
||||
Callers are responsible for freeing these pointers after the call has been completed.
|
||||
|
||||
## Returned Pointers
|
||||
|
||||
All pointers **returned** by functions in libCwtch should be assumed to owned by libCwtch
|
||||
|
||||
Callers **must not** modifying the underlying memory of these pointers, nor attempt to free or otherwise modify these pointers
|
||||
through any method other than the one provided below.
|
||||
|
||||
Calling functions **must** copy the contents of the memory into their own memory space and then call `c_FreePointer` providing
|
||||
the returned pointer as a parameter.
|
||||
|
||||
libCwtch guarantees that it will not modify or free the underlying memory of these pointers prior to a call to `c_FreePointer`.
|
51
Makefile
51
Makefile
|
@ -1,26 +1,65 @@
|
|||
.PHONY: all clean linux android
|
||||
IOS_OUT := ./ios
|
||||
|
||||
.PHONY: all linux android windows macos clean ios
|
||||
|
||||
DEFAULT_GOAL: linux
|
||||
|
||||
all: linux android
|
||||
all: linux android windows
|
||||
|
||||
linux: libCwtch.so
|
||||
|
||||
macos: libCwtch.x64.dylib libCwtch.arm64.dylib
|
||||
|
||||
android: cwtch.aar
|
||||
|
||||
windows: libCwtch.dll
|
||||
|
||||
libCwtch.so: lib.go
|
||||
./switch-ffi.sh
|
||||
go build -buildmode c-shared -o libCwtch.so
|
||||
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
|
||||
|
||||
libCwtch.x64.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
|
||||
|
||||
cwtch.aar: lib.go
|
||||
./switch-gomobile.sh
|
||||
gomobile bind -target android
|
||||
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)"
|
||||
|
||||
libCwtch.dll: lib.go
|
||||
./switch-ffi.sh
|
||||
GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc-win32 go build -buildmode c-shared -o libCwtch.dll
|
||||
# '-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
|
||||
|
||||
clean:
|
||||
rm -f cwtch.aar cwtch_go.apk libCwtch.h libCwtch.so cwtch-sources.jar libCwtch.dll
|
||||
rm -f cwtch.aar cwtch_go.apk libCwtch.h libCwtch.so cwtch-sources.jar libCwtch.dll libCwtch.dylib
|
||||
|
||||
# iOS - for testing purposes only for now, not officially supported
|
||||
|
||||
ios-arm64:
|
||||
CGO_ENABLED=1 \
|
||||
GOOS=darwin \
|
||||
GOARCH=arm64 \
|
||||
SDK=iphoneos \
|
||||
CGO_CFLAGS="-fembed-bitcode" \
|
||||
CC=$(PWD)/clangwrap.sh \
|
||||
go build -buildmode=c-archive -tags ios -o $(IOS_OUT)/arm64.a .
|
||||
|
||||
ios-x86_64:
|
||||
CGO_ENABLED=1 \
|
||||
GOOS=darwin \
|
||||
GOARCH=amd64 \
|
||||
SDK=iphonesimulator \
|
||||
CC=$(PWD)/clangwrap.sh \
|
||||
go build -buildmode=c-archive -tags ios -o $(IOS_OUT)/x86_64.a .
|
||||
|
||||
ios: ios-arm64 ios-x86_64
|
||||
lipo $(IOS_OUT)/x86_64.a $(IOS_OUT)/arm64.a -create -output $(IOS_OUT)/cwtch.a
|
||||
cp $(IOS_OUT)/arm64.h $(IOS_OUT)/cwtch.h
|
||||
|
|
39
README.md
39
README.md
|
@ -1,13 +1,42 @@
|
|||
# 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.
|
||||
|
||||
# Build Instructions...
|
||||
# Build Instructions
|
||||
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`
|
||||
|
@ -16,3 +45,11 @@ C-bindings for the Go Cwtch Library.
|
|||
## Android
|
||||
|
||||
- copy `cwtch.aar` into `flutter_app/android/cwtch`
|
||||
|
||||
## 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
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/sh
|
||||
|
||||
# go/clangwrap.sh
|
||||
|
||||
SDK_PATH=`xcrun --sdk $SDK --show-sdk-path`
|
||||
CLANG=`xcrun --sdk $SDK --find clang`
|
||||
|
||||
if [ "$GOARCH" == "amd64" ]; then
|
||||
CARCH="x86_64"
|
||||
elif [ "$GOARCH" == "arm64" ]; then
|
||||
CARCH="arm64"
|
||||
fi
|
||||
|
||||
exec $CLANG -arch $CARCH -isysroot $SDK_PATH -mios-version-min=10.0 "$@"
|
|
@ -5,7 +5,10 @@ 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"
|
||||
|
@ -13,6 +16,14 @@ 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"
|
||||
|
@ -21,3 +32,6 @@ const StateSelectedProfileTime = "state-selected-profile-time"
|
|||
const BlockUnknownPeersSetting = "blockunknownpeers"
|
||||
const LocaleSetting = "locale"
|
||||
const ZoomSetting = "zoom"
|
||||
|
||||
// App Experiments
|
||||
const MessageFormattingExperiment = "message-formatting"
|
||||
|
|
|
@ -2,4 +2,34 @@ package constants
|
|||
|
||||
// We offer "un-passworded" profiles but our storage encrypts everything with a password. We need an agreed upon
|
||||
// password to use in that case, that the app case use behind the scenes to password and unlock with
|
||||
// https://docs.openprivacy.ca/cwtch-security-handbook/profile_encryption_and_storage.html
|
||||
const DefactoPasswordForUnencryptedProfiles = "be gay do crime"
|
||||
|
||||
const (
|
||||
// StatusSuccess is an event response for event.Status signifying a call succeeded
|
||||
StatusSuccess = "success"
|
||||
// StatusError is an event response for event.Status signifying a call failed in error, ideally accompanied by a event.Error
|
||||
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"
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
package constants
|
||||
|
||||
import "cwtch.im/cwtch/event"
|
||||
|
||||
// The server manager defines its own events, most should be self-explanatory:
|
||||
const (
|
||||
NewServer = event.Type("NewServer")
|
||||
|
||||
// Force a UI update
|
||||
ListServers = event.Type("ListServers")
|
||||
|
||||
// Takes an Onion, used to toggle off/on Server availability
|
||||
StartServer = event.Type("StartServer")
|
||||
StopServer = event.Type("StopServer")
|
||||
|
||||
// Takes an Onion and a AutoStartEnabled boolean
|
||||
AutoStart = event.Type("AutoStart")
|
||||
|
||||
// Get the status of a particular server (takes an Onion)
|
||||
CheckServerStatus = event.Type("CheckServerStatus")
|
||||
ServerStatusUpdate = event.Type("ServerStatusUpdate")
|
||||
)
|
||||
|
||||
const (
|
||||
AutoStartEnabled = event.Field("AutoStartEnabled")
|
||||
)
|
|
@ -1,41 +0,0 @@
|
|||
package contact
|
||||
|
||||
import (
|
||||
"cwtch.im/cwtch/model"
|
||||
"cwtch.im/cwtch/peer"
|
||||
"git.openprivacy.ca/flutter/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 {
|
||||
eventID := peer.SendMessageToPeer(handle, message)
|
||||
return features.ConstructResponse(sendMessagePrefix, eventID)
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
package contact
|
||||
|
||||
import (
|
||||
"cwtch.im/cwtch/model"
|
||||
"git.openprivacy.ca/flutter/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,21 +3,16 @@ 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"
|
||||
"encoding/base64"
|
||||
"cwtch.im/cwtch/protocol/connections"
|
||||
"fmt"
|
||||
"git.openprivacy.ca/flutter/libcwtch-go/features"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"strings"
|
||||
"git.openprivacy.ca/cwtch.im/libcwtch-go/constants"
|
||||
)
|
||||
|
||||
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")
|
||||
|
@ -28,12 +23,6 @@ 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 {
|
||||
}
|
||||
|
@ -46,27 +35,8 @@ 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.SendMessageToGroupTracked(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 ReadServerInfo) []Server {
|
||||
func (gf *GroupFunctionality) GetServerInfoList(profile peer.CwtchPeer) []Server {
|
||||
var servers []Server
|
||||
for _, server := range profile.GetServers() {
|
||||
servers = append(servers, gf.GetServerInfo(server, profile))
|
||||
|
@ -76,52 +46,21 @@ func (gf *GroupFunctionality) GetServerInfoList(profile ReadServerInfo) []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.ReadContacts) Server {
|
||||
serverInfo := profile.GetContact(serverOnion)
|
||||
func (gf *GroupFunctionality) GetServerInfo(serverOnion string, profile peer.CwtchPeer) Server {
|
||||
serverInfo, _ := profile.FetchConversationInfo(serverOnion)
|
||||
keyTypes := []model.KeyType{model.KeyTypeServerOnion, model.KeyTypeTokenOnion, model.KeyTypePrivacyPass}
|
||||
var serverKeys []ServerKey
|
||||
|
||||
for _, keyType := range keyTypes {
|
||||
if key, has := serverInfo.GetAttribute(string(keyType)); has {
|
||||
if key, has := serverInfo.GetAttribute(attr.PublicScope, attr.ServerKeyZone, string(keyType)); has {
|
||||
serverKeys = append(serverKeys, ServerKey{Type: string(keyType), Key: key})
|
||||
}
|
||||
}
|
||||
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 !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 {
|
||||
if err = peer.AddServer(string(bundle)); err != nil {
|
||||
return features.ConstructResponse(importBundlePrefix, err.Error())
|
||||
}
|
||||
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")
|
||||
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}
|
||||
}
|
||||
|
|
|
@ -2,22 +2,6 @@ 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,8 +5,16 @@ type ServerKey struct {
|
|||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
Onion string `json:"onion"`
|
||||
Status string `json:"status"`
|
||||
Keys []ServerKey `json:"keys"`
|
||||
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"`
|
||||
}
|
||||
|
|
|
@ -0,0 +1,220 @@
|
|||
package servers
|
||||
|
||||
import (
|
||||
"cwtch.im/cwtch/event"
|
||||
"fmt"
|
||||
"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")
|
||||
ServerIntentUpdate = event.Type("ServerIntentUpdate")
|
||||
ServerDeleted = event.Type("ServerDeleted")
|
||||
ServerStatsUpdate = event.Type("ServerStatsUpdate")
|
||||
)
|
||||
|
||||
const (
|
||||
Intent = event.Field("Intent")
|
||||
TotalMessages = event.Field("TotalMessages")
|
||||
Connections = event.Field("Connections")
|
||||
)
|
||||
|
||||
const (
|
||||
IntentRunning = "running"
|
||||
IntentStopped = "stopped"
|
||||
)
|
||||
|
||||
// TODO: move into Cwtch model/attr
|
||||
|
||||
type ServerInfo struct {
|
||||
Onion string
|
||||
ServerBundle 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) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
if appServers == nil {
|
||||
serversDir := filepath.Join(appdir, "servers")
|
||||
err := os.MkdirAll(serversDir, 0700)
|
||||
if err != nil {
|
||||
log.Errorf("Could not init servers directory: %s", err)
|
||||
}
|
||||
appServers = server.NewServers(acn, serversDir)
|
||||
publishFn = pfn
|
||||
}
|
||||
}
|
||||
|
||||
func Disable() {
|
||||
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
|
||||
type ServersFunctionality struct {
|
||||
}
|
||||
|
||||
// ExperimentGate returns ServersFunctionality if the experiment is enabled, and an error otherwise.
|
||||
func ExperimentGate(experimentMap map[string]bool) (*ServersFunctionality, error) {
|
||||
if experimentMap[serversExperiment] {
|
||||
lock.Lock()
|
||||
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
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) CreateServer(password string) (server.Server, error) {
|
||||
return appServers.CreateServer(password)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) DeleteServer(onion string, currentPassword string) error {
|
||||
return appServers.DeleteServer(onion, currentPassword)
|
||||
}
|
||||
|
||||
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) {
|
||||
appServers.StopServer(onion)
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) DestroyServers() {
|
||||
appServers.Destroy()
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) GetServerInfo(onion string) *ServerInfo {
|
||||
s := sf.GetServer(onion)
|
||||
var serverInfo ServerInfo
|
||||
serverInfo.Onion = s.Onion()
|
||||
serverInfo.ServerBundle = s.ServerBundle()
|
||||
serverInfo.Autostart = s.GetAttribute(server.AttrAutostart) == "true"
|
||||
running, _ := s.CheckStatus()
|
||||
serverInfo.Running = running
|
||||
serverInfo.Description = s.GetAttribute(server.AttrDescription)
|
||||
serverInfo.StorageType = s.GetAttribute(server.AttrStorageType)
|
||||
return &serverInfo
|
||||
}
|
||||
|
||||
func (si *ServerInfo) EnrichEvent(e *event.Event) {
|
||||
e.Data["Onion"] = si.Onion
|
||||
e.Data["ServerBundle"] = si.ServerBundle
|
||||
e.Data["Description"] = si.Description
|
||||
e.Data["StorageType"] = si.StorageType
|
||||
if si.Autostart {
|
||||
e.Data["Autostart"] = "true"
|
||||
} else {
|
||||
e.Data["Autostart"] = "false"
|
||||
}
|
||||
if si.Running {
|
||||
e.Data["Running"] = "true"
|
||||
} else {
|
||||
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()
|
||||
}
|
33
go.mod
33
go.mod
|
@ -1,10 +1,31 @@
|
|||
module git.openprivacy.ca/flutter/libcwtch-go
|
||||
module git.openprivacy.ca/cwtch.im/libcwtch-go
|
||||
|
||||
go 1.15
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
cwtch.im/cwtch v0.8.12
|
||||
git.openprivacy.ca/openprivacy/connectivity v1.4.5
|
||||
git.openprivacy.ca/openprivacy/log v1.0.2
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
|
||||
cwtch.im/cwtch v0.18.10
|
||||
git.openprivacy.ca/cwtch.im/server v1.4.5
|
||||
git.openprivacy.ca/openprivacy/connectivity v1.8.6
|
||||
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
|
||||
)
|
216
go.sum
216
go.sum
|
@ -1,92 +1,202 @@
|
|||
cwtch.im/cwtch v0.8.11 h1:nUrd6srjLxInSJ0q1JdmRBhN4RRlZRL+2vIyE/AXkfY=
|
||||
cwtch.im/cwtch v0.8.11/go.mod h1:D9dtO+WnKqdmufKSfFeFlUYaxLTfE/RtqVe1OD0kiKc=
|
||||
cwtch.im/cwtch v0.8.12 h1:U+GG+tgCayVSfxd3OlXCFfSB0qfPWhtdL6gt5V6Z6UE=
|
||||
cwtch.im/cwtch v0.8.12/go.mod h1:EwUUVWIU4OAcz0HmHUxaY4orzKH6ZiNXaVI8y/5UP5k=
|
||||
git.openprivacy.ca/cwtch.im/tapir v0.4.3 h1:sctSfUXHDIqaHfJPDl+5lHtmoEJolQiHTcHZGAe5Qc4=
|
||||
git.openprivacy.ca/cwtch.im/tapir v0.4.3/go.mod h1:10qEaib5x021zgyZ/97JKWsEpedH5+Vfy2CvB2V+08E=
|
||||
git.openprivacy.ca/cwtch.im/tapir v0.4.4 h1:KyuTVmr9GYptTCeR7JDODjmhBBbnIBf9V3NSC4+6bHc=
|
||||
git.openprivacy.ca/cwtch.im/tapir v0.4.4/go.mod h1:qMFTdmDZITc1BLP1jSW0gVpLmvpg+Zjsh5ek8StwbFE=
|
||||
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=
|
||||
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/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.4.4 h1:11M3akVCyy/luuhMpZTM1r9Jayl7IHD944Bxsn2FDpU=
|
||||
git.openprivacy.ca/openprivacy/connectivity v1.4.4/go.mod h1:JVRCIdL+lAG6ohBFWiKeC/MN42nnC0sfFszR9XG6vPQ=
|
||||
git.openprivacy.ca/openprivacy/connectivity v1.4.5 h1:UYMdCWPzEAP7LbqdMXGNXmfKjWlvfnKdmewBtnbgQRI=
|
||||
git.openprivacy.ca/openprivacy/connectivity v1.4.5/go.mod h1:JVRCIdL+lAG6ohBFWiKeC/MN42nnC0sfFszR9XG6vPQ=
|
||||
git.openprivacy.ca/openprivacy/log v1.0.2 h1:HLP4wsw4ljczFAelYnbObIs821z+jgMPCe8uODPnGQM=
|
||||
git.openprivacy.ca/openprivacy/log v1.0.2/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw=
|
||||
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/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/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.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/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/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/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 h1:hLDRPB66XQT/8+wG9WsDpiCvZf1yKO7sz7scAjSlBa0=
|
||||
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/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM=
|
||||
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/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/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.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/struCoder/pidusage v0.1.3 h1:pZcSa6asBE38TJtW0Nui6GeCjLTpaT/jAnNP7dUTLSQ=
|
||||
github.com/struCoder/pidusage v0.1.3/go.mod h1:pWBlW3YuSwRl6h7R5KbvA4N8oOqe9LjaKW5CwT1SPjI=
|
||||
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=
|
||||
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=
|
||||
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/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
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/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/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/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.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
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/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-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
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/sync v0.0.0-20190423024810-112230192c58/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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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-20200202164722-d101bd2416d5/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-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-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-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
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/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-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
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/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-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
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-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/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/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:"
|
||||
|
||||
go list ./... | xargs golint
|
||||
staticcheck ./...
|
||||
|
||||
|
||||
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 "vendor/" | grep -v "go.sum" | grep -v ".idea"
|
||||
misspell . | grep -v "testing/" | grep -v "vendor/" | grep -v "go.sum" | grep -v ".idea"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#!/bin/sh
|
||||
|
||||
sed -i "s/^package cwtch/\/\/package cwtch/" lib.go
|
||||
sed -i "s/^\/\/package main/package main/" lib.go
|
||||
sed -i "s/^\/\/func main()/func main()/" lib.go
|
||||
# using '-i.bak' and 'rm .bak' for gnu sed (linux) and bsd sed (macosx) compat
|
||||
sed -i.bak "s/^package cwtch/\/\/package cwtch/" lib.go
|
||||
sed -i.bak "s/^\/\/package main/package main/" lib.go
|
||||
sed -i.bak "s/^\/\/func main()/func main()/" lib.go
|
||||
rm lib.go.bak
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#!/bin/sh
|
||||
|
||||
sed -i "s/^\/\/package cwtch/package cwtch/" lib.go
|
||||
sed -i "s/^package main/\/\/package main/" lib.go
|
||||
sed -i "s/^func main()/\/\/func main()/" lib.go
|
||||
# using '-i.bak' and 'rm .bak' for gnu sed (linux) and bsd sed (macosx) compat
|
||||
sed -i.bak "s/^\/\/package cwtch/package cwtch/" lib.go
|
||||
sed -i.bak "s/^package main/\/\/package main/" lib.go
|
||||
sed -i.bak "s/^func main()/\/\/func main()/" lib.go
|
||||
rm lib.go.bak
|
||||
|
|
|
@ -1,15 +1,25 @@
|
|||
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"`
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
|
|
@ -1,41 +1,54 @@
|
|||
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"
|
||||
"git.openprivacy.ca/flutter/libcwtch-go/constants"
|
||||
"git.openprivacy.ca/flutter/libcwtch-go/features/groups"
|
||||
constants2 "git.openprivacy.ca/cwtch.im/libcwtch-go/constants"
|
||||
"git.openprivacy.ca/cwtch.im/libcwtch-go/features/groups"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"strconv"
|
||||
|
||||
"time"
|
||||
|
||||
"cwtch.im/cwtch/event"
|
||||
"cwtch.im/cwtch/functionality/filesharing"
|
||||
)
|
||||
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
|
||||
}
|
||||
|
||||
func NewEventHandler() *EventHandler {
|
||||
eh := &EventHandler{app: nil, appBusQueue: event.NewQueue(), profileEvents: make(chan EventProfileEnvelope)}
|
||||
// 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}
|
||||
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)
|
||||
|
@ -44,10 +57,15 @@ 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 {
|
||||
|
@ -56,160 +74,252 @@ func (eh *EventHandler) GetNextEvent() string {
|
|||
select {
|
||||
case e := <-appChan:
|
||||
return eh.handleAppBusEvent(&e)
|
||||
case ev := <-eh.profileEvents:
|
||||
return eh.handleProfileEvent(&ev)
|
||||
default:
|
||||
select {
|
||||
case e := <-appChan:
|
||||
return eh.handleAppBusEvent(&e)
|
||||
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:
|
||||
if e.Data[event.Progress] == "100" {
|
||||
for onion := range eh.app.ListPeers() {
|
||||
// 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()
|
||||
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()
|
||||
}
|
||||
}
|
||||
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 {
|
||||
eh.startHandlingPeer(onion)
|
||||
}
|
||||
|
||||
tag,isTagged := profile.GetAttribute(app.AttributeTag)
|
||||
if isTagged {
|
||||
e.Data[app.AttributeTag] = tag
|
||||
} else {
|
||||
// Assume encrypted for non-tagged profiles - this isn't always true, but all post-beta profiles
|
||||
// are tagged on creation.
|
||||
e.Data[app.AttributeTag] = constants.ProfileTypeV1Password
|
||||
}
|
||||
// CwtchPeer will always set this now...
|
||||
tag, _ := profile.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Tag)
|
||||
e.Data[constants.Tag] = tag
|
||||
|
||||
if e.Data[event.Created] == event.True {
|
||||
name, _ := profile.GetAttribute(attr.GetLocalScope(constants.Name))
|
||||
profile.SetAttribute(attr.GetPublicScope(constants.Name), name)
|
||||
profile.SetAttribute(attr.GetPublicScope(constants.Picture), ImageToString(NewImage(RandomProfileImage(onion), TypeImageDistro)))
|
||||
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.SetAttribute(attr.GetLocalScope(constants.PeerOnline), event.False)
|
||||
eh.app.AddPeerPlugin(onion, plugins.CONNECTIONRETRY)
|
||||
eh.app.AddPeerPlugin(onion, plugins.NETWORKCHECK)
|
||||
|
||||
// If the user has chosen to block unknown profiles
|
||||
// then explicitly configure the protocol engine to do so..
|
||||
if ReadGlobalSettings().BlockUnknownConnections {
|
||||
profile.BlockUnknownConnections()
|
||||
} else {
|
||||
// For completeness
|
||||
profile.AllowUnknownConnections()
|
||||
}
|
||||
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)
|
||||
|
||||
// Start up the Profile
|
||||
profile.Listen()
|
||||
profile.StartPeersConnections()
|
||||
if _, err := groups.ExperimentGate(ReadGlobalSettings().Experiments); err == nil {
|
||||
profile.StartServerConnections()
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
nick, exists := profile.GetAttribute(attr.GetPublicScope(constants.Name))
|
||||
if !exists {
|
||||
nick = onion
|
||||
}
|
||||
|
||||
picVal, ok := profile.GetAttribute(attr.GetPublicScope(constants.Picture))
|
||||
if !ok {
|
||||
picVal = ImageToString(NewImage(RandomProfileImage(onion), TypeImageDistro))
|
||||
}
|
||||
pic, err := StringToImage(picVal)
|
||||
if err != nil {
|
||||
pic = NewImage(RandomProfileImage(onion), TypeImageDistro)
|
||||
}
|
||||
picPath := GetPicturePath(pic)
|
||||
|
||||
//tag, _ := profile.GetAttribute(app.AttributeTag)
|
||||
|
||||
online, _ := profile.GetAttribute(attr.GetLocalScope(constants.PeerOnline))
|
||||
|
||||
e.Data[constants.Name] = nick
|
||||
e.Data[constants.Picture] = picPath
|
||||
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"
|
||||
}
|
||||
e.Data["autostart"] = autostart
|
||||
|
||||
// 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
|
||||
var contacts []Contact
|
||||
var servers []groups.Server
|
||||
for _, contact := range profile.GetContacts() {
|
||||
|
||||
// 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))
|
||||
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
|
||||
}
|
||||
continue
|
||||
|
||||
// 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,
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
contacts = append(contacts, Contact{
|
||||
Name: ph.GetNick(groupId),
|
||||
Onion: group.GroupID,
|
||||
Status: group.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,
|
||||
})
|
||||
}
|
||||
|
||||
bytes, _ := json.Marshal(contacts)
|
||||
|
@ -227,97 +337,302 @@ 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 {
|
||||
peer := eh.app.GetPeer(ev.Profile)
|
||||
ph := NewPeerHelper(peer)
|
||||
profile := eh.app.GetPeer(ev.Profile)
|
||||
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
|
||||
ev.Event.Data["Nick"] = ph.GetNick(ev.Event.Data["RemotePeer"])
|
||||
ev.Event.Data["Picture"] = ph.GetProfilePic(ev.Event.Data["RemotePeer"])
|
||||
case event.NewMessageFromGroup:
|
||||
// only needs contact nickname and picture, for displaying on popup notifications
|
||||
ev.Event.Data["Nick"] = ph.GetNick(ev.Event.Data[event.GroupID])
|
||||
ev.Event.Data["Picture"] = ph.GetProfilePic(ev.Event.Data[event.GroupID])
|
||||
case event.PeerAcknowledgement:
|
||||
// No enrichement required
|
||||
case event.PeerCreated:
|
||||
handle := ev.Event.Data[event.RemotePeer]
|
||||
err := EnrichNewPeer(handle, ph, ev)
|
||||
if err != nil {
|
||||
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))
|
||||
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)
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
log.Errorf("error fetching channel message count %v %v", conversationID, err)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
groupPic := ph.GetProfilePic(ev.Event.Data[event.GroupID])
|
||||
ev.Event.Data["PicturePath"] = groupPic
|
||||
ev.Event.Data["GroupName"] = ph.GetNick(ev.Event.Data[event.GroupID])
|
||||
|
||||
conversationID, _ := strconv.Atoi(ev.Event.Data[event.ConversationID])
|
||||
conversationInfo, _ := profile.GetConversationInfo(conversationID)
|
||||
acl, _ := json.Marshal(conversationInfo.ACL)
|
||||
ev.Event.Data["accessControlList"] = string(acl)
|
||||
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 := ph.GetProfilePic(invite.GroupID)
|
||||
ev.Event.Data["PicturePath"] = groupPic
|
||||
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)
|
||||
} 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.openprivcy.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.openprivacy.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])
|
||||
|
||||
if cxnState == connections.AUTHENTICATED && contact == nil {
|
||||
peer.AddContact(ev.Event.Data[event.RemotePeer], ev.Event.Data[event.RemotePeer], model.AuthUnknown)
|
||||
// skip events the UI doesn't act on
|
||||
if cxnState == connections.CONNECTING || cxnState == connections.CONNECTED {
|
||||
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
|
||||
peer.SendGetValToPeer(ev.Event.Data[event.RemotePeer], attr.PublicScope, constants.Name)
|
||||
peer.SendGetValToPeer(ev.Event.Data[event.RemotePeer], attr.PublicScope, constants.Picture)
|
||||
profile.SendScopedZonedGetValToContact(contact.ID, attr.PublicScope, attr.ProfileZone, constants.Name)
|
||||
profile.SendScopedZonedGetValToContact(contact.ID, attr.PublicScope, attr.ProfileZone, constants.CustomProfileImageKey)
|
||||
}
|
||||
}
|
||||
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])
|
||||
|
||||
if exists && scope == attr.PublicScope {
|
||||
if _, exists := peer.GetContactAttribute(onion, attr.GetLocalScope(path)); exists {
|
||||
// we have a locally set ovverride, don't pass this remote set public scope update to UI
|
||||
return ""
|
||||
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 ""
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -334,29 +649,32 @@ 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.GroupCreated, q)
|
||||
eventBus.Subscribe(event.NewGroup, q)
|
||||
eventBus.Subscribe(event.AcceptGroupInvite, 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)
|
||||
eventBus.Subscribe(event.ManifestReceived, q)
|
||||
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)
|
||||
|
||||
}
|
||||
|
@ -374,6 +692,65 @@ 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
368
utils/manager.go
368
utils/manager.go
|
@ -1,368 +0,0 @@
|
|||
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/flutter/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
|
||||
}
|
||||
|
||||
/*
|
||||
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 + "]"
|
||||
// re-request
|
||||
p.peer.SendGetValToPeer(id, attr.PublicScope, 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())
|
||||
}
|
||||
|
||||
/*
|
||||
// AddProfile adds a new profile to the UI
|
||||
func AddProfile(gcd *GrandCentralDispatcher, handle string) {
|
||||
p := the.CwtchApp.GetPeer(handle)
|
||||
if p != nil {
|
||||
nick, exists := p.GetAttribute(attr.GetPublicScope(constants.Name))
|
||||
if !exists {
|
||||
nick = handle
|
||||
}
|
||||
|
||||
picVal, ok := p.GetAttribute(attr.GetPublicScope(constants.Picture))
|
||||
if !ok {
|
||||
picVal = ImageToString(NewImage(RandomProfileImage(handle), TypeImageDistro))
|
||||
}
|
||||
pic, err := StringToImage(picVal)
|
||||
if err != nil {
|
||||
pic = NewImage(RandomProfileImage(handle), TypeImageDistro)
|
||||
}
|
||||
picPath := getPicturePath(pic)
|
||||
|
||||
tag, _ := p.GetAttribute(app.AttributeTag)
|
||||
|
||||
online, _ := p.GetAttribute(attr.GetLocalScope(constants.PeerOnline))
|
||||
|
||||
log.Debugf("AddProfile %v %v %v %v %v\n", handle, nick, picPath, tag, online)
|
||||
gcd.AddProfile(handle, nick, picPath, tag, online == event.True)
|
||||
}
|
||||
}*/
|
||||
/*
|
||||
type manager struct {
|
||||
gcd *GrandCentralDispatcher
|
||||
profile string
|
||||
}
|
||||
|
||||
// Manager is a middleware helper for entities like peer event listeners wishing to trigger ui changes (via the gcd)
|
||||
// each manager is for one profile/peer
|
||||
// manager takes minimal arguments and builds the full struct of data (usually pulled from a cwtch peer) required to call the GCD to perform the ui action
|
||||
// manager also performs call filtering based on UI state: users of manager can safely always call it on events and not have to worry about weather the relevant ui is active
|
||||
// ie: you can always safely call AddMessage even if in the ui a different profile is selected. manager will check with gcd, and if the correct conditions are not met, it will not call on gcd to update the ui incorrectly
|
||||
type Manager interface {
|
||||
Acknowledge(handle, mID string)
|
||||
AddContact(Handle string)
|
||||
AddSendMessageError(peer string, signature string, err string)
|
||||
AddMessage(handle string, from string, message string, fromMe bool, messageID string, timestamp time.Time, Acknowledged bool)
|
||||
|
||||
ReloadProfiles()
|
||||
|
||||
UpdateContactDisplayName(handle string)
|
||||
UpdateContactPicture(handle string)
|
||||
UpdateContactStatus(handle string, status int, loading bool)
|
||||
UpdateContactAttribute(handle, key, value string)
|
||||
|
||||
ChangePasswordResponse(error bool)
|
||||
|
||||
AboutToAddMessage()
|
||||
MessageJustAdded()
|
||||
StoreAndNotify(peer.CwtchPeer, string, string, time.Time, string)
|
||||
|
||||
UpdateNetworkStatus(online bool)
|
||||
}
|
||||
|
||||
// NewManager returns a new Manager interface for a profile to the gcd
|
||||
func NewManager(profile string, gcd *GrandCentralDispatcher) Manager {
|
||||
return &manager{gcd: gcd, profile: profile}
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
// 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["nick"] = ph.GetNick(handle)
|
||||
ev.Event.Data["status"] = strconv.Itoa(int(connections.ConnectionStateToType()[group.State]))
|
||||
ev.Event.Data["authorization"] = string(model.AuthApproved)
|
||||
ev.Event.Data["loading"] = "false"
|
||||
ev.Event.Data["lastMsgTime"] = strconv.Itoa(getLastMessageTime(&group.Timeline))
|
||||
}
|
||||
} 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
|
||||
}
|
||||
|
||||
/*
|
||||
// AddSendMessageError adds an error not and icon to a message in a conversation in the ui for the message identified by the peer/sig combo
|
||||
func (this *manager) AddSendMessageError(peer string, signature string, err string) {
|
||||
this.gcd.DoIfProfile(this.profile, func() {
|
||||
this.gcd.DoIfConversation(peer, func() {
|
||||
log.Debugf("Received Error Sending Message: %v", err)
|
||||
// FIXME: Sometimes, for the first Peer message we send our error beats our message to the UI
|
||||
time.Sleep(time.Second * 1)
|
||||
this.gcd.GroupSendError(signature, err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (this *manager) AboutToAddMessage() {
|
||||
this.gcd.TimelineInterface.AddMessage(this.gcd.TimelineInterface.num())
|
||||
}
|
||||
|
||||
func (this *manager) MessageJustAdded() {
|
||||
this.gcd.TimelineInterface.RequestEIR()
|
||||
}*/
|
||||
|
||||
/*
|
||||
// AddMessage adds a message to the message pane for the supplied conversation if it is active
|
||||
func (this *manager) AddMessage(handle string, from string, message string, fromMe bool, messageID string, timestamp time.Time, Acknowledged bool) {
|
||||
this.gcd.DoIfProfile(this.profile, func() {
|
||||
this.gcd.DoIfConversation(handle, func() {
|
||||
updateLastReadTime(handle)
|
||||
// If the message is not from the user then add it, otherwise, just acknowledge.
|
||||
if !fromMe || !Acknowledged {
|
||||
this.gcd.TimelineInterface.AddMessage(this.gcd.TimelineInterface.num() - 1)
|
||||
this.gcd.TimelineInterface.RequestEIR()
|
||||
} else {
|
||||
this.gcd.Acknowledged(messageID)
|
||||
}
|
||||
})
|
||||
this.gcd.IncContactUnreadCount(handle)
|
||||
})
|
||||
if !fromMe {
|
||||
this.gcd.Notify(handle)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *manager) ReloadProfiles() {
|
||||
this.gcd.reloadProfileList()
|
||||
}
|
||||
|
||||
// UpdateContactDisplayName updates a contact's display name in the contact list and conversations
|
||||
func (this *manager) UpdateContactDisplayName(handle string) {
|
||||
this.gcd.DoIfProfile(this.profile, func() {
|
||||
this.gcd.UpdateContactDisplayName(handle, GetNick(handle))
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateContactPicture updates a contact's picture in the contact list and conversations
|
||||
func (this *manager) UpdateContactPicture(handle string) {
|
||||
this.gcd.DoIfProfile(this.profile, func() {
|
||||
this.gcd.UpdateContactPicture(handle, GetProfilePic(handle))
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateContactAttribute update's a contacts attribute in the ui
|
||||
func (this *manager) UpdateContactAttribute(handle, key, value string) {
|
||||
this.gcd.DoIfProfile(this.profile, func() {
|
||||
this.gcd.UpdateContactAttribute(handle, key, value)
|
||||
})
|
||||
}
|
||||
|
||||
func (this *manager) ChangePasswordResponse(error bool) {
|
||||
this.gcd.ChangePasswordResponse(error)
|
||||
}
|
||||
|
||||
func (this *manager) UpdateNetworkStatus(online bool) {
|
||||
this.gcd.UpdateProfileNetworkStatus(this.profile, online)
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,51 @@
|
|||
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,14 +1,17 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"git.openprivacy.ca/cwtch.im/libcwtch-go/constants"
|
||||
path "path/filepath"
|
||||
"sync"
|
||||
|
||||
"cwtch.im/cwtch/event"
|
||||
"cwtch.im/cwtch/storage/v1"
|
||||
|
||||
"encoding/json"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -18,21 +21,43 @@ const (
|
|||
)
|
||||
|
||||
var GlobalSettingsFile v1.FileStore
|
||||
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{
|
||||
|
@ -40,17 +65,30 @@ var DefaultGlobalSettings = GlobalSettings{
|
|||
Theme: "dark",
|
||||
PreviousPid: -1,
|
||||
ExperimentsEnabled: false,
|
||||
Experiments: make(map[string]bool),
|
||||
Experiments: map[string]bool{constants.MessageFormattingExperiment: true},
|
||||
StateRootPane: 0,
|
||||
FirstTime: true,
|
||||
BlockUnknownConnections: false,
|
||||
UIColumnModePortrait: "DualpaneMode.Single",
|
||||
UIColumnModeLandscape: "DualpaneMode.CopyPortrait",
|
||||
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 := ioutil.ReadFile(path.Join(directory, saltFile))
|
||||
salt, err := os.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
|
||||
|
@ -60,7 +98,7 @@ func InitGlobalSettingsFile(directory string, password string) error {
|
|||
return err
|
||||
}
|
||||
os.Mkdir(directory, 0700)
|
||||
err := ioutil.WriteFile(path.Join(directory, saltFile), newSalt[:], 0600)
|
||||
err := os.WriteFile(path.Join(directory, saltFile), newSalt[:], 0600)
|
||||
if err != nil {
|
||||
log.Errorf("Could not write salt file: %v", err)
|
||||
return err
|
||||
|
@ -75,6 +113,8 @@ func InitGlobalSettingsFile(directory string, password string) error {
|
|||
}
|
||||
|
||||
func ReadGlobalSettings() *GlobalSettings {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
settings := DefaultGlobalSettings
|
||||
|
||||
if GlobalSettingsFile == nil {
|
||||
|
@ -100,6 +140,8 @@ func ReadGlobalSettings() *GlobalSettings {
|
|||
}
|
||||
|
||||
func WriteGlobalSettings(globalSettings GlobalSettings) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
bytes, _ := json.Marshal(globalSettings)
|
||||
// override first time setting
|
||||
globalSettings.FirstTime = true
|
||||
|
|
|
@ -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: %v %v %v\n", onion, err, barr)
|
||||
return "extra/openprivacy.png"
|
||||
log.Errorf("error finding image for profile: %v %v %v\n", onion, err, barr)
|
||||
return "assets/extra/openprivacy.png"
|
||||
}
|
||||
return "profiles/" + choices[int(barr[33])%len(choices)] + ".png"
|
||||
return "assets/profiles/" + choices[int(barr[33])%len(choices)] + ".png"
|
||||
}
|
||||
|
||||
func RandomGroupImage(handle string) string {
|
||||
choices := []string{"001-borobudur", "002-opera-house", "003-burj-al-arab", "004-chrysler", "005-acropolis", "006-empire-state-building", "007-temple", "008-indonesia-1", "009-new-zealand", "010-notre-dame", "011-space-needle", "012-seoul", "013-mosque", "014-milan", "015-statue", "016-pyramid", "017-cologne", "018-brandenburg-gate", "019-berlin-cathedral", "020-hungarian-parliament", "021-buckingham", "022-thailand", "023-independence", "024-angkor-wat", "025-vaticano", "026-christ-the-redeemer", "027-colosseum", "028-golden-gate-bridge", "029-sphinx", "030-statue-of-liberty", "031-cradle-of-humankind", "032-istanbul", "033-london-eye", "034-sagrada-familia", "035-tower-bridge", "036-burj-khalifa", "037-washington", "038-big-ben", "039-stonehenge", "040-white-house", "041-ahu-tongariki", "042-capitol", "043-eiffel-tower", "044-church-of-the-savior-on-spilled-blood", "045-arc-de-triomphe", "046-windmill", "047-louvre", "048-torii-gate", "049-petronas", "050-matsumoto-castle", "051-fuji", "052-temple-of-heaven", "053-pagoda", "054-chichen-itza", "055-forbidden-city", "056-merlion", "057-great-wall-of-china", "058-taj-mahal", "059-pisa", "060-indonesia"}
|
||||
barr, err := hex.DecodeString(handle)
|
||||
if err != nil || len(barr) == 0 {
|
||||
log.Errorf("error: %v %v %v\n", handle, err, barr)
|
||||
return "extra/openprivacy.png"
|
||||
log.Errorf("error finding image for group: %v %v %v\n", handle, err, barr)
|
||||
return "assets/extra/openprivacy.png"
|
||||
}
|
||||
return "servers/" + choices[int(barr[0])%len(choices)] + ".png"
|
||||
return "assets/servers/" + choices[int(barr[0])%len(choices)] + ".png"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue