app suports multiple peers; cli supports multiple peers; massive cli command change (irc /style) and live follow groups
This commit is contained in:
parent
0351de1ff1
commit
5f046e6d53
136
app/app.go
136
app/app.go
|
@ -1,27 +1,44 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"cwtch.im/cwtch/connectivity/tor"
|
"cwtch.im/cwtch/connectivity/tor"
|
||||||
"cwtch.im/cwtch/peer"
|
"cwtch.im/cwtch/peer"
|
||||||
"errors"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Application is a facade over a cwtchPeer that provides some wrapping logic.
|
type application struct {
|
||||||
type Application struct {
|
peers map[string]peer.CwtchPeer
|
||||||
Peer peer.CwtchPeerInterface
|
torManager *tor.Manager
|
||||||
TorManager *tor.Manager
|
|
||||||
directory string
|
directory string
|
||||||
|
mutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Application is a full cwtch peer application. It allows management, usage and storage of multiple peers
|
||||||
|
type Application interface {
|
||||||
|
LoadProfiles(password string) error
|
||||||
|
CreatePeer(name string, password string) (peer.CwtchPeer, error)
|
||||||
|
|
||||||
|
GetPeer(onion string) peer.CwtchPeer
|
||||||
|
ListPeers() map[string]string
|
||||||
|
|
||||||
|
Shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewApp creates a new app with some environment awareness and initializes a Tor Manager
|
// NewApp creates a new app with some environment awareness and initializes a Tor Manager
|
||||||
func NewApp(appDirectory string, torPath string) (*Application, error) {
|
func NewApp(appDirectory string, torPath string) (Application, error) {
|
||||||
log.Printf("NewApp(%v, %v)\n", appDirectory, torPath)
|
log.Printf("NewApp(%v, %v)\n", appDirectory, torPath)
|
||||||
app := &Application{Peer: nil, directory: appDirectory}
|
app := &application{peers: make(map[string]peer.CwtchPeer), directory: appDirectory}
|
||||||
os.MkdirAll(path.Join(appDirectory, "tor"), 0700)
|
os.MkdirAll(path.Join(appDirectory, "tor"), 0700)
|
||||||
|
os.Mkdir(path.Join(app.directory, "profiles"), 0700)
|
||||||
|
|
||||||
err := app.startTor(torPath)
|
err := app.startTor(torPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -29,22 +46,64 @@ func NewApp(appDirectory string, torPath string) (*Application, error) {
|
||||||
return app, nil
|
return app, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func generateRandomFilename() string {
|
||||||
|
randBytes := make([]byte, 16)
|
||||||
|
rand.Read(randBytes)
|
||||||
|
return filepath.Join(hex.EncodeToString(randBytes))
|
||||||
|
}
|
||||||
|
|
||||||
// NewProfile creates a new cwtchPeer with a given name.
|
// NewProfile creates a new cwtchPeer with a given name.
|
||||||
func (app *Application) NewProfile(name string, password string) error {
|
func (app *application) CreatePeer(name string, password string) (peer.CwtchPeer, error) {
|
||||||
log.Printf("NewProfile(%v, %v)\n", name, password)
|
log.Printf("CreatePeer(%v)\n", name)
|
||||||
if app.Peer != nil {
|
|
||||||
return errors.New("Profile already created")
|
randomFileName := generateRandomFilename()
|
||||||
|
p := peer.NewCwtchPeer(name, password, path.Join(app.directory, "profiles", randomFileName))
|
||||||
|
err := p.Save()
|
||||||
|
if err != nil {
|
||||||
|
p.Shutdown() //attempt
|
||||||
|
return nil, fmt.Errorf("Error attempting to save new profile: %v", err)
|
||||||
}
|
}
|
||||||
app.Peer = peer.NewCwtchPeer(name, password, path.Join(app.directory, name+".json"))
|
app.startPeer(p)
|
||||||
err := app.Peer.Save()
|
|
||||||
if err == nil {
|
_, exists := app.peers[p.GetProfile().Onion]
|
||||||
err = app.startPeer()
|
if exists {
|
||||||
|
p.Shutdown()
|
||||||
|
return nil, fmt.Errorf("Error: profile for onion %v already exists", p.GetProfile().Onion)
|
||||||
}
|
}
|
||||||
return err
|
app.mutex.Lock()
|
||||||
|
app.peers[p.GetProfile().Onion] = p
|
||||||
|
app.mutex.Unlock()
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *application) LoadProfiles(password string) error {
|
||||||
|
files, err := ioutil.ReadDir(path.Join(app.directory, "profiles"))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error: cannot read profiles directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
p, err := peer.LoadCwtchPeer(path.Join(app.directory, "profiles", file.Name()), password)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, exists := app.peers[p.GetProfile().Onion]
|
||||||
|
if exists {
|
||||||
|
p.Shutdown()
|
||||||
|
log.Printf("Error: profile for onion %v already exists", p.GetProfile().Onion)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
app.startPeer(p)
|
||||||
|
app.mutex.Lock()
|
||||||
|
app.peers[p.GetProfile().Onion] = p
|
||||||
|
app.mutex.Unlock()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// startTor will create a local torrc if needed
|
// startTor will create a local torrc if needed
|
||||||
func (app *Application) startTor(torPath string) error {
|
func (app *application) startTor(torPath string) error {
|
||||||
// Creating a local cwtch tor server config for the user
|
// Creating a local cwtch tor server config for the user
|
||||||
// creating $app.directory/torrc file
|
// creating $app.directory/torrc file
|
||||||
// SOCKSPort socksPort
|
// SOCKSPort socksPort
|
||||||
|
@ -64,35 +123,40 @@ func (app *Application) startTor(torPath string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
app.TorManager = tm
|
app.torManager = tm
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetProfile loads an existing profile from the given filename.
|
func (app *application) startPeer(peer peer.CwtchPeer) {
|
||||||
func (app *Application) SetProfile(filename string, password string) error {
|
|
||||||
if app.Peer == nil {
|
|
||||||
profile, err := peer.LoadCwtchPeer(path.Join(app.directory, filename), password)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
app.Peer = profile
|
|
||||||
return app.startPeer()
|
|
||||||
}
|
|
||||||
return errors.New("profile is already loaded, to load a different profile you will need to restart the application")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (app *Application) startPeer() error {
|
|
||||||
go func() {
|
go func() {
|
||||||
e := app.Peer.Listen()
|
e := peer.Listen()
|
||||||
if e != nil {
|
if e != nil {
|
||||||
log.Panic(e)
|
log.Panic(e)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListPeers returns a map of onions to their profile's Name
|
||||||
|
func (app *application) ListPeers() map[string]string {
|
||||||
|
keys := map[string]string{}
|
||||||
|
for k, p := range app.peers {
|
||||||
|
keys[k] = p.GetProfile().Name
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPeer returns a Peer for a given onion address
|
||||||
|
func (app *application) GetPeer(onion string) peer.CwtchPeer {
|
||||||
|
if peer, ok := app.peers[onion]; ok {
|
||||||
|
return peer
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PeerRequest attempts to setup peer relationship with the given onion address.`
|
// Shutdown shutsdown all peers of an app and then the tormanager
|
||||||
func (app *Application) PeerRequest(onion string) {
|
func (app *application) Shutdown() {
|
||||||
app.Peer.PeerWithOnion(onion)
|
for _, peer := range app.peers {
|
||||||
|
peer.Shutdown()
|
||||||
|
}
|
||||||
|
app.torManager.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
446
app/cli/main.go
446
app/cli/main.go
|
@ -2,64 +2,130 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
app2 "cwtch.im/cwtch/app"
|
app2 "cwtch.im/cwtch/app"
|
||||||
"fmt"
|
peer2 "cwtch.im/cwtch/peer"
|
||||||
|
|
||||||
"github.com/c-bata/go-prompt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"cwtch.im/cwtch/model"
|
||||||
|
"cwtch.im/cwtch/peer/connections"
|
||||||
|
"fmt"
|
||||||
|
"github.com/c-bata/go-prompt"
|
||||||
"golang.org/x/crypto/ssh/terminal"
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var app *app2.Application
|
var app app2.Application
|
||||||
|
var peer peer2.CwtchPeer
|
||||||
|
var group *model.Group
|
||||||
|
var groupFollowBreakChan chan bool
|
||||||
|
var prmpt string
|
||||||
|
|
||||||
var suggestions = []prompt.Suggest{
|
var suggestionsBase = []prompt.Suggest{
|
||||||
{Text: "new-profile", Description: "create a new profile in ~/.cwtch/$USERNAME.json"},
|
{Text: "/new-profile", Description: "create a new profile"},
|
||||||
{Text: "load-profile", Description: "load a new profile"},
|
{Text: "/load-profiles", Description: "loads profiles with a password"},
|
||||||
{Text: "quit", Description: "quit cwtch"},
|
{Text: "/list-profiles", Description: "list active profiles"},
|
||||||
{Text: "info", Description: "show user info"},
|
{Text: "/select-profile", Description: "selects an active profile to use"},
|
||||||
{Text: "servers", Description: "retrieve a list of servers and their connection status"},
|
{Text: "/help", Description: "print list of commands"},
|
||||||
{Text: "peers", Description: "retrieve a list of peers and their connection status"},
|
{Text: "/quit", Description: "quit cwtch"},
|
||||||
{Text: "contacts", Description: "retrieve a list of contacts"},
|
|
||||||
{Text: "groups", Description: "retrieve a list of groups"},
|
|
||||||
{Text: "export-group", Description: "export a group invite: prints as a string"},
|
|
||||||
{Text: "send", Description: "send a message to a group"},
|
|
||||||
{Text: "timeline", Description: "read the timeline of a given group"},
|
|
||||||
{Text: "accept-invite", Description: "accept the invite of a group"},
|
|
||||||
{Text: "invite", Description: "invite a new contact"},
|
|
||||||
{Text: "invite-to-group", Description: "invite an existing contact to join an existing group"},
|
|
||||||
{Text: "new-group", Description: "create a new group"},
|
|
||||||
{Text: "help", Description: "print list of commands"},
|
|
||||||
{Text: "trust", Description: "trust a peer"},
|
|
||||||
{Text: "block", Description: "block a peer - you will no longer see messages or connect to this peer"},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var suggestionsSelectedProfile = []prompt.Suggest{
|
||||||
|
{Text: "/info", Description: "show user info"},
|
||||||
|
{Text: "/list-contacts", Description: "retrieve a list of contacts"},
|
||||||
|
{Text: "/list-groups", Description: "retrieve a list of groups"},
|
||||||
|
{Text: "/new-group", Description: "create a new group on a server"},
|
||||||
|
{Text: "/select-group", Description: "selects a group to follow"},
|
||||||
|
{Text: "/unselect-group", Description: "stop following the current group"},
|
||||||
|
{Text: "/invite", Description: "invite a new contact"},
|
||||||
|
{Text: "/invite-to-group", Description: "invite an existing contact to join an existing group"},
|
||||||
|
{Text: "/accept-invite", Description: "accept the invite of a group"},
|
||||||
|
{Text: "/list-servers", Description: "retrieve a list of servers and their connection status"},
|
||||||
|
{Text: "/list-peers", Description: "retrieve a list of peers and their connection status"},
|
||||||
|
{Text: "/export-group", Description: "export a group invite: prints as a string"},
|
||||||
|
{Text: "/trust", Description: "trust a peer"},
|
||||||
|
{Text: "/block", Description: "block a peer - you will no longer see messages or connect to this peer"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var suggestions = suggestionsBase
|
||||||
|
|
||||||
var usages = map[string]string{
|
var usages = map[string]string{
|
||||||
"new-profile": "new-profile [name]",
|
"/new-profile": "/new-profile [name]",
|
||||||
"load-profile": "load-profile [filename]",
|
"/load-profiles": "/load-profiles",
|
||||||
"quit": "",
|
"/list-profiles": "",
|
||||||
"servers": "",
|
"/select-profile": "/select-profile [onion]",
|
||||||
"peers": "",
|
"/quit": "",
|
||||||
"contacts": "",
|
"/list-servers": "",
|
||||||
"groups": "",
|
"/list-peers": "",
|
||||||
"export-group": "export-group [groupid]",
|
"/list-contacts": "",
|
||||||
"info": "",
|
"/list-groups": "",
|
||||||
"send": "send [groupid] [message]",
|
"/select-group": "/select-group [groupid]",
|
||||||
"timeline": "timeline [groupid]",
|
"/unselect-group": "",
|
||||||
"accept-invite": "accept-invite [groupid]",
|
"/export-group": "/export-group [groupid]",
|
||||||
"invite": "invite [peerid]",
|
"/info": "",
|
||||||
"invite-to-group": "invite-to-group [peerid] [groupid]",
|
"/send": "/send [groupid] [message]",
|
||||||
"new-group": "new-group [server]",
|
"/timeline": "/timeline [groupid]",
|
||||||
"help": "",
|
"/accept-invite": "/accept-invite [groupid]",
|
||||||
"trust": "trust [peerid]",
|
"/invite": "/invite [peerid]",
|
||||||
"block": "block [peerid]",
|
"/invite-to-group": "/invite-to-group [peerid] [groupid]",
|
||||||
|
"/new-group": "/new-group [server]",
|
||||||
|
"/help": "",
|
||||||
|
"/trust": "/trust [peerid]",
|
||||||
|
"/block": "/block [peerid]",
|
||||||
|
}
|
||||||
|
|
||||||
|
func printMessage(m model.Message) {
|
||||||
|
verified := "not-verified"
|
||||||
|
if m.Verified {
|
||||||
|
verified = "verified"
|
||||||
|
}
|
||||||
|
|
||||||
|
p := peer.GetContact(m.PeerID)
|
||||||
|
name := "unknown"
|
||||||
|
if p != nil {
|
||||||
|
name = p.Name
|
||||||
|
} else if peer.GetProfile().Onion == m.PeerID {
|
||||||
|
name = peer.GetProfile().Name
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%v %v (%v): %v [%s]\n", m.Timestamp, name, m.PeerID, m.Message, verified)
|
||||||
|
}
|
||||||
|
|
||||||
|
func startGroupFollow() {
|
||||||
|
groupFollowBreakChan = make(chan bool)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
l := len(group.Timeline.GetMessages())
|
||||||
|
select {
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
if group == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gms := group.Timeline.GetMessages()
|
||||||
|
if len(gms) != l {
|
||||||
|
fmt.Printf("\n")
|
||||||
|
for ; l < len(gms); l++ {
|
||||||
|
printMessage(gms[l])
|
||||||
|
}
|
||||||
|
fmt.Printf(prmpt)
|
||||||
|
}
|
||||||
|
case <-groupFollowBreakChan:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopGroupFollow() {
|
||||||
|
if group != nil {
|
||||||
|
groupFollowBreakChan <- true
|
||||||
|
group = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func completer(d prompt.Document) []prompt.Suggest {
|
func completer(d prompt.Document) []prompt.Suggest {
|
||||||
|
@ -70,46 +136,62 @@ func completer(d prompt.Document) []prompt.Suggest {
|
||||||
return prompt.FilterHasPrefix(suggestions, d.GetWordBeforeCursor(), true)
|
return prompt.FilterHasPrefix(suggestions, d.GetWordBeforeCursor(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if app.Peer == nil {
|
w := d.CurrentLine()
|
||||||
|
|
||||||
|
// Suggest a profile id
|
||||||
|
if strings.HasPrefix(w, "/select-profile") {
|
||||||
|
s = []prompt.Suggest{}
|
||||||
|
peerlist := app.ListPeers()
|
||||||
|
for onion, peername := range peerlist {
|
||||||
|
s = append(s, prompt.Suggest{Text: onion, Description: peername})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if peer == nil {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
w := d.CurrentLine()
|
// Suggest groupid
|
||||||
if strings.HasPrefix(w, "send") || strings.HasPrefix(w, "timeline") || strings.HasPrefix(w, "export-group") {
|
if /*strings.HasPrefix(w, "send") || strings.HasPrefix(w, "timeline") ||*/ strings.HasPrefix(w, "/export-group") || strings.HasPrefix(w, "/select-group") {
|
||||||
s = []prompt.Suggest{}
|
s = []prompt.Suggest{}
|
||||||
groups := app.Peer.GetGroups()
|
groups := peer.GetGroups()
|
||||||
for _, groupID := range groups {
|
for _, groupID := range groups {
|
||||||
group := app.Peer.GetGroup(groupID)
|
group := peer.GetGroup(groupID)
|
||||||
s = append(s, prompt.Suggest{Text: group.GroupID, Description: "Group owned by " + group.Owner + " on " + group.GroupServer})
|
s = append(s, prompt.Suggest{Text: group.GroupID, Description: "Group owned by " + group.Owner + " on " + group.GroupServer})
|
||||||
}
|
}
|
||||||
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
|
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(w, "block") || strings.HasPrefix(w, "trust") {
|
// Suggest unaccepted group
|
||||||
|
if strings.HasPrefix(w, "/accept-invite") {
|
||||||
s = []prompt.Suggest{}
|
s = []prompt.Suggest{}
|
||||||
contacts := app.Peer.GetContacts()
|
groups := peer.GetGroups()
|
||||||
for _, onion := range contacts {
|
for _, groupID := range groups {
|
||||||
contact := app.Peer.GetContact(onion)
|
group := peer.GetGroup(groupID)
|
||||||
s = append(s, prompt.Suggest{Text: contact.Onion, Description: contact.Name})
|
if group.Accepted == false {
|
||||||
|
s = append(s, prompt.Suggest{Text: group.GroupID, Description: "Group owned by " + group.Owner + " on " + group.GroupServer})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
|
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(w, "invite-to-group") {
|
// suggest peerid AND groupid
|
||||||
|
if strings.HasPrefix(w, "/invite-to-group") {
|
||||||
|
|
||||||
if d.FindStartOfPreviousWordWithSpace() == 0 {
|
if d.FindStartOfPreviousWordWithSpace() == 0 {
|
||||||
s = []prompt.Suggest{}
|
s = []prompt.Suggest{}
|
||||||
contacts := app.Peer.GetContacts()
|
contacts := peer.GetContacts()
|
||||||
for _, onion := range contacts {
|
for _, onion := range contacts {
|
||||||
contact := app.Peer.GetContact(onion)
|
contact := peer.GetContact(onion)
|
||||||
s = append(s, prompt.Suggest{Text: contact.Onion, Description: contact.Name})
|
s = append(s, prompt.Suggest{Text: contact.Onion, Description: contact.Name})
|
||||||
}
|
}
|
||||||
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
|
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
s = []prompt.Suggest{}
|
s = []prompt.Suggest{}
|
||||||
groups := app.Peer.GetGroups()
|
groups := peer.GetGroups()
|
||||||
for _, groupID := range groups {
|
for _, groupID := range groups {
|
||||||
group := app.Peer.GetGroup(groupID)
|
group := peer.GetGroup(groupID)
|
||||||
if group.Owner == "self" {
|
if group.Owner == "self" {
|
||||||
s = append(s, prompt.Suggest{Text: group.GroupID, Description: "Group owned by " + group.Owner + " on " + group.GroupServer})
|
s = append(s, prompt.Suggest{Text: group.GroupID, Description: "Group owned by " + group.Owner + " on " + group.GroupServer})
|
||||||
}
|
}
|
||||||
|
@ -117,14 +199,13 @@ func completer(d prompt.Document) []prompt.Suggest {
|
||||||
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
|
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(w, "accept-invite") {
|
// Suggest contact onion / peerid
|
||||||
|
if strings.HasPrefix(w, "/block") || strings.HasPrefix(w, "/trust") || strings.HasPrefix(w, "/invite") {
|
||||||
s = []prompt.Suggest{}
|
s = []prompt.Suggest{}
|
||||||
groups := app.Peer.GetGroups()
|
contacts := peer.GetContacts()
|
||||||
for _, groupID := range groups {
|
for _, onion := range contacts {
|
||||||
group := app.Peer.GetGroup(groupID)
|
contact := peer.GetContact(onion)
|
||||||
if group.Accepted == false {
|
s = append(s, prompt.Suggest{Text: contact.Onion, Description: contact.Name})
|
||||||
s = append(s, prompt.Suggest{Text: group.GroupID, Description: "Group owned by " + group.Owner + " on " + group.GroupServer})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
|
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
|
||||||
}
|
}
|
||||||
|
@ -188,13 +269,21 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error initializing application: %v", err)
|
log.Fatalf("Error initializing application: %v", err)
|
||||||
}
|
}
|
||||||
|
fmt.Printf("\nWelcome to Cwtch!\n")
|
||||||
|
fmt.Printf("If this if your first time you should create a profile by running `/new-profile`\n")
|
||||||
|
fmt.Printf("`/load-profiles` will prompt you for a password and load profiles from storage\n")
|
||||||
|
fmt.Printf("`/help` will show you other available commands\n")
|
||||||
|
fmt.Printf("There is full [TAB] completion support\n\n")
|
||||||
|
|
||||||
var history []string
|
var history []string
|
||||||
for !quit {
|
for !quit {
|
||||||
profile := "unset"
|
|
||||||
if app.Peer != nil {
|
prmpt = "cwtch> "
|
||||||
profile = app.Peer.GetProfile().Name
|
if group != nil {
|
||||||
|
prmpt = fmt.Sprintf("cwtch %v (%v) [%v] say> ", peer.GetProfile().Name, peer.GetProfile().Onion, group.GroupID)
|
||||||
|
} else if peer != nil {
|
||||||
|
prmpt = fmt.Sprintf("cwtch %v (%v)> ", peer.GetProfile().Name, peer.GetProfile().Onion)
|
||||||
}
|
}
|
||||||
prmpt := fmt.Sprintf("cwtch [%v]> ", profile)
|
|
||||||
|
|
||||||
text := prompt.Input(prmpt, completer, prompt.OptionSuggestionBGColor(prompt.Purple),
|
text := prompt.Input(prmpt, completer, prompt.OptionSuggestionBGColor(prompt.Purple),
|
||||||
prompt.OptionDescriptionBGColor(prompt.White),
|
prompt.OptionDescriptionBGColor(prompt.White),
|
||||||
|
@ -203,20 +292,28 @@ func main() {
|
||||||
commands := strings.Split(text[0:], " ")
|
commands := strings.Split(text[0:], " ")
|
||||||
history = append(history, text)
|
history = append(history, text)
|
||||||
|
|
||||||
if app.Peer == nil {
|
if peer == nil {
|
||||||
if commands[0] != "help" && commands[0] != "quit" && commands[0] != "new-profile" && commands[0] != "load-profile" {
|
if commands[0] != "/help" && commands[0] != "/quit" && commands[0] != "/new-profile" && commands[0] != "/load-profiles" && commands[0] != "/select-profile" && commands[0] != "/list-profiles" {
|
||||||
fmt.Printf("Profile needs to be set\n")
|
fmt.Printf("Profile needs to be set\n")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send
|
||||||
|
if group != nil && !strings.HasPrefix(commands[0], "/") {
|
||||||
|
err := peer.SendMessageToGroup(group.GroupID, text)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error sending message: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch commands[0] {
|
switch commands[0] {
|
||||||
case "quit":
|
case "/quit":
|
||||||
if app.Peer != nil {
|
if peer != nil {
|
||||||
app.Peer.Save()
|
peer.Save()
|
||||||
}
|
}
|
||||||
quit = true
|
quit = true
|
||||||
case "new-profile":
|
case "/new-profile":
|
||||||
if len(commands) == 2 {
|
if len(commands) == 2 {
|
||||||
fmt.Print("** WARNING: PASSWORDS CANNOT BE RECOVERED! **\n")
|
fmt.Print("** WARNING: PASSWORDS CANNOT BE RECOVERED! **\n")
|
||||||
|
|
||||||
|
@ -242,187 +339,197 @@ func main() {
|
||||||
if failcount >= 3 {
|
if failcount >= 3 {
|
||||||
fmt.Printf("Error creating profile for %v: Your password entries must match!\n", commands[1])
|
fmt.Printf("Error creating profile for %v: Your password entries must match!\n", commands[1])
|
||||||
} else {
|
} else {
|
||||||
err := app.NewProfile(commands[1], password)
|
p, err := app.CreatePeer(commands[1], password)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
stopGroupFollow()
|
||||||
fmt.Printf("\nNew profile created for %v\n", commands[1])
|
fmt.Printf("\nNew profile created for %v\n", commands[1])
|
||||||
|
peer = p
|
||||||
|
suggestions = append(suggestionsBase, suggestionsSelectedProfile...)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("\nError creating profile for %v: %v\n", commands[1], err)
|
fmt.Printf("\nError creating profile for %v: %v\n", commands[1], err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Error creating NewProfile, usage: %s\n", usages["new-profile"])
|
fmt.Printf("Error creating New Profile, usage: %s\n", usages[commands[0]])
|
||||||
}
|
}
|
||||||
case "load-profile":
|
case "/load-profiles":
|
||||||
|
fmt.Print("Enter a password to decrypt the profile: ")
|
||||||
|
bytePassword, err := terminal.ReadPassword(int(syscall.Stdin))
|
||||||
|
|
||||||
|
err = app.LoadProfiles(string(bytePassword))
|
||||||
|
if err == nil {
|
||||||
|
profiles := app.ListPeers()
|
||||||
|
fmt.Printf("\n%v profiles active now\n", len(profiles))
|
||||||
|
fmt.Printf("You should run `select-profile` to use a profile or `list-profiles` to view loaded profiles\n")
|
||||||
|
} else {
|
||||||
|
fmt.Printf("\nError loading profiles: %v\n", err)
|
||||||
|
}
|
||||||
|
case "/list-profiles":
|
||||||
|
peerlist := app.ListPeers()
|
||||||
|
for onion, peername := range peerlist {
|
||||||
|
fmt.Printf(" %v\t%v\n", onion, peername)
|
||||||
|
}
|
||||||
|
case "/select-profile":
|
||||||
if len(commands) == 2 {
|
if len(commands) == 2 {
|
||||||
fmt.Print("Enter a password to decrypt the profile: ")
|
p := app.GetPeer(commands[1])
|
||||||
bytePassword, err := terminal.ReadPassword(int(syscall.Stdin))
|
if p == nil {
|
||||||
err = app.SetProfile(commands[1]+".json", string(bytePassword))
|
fmt.Printf("Error: profile '%v' does not exist\n", commands[1])
|
||||||
if err == nil {
|
|
||||||
fmt.Printf("\nLoaded profile for %v\n", commands[1])
|
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("\nError loading profile for %v: %v\n", commands[1], err)
|
stopGroupFollow()
|
||||||
|
peer = p
|
||||||
|
suggestions = append(suggestionsBase, suggestionsSelectedProfile...)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Error Loading profile, usage: %s\n", usages["load-profile"])
|
fmt.Printf("Error selecting profile, usage: %s\n", usages[commands[0]])
|
||||||
}
|
}
|
||||||
|
case "/info":
|
||||||
case "info":
|
if peer != nil {
|
||||||
if app.Peer != nil {
|
fmt.Printf("Address cwtch:%v\n", peer.GetProfile().Onion)
|
||||||
fmt.Printf("Address cwtch:%v\n", app.Peer.GetProfile().Onion)
|
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Profile needs to be set\n")
|
fmt.Printf("Profile needs to be set\n")
|
||||||
}
|
}
|
||||||
case "invite":
|
case "/invite":
|
||||||
if len(commands) == 2 {
|
if len(commands) == 2 {
|
||||||
fmt.Printf("Inviting cwtch:%v\n", commands[1])
|
fmt.Printf("Inviting cwtch:%v\n", commands[1])
|
||||||
app.PeerRequest(commands[1])
|
peer.PeerWithOnion(commands[1])
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Error inviting peer, usage: %s\n", usages["invite"])
|
fmt.Printf("Error inviting peer, usage: %s\n", usages[commands[0]])
|
||||||
}
|
}
|
||||||
case "peers":
|
case "/list-peers":
|
||||||
peers := app.Peer.GetPeers()
|
peers := peer.GetPeers()
|
||||||
for p, s := range peers {
|
for p, s := range peers {
|
||||||
fmt.Printf("Name: %v Status: %v\n", p, s)
|
fmt.Printf("Name: %v Status: %v\n", p, connections.ConnectionStateName[s])
|
||||||
}
|
}
|
||||||
case "servers":
|
case "/list-servers":
|
||||||
servers := app.Peer.GetServers()
|
servers := peer.GetServers()
|
||||||
for s, st := range servers {
|
for s, st := range servers {
|
||||||
fmt.Printf("Name: %v Status: %v\n", s, st)
|
fmt.Printf("Name: %v Status: %v\n", s, st)
|
||||||
}
|
}
|
||||||
case "contacts":
|
case "/list-contacts":
|
||||||
contacts := app.Peer.GetContacts()
|
contacts := peer.GetContacts()
|
||||||
for _, onion := range contacts {
|
for _, onion := range contacts {
|
||||||
c := app.Peer.GetContact(onion)
|
c := peer.GetContact(onion)
|
||||||
fmt.Printf("Name: %v Onion: %v Trusted: %v\n", c.Name, c.Onion, c.Trusted)
|
fmt.Printf("Name: %v Onion: %v Trusted: %v\n", c.Name, c.Onion, c.Trusted)
|
||||||
}
|
}
|
||||||
case "groups":
|
case "/list-groups":
|
||||||
for _, gid := range app.Peer.GetGroups() {
|
for _, gid := range peer.GetGroups() {
|
||||||
g := app.Peer.GetGroup(gid)
|
g := peer.GetGroup(gid)
|
||||||
fmt.Printf("Group Id: %v Owner: %v Accepted:%v\n", gid, g.Owner, g.Accepted)
|
fmt.Printf("Group Id: %v Owner: %v Accepted:%v\n", gid, g.Owner, g.Accepted)
|
||||||
}
|
}
|
||||||
case "trust":
|
case "/trust":
|
||||||
if len(commands) == 2 {
|
if len(commands) == 2 {
|
||||||
app.Peer.TrustPeer(commands[1])
|
peer.TrustPeer(commands[1])
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Error trusting peer, usage: %s\n", usages["trust"])
|
fmt.Printf("Error trusting peer, usage: %s\n", usages[commands[0]])
|
||||||
}
|
}
|
||||||
case "block":
|
case "/block":
|
||||||
if len(commands) == 2 {
|
if len(commands) == 2 {
|
||||||
app.Peer.BlockPeer(commands[1])
|
peer.BlockPeer(commands[1])
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Error blocking peer, usage: %s\n", usages["trust"])
|
fmt.Printf("Error blocking peer, usage: %s\n", usages[commands[0]])
|
||||||
}
|
}
|
||||||
case "accept-invite":
|
case "/accept-invite":
|
||||||
if len(commands) == 2 {
|
if len(commands) == 2 {
|
||||||
groupID := commands[1]
|
groupID := commands[1]
|
||||||
err := app.Peer.AcceptInvite(groupID)
|
err := peer.AcceptInvite(groupID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error: %v\n", err)
|
fmt.Printf("Error: %v\n", err)
|
||||||
} else {
|
} else {
|
||||||
app.Peer.Save()
|
peer.Save()
|
||||||
group := app.Peer.GetGroup(groupID)
|
group := peer.GetGroup(groupID)
|
||||||
if group == nil {
|
if group == nil {
|
||||||
fmt.Printf("Error: group does not exist\n")
|
fmt.Printf("Error: group does not exist\n")
|
||||||
} else {
|
} else {
|
||||||
app.Peer.JoinServer(group.GroupServer)
|
peer.JoinServer(group.GroupServer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Error accepting invite, usage: %s\n", usages["accept-invite"])
|
fmt.Printf("Error accepting invite, usage: %s\n", usages[commands[0]])
|
||||||
}
|
}
|
||||||
case "invite-to-group":
|
case "/invite-to-group":
|
||||||
if len(commands) == 3 {
|
if len(commands) == 3 {
|
||||||
fmt.Printf("Inviting %v to %v\n", commands[1], commands[2])
|
fmt.Printf("Inviting %v to %v\n", commands[1], commands[2])
|
||||||
err := app.Peer.InviteOnionToGroup(commands[1], commands[2])
|
err := peer.InviteOnionToGroup(commands[1], commands[2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error: %v\n", err)
|
fmt.Printf("Error: %v\n", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Error inviting peer to group, usage: %s\n", usages["invite-to-group"])
|
fmt.Printf("Error inviting peer to group, usage: %s\n", usages[commands[0]])
|
||||||
}
|
}
|
||||||
case "new-group":
|
case "/new-group":
|
||||||
if len(commands) == 2 && commands[1] != "" {
|
if len(commands) == 2 && commands[1] != "" {
|
||||||
fmt.Printf("Setting up a new group on server:%v\n", commands[1])
|
fmt.Printf("Setting up a new group on server:%v\n", commands[1])
|
||||||
id, _, err := app.Peer.StartGroup(commands[1])
|
id, _, err := peer.StartGroup(commands[1])
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fmt.Printf("New Group [%v] created for server %v\n", id, commands[1])
|
fmt.Printf("New Group [%v] created for server %v\n", id, commands[1])
|
||||||
app.Peer.Save()
|
peer.Save()
|
||||||
group := app.Peer.GetGroup(id)
|
group := peer.GetGroup(id)
|
||||||
if group == nil {
|
if group == nil {
|
||||||
fmt.Printf("Error: group does not exist\n")
|
fmt.Printf("Error: group does not exist\n")
|
||||||
} else {
|
} else {
|
||||||
app.Peer.JoinServer(group.GroupServer)
|
peer.JoinServer(group.GroupServer)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Error creating new group: %v", err)
|
fmt.Printf("Error creating new group: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Error creating a new group, usage: %s\n", usages["new-group"])
|
fmt.Printf("Error creating a new group, usage: %s\n", usages[commands[0]])
|
||||||
}
|
}
|
||||||
case "send":
|
case "/select-group":
|
||||||
if len(commands) > 2 {
|
|
||||||
message := strings.Join(commands[2:], " ")
|
|
||||||
err := app.Peer.SendMessageToGroup(commands[1], message)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error: %v\n", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Error sending message to group, usage: %s\n", usages["send"])
|
|
||||||
}
|
|
||||||
case "timeline":
|
|
||||||
if len(commands) == 2 {
|
if len(commands) == 2 {
|
||||||
group := app.Peer.GetGroup(commands[1])
|
g := peer.GetGroup(commands[1])
|
||||||
if group == nil {
|
if g == nil {
|
||||||
fmt.Printf("Error: group does not exist\n")
|
fmt.Printf("Error: group %s not found!\n", commands[1])
|
||||||
} else {
|
} else {
|
||||||
timeline := group.GetTimeline()
|
stopGroupFollow()
|
||||||
for _, m := range timeline {
|
group = g
|
||||||
verified := "not-verified"
|
|
||||||
if m.Verified {
|
|
||||||
verified = "verified"
|
|
||||||
}
|
|
||||||
|
|
||||||
p := app.Peer.GetContact(m.PeerID)
|
fmt.Printf("--------------- %v ---------------\n", group.GroupID)
|
||||||
name := "unknown"
|
gms := group.Timeline.Messages
|
||||||
if p != nil {
|
max := 20
|
||||||
name = p.Name
|
if len(gms) < max {
|
||||||
} else if app.Peer.GetProfile().Onion == m.PeerID {
|
max = len(gms)
|
||||||
name = app.Peer.GetProfile().Name
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("%v %v (%v): %v [%s]\n", m.Timestamp, name, m.PeerID, m.Message, verified)
|
|
||||||
}
|
}
|
||||||
|
for i := len(gms) - max; i < len(gms); i++ {
|
||||||
|
printMessage(gms[i])
|
||||||
|
}
|
||||||
|
fmt.Printf("------------------------------\n")
|
||||||
|
|
||||||
|
startGroupFollow()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Error reading timeline from group, usage: %s\n", usages["timeline"])
|
fmt.Printf("Error selecting a group, usage: %s\n", usages[commands[0]])
|
||||||
}
|
}
|
||||||
case "export-group":
|
case "/unselect-group":
|
||||||
|
stopGroupFollow()
|
||||||
|
case "/export-group":
|
||||||
if len(commands) == 2 {
|
if len(commands) == 2 {
|
||||||
group := app.Peer.GetGroup(commands[1])
|
group := peer.GetGroup(commands[1])
|
||||||
if group == nil {
|
if group == nil {
|
||||||
fmt.Printf("Error: group does not exist\n")
|
fmt.Printf("Error: group does not exist\n")
|
||||||
} else {
|
} else {
|
||||||
invite, _ := app.Peer.ExportGroup(commands[1])
|
invite, _ := peer.ExportGroup(commands[1])
|
||||||
fmt.Printf("Invite: %v\n", invite)
|
fmt.Printf("Invite: %v\n", invite)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Error exporting group, usage: %s\n", usages["export-group"])
|
fmt.Printf("Error exporting group, usage: %s\n", usages[commands[0]])
|
||||||
}
|
}
|
||||||
case "save":
|
case "/save":
|
||||||
app.Peer.Save()
|
peer.Save()
|
||||||
case "help":
|
case "/help":
|
||||||
for _, command := range suggestions {
|
for _, command := range suggestions {
|
||||||
fmt.Printf("%-18s%-56s%s\n", command.Text, command.Description, usages[command.Text])
|
fmt.Printf("%-18s%-56s%s\n", command.Text, command.Description, usages[command.Text])
|
||||||
}
|
}
|
||||||
case "sendlots":
|
case "/sendlots":
|
||||||
if len(commands) == 2 {
|
if len(commands) == 2 {
|
||||||
group := app.Peer.GetGroup(commands[1])
|
group := peer.GetGroup(commands[1])
|
||||||
if group == nil {
|
if group == nil {
|
||||||
fmt.Printf("Error: group does not exist\n")
|
fmt.Printf("Error: group does not exist\n")
|
||||||
} else {
|
} else {
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
fmt.Printf("Sending message: %v\n", i)
|
fmt.Printf("Sending message: %v\n", i)
|
||||||
err := app.Peer.SendMessageToGroup(commands[1], fmt.Sprintf("this is message %v", i))
|
err := peer.SendMessageToGroup(commands[1], fmt.Sprintf("this is message %v", i))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("could not send message %v because %v\n", i, err)
|
fmt.Printf("could not send message %v because %v\n", i, err)
|
||||||
}
|
}
|
||||||
|
@ -436,7 +543,7 @@ func main() {
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
found := false
|
found := false
|
||||||
for _, m := range timeline {
|
for _, m := range timeline {
|
||||||
if m.Message == fmt.Sprintf("this is message %v", i) && m.PeerID == app.Peer.GetProfile().Onion {
|
if m.Message == fmt.Sprintf("this is message %v", i) && m.PeerID == peer.GetProfile().Onion {
|
||||||
found = true
|
found = true
|
||||||
latency := m.Received.Sub(m.Timestamp)
|
latency := m.Received.Sub(m.Timestamp)
|
||||||
fmt.Printf("Latency for Message %v was %v\n", i, latency)
|
fmt.Printf("Latency for Message %v was %v\n", i, latency)
|
||||||
|
@ -459,15 +566,12 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if app.Peer != nil {
|
if peer != nil {
|
||||||
app.Peer.Save()
|
peer.Save()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if app.TorManager != nil {
|
app.Shutdown()
|
||||||
fmt.Println("Shutting down Tor process...")
|
|
||||||
app.TorManager.Shutdown()
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,3 +16,8 @@ const (
|
||||||
FAILED
|
FAILED
|
||||||
KILLED
|
KILLED
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ConnectionStateName allows conversaion of states to their string representations
|
||||||
|
ConnectionStateName = []string{"Disconnected", "Connecting", "Connected", "Authenticated", "Failed", "Killed"}
|
||||||
|
)
|
||||||
|
|
|
@ -40,9 +40,9 @@ type cwtchPeer struct {
|
||||||
salt [128]byte
|
salt [128]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// CwtchPeerInterface provides us with a way of testing systems built on top of cwtch without having to
|
// CwtchPeer provides us with a way of testing systems built on top of cwtch without having to
|
||||||
// directly implement a cwtchPeer.
|
// directly implement a cwtchPeer.
|
||||||
type CwtchPeerInterface interface {
|
type CwtchPeer interface {
|
||||||
Save() error
|
Save() error
|
||||||
PeerWithOnion(string)
|
PeerWithOnion(string)
|
||||||
InviteOnionToGroup(string, string) error
|
InviteOnionToGroup(string, string) error
|
||||||
|
@ -145,7 +145,7 @@ func (cp *cwtchPeer) setup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCwtchPeer creates and returns a new cwtchPeer with the given name.
|
// NewCwtchPeer creates and returns a new cwtchPeer with the given name.
|
||||||
func NewCwtchPeer(name string, password string, profilefile string) CwtchPeerInterface {
|
func NewCwtchPeer(name string, password string, profilefile string) CwtchPeer {
|
||||||
cp := new(cwtchPeer)
|
cp := new(cwtchPeer)
|
||||||
cp.profilefile = profilefile
|
cp.profilefile = profilefile
|
||||||
cp.Profile = model.GenerateNewProfile(name)
|
cp.Profile = model.GenerateNewProfile(name)
|
||||||
|
@ -169,7 +169,7 @@ func (cp *cwtchPeer) Save() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadCwtchPeer loads an existing cwtchPeer from a file.
|
// LoadCwtchPeer loads an existing cwtchPeer from a file.
|
||||||
func LoadCwtchPeer(profilefile string, password string) (CwtchPeerInterface, error) {
|
func LoadCwtchPeer(profilefile string, password string) (CwtchPeer, error) {
|
||||||
encryptedbytes, err := ioutil.ReadFile(profilefile)
|
encryptedbytes, err := ioutil.ReadFile(profilefile)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
var dkr [32]byte
|
var dkr [32]byte
|
||||||
|
@ -410,6 +410,7 @@ func (cp *cwtchPeer) Listen() error {
|
||||||
func (cp *cwtchPeer) Shutdown() {
|
func (cp *cwtchPeer) Shutdown() {
|
||||||
cp.connectionsManager.Shutdown()
|
cp.connectionsManager.Shutdown()
|
||||||
cp.app.Shutdown()
|
cp.app.Shutdown()
|
||||||
|
cp.Save()
|
||||||
}
|
}
|
||||||
|
|
||||||
// CwtchPeerInstance encapsulates incoming peer connections
|
// CwtchPeerInstance encapsulates incoming peer connections
|
||||||
|
|
|
@ -84,7 +84,7 @@ func serverCheck(t *testing.T, serverAddr string) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForPeerConnection(t *testing.T, peer peer.CwtchPeerInterface, server string) {
|
func waitForPeerConnection(t *testing.T, peer peer.CwtchPeer, server string) {
|
||||||
for {
|
for {
|
||||||
servers := peer.GetServers()
|
servers := peer.GetServers()
|
||||||
state, ok := servers[server]
|
state, ok := servers[server]
|
||||||
|
|
Loading…
Reference in New Issue