Merge pull request 'Port Autodownload / Image Previews / Profile Image Experiment to Cwtch' (#501) from autodownload into master
continuous-integration/drone/push Build is pending
Details
continuous-integration/drone/push Build is pending
Details
Reviewed-on: #501 Reviewed-by: Dan Ballard <dan@openprivacy.ca>pull/502/head v0.19.2
commit
b02d9f7fb9
@ -0,0 +1,139 @@
|
||||
package filesharing
|
||||
|
||||
import (
|
||||
"cwtch.im/cwtch/event"
|
||||
"cwtch.im/cwtch/model"
|
||||
"cwtch.im/cwtch/model/attr"
|
||||
"cwtch.im/cwtch/model/constants"
|
||||
"cwtch.im/cwtch/peer"
|
||||
"cwtch.im/cwtch/settings"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ImagePreviewsFunctionality struct {
|
||||
downloadFolder string
|
||||
}
|
||||
|
||||
func (i *ImagePreviewsFunctionality) NotifySettingsUpdate(settings settings.GlobalSettings) {
|
||||
i.downloadFolder = settings.DownloadPath
|
||||
}
|
||||
|
||||
func (i ImagePreviewsFunctionality) EventsToRegister() []event.Type {
|
||||
return []event.Type{event.ProtocolEngineCreated, event.NewMessageFromPeer, event.NewMessageFromGroup}
|
||||
}
|
||||
|
||||
func (i ImagePreviewsFunctionality) ExperimentsToRegister() []string {
|
||||
return []string{constants.FileSharingExperiment, constants.ImagePreviewsExperiment}
|
||||
}
|
||||
|
||||
func (i *ImagePreviewsFunctionality) OnEvent(ev event.Event, profile peer.CwtchPeer) {
|
||||
if profile.IsFeatureEnabled(constants.FileSharingExperiment) && profile.IsFeatureEnabled(constants.ImagePreviewsExperiment) {
|
||||
switch ev.EventType {
|
||||
case event.NewMessageFromPeer:
|
||||
ci, err := profile.FetchConversationInfo(ev.Data["RemotePeer"])
|
||||
if err == nil {
|
||||
if ci.Accepted {
|
||||
i.handleImagePreviews(profile, &ev, ci.ID, ci.ID)
|
||||
}
|
||||
}
|
||||
case event.NewMessageFromGroup:
|
||||
ci, err := profile.FetchConversationInfo(ev.Data["RemotePeer"])
|
||||
if err == nil {
|
||||
if ci.Accepted {
|
||||
i.handleImagePreviews(profile, &ev, ci.ID, ci.ID)
|
||||
}
|
||||
}
|
||||
case event.ProtocolEngineCreated:
|
||||
// Now that the Peer Engine is Activated, Reshare Profile Images
|
||||
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 := FunctionalityGate()
|
||||
fsf.ReShareFiles(profile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i ImagePreviewsFunctionality) OnContactRequestValue(profile peer.CwtchPeer, conversation model.Conversation, eventID string, path attr.ScopedZonedPath) {
|
||||
}
|
||||
|
||||
func (i *ImagePreviewsFunctionality) OnContactReceiveValue(profile peer.CwtchPeer, conversation model.Conversation, path attr.ScopedZonedPath, value string, exists bool) {
|
||||
if profile.IsFeatureEnabled(constants.FileSharingExperiment) && profile.IsFeatureEnabled(constants.ImagePreviewsExperiment) {
|
||||
_, zone, path := path.GetScopeZonePath()
|
||||
if zone == attr.ProfileZone && path == constants.CustomProfileImageKey {
|
||||
fileKey := value
|
||||
|
||||
if conversation.Accepted {
|
||||
fsf := FunctionalityGate()
|
||||
basepath := i.downloadFolder
|
||||
fp, mp := 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...
|
||||
} 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, value, constants.ImagePreviewMaxSizeInBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleImagePreviews checks settings and, if appropriate, auto-downloads any images
|
||||
func (i *ImagePreviewsFunctionality) handleImagePreviews(profile peer.CwtchPeer, ev *event.Event, conversationID, senderID int) {
|
||||
if profile.IsFeatureEnabled(constants.FileSharingExperiment) && profile.IsFeatureEnabled(constants.ImagePreviewsExperiment) {
|
||||
|
||||
// Short-circuit failures
|
||||
// Don't autodownload images if the download path does not exist.
|
||||
if i.downloadFolder == "" {
|
||||
log.Errorf("download folder %v is not set", i.downloadFolder)
|
||||
return
|
||||
}
|
||||
|
||||
// Don't autodownload images if the download path does not exist.
|
||||
if _, err := os.Stat(i.downloadFolder); os.IsNotExist(err) {
|
||||
log.Errorf("download folder %v does not exist", i.downloadFolder)
|
||||
return
|
||||
}
|
||||
|
||||
// If file sharing is enabled then reshare all active files...
|
||||
fsf := FunctionalityGate()
|
||||
|
||||
// Now look at the image preview experiment
|
||||
var cm model.MessageWrapper
|
||||
err := json.Unmarshal([]byte(ev.Data[event.Data]), &cm)
|
||||
if err == nil && cm.Overlay == model.OverlayFileSharing {
|
||||
log.Debugf("Received File Sharing Message")
|
||||
var fm OverlayMessage
|
||||
err = json.Unmarshal([]byte(cm.Data), &fm)
|
||||
if err == nil {
|
||||
if fm.ShouldAutoDL() {
|
||||
basepath := i.downloadFolder
|
||||
fp, mp := GenerateDownloadPath(basepath, fm.Name, false)
|
||||
log.Debugf("autodownloading file! %v %v %v", basepath, fp, i.downloadFolder)
|
||||
ev.Data["Auto"] = constants.True
|
||||
mID, _ := strconv.Atoi(ev.Data["Index"])
|
||||
profile.UpdateMessageAttribute(conversationID, 0, mID, constants.AttrDownloaded, constants.True)
|
||||
fsf.DownloadFile(profile, senderID, fp, mp, fm.FileKey(), constants.ImagePreviewMaxSizeInBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package app
|
||||
package settings
|
||||
|
||||
import (
|
||||
"cwtch.im/cwtch/event"
|
After Width: | Height: | Size: 51 KiB |
@ -0,0 +1,184 @@
|
||||
package filesharing
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
app2 "cwtch.im/cwtch/app"
|
||||
"cwtch.im/cwtch/event"
|
||||
"cwtch.im/cwtch/functionality/filesharing"
|
||||
"cwtch.im/cwtch/model"
|
||||
"cwtch.im/cwtch/model/attr"
|
||||
"cwtch.im/cwtch/model/constants"
|
||||
"cwtch.im/cwtch/peer"
|
||||
"cwtch.im/cwtch/protocol/connections"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"path/filepath"
|
||||
|
||||
// Import SQL Cipher
|
||||
mrand "math/rand"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
_ "github.com/mutecomm/go-sqlcipher/v4"
|
||||
)
|
||||
|
||||
func waitForPeerPeerConnection(t *testing.T, peera peer.CwtchPeer, peerb peer.CwtchPeer) {
|
||||
for {
|
||||
state := peera.GetPeerState(peerb.GetOnion())
|
||||
if state == connections.FAILED {
|
||||
t.Fatalf("%v could not connect to %v", peera.GetOnion(), peerb.GetOnion())
|
||||
}
|
||||
if state != connections.AUTHENTICATED {
|
||||
fmt.Printf("peer %v waiting connect to peer %v, currently: %v\n", peera.GetOnion(), peerb.GetOnion(), connections.ConnectionStateName[state])
|
||||
time.Sleep(time.Second * 5)
|
||||
continue
|
||||
} else {
|
||||
peerAName, _ := peera.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name)
|
||||
peerBName, _ := peerb.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name)
|
||||
fmt.Printf("%v CONNECTED and AUTHED to %v\n", peerAName, peerBName)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSharing(t *testing.T) {
|
||||
numGoRoutinesStart := runtime.NumGoroutine()
|
||||
os.RemoveAll("cwtch.out.png")
|
||||
os.RemoveAll("cwtch.out.png.manifest")
|
||||
os.RemoveAll("storage")
|
||||
os.RemoveAll("tordir")
|
||||
os.RemoveAll("./download_dir")
|
||||
|
||||
log.SetLevel(log.LevelDebug)
|
||||
|
||||
os.Mkdir("tordir", 0700)
|
||||
dataDir := path.Join("tordir", "tor")
|
||||
os.MkdirAll(dataDir, 0700)
|
||||
|
||||
// we don't need real randomness for the port, just to avoid a possible conflict...
|
||||
mrand.Seed(int64(time.Now().Nanosecond()))
|
||||
socksPort := mrand.Intn(1000) + 9051
|
||||
controlPort := mrand.Intn(1000) + 9052
|
||||
|
||||
// generate a random password
|
||||
key := make([]byte, 64)
|
||||
_, err := rand.Read(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
useCache := os.Getenv("TORCACHE") == "true"
|
||||
|
||||
torDataDir := ""
|
||||
if useCache {
|
||||
log.Infof("using tor cache")
|
||||
torDataDir = filepath.Join(dataDir, "data-dir-torcache")
|
||||
os.MkdirAll(torDataDir, 0700)
|
||||
} else {
|
||||
log.Infof("using clean tor data dir")
|
||||
if torDataDir, err = os.MkdirTemp(dataDir, "data-dir-"); err != nil {
|
||||
t.Fatalf("could not create data dir")
|
||||
}
|
||||
}
|
||||
|
||||
tor.NewTorrc().WithSocksPort(socksPort).WithOnionTrafficOnly().WithHashedPassword(base64.StdEncoding.EncodeToString(key)).WithControlPort(controlPort).Build("tordir/tor/torrc")
|
||||
acn, err := tor.NewTorACNWithAuth("./tordir", path.Join("..", "tor"), torDataDir, controlPort, tor.HashedPasswordAuthenticator{Password: base64.StdEncoding.EncodeToString(key)})
|
||||
if err != nil {
|
||||
t.Fatalf("Could not start Tor: %v", err)
|
||||
}
|
||||
acn.WaitTillBootstrapped()
|
||||
defer acn.Close()
|
||||
|
||||
app := app2.NewApp(acn, "./storage", app2.LoadAppSettings("./storage"))
|
||||
|
||||
usr, _ := user.Current()
|
||||
cwtchDir := path.Join(usr.HomeDir, ".cwtch")
|
||||
os.Mkdir(cwtchDir, 0700)
|
||||
os.RemoveAll(path.Join(cwtchDir, "testing"))
|
||||
os.Mkdir(path.Join(cwtchDir, "testing"), 0700)
|
||||
|
||||
t.Logf("Creating Alice...")
|
||||
app.CreateProfile("alice", "asdfasdf", true)
|
||||
|
||||
t.Logf("Creating Bob...")
|
||||
app.CreateProfile("bob", "asdfasdf", true)
|
||||
|
||||
t.Logf("** Waiting for Alice, Bob...")
|
||||
alice := app2.WaitGetPeer(app, "alice")
|
||||
app.ActivatePeerEngine(alice.GetOnion())
|
||||
bob := app2.WaitGetPeer(app, "bob")
|
||||
app.ActivatePeerEngine(bob.GetOnion())
|
||||
|
||||
alice.AutoHandleEvents([]event.Type{event.PeerStateChange, event.NewRetValMessageFromPeer})
|
||||
bob.AutoHandleEvents([]event.Type{event.PeerStateChange, event.NewRetValMessageFromPeer})
|
||||
|
||||
// Turn on File Sharing Experiment...
|
||||
settings := app.ReadSettings()
|
||||
settings.ExperimentsEnabled = true
|
||||
settings.DownloadPath = "./download_dir"
|
||||
os.RemoveAll(path.Join(settings.DownloadPath, "cwtch.png"))
|
||||
os.RemoveAll(path.Join(settings.DownloadPath, "cwtch.png.manifest"))
|
||||
os.MkdirAll(settings.DownloadPath, 0700)
|
||||
settings.Experiments[constants.FileSharingExperiment] = true
|
||||
// Turn Auto Downloading On... (Part of the Image Previews / Profile Images Experiment)
|
||||
settings.Experiments[constants.ImagePreviewsExperiment] = true
|
||||
app.UpdateSettings(settings)
|
||||
|
||||
t.Logf("** Launching Peers...")
|
||||
waitTime := time.Duration(30) * time.Second
|
||||
t.Logf("** Waiting for Alice, Bob to connect with onion network... (%v)\n", waitTime)
|
||||
time.Sleep(waitTime)
|
||||
|
||||
bob.NewContactConversation(alice.GetOnion(), model.DefaultP2PAccessControl(), true)
|
||||
alice.NewContactConversation(bob.GetOnion(), model.DefaultP2PAccessControl(), true)
|
||||
alice.PeerWithOnion(bob.GetOnion())
|
||||
|
||||
t.Logf("Waiting for alice and Bob to peer...")
|
||||
waitForPeerPeerConnection(t, alice, bob)
|
||||
alice.AcceptConversation(1)
|
||||
bob.AcceptConversation(1)
|
||||
t.Logf("Alice and Bob are Connected!!")
|
||||
|
||||
filesharingFunctionality := filesharing.FunctionalityGate()
|
||||
|
||||
_, fileSharingMessage, err := filesharingFunctionality.ShareFile("cwtch.png", alice)
|
||||
alice.SendMessage(1, fileSharingMessage)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error!: %v", err)
|
||||
}
|
||||
|
||||
// test that bob can download and verify the file
|
||||
// The main difference here is that bob doesn't need to do anything...
|
||||
// testBobDownloadFile(t, bob, filesharingFunctionality, queueOracle)
|
||||
|
||||
// Wait for say...
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
if _, err := os.Stat(path.Join(settings.DownloadPath, "cwtch.png")); errors.Is(err, os.ErrNotExist) {
|
||||
// path/to/whatever does not exist
|
||||
t.Fatalf("cwthc.png should have been automatically downloadeded...")
|
||||
}
|
||||
|
||||
app.Shutdown()
|
||||
acn.Close()
|
||||
time.Sleep(20 * time.Second)
|
||||
numGoRoutinesPostACN := runtime.NumGoroutine()
|
||||
|
||||
// Printing out the current goroutines
|
||||
// Very useful if we are leaking any.
|
||||
pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
|
||||
|
||||
if numGoRoutinesStart != numGoRoutinesPostACN {
|
||||
t.Errorf("Number of GoRoutines at start (%v) does not match number of goRoutines after cleanup of peers and servers (%v), clean up failed, leak detected!", numGoRoutinesStart, numGoRoutinesPostACN)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in new issue