forked from cwtch.im/cwtch
FileSharing Experiments / Move Experiment Handling to App and Cwtch Peer
This commit is contained in:
parent
26c5c11216
commit
f246ea1e40
38
app/app.go
38
app/app.go
|
@ -31,6 +31,8 @@ type application struct {
|
||||||
engines map[string]connections.Engine
|
engines map[string]connections.Engine
|
||||||
appBus event.Manager
|
appBus event.Manager
|
||||||
appmutex sync.Mutex
|
appmutex sync.Mutex
|
||||||
|
|
||||||
|
settings *GlobalSettingsFile
|
||||||
}
|
}
|
||||||
|
|
||||||
// Application is a full cwtch peer application. It allows management, usage and storage of multiple peers
|
// Application is a full cwtch peer application. It allows management, usage and storage of multiple peers
|
||||||
|
@ -52,6 +54,9 @@ type Application interface {
|
||||||
ActivatePeerEngine(onion string, doListen, doPeers, doServers bool)
|
ActivatePeerEngine(onion string, doListen, doPeers, doServers bool)
|
||||||
DeactivatePeerEngine(onion string)
|
DeactivatePeerEngine(onion string)
|
||||||
|
|
||||||
|
ReadSettings() GlobalSettings
|
||||||
|
UpdateSettings(settings GlobalSettings)
|
||||||
|
|
||||||
ShutdownPeer(string)
|
ShutdownPeer(string)
|
||||||
Shutdown()
|
Shutdown()
|
||||||
|
|
||||||
|
@ -67,7 +72,15 @@ func NewApp(acn connectivity.ACN, appDirectory string) Application {
|
||||||
log.Debugf("NewApp(%v)\n", appDirectory)
|
log.Debugf("NewApp(%v)\n", appDirectory)
|
||||||
os.MkdirAll(path.Join(appDirectory, "profiles"), 0700)
|
os.MkdirAll(path.Join(appDirectory, "profiles"), 0700)
|
||||||
|
|
||||||
app := &application{engines: make(map[string]connections.Engine), eventBuses: make(map[string]event.Manager), directory: appDirectory, appBus: event.NewEventManager()}
|
// Note: we basically presume this doesn't fail. If the file doesn't exist we create it, and as such the
|
||||||
|
// only plausible error conditions are related to file create e.g. low disk space. If that is the case then
|
||||||
|
// many other parts of Cwtch are likely to fail also.
|
||||||
|
settings, err := InitGlobalSettingsFile(appDirectory, DefactoPasswordForUnencryptedProfiles)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error initializing global settings file %. Global settings might not be loaded or saves", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
app := &application{engines: make(map[string]connections.Engine), eventBuses: make(map[string]event.Manager), directory: appDirectory, appBus: event.NewEventManager(), settings: settings}
|
||||||
app.peers = make(map[string]peer.CwtchPeer)
|
app.peers = make(map[string]peer.CwtchPeer)
|
||||||
|
|
||||||
app.acn = acn
|
app.acn = acn
|
||||||
|
@ -80,6 +93,26 @@ func NewApp(acn connectivity.ACN, appDirectory string) Application {
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *application) ReadSettings() GlobalSettings {
|
||||||
|
app.appmutex.Lock()
|
||||||
|
defer app.appmutex.Unlock()
|
||||||
|
return app.settings.ReadGlobalSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *application) UpdateSettings(settings GlobalSettings) {
|
||||||
|
// don't allow any other application changes while settings update
|
||||||
|
app.appmutex.Lock()
|
||||||
|
defer app.appmutex.Unlock()
|
||||||
|
app.settings.WriteGlobalSettings(settings)
|
||||||
|
|
||||||
|
// we now need to propagate changes to all peers
|
||||||
|
app.peerLock.Lock()
|
||||||
|
defer app.peerLock.Unlock()
|
||||||
|
for _, profile := range app.peers {
|
||||||
|
profile.UpdateExperiments(settings.ExperimentsEnabled, settings.Experiments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ListProfiles returns a map of onions to their profile's Name
|
// ListProfiles returns a map of onions to their profile's Name
|
||||||
func (app *application) ListProfiles() []string {
|
func (app *application) ListProfiles() []string {
|
||||||
var keys []string
|
var keys []string
|
||||||
|
@ -160,7 +193,7 @@ func (app *application) CreatePeer(name string, password string, attributes map[
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *application) DeletePeer(onion string, password string) {
|
func (app *application) DeletePeer(onion string, password string) {
|
||||||
log.Infof("DeletePeer called on %v\n", onion)
|
log.Debugf("DeletePeer called on %v\n", onion)
|
||||||
app.appmutex.Lock()
|
app.appmutex.Lock()
|
||||||
defer app.appmutex.Unlock()
|
defer app.appmutex.Unlock()
|
||||||
|
|
||||||
|
@ -249,7 +282,6 @@ func (app *application) registerHooks(profile peer.CwtchPeer) {
|
||||||
// Register Hooks
|
// Register Hooks
|
||||||
profile.RegisterHook(extensions.ProfileValueExtension{})
|
profile.RegisterHook(extensions.ProfileValueExtension{})
|
||||||
profile.RegisterHook(filesharing.Functionality{})
|
profile.RegisterHook(filesharing.Functionality{})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// installProfile takes a profile and if it isn't loaded in the app, installs it and returns true
|
// installProfile takes a profile and if it isn't loaded in the app, installs it and returns true
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
// 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"
|
|
@ -27,7 +27,7 @@ func (a antispam) Shutdown() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *antispam) run() {
|
func (a *antispam) run() {
|
||||||
log.Infof("running antispam trigger plugin")
|
log.Debugf("running antispam trigger plugin")
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-time.After(antispamTickTime):
|
case <-time.After(antispamTickTime):
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cwtch.im/cwtch/event"
|
||||||
|
"cwtch.im/cwtch/model/constants"
|
||||||
|
"cwtch.im/cwtch/storage/v1"
|
||||||
|
"encoding/json"
|
||||||
|
"git.openprivacy.ca/openprivacy/log"
|
||||||
|
"os"
|
||||||
|
path "path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CwtchStarted = event.Type("CwtchStarted")
|
||||||
|
CwtchStartError = event.Type("CwtchStartError")
|
||||||
|
UpdateGlobalSettings = event.Type("UpdateGlobalSettings")
|
||||||
|
)
|
||||||
|
|
||||||
|
const GlobalSettingsFilename = "ui.globals"
|
||||||
|
const saltFile = "SALT"
|
||||||
|
|
||||||
|
type NotificationPolicy string
|
||||||
|
|
||||||
|
const (
|
||||||
|
NotificationPolicyMute = NotificationPolicy("NotificationPolicy.Mute")
|
||||||
|
NotificationPolicyOptIn = NotificationPolicy("NotificationPolicy.OptIn")
|
||||||
|
NotificationPolicyDefaultAll = NotificationPolicy("NotificationPolicy.DefaultAll")
|
||||||
|
)
|
||||||
|
|
||||||
|
type GlobalSettingsFile struct {
|
||||||
|
v1.FileStore
|
||||||
|
}
|
||||||
|
|
||||||
|
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{
|
||||||
|
Locale: "en",
|
||||||
|
Theme: "dark",
|
||||||
|
PreviousPid: -1,
|
||||||
|
ExperimentsEnabled: false,
|
||||||
|
Experiments: map[string]bool{constants.MessageFormattingExperiment: true},
|
||||||
|
StateRootPane: 0,
|
||||||
|
FirstTime: true,
|
||||||
|
BlockUnknownConnections: false,
|
||||||
|
StreamerMode: false,
|
||||||
|
UIColumnModePortrait: "DualpaneMode.Single",
|
||||||
|
UIColumnModeLandscape: "DualpaneMode.CopyPortrait",
|
||||||
|
NotificationPolicy: "NotificationPolicy.Mute",
|
||||||
|
NotificationContent: "NotificationContent.SimpleEvent",
|
||||||
|
DownloadPath: "",
|
||||||
|
AllowAdvancedTorConfig: false,
|
||||||
|
CustomTorrc: "",
|
||||||
|
UseCustomTorrc: false,
|
||||||
|
CustomSocksPort: -1,
|
||||||
|
CustomControlPort: -1,
|
||||||
|
UseTorCache: false,
|
||||||
|
TorCacheDir: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitGlobalSettingsFile(directory string, password string) (*GlobalSettingsFile, error) {
|
||||||
|
var key [32]byte
|
||||||
|
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
|
||||||
|
key, newSalt, err = v1.CreateKeySalt(password)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Could not initialize salt: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
os.Mkdir(directory, 0700)
|
||||||
|
err := os.WriteFile(path.Join(directory, saltFile), newSalt[:], 0600)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Could not write salt file: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
key = v1.CreateKey(password, salt)
|
||||||
|
}
|
||||||
|
|
||||||
|
gsFile := v1.NewFileStore(directory, GlobalSettingsFilename, key)
|
||||||
|
log.Infof("initialized global settings file: %v", gsFile)
|
||||||
|
globalSettingsFile := GlobalSettingsFile{
|
||||||
|
gsFile,
|
||||||
|
}
|
||||||
|
return &globalSettingsFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (globalSettingsFile *GlobalSettingsFile) ReadGlobalSettings() GlobalSettings {
|
||||||
|
settings := DefaultGlobalSettings
|
||||||
|
|
||||||
|
if globalSettingsFile == nil {
|
||||||
|
log.Errorf("Global Settings File was not Initialized Properly")
|
||||||
|
return settings
|
||||||
|
}
|
||||||
|
|
||||||
|
settingsBytes, err := globalSettingsFile.Read()
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("Could not read global ui settings: %v (assuming this is a first time app deployment...)", err)
|
||||||
|
return settings //firstTime = true
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(settingsBytes, &settings)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Could not parse global ui settings: %v\n", err)
|
||||||
|
// TODO if settings is corrupted, we probably want to alert the UI.
|
||||||
|
return settings //firstTime = true
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Settings: %#v", settings)
|
||||||
|
return settings
|
||||||
|
}
|
||||||
|
|
||||||
|
func (globalSettingsFile *GlobalSettingsFile) WriteGlobalSettings(globalSettings GlobalSettings) {
|
||||||
|
bytes, _ := json.Marshal(globalSettings)
|
||||||
|
// override first time setting
|
||||||
|
globalSettings.FirstTime = true
|
||||||
|
err := globalSettingsFile.Write(bytes)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Could not write global ui settings: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,7 +41,7 @@ func (pne ProfileValueExtension) OnContactReceiveValue(profile peer.CwtchPeer, c
|
||||||
// OnContactRequestValue for ProfileValueExtension handles returning Public Profile Values
|
// OnContactRequestValue for ProfileValueExtension handles returning Public Profile Values
|
||||||
func (pne ProfileValueExtension) OnContactRequestValue(profile peer.CwtchPeer, conversation model.Conversation, eventID string, szp attr.ScopedZonedPath) {
|
func (pne ProfileValueExtension) OnContactRequestValue(profile peer.CwtchPeer, conversation model.Conversation, eventID string, szp attr.ScopedZonedPath) {
|
||||||
scope, zone, zpath := szp.GetScopeZonePath()
|
scope, zone, zpath := szp.GetScopeZonePath()
|
||||||
log.Infof("Looking up public | conversation scope/zone %v", szp.ToString())
|
log.Debugf("Looking up public | conversation scope/zone %v", szp.ToString())
|
||||||
if scope.IsPublic() || scope.IsConversation() {
|
if scope.IsPublic() || scope.IsConversation() {
|
||||||
val, exists := profile.GetScopedZonedAttribute(scope, zone, zpath)
|
val, exists := profile.GetScopedZonedAttribute(scope, zone, zpath)
|
||||||
|
|
||||||
|
|
|
@ -40,66 +40,70 @@ func (f Functionality) RegisterExperiments() []string {
|
||||||
|
|
||||||
// OnEvent handles File Sharing Hooks like Manifest Received and FileDownloaded
|
// OnEvent handles File Sharing Hooks like Manifest Received and FileDownloaded
|
||||||
func (f Functionality) OnEvent(ev event.Event, profile peer.CwtchPeer) {
|
func (f Functionality) OnEvent(ev event.Event, profile peer.CwtchPeer) {
|
||||||
switch ev.EventType {
|
if profile.IsFeatureEnabled(constants.FileSharingExperiment) {
|
||||||
case event.ManifestReceived:
|
switch ev.EventType {
|
||||||
log.Debugf("Manifest Received Event!: %v", ev)
|
case event.ManifestReceived:
|
||||||
handle := ev.Data[event.Handle]
|
log.Debugf("Manifest Received Event!: %v", ev)
|
||||||
fileKey := ev.Data[event.FileKey]
|
handle := ev.Data[event.Handle]
|
||||||
serializedManifest := ev.Data[event.SerializedManifest]
|
fileKey := ev.Data[event.FileKey]
|
||||||
|
serializedManifest := ev.Data[event.SerializedManifest]
|
||||||
|
|
||||||
manifestFilePath, exists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%v.manifest", fileKey))
|
manifestFilePath, exists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%v.manifest", fileKey))
|
||||||
if exists {
|
|
||||||
downloadFilePath, exists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%v.path", fileKey))
|
|
||||||
if exists {
|
if exists {
|
||||||
log.Debugf("downloading manifest to %v, file to %v", manifestFilePath, downloadFilePath)
|
downloadFilePath, exists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%v.path", fileKey))
|
||||||
var manifest files.Manifest
|
if exists {
|
||||||
err := json.Unmarshal([]byte(serializedManifest), &manifest)
|
log.Debugf("downloading manifest to %v, file to %v", manifestFilePath, downloadFilePath)
|
||||||
|
var manifest files.Manifest
|
||||||
|
err := json.Unmarshal([]byte(serializedManifest), &manifest)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// We only need to check the file size here, as manifest is sent to engine and the file created
|
// We only need to check the file size here, as manifest is sent to engine and the file created
|
||||||
// will be bound to the size advertised in manifest.
|
// will be bound to the size advertised in manifest.
|
||||||
fileSizeLimitValue, fileSizeLimitExists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%v.limit", fileKey))
|
fileSizeLimitValue, fileSizeLimitExists := profile.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%v.limit", fileKey))
|
||||||
if fileSizeLimitExists {
|
if fileSizeLimitExists {
|
||||||
fileSizeLimit, err := strconv.ParseUint(fileSizeLimitValue, 10, bits.UintSize)
|
fileSizeLimit, err := strconv.ParseUint(fileSizeLimitValue, 10, bits.UintSize)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if manifest.FileSizeInBytes >= fileSizeLimit {
|
if manifest.FileSizeInBytes >= fileSizeLimit {
|
||||||
log.Errorf("could not download file, size %v greater than limit %v", manifest.FileSizeInBytes, fileSizeLimitValue)
|
log.Errorf("could not download file, size %v greater than limit %v", manifest.FileSizeInBytes, fileSizeLimitValue)
|
||||||
} else {
|
|
||||||
manifest.Title = manifest.FileName
|
|
||||||
manifest.FileName = downloadFilePath
|
|
||||||
log.Debugf("saving manifest")
|
|
||||||
err = manifest.Save(manifestFilePath)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("could not save manifest: %v", err)
|
|
||||||
} else {
|
} else {
|
||||||
tempFile := ""
|
manifest.Title = manifest.FileName
|
||||||
if runtime.GOOS == "android" {
|
manifest.FileName = downloadFilePath
|
||||||
tempFile = manifestFilePath[0 : len(manifestFilePath)-len(".manifest")]
|
log.Debugf("saving manifest")
|
||||||
log.Debugf("derived android temp path: %v", tempFile)
|
err = manifest.Save(manifestFilePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("could not save manifest: %v", err)
|
||||||
|
} else {
|
||||||
|
tempFile := ""
|
||||||
|
if runtime.GOOS == "android" {
|
||||||
|
tempFile = manifestFilePath[0 : len(manifestFilePath)-len(".manifest")]
|
||||||
|
log.Debugf("derived android temp path: %v", tempFile)
|
||||||
|
}
|
||||||
|
profile.PublishEvent(event.NewEvent(event.ManifestSaved, map[event.Field]string{
|
||||||
|
event.FileKey: fileKey,
|
||||||
|
event.Handle: handle,
|
||||||
|
event.SerializedManifest: string(manifest.Serialize()),
|
||||||
|
event.TempFile: tempFile,
|
||||||
|
event.NameSuggestion: manifest.Title,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
profile.PublishEvent(event.NewEvent(event.ManifestSaved, map[event.Field]string{
|
|
||||||
event.FileKey: fileKey,
|
|
||||||
event.Handle: handle,
|
|
||||||
event.SerializedManifest: string(manifest.Serialize()),
|
|
||||||
event.TempFile: tempFile,
|
|
||||||
event.NameSuggestion: manifest.Title,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
log.Errorf("error saving manifest: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Errorf("error saving manifest: %v", err)
|
log.Errorf("found manifest path but not download path for %v", fileKey)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Errorf("found manifest path but not download path for %v", fileKey)
|
log.Errorf("no download path found for manifest: %v", fileKey)
|
||||||
}
|
}
|
||||||
} else {
|
case event.FileDownloaded:
|
||||||
log.Errorf("no download path found for manifest: %v", fileKey)
|
fileKey := ev.Data[event.FileKey]
|
||||||
|
profile.SetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.complete", fileKey), "true")
|
||||||
}
|
}
|
||||||
case event.FileDownloaded:
|
} else {
|
||||||
fileKey := ev.Data[event.FileKey]
|
log.Errorf("profile called filesharing experiment OnContactReceiveValue even though file sharing was not enabled. This is likely a programming error.")
|
||||||
profile.SetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.complete", fileKey), "true")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,21 +112,25 @@ func (f Functionality) OnContactRequestValue(profile peer.CwtchPeer, conversatio
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Functionality) OnContactReceiveValue(profile peer.CwtchPeer, conversation model.Conversation, path attr.ScopedZonedPath, value string, exists bool) {
|
func (f Functionality) OnContactReceiveValue(profile peer.CwtchPeer, conversation model.Conversation, path attr.ScopedZonedPath, value string, exists bool) {
|
||||||
scope, zone, zpath := path.GetScopeZonePath()
|
// Profile should not call us if FileSharing is disabled
|
||||||
log.Infof("file sharing contact receive value")
|
if profile.IsFeatureEnabled(constants.FileSharingExperiment) {
|
||||||
if exists && scope.IsConversation() && zone == attr.FilesharingZone && strings.HasSuffix(zpath, ".manifest.size") {
|
scope, zone, zpath := path.GetScopeZonePath()
|
||||||
fileKey := strings.Replace(zpath, ".manifest.size", "", 1)
|
log.Debugf("file sharing contact receive value")
|
||||||
size, err := strconv.Atoi(value)
|
if exists && scope.IsConversation() && zone == attr.FilesharingZone && strings.HasSuffix(zpath, ".manifest.size") {
|
||||||
// if size is valid and below the maximum size for a manifest
|
fileKey := strings.Replace(zpath, ".manifest.size", "", 1)
|
||||||
// this is to prevent malicious sharers from using large amounts of memory when distributing
|
size, err := strconv.Atoi(value)
|
||||||
// a manifest as we reconstruct this in-memory
|
// if size is valid and below the maximum size for a manifest
|
||||||
if err == nil && size < files.MaxManifestSize {
|
// this is to prevent malicious sharers from using large amounts of memory when distributing
|
||||||
profile.PublishEvent(event.NewEvent(event.ManifestSizeReceived, map[event.Field]string{event.FileKey: fileKey, event.ManifestSize: value, event.Handle: conversation.Handle}))
|
// a manifest as we reconstruct this in-memory
|
||||||
} else {
|
if err == nil && size < files.MaxManifestSize {
|
||||||
profile.PublishEvent(event.NewEvent(event.ManifestError, map[event.Field]string{event.FileKey: fileKey, event.Handle: conversation.Handle}))
|
profile.PublishEvent(event.NewEvent(event.ManifestSizeReceived, map[event.Field]string{event.FileKey: fileKey, event.ManifestSize: value, event.Handle: conversation.Handle}))
|
||||||
|
} else {
|
||||||
|
profile.PublishEvent(event.NewEvent(event.ManifestError, map[event.Field]string{event.FileKey: fileKey, event.Handle: conversation.Handle}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
log.Errorf("profile called filesharing experiment OnContactReceiveValue even though file sharing was not enabled. This is likely a programming error.")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FunctionalityGate returns filesharing if enabled in the given experiment map
|
// FunctionalityGate returns filesharing if enabled in the given experiment map
|
||||||
|
@ -174,6 +182,11 @@ func (om *OverlayMessage) ShouldAutoDL() bool {
|
||||||
// to downloadFilePath
|
// to downloadFilePath
|
||||||
func (f *Functionality) DownloadFile(profile peer.CwtchPeer, conversationID int, downloadFilePath string, manifestFilePath string, key string, limit uint64) error {
|
func (f *Functionality) DownloadFile(profile peer.CwtchPeer, conversationID int, downloadFilePath string, manifestFilePath string, key string, limit uint64) error {
|
||||||
|
|
||||||
|
// assert that we are allowed to download the file
|
||||||
|
if !profile.IsFeatureEnabled(constants.FileSharingExperiment) {
|
||||||
|
return errors.New("filesharing functionality is not enabled")
|
||||||
|
}
|
||||||
|
|
||||||
// Don't download files if the download or manifest path is not set
|
// Don't download files if the download or manifest path is not set
|
||||||
if downloadFilePath == "" || manifestFilePath == "" {
|
if downloadFilePath == "" || manifestFilePath == "" {
|
||||||
return errors.New("download path or manifest path is empty")
|
return errors.New("download path or manifest path is empty")
|
||||||
|
@ -225,6 +238,12 @@ func (f *Functionality) startFileShare(profile peer.CwtchPeer, filekey string, m
|
||||||
|
|
||||||
// RestartFileShare takes in an existing filekey and, assuming the manifest exists, restarts sharing of the manifest
|
// RestartFileShare takes in an existing filekey and, assuming the manifest exists, restarts sharing of the manifest
|
||||||
func (f *Functionality) RestartFileShare(profile peer.CwtchPeer, filekey string) error {
|
func (f *Functionality) RestartFileShare(profile peer.CwtchPeer, filekey string) error {
|
||||||
|
|
||||||
|
// assert that we are allowed to restart filesharing
|
||||||
|
if !profile.IsFeatureEnabled(constants.FileSharingExperiment) {
|
||||||
|
return errors.New("filesharing functionality is not enabled")
|
||||||
|
}
|
||||||
|
|
||||||
// check that a manifest exists
|
// check that a manifest exists
|
||||||
manifest, manifestExists := profile.GetScopedZonedAttribute(attr.ConversationScope, attr.FilesharingZone, fmt.Sprintf("%s.manifest", filekey))
|
manifest, manifestExists := profile.GetScopedZonedAttribute(attr.ConversationScope, attr.FilesharingZone, fmt.Sprintf("%s.manifest", filekey))
|
||||||
if manifestExists {
|
if manifestExists {
|
||||||
|
@ -238,6 +257,12 @@ func (f *Functionality) RestartFileShare(profile peer.CwtchPeer, filekey string)
|
||||||
// ReShareFiles given a profile we iterate through all existing fileshares and re-share them
|
// ReShareFiles given a profile we iterate through all existing fileshares and re-share them
|
||||||
// if the time limit has not expired
|
// if the time limit has not expired
|
||||||
func (f *Functionality) ReShareFiles(profile peer.CwtchPeer) error {
|
func (f *Functionality) ReShareFiles(profile peer.CwtchPeer) error {
|
||||||
|
|
||||||
|
// assert that we are allowed to restart filesharing
|
||||||
|
if !profile.IsFeatureEnabled(constants.FileSharingExperiment) {
|
||||||
|
return errors.New("filesharing functionality is not enabled")
|
||||||
|
}
|
||||||
|
|
||||||
keys, err := profile.GetScopedZonedAttributeKeys(attr.LocalScope, attr.FilesharingZone)
|
keys, err := profile.GetScopedZonedAttributeKeys(attr.LocalScope, attr.FilesharingZone)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -304,6 +329,12 @@ func (f *Functionality) GetFileShareInfo(profile peer.CwtchPeer, filekey string)
|
||||||
// ShareFile given a profile and a conversation handle, sets up a file sharing process to share the file
|
// ShareFile given a profile and a conversation handle, sets up a file sharing process to share the file
|
||||||
// at filepath
|
// at filepath
|
||||||
func (f *Functionality) ShareFile(filepath string, profile peer.CwtchPeer) (string, string, error) {
|
func (f *Functionality) ShareFile(filepath string, profile peer.CwtchPeer) (string, string, error) {
|
||||||
|
|
||||||
|
// assert that we are allowed to share files
|
||||||
|
if !profile.IsFeatureEnabled(constants.FileSharingExperiment) {
|
||||||
|
return "", "", errors.New("filesharing functionality is not enabled")
|
||||||
|
}
|
||||||
|
|
||||||
manifest, err := files.CreateManifest(filepath)
|
manifest, err := files.CreateManifest(filepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
|
@ -446,6 +477,7 @@ func GenerateDownloadPath(basePath, fileName string, overwrite bool) (filePath,
|
||||||
|
|
||||||
// StopFileShare sends a message to the ProtocolEngine to cease sharing a particular file
|
// StopFileShare sends a message to the ProtocolEngine to cease sharing a particular file
|
||||||
func (f *Functionality) StopFileShare(profile peer.CwtchPeer, fileKey string) {
|
func (f *Functionality) StopFileShare(profile peer.CwtchPeer, fileKey string) {
|
||||||
|
// Note we do not do a permissions check here, as we are *always* permitted to stop sharing files.
|
||||||
// set the filekey status to inactive
|
// set the filekey status to inactive
|
||||||
profile.SetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.active", fileKey), constants.False)
|
profile.SetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.active", fileKey), constants.False)
|
||||||
profile.PublishEvent(event.NewEvent(event.StopFileShare, map[event.Field]string{event.FileKey: fileKey}))
|
profile.PublishEvent(event.NewEvent(event.StopFileShare, map[event.Field]string{event.FileKey: fileKey}))
|
||||||
|
@ -453,5 +485,6 @@ func (f *Functionality) StopFileShare(profile peer.CwtchPeer, fileKey string) {
|
||||||
|
|
||||||
// StopAllFileShares sends a message to the ProtocolEngine to cease sharing all files
|
// StopAllFileShares sends a message to the ProtocolEngine to cease sharing all files
|
||||||
func (f *Functionality) StopAllFileShares(profile peer.CwtchPeer) {
|
func (f *Functionality) StopAllFileShares(profile peer.CwtchPeer) {
|
||||||
|
// Note we do not do a permissions check here, as we are *always* permitted to stop sharing files.
|
||||||
profile.PublishEvent(event.NewEvent(event.StopAllFileShares, map[event.Field]string{}))
|
profile.PublishEvent(event.NewEvent(event.StopAllFileShares, map[event.Field]string{}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,5 +10,7 @@ const ImagePreviewsExperiment = "filesharing-images"
|
||||||
// ImagePreviewMaxSizeInBytes Files up to this size will be autodownloaded using ImagePreviewsExperiment
|
// ImagePreviewMaxSizeInBytes Files up to this size will be autodownloaded using ImagePreviewsExperiment
|
||||||
const ImagePreviewMaxSizeInBytes = 20971520
|
const ImagePreviewMaxSizeInBytes = 20971520
|
||||||
|
|
||||||
|
const MessageFormattingExperiment = "message-formatting"
|
||||||
|
|
||||||
// AutoDLFileExts Files with these extensions will be autodownloaded using ImagePreviewsExperiment
|
// AutoDLFileExts Files with these extensions will be autodownloaded using ImagePreviewsExperiment
|
||||||
var AutoDLFileExts = [...]string{".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"}
|
var AutoDLFileExts = [...]string{".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
// Experiments are optional functionality that can be enabled/disabled by an application either completely or individually.
|
||||||
|
// examples of experiments include File Sharing, Profile Images and Groups.
|
||||||
|
type Experiments struct {
|
||||||
|
enabled bool
|
||||||
|
experiments map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitExperiments encapsulates a set of experiments separate from their storage in GlobalSettings.
|
||||||
|
func InitExperiments(enabled bool, experiments map[string]bool) Experiments {
|
||||||
|
return Experiments{
|
||||||
|
enabled: enabled,
|
||||||
|
experiments: experiments,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEnabled is a convenience function that takes in an experiment and returns true if it is enabled. Experiments
|
||||||
|
// are only enabled if both global experiments are turned on and if the specific experiment is also turned on.
|
||||||
|
// The one exception to this is experiments that have been promoted to default functionality which may be turned on
|
||||||
|
// even if experiments turned off globally. These experiments are defined by DefaultEnabledFunctionality.
|
||||||
|
func (e *Experiments) IsEnabled(experiment string) bool {
|
||||||
|
if !e.enabled {
|
||||||
|
// todo handle default-enabled functionality
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
enabled, exists := e.experiments[experiment]
|
||||||
|
if !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return enabled
|
||||||
|
}
|
|
@ -69,11 +69,30 @@ type cwtchPeer struct {
|
||||||
queue event.Queue
|
queue event.Queue
|
||||||
eventBus event.Manager
|
eventBus event.Manager
|
||||||
|
|
||||||
extensions []ProfileHook
|
extensions []ProfileHook
|
||||||
extensionLock sync.Mutex // we don't want to hold up all of cwtch for managing thread safe access to extensions
|
extensionLock sync.Mutex // we don't want to hold up all of cwtch for managing thread safe access to extensions
|
||||||
|
experiments model.Experiments
|
||||||
|
experimentsLock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsFeatureEnabled returns true if the functionality defined by featureName has been enabled by the application, false otherwise.
|
||||||
|
// this function is intended to be used by ProfileHooks to determine if they should execute experimental functionality.
|
||||||
|
func (cp *cwtchPeer) IsFeatureEnabled(featureName string) bool {
|
||||||
|
cp.experimentsLock.Lock()
|
||||||
|
defer cp.experimentsLock.Unlock()
|
||||||
|
return cp.experiments.IsEnabled(featureName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateExperiments notifies a Cwtch profile of a change in the nature of global experiments. The Cwtch Profile uses
|
||||||
|
// this information to update registered extensions.
|
||||||
|
func (cp *cwtchPeer) UpdateExperiments(enabled bool, experiments map[string]bool) {
|
||||||
|
cp.experimentsLock.Lock()
|
||||||
|
defer cp.experimentsLock.Unlock()
|
||||||
|
cp.experiments = model.InitExperiments(enabled, experiments)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *cwtchPeer) PublishEvent(resp event.Event) {
|
func (cp *cwtchPeer) PublishEvent(resp event.Event) {
|
||||||
|
log.Debugf("Publishing Event: %v %v", resp.EventType, resp.Data)
|
||||||
cp.eventBus.Publish(resp)
|
cp.eventBus.Publish(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +173,7 @@ func (cp *cwtchPeer) ChangePassword(password string, newpassword string, newpass
|
||||||
// probably redundant but we like api safety
|
// probably redundant but we like api safety
|
||||||
if newpassword == newpasswordAgain {
|
if newpassword == newpasswordAgain {
|
||||||
rekey := createKey(newpassword, salt)
|
rekey := createKey(newpassword, salt)
|
||||||
log.Infof("rekeying database...")
|
log.Debugf("rekeying database...")
|
||||||
return cp.storage.Rekey(rekey)
|
return cp.storage.Rekey(rekey)
|
||||||
}
|
}
|
||||||
return errors.New(constants.PasswordsDoNotMatchError)
|
return errors.New(constants.PasswordsDoNotMatchError)
|
||||||
|
@ -1286,9 +1305,15 @@ func (cp *cwtchPeer) eventHandler() {
|
||||||
|
|
||||||
// Safe Access to Extensions
|
// Safe Access to Extensions
|
||||||
cp.extensionLock.Lock()
|
cp.extensionLock.Lock()
|
||||||
log.Infof("checking extension...%v", cp.extensions)
|
log.Debugf("checking extension...%v", cp.extensions)
|
||||||
for _, extension := range cp.extensions {
|
for _, extension := range cp.extensions {
|
||||||
log.Infof("checking extension...%v", extension)
|
log.Debugf("checking extension...%v", extension)
|
||||||
|
// check if the current map of experiments satisfies the extension requirements
|
||||||
|
if !cp.checkExtensionExperiment(extension) {
|
||||||
|
log.Debugf("skipping extension...not all experiments satisfied")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
extension.extension.OnContactRequestValue(cp, *conversationInfo, ev.EventID, scopedZonedPath)
|
extension.extension.OnContactRequestValue(cp, *conversationInfo, ev.EventID, scopedZonedPath)
|
||||||
}
|
}
|
||||||
cp.extensionLock.Unlock()
|
cp.extensionLock.Unlock()
|
||||||
|
@ -1313,6 +1338,12 @@ func (cp *cwtchPeer) eventHandler() {
|
||||||
// Safe Access to Extensions
|
// Safe Access to Extensions
|
||||||
cp.extensionLock.Lock()
|
cp.extensionLock.Lock()
|
||||||
for _, extension := range cp.extensions {
|
for _, extension := range cp.extensions {
|
||||||
|
log.Debugf("checking extension...%v", extension)
|
||||||
|
// check if the current map of experiments satisfies the extension requirements
|
||||||
|
if !cp.checkExtensionExperiment(extension) {
|
||||||
|
log.Debugf("skipping extension...not all experiments satisfied")
|
||||||
|
continue
|
||||||
|
}
|
||||||
extension.extension.OnContactReceiveValue(cp, *conversationInfo, scopedZonedPath, val, exists)
|
extension.extension.OnContactReceiveValue(cp, *conversationInfo, scopedZonedPath, val, exists)
|
||||||
}
|
}
|
||||||
cp.extensionLock.Unlock()
|
cp.extensionLock.Unlock()
|
||||||
|
@ -1406,6 +1437,13 @@ func (cp *cwtchPeer) eventHandler() {
|
||||||
processed := false
|
processed := false
|
||||||
cp.extensionLock.Lock()
|
cp.extensionLock.Lock()
|
||||||
for _, extension := range cp.extensions {
|
for _, extension := range cp.extensions {
|
||||||
|
|
||||||
|
// check if the current map of experiments satisfies the extension requirements
|
||||||
|
if !cp.checkExtensionExperiment(extension) {
|
||||||
|
log.Debugf("skipping extension...not all experiments satisfied")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// if the extension is registered for this event type then process
|
// if the extension is registered for this event type then process
|
||||||
if _, contains := extension.events[ev.EventType]; contains {
|
if _, contains := extension.events[ev.EventType]; contains {
|
||||||
extension.extension.OnEvent(ev, cp)
|
extension.extension.OnEvent(ev, cp)
|
||||||
|
@ -1421,6 +1459,17 @@ func (cp *cwtchPeer) eventHandler() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cp *cwtchPeer) checkExtensionExperiment(hook ProfileHook) bool {
|
||||||
|
cp.experimentsLock.Lock()
|
||||||
|
defer cp.experimentsLock.Unlock()
|
||||||
|
for experiment := range hook.experiments {
|
||||||
|
if !cp.experiments.IsEnabled(experiment) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// attemptInsertOrAcknowledgeLegacyGroupConversation is a convenience method that looks up the conversation
|
// attemptInsertOrAcknowledgeLegacyGroupConversation is a convenience method that looks up the conversation
|
||||||
// by the given handle and attempts to mark the message as acknowledged. returns error on failure
|
// by the given handle and attempts to mark the message as acknowledged. returns error on failure
|
||||||
// to either find the contact or the associated message
|
// to either find the contact or the associated message
|
||||||
|
|
|
@ -125,7 +125,9 @@ func NewCwtchProfileStorage(db *sql.DB, profileDirectory string) (*CwtchProfileS
|
||||||
insertProfileKeyValueStmt, err := db.Prepare(insertProfileKeySQLStmt)
|
insertProfileKeyValueStmt, err := db.Prepare(insertProfileKeySQLStmt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.Close()
|
db.Close()
|
||||||
log.Errorf("error preparing query: %v %v", insertProfileKeySQLStmt, err)
|
// note: this is debug because we expect failure here when opening an encrypted database with an
|
||||||
|
// incorrect password. The rest are errors because failure is not expected.
|
||||||
|
log.Debugf("error preparing query: %v %v", insertProfileKeySQLStmt, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -772,7 +774,7 @@ func (cps *CwtchProfileStorage) PurgeNonSavedMessages() {
|
||||||
for _, conversation := range ci {
|
for _, conversation := range ci {
|
||||||
if !conversation.IsGroup() && !conversation.IsServer() {
|
if !conversation.IsGroup() && !conversation.IsServer() {
|
||||||
if conversation.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(event.SaveHistoryKey)).ToString()] != event.SaveHistoryConfirmed {
|
if conversation.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(event.SaveHistoryKey)).ToString()] != event.SaveHistoryConfirmed {
|
||||||
log.Infof("purging conversation...")
|
log.Debugf("purging conversation...")
|
||||||
// TODO: At some point in the future this needs to iterate over channels and make a decision for each on..
|
// TODO: At some point in the future this needs to iterate over channels and make a decision for each on..
|
||||||
cps.PurgeConversationChannel(conversation.ID, 0)
|
cps.PurgeConversationChannel(conversation.ID, 0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProfileHooks interface {
|
type ProfileHooks interface {
|
||||||
|
|
||||||
// RegisterEvents returns a set of events that the extension is interested hooking
|
// RegisterEvents returns a set of events that the extension is interested hooking
|
||||||
RegisterEvents() []event.Type
|
RegisterEvents() []event.Type
|
||||||
|
|
||||||
// RegisterExperiments RegisterExperiments returns a set of experiments that the extension is interested in being notified about
|
// RegisterExperiments returns a set of experiments that the extension is interested in being notified about
|
||||||
RegisterExperiments() []string
|
RegisterExperiments() []string
|
||||||
|
|
||||||
// OnEvent is called whenever an event Registered with RegisterEvents is called
|
// OnEvent is called whenever an event Registered with RegisterEvents is called
|
||||||
|
|
|
@ -132,4 +132,6 @@ type CwtchPeer interface {
|
||||||
Delete()
|
Delete()
|
||||||
PublishEvent(resp event.Event)
|
PublishEvent(resp event.Event)
|
||||||
RegisterHook(hook ProfileHooks)
|
RegisterHook(hook ProfileHooks)
|
||||||
|
UpdateExperiments(enabled bool, experiments map[string]bool)
|
||||||
|
IsFeatureEnabled(featureName string) bool
|
||||||
}
|
}
|
||||||
|
|
|
@ -176,7 +176,7 @@ func ImportProfile(exportedCwtchFile string, profilesDir string, password string
|
||||||
log.Errorf("%s is an invalid cwtch backup file: %s", profileID, err)
|
log.Errorf("%s is an invalid cwtch backup file: %s", profileID, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
log.Infof("%s is a valid cwtch backup file", profileID)
|
log.Debugf("%s is a valid cwtch backup file", profileID)
|
||||||
|
|
||||||
profileDBFile := filepath.Join(profilesDir, profileID, dbFile)
|
profileDBFile := filepath.Join(profilesDir, profileID, dbFile)
|
||||||
log.Debugf("checking %v", profileDBFile)
|
log.Debugf("checking %v", profileDBFile)
|
||||||
|
|
|
@ -73,7 +73,7 @@ func (ps *ProfileStoreV1) load() error {
|
||||||
|
|
||||||
for gid, group := range cp.Groups {
|
for gid, group := range cp.Groups {
|
||||||
if group.Version == 0 {
|
if group.Version == 0 {
|
||||||
log.Infof("group %v is of unsupported version 0. dropping group...\n", group.GroupID)
|
log.Debugf("group %v is of unsupported version 0. dropping group...\n", group.GroupID)
|
||||||
delete(cp.Groups, gid)
|
delete(cp.Groups, gid)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,7 +152,7 @@ func (ss *streamStore) WriteN(messages []model.Message) {
|
||||||
ss.lock.Lock()
|
ss.lock.Lock()
|
||||||
defer ss.lock.Unlock()
|
defer ss.lock.Unlock()
|
||||||
|
|
||||||
log.Infof("WriteN %v messages\n", len(messages))
|
log.Debugf("WriteN %v messages\n", len(messages))
|
||||||
i := 0
|
i := 0
|
||||||
for _, m := range messages {
|
for _, m := range messages {
|
||||||
ss.updateBuffer(m)
|
ss.updateBuffer(m)
|
||||||
|
|
|
@ -118,13 +118,18 @@ func TestFileSharing(t *testing.T) {
|
||||||
app.ActivatePeerEngine(bob.GetOnion(), true, true, true)
|
app.ActivatePeerEngine(bob.GetOnion(), true, true, true)
|
||||||
|
|
||||||
alice.AutoHandleEvents([]event.Type{event.PeerStateChange, event.NewRetValMessageFromPeer})
|
alice.AutoHandleEvents([]event.Type{event.PeerStateChange, event.NewRetValMessageFromPeer})
|
||||||
bob.AutoHandleEvents([]event.Type{event.PeerStateChange, event.NewRetValMessageFromPeer, event.ManifestReceived})
|
bob.AutoHandleEvents([]event.Type{event.PeerStateChange, event.NewRetValMessageFromPeer})
|
||||||
|
|
||||||
queueOracle := event.NewQueue()
|
queueOracle := event.NewQueue()
|
||||||
app.GetEventBus(bob.GetOnion()).Subscribe(event.FileDownloaded, queueOracle)
|
app.GetEventBus(bob.GetOnion()).Subscribe(event.FileDownloaded, queueOracle)
|
||||||
|
|
||||||
t.Logf("** Launching Peers...")
|
// Turn on File Sharing Experiment...
|
||||||
|
settings := app.ReadSettings()
|
||||||
|
settings.ExperimentsEnabled = true
|
||||||
|
settings.Experiments[constants.FileSharingExperiment] = true
|
||||||
|
app.UpdateSettings(settings)
|
||||||
|
|
||||||
|
t.Logf("** Launching Peers...")
|
||||||
waitTime := time.Duration(30) * time.Second
|
waitTime := time.Duration(30) * time.Second
|
||||||
t.Logf("** Waiting for Alice, Bob to connect with onion network... (%v)\n", waitTime)
|
t.Logf("** Waiting for Alice, Bob to connect with onion network... (%v)\n", waitTime)
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
|
@ -163,7 +168,11 @@ func TestFileSharing(t *testing.T) {
|
||||||
|
|
||||||
// Restart
|
// Restart
|
||||||
t.Logf("Restarting File Share")
|
t.Logf("Restarting File Share")
|
||||||
filesharingFunctionality.ReShareFiles(alice)
|
err = filesharingFunctionality.ReShareFiles(alice)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error!: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// run the same download test again...to check that we can actually download the file
|
// run the same download test again...to check that we can actually download the file
|
||||||
testBobDownloadFile(t, bob, filesharingFunctionality, queueOracle)
|
testBobDownloadFile(t, bob, filesharingFunctionality, queueOracle)
|
||||||
|
|
Loading…
Reference in New Issue