diff --git a/event/common.go b/event/common.go index ca9984c..182937a 100644 --- a/event/common.go +++ b/event/common.go @@ -327,19 +327,25 @@ const ( ContextSendFile = "im.cwtch.file.send.chunk" ) -// Define Default Attribute Keys +// Define Attribute Keys related to history preservation const ( - SaveHistoryKey = "SavePeerHistory" + PreserveHistoryDefaultSettingKey = "SaveHistoryDefault" // profile level default + SaveHistoryKey = "SavePeerHistory" // peer level setting ) // Define Default Attribute Values const ( - // Save History has 3 distinct states. By default we don't save history (DefaultDeleteHistory), if the peer confirms this - // we change to DeleteHistoryConfirmed, if they confirm they want to save then this becomes SaveHistoryConfirmed - // We use this distinction between default and confirmed to drive UI - DeleteHistoryDefault = "DefaultDeleteHistory" + // Save History has 3 distinct states. By default we refer to the profile level + // attribute PreserveHistoryDefaultSettingKey ( default: false i.e. DefaultDeleteHistory), + // For each contact, if the profile owner confirms deletion we change to DeleteHistoryConfirmed, + // if the profile owner confirms they want to save history then this becomes SaveHistoryConfirmed + // These settings are set at the UI level using Get/SetScopeZoneAttribute with scoped zone: local.profile.* SaveHistoryConfirmed = "SaveHistory" DeleteHistoryConfirmed = "DeleteHistoryConfirmed" + + // NOTE: While this says "[DeleteHistory]Default", The actual behaviour will now depend on the + // global app/profile value of PreserveHistoryDefaultSettingKey + DeleteHistoryDefault = "DefaultDeleteHistory" ) // Bool strings diff --git a/functionality/servers/servers_functionality.go b/functionality/servers/servers_functionality.go index ccafc8e..4a039be 100644 --- a/functionality/servers/servers_functionality.go +++ b/functionality/servers/servers_functionality.go @@ -101,6 +101,21 @@ func (f *Functionality) GetServerInfoList(profile peer.CwtchPeer) []Server { return servers } +// DeleteServer purges a server and all related keys from a profile +func (f *Functionality) DeleteServerInfo(profile peer.CwtchPeer, serverOnion string) error { + // Servers are stores as special conversations + ci, err := profile.FetchConversationInfo(serverOnion) + if err != nil { + return err + } + // Purge keys... + // NOTE: This will leave some groups in the state of being unable to connect to a particular + // server. + profile.DeleteConversation(ci.ID) + f.PublishServerUpdate(profile) + return nil +} + // GetServerInfo compiles all the information the UI might need regarding a particular server including any verified // cryptographic keys func (f *Functionality) GetServerInfo(profile peer.CwtchPeer, serverOnion string) Server { diff --git a/go.mod b/go.mod index 22f10aa..f1a2219 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.17 require ( git.openprivacy.ca/cwtch.im/tapir v0.6.0 - git.openprivacy.ca/openprivacy/connectivity v1.8.6 + git.openprivacy.ca/openprivacy/connectivity v1.11.0 git.openprivacy.ca/openprivacy/log v1.0.3 github.com/gtank/ristretto255 v0.1.3-0.20210930101514-6bb39798585c github.com/mutecomm/go-sqlcipher/v4 v4.4.2 @@ -15,7 +15,7 @@ require ( require ( filippo.io/edwards25519 v1.0.0 // indirect - git.openprivacy.ca/openprivacy/bine v0.0.4 // indirect + git.openprivacy.ca/openprivacy/bine v0.0.5 // indirect github.com/google/go-cmp v0.5.8 // indirect github.com/gtank/merlin v0.1.1 // indirect github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b // indirect diff --git a/go.sum b/go.sum index d5839b5..a5222bf 100644 --- a/go.sum +++ b/go.sum @@ -3,10 +3,12 @@ filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= git.openprivacy.ca/cwtch.im/tapir v0.6.0 h1:TtnKjxitkIDMM7Qn0n/u+mOHRLJzuQUYjYRu5n0/QFY= git.openprivacy.ca/cwtch.im/tapir v0.6.0/go.mod h1:iQIq4y7N+DuP3CxyG66WNEC/d6vzh+wXvvOmelB+KoY= -git.openprivacy.ca/openprivacy/bine v0.0.4 h1:CO7EkGyz+jegZ4ap8g5NWRuDHA/56KKvGySR6OBPW+c= git.openprivacy.ca/openprivacy/bine v0.0.4/go.mod h1:13ZqhKyqakDsN/ZkQkIGNULsmLyqtXc46XBcnuXm/mU= -git.openprivacy.ca/openprivacy/connectivity v1.8.6 h1:g74PyDGvpMZ3+K0dXy3mlTJh+e0rcwNk0XF8owzkmOA= +git.openprivacy.ca/openprivacy/bine v0.0.5 h1:DJs5gqw3SkvLSgRDvroqJxZ7F+YsbxbBRg5t0rU5gYE= +git.openprivacy.ca/openprivacy/bine v0.0.5/go.mod h1:fwdeq6RO08WDkV0k7HfArsjRvurVULoUQmT//iaABZM= git.openprivacy.ca/openprivacy/connectivity v1.8.6/go.mod h1:Hn1gpOx/bRZp5wvCtPQVJPXrfeUH0EGiG/Aoa0vjGLg= +git.openprivacy.ca/openprivacy/connectivity v1.11.0 h1:roASjaFtQLu+HdH5fa2wx6F00NL3YsUTlmXBJh8aLZk= +git.openprivacy.ca/openprivacy/connectivity v1.11.0/go.mod h1:OQO1+7OIz/jLxDrorEMzvZA6SEbpbDyLGpjoFqT3z1Y= git.openprivacy.ca/openprivacy/log v1.0.3 h1:E/PMm4LY+Q9s3aDpfySfEDq/vYQontlvNj/scrPaga0= git.openprivacy.ca/openprivacy/log v1.0.3/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= diff --git a/peer/cwtch_peer.go b/peer/cwtch_peer.go index 46c7e3d..3edbacb 100644 --- a/peer/cwtch_peer.go +++ b/peer/cwtch_peer.go @@ -194,10 +194,16 @@ func (cp *cwtchPeer) UpdateExperiments(enabled bool, experiments map[string]bool cp.experiments = model.InitExperiments(enabled, experiments) } -// NotifySettingsUpdate notifies a Cwtch profile of a change in the nature of global experiments. The Cwtch Profile uses -// this information to update registered extensions. +// NotifySettingsUpdate notifies a Cwtch profile of a change in the nature of global settings. +// The Cwtch Profile uses this information to update registered extensions in addition +// to updating internal settings. func (cp *cwtchPeer) NotifySettingsUpdate(settings settings.GlobalSettings) { log.Debugf("Cwtch Profile Settings Update: %v", settings) + + // update the save history default... + cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, event.PreserveHistoryDefaultSettingKey, strconv.FormatBool(settings.DefaultSaveHistory)) + + // pass these seetings updates cp.extensionLock.Lock() defer cp.extensionLock.Unlock() for _, extension := range cp.extensions { @@ -981,8 +987,8 @@ func (cp *cwtchPeer) AddServer(serverSpecification string) (string, error) { // we haven't seen this key associated with the server before } - // // If we have gotten to this point we can assume this is a safe key bundle signed by the - // // server with no conflicting keys. So we are going to save all the keys + // If we have gotten to this point we can assume this is a safe key bundle signed by the + // server with no conflicting keys. So we are going to save all the keys for k, v := range ab { cp.SetConversationAttribute(conversationInfo.ID, attr.PublicScope.ConstructScopedZonedPath(attr.ServerKeyZone.ConstructZonedPath(k)), v) } @@ -1537,6 +1543,13 @@ func (cp *cwtchPeer) eventHandler() { } case event.PeerStateChange: handle := ev.Data[event.RemotePeer] + + // we need to do this first because calls in the rest of this block may result in + // events that result the UI or bindings fetching new data. + cp.mutex.Lock() + cp.state[handle] = connections.ConnectionStateToType()[ev.Data[event.ConnectionState]] + cp.mutex.Unlock() + if connections.ConnectionStateToType()[ev.Data[event.ConnectionState]] == connections.AUTHENTICATED { ci, err := cp.FetchConversationInfo(handle) var cid int @@ -1577,9 +1590,6 @@ func (cp *cwtchPeer) eventHandler() { } cp.extensionLock.Unlock() - cp.mutex.Lock() - cp.state[ev.Data[event.RemotePeer]] = connections.ConnectionStateToType()[ev.Data[event.ConnectionState]] - cp.mutex.Unlock() case event.ServerStateChange: cp.mutex.Lock() prevState := cp.state[ev.Data[event.GroupServer]] diff --git a/peer/cwtchprofilestorage.go b/peer/cwtchprofilestorage.go index 7bf8e7b..74496c8 100644 --- a/peer/cwtchprofilestorage.go +++ b/peer/cwtchprofilestorage.go @@ -13,6 +13,7 @@ import ( "io" "os" "path/filepath" + "strconv" "strings" "sync" ) @@ -835,12 +836,30 @@ func (cps *CwtchProfileStorage) PurgeConversationChannel(conversation int, chann // PurgeNonSavedMessages deletes all message conversations that are not explicitly set to saved. func (cps *CwtchProfileStorage) PurgeNonSavedMessages() { - // Purge Messages that are not stored... + + // check to see if the profile global setting has been explicitly set to save (peer) conversations by default. + defaultSave := false + key, err := cps.LoadProfileKeyValue(TypeAttribute, attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(event.PreserveHistoryDefaultSettingKey)).ToString()) + if err == nil { + if defaultSaveSetting, err := strconv.ParseBool(string(key)); err == nil { + defaultSave = defaultSaveSetting + } + } + + // For each conversation, all that is not explicitly saved will be lost... ci, err := cps.FetchConversations() if err == nil { for _, conversation := range ci { + // unless this is a server or a group...for which we default save always (for legacy reasons) + // FIXME: revisit this for hybrid groups. if !conversation.IsGroup() && !conversation.IsServer() { - if conversation.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(event.SaveHistoryKey)).ToString()] != event.SaveHistoryConfirmed { + // Note that we only check for confirmed status here...if it is set to any other value we will fallthrough to the default. + saveHistoryConfirmed := conversation.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(event.SaveHistoryKey)).ToString()] == event.SaveHistoryConfirmed + deleteHistoryConfirmed := conversation.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(event.SaveHistoryKey)).ToString()] == event.DeleteHistoryConfirmed + // we purge conversation history in two specific instances... + // if the conversation has been explicitly marked as delete history confirmed OR + // if save history hasn't been confirmed and default save history is false - i.e. in all other cases + if deleteHistoryConfirmed || (!saveHistoryConfirmed && !defaultSave) { log.Debugf("purging conversation...") // 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) diff --git a/protocol/connections/engine.go b/protocol/connections/engine.go index 533a81a..1e4b81c 100644 --- a/protocol/connections/engine.go +++ b/protocol/connections/engine.go @@ -464,6 +464,10 @@ func (e *engine) peerWithTokenServer(onion string, tokenServerOnion string, toke e.ignoreOnShutdown(e.serverAuthed)(onion) return } + + // if we are not authed or synced then we are stuck... + e.ignoreOnShutdown(e.serverConnecting)(onion) + log.Errorf("server connection attempt issued to active connection") } } diff --git a/settings/settings.go b/settings/settings.go index 0093544..e486516 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -57,6 +57,7 @@ type GlobalSettings struct { TorCacheDir string BlodeuweddPath string FontScaling float64 + DefaultSaveHistory bool } var DefaultGlobalSettings = GlobalSettings{ @@ -83,6 +84,7 @@ var DefaultGlobalSettings = GlobalSettings{ TorCacheDir: "", BlodeuweddPath: "", FontScaling: 1.0, // use the system pixel scaling default + DefaultSaveHistory: false, } func InitGlobalSettingsFile(directory string, password string) (*GlobalSettingsFile, error) { @@ -131,6 +133,8 @@ func (globalSettingsFile *GlobalSettingsFile) ReadGlobalSettings() GlobalSetting return settings //firstTime = true } + // note: by giving json.Unmarshal settings we are providing it defacto defaults + // from DefaultGlobalSettings err = json.Unmarshal(settingsBytes, &settings) if err != nil { log.Errorf("Could not parse global ui settings: %v\n", err)