diff --git a/client/cli-input.go b/client/cli-input.go index 291c624..90ce0dd 100644 --- a/client/cli-input.go +++ b/client/cli-input.go @@ -54,7 +54,7 @@ var cliCommands = []cliCommand{ {"remove", removeCommand{}, "Remove an attachment or detachment from a draft message", contextDraft}, {"rename", renameCommand{}, "Rename an existing contact", contextContact}, {"reply", replyCommand{}, "Reply to the current message", contextInbox}, - {"retain", retainCommand{}, "Retain the current message", contextInbox}, + {"retain", retainCommand{}, "Retain the current message", contextInbox | contextOutbox}, {"dont-retain", dontRetainCommand{}, "Do not retain the current message", contextInbox}, {"save", saveCommand{}, "Save a numbered attachment to disk", contextInbox}, {"save-key", saveKeyCommand{}, "Save the key to a detachment to disk", contextInbox}, diff --git a/client/cli.go b/client/cli.go index 4a195b4..78a89b8 100644 --- a/client/cli.go +++ b/client/cli.go @@ -1611,24 +1611,29 @@ Handle: } case retainCommand: - msg, ok := c.currentObj.(*InboxMessage) - if !ok { - c.Printf("%s Select inbox message first\n", termWarnPrefix) + if msg, ok := c.currentObj.(*InboxMessage); ok { + msg.retained = true + } else if msg, ok := c.currentObj.(*queuedMessage); ok { + msg.retained = true + } else { + c.Printf("%s Select inbox or outbox message first\n", termWarnPrefix) return } - msg.retained = true c.save() case dontRetainCommand: - msg, ok := c.currentObj.(*InboxMessage) - if !ok { - c.Printf("%s Select inbox message first\n", termWarnPrefix) + if msg, ok := c.currentObj.(*InboxMessage); ok { + msg.retained = false + msg.exposureTime = c.Now() + // TODO: the CLI needs to expire messages when open as the GUI + // does. See guiClient.processTimer. + } else if msg, ok := c.currentObj.(*queuedMessage); ok { + msg.retained = false + msg.exposureTime = c.Now() + } else { + c.Printf("%s Select inbox or outbox message first\n", termWarnPrefix) return } - msg.retained = false - msg.exposureTime = c.Now() - // TODO: the CLI needs to expire messages when open as the GUI - // does. See guiClient.processTimer. c.save() default: @@ -1775,6 +1780,7 @@ func (c *cliClient) showOutbox(msg *queuedMessage) { cliRow{cols: []string{"Sent", sentTime}}, cliRow{cols: []string{"Acknowledged", formatTime(msg.acked)}}, cliRow{cols: []string{"Erase", eraseTime}}, + cliRow{cols: []string{"Retain", fmt.Sprintf("%t", msg.retained)}}, }, } table.WriteTo(c.term) diff --git a/client/client.go b/client/client.go index 0d5ddef..abe86ca 100644 --- a/client/client.go +++ b/client/client.go @@ -628,6 +628,17 @@ type queuedMessage struct { // identity this message for the duration of the session. It's not // saved to disk. cliId cliId + // retained is true if the user has chosen to retain this message - + // i.e. to opt it out of the usual, time-based, auto-deletion. + retained bool + // exposureTime contains the time when the message was last "exposed". + // This is used to allow a small period of time for the user to mark a + // message as retained (messageGraceTime). For example, if a message is + // loaded at startup and has expired then it's a candidate for + // deletion, but the exposureTime will be the startup time, which + // ensures that we leave it a few minutes before deletion. Setting + // retained to false also resets the exposureTime. + exposureTime time.Time } func (qm *queuedMessage) indicator(contact *Contact) Indicator { diff --git a/client/client_test.go b/client/client_test.go index 07acac7..ebdc6d1 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -2071,85 +2071,98 @@ func TestRetainMessage(t *testing.T) { t.Fatalf("Bad initial number of messages in listUI: %d", n) } - msg := client2.inbox[0] - if msg.retained { - t.Fatalf("Retained flag is initially set") + retainedCheck := func(b bool, s string) { + msg1 := client1.outbox[0] + msg2 := client2.inbox[0] + if msg2.retained != b || msg1.retained != b { + t.Fatalf(s) + } } + retainedCheck(false, "Retained flag is initially set") - client2.gui.events <- Click{ - name: client2.inboxUI.entries[0].boxName, - } - client2.AdvanceTo(uiStateInbox) - client2.gui.events <- Click{ - name: "retain", - checks: map[string]bool{"retain": true}, + retainedClick := func(client *TestClient, st int, l *listUI, b bool) { + client.gui.events <- Click{ + name: l.entries[0].boxName, + } + client.AdvanceTo(st) + client.gui.events <- Click{ + name: "retain", + checks: map[string]bool{"retain": b}, + } + client.AdvanceTo(st) } - client2.AdvanceTo(uiStateInbox) + retainedClick(client1, uiStateOutbox, client1.outboxUI, true) + retainedClick(client2, uiStateInbox, client2.inboxUI, true) - if !msg.retained { - t.Fatalf("Retained flag not set") - } + retainedCheck(true, "Retained flag not set") + + client1.Reload() + client1.AdvanceTo(uiStateMain) client2.Reload() client2.AdvanceTo(uiStateMain) - msg = client2.inbox[0] - if !msg.retained { - t.Fatalf("Retained flag lost") - } + retainedCheck(true, "Retained flag lost") baseTime := time.Now() - client2.nowFunc = func() time.Time { - return baseTime.Add(messageLifetime + 10*time.Second) + bumpTime := func(client *TestClient, t time.Duration) { + client.nowFunc = func() time.Time { + return baseTime.Add(t) + } + client.testTimerChan <- baseTime + client.AdvanceTo(uiStateTimerComplete) } - - client2.testTimerChan <- baseTime - client2.AdvanceTo(uiStateTimerComplete) - + bumpTime(client1, messageLifetime+10*time.Second) + if n := len(client1.outbox); n != 1 { + t.Fatalf("Message was deleted while retain flag set") + } + bumpTime(client2, messageLifetime+10*time.Second) if n := len(client2.inbox); n != 1 { t.Fatalf("Message was deleted while retain flag set") } - client2.gui.events <- Click{ - name: client2.inboxUI.entries[0].boxName, - } - client2.AdvanceTo(uiStateInbox) - client2.gui.events <- Click{ - name: "retain", - checks: map[string]bool{"retain": false}, - } - client2.AdvanceTo(uiStateInbox) + retainedClick(client1, uiStateOutbox, client1.outboxUI, false) + retainedClick(client2, uiStateInbox, client2.inboxUI, false) - if msg.retained { - t.Fatalf("Retain flag not cleared") + retainedCheck(false, "Retained flag not cleared") + + client1.testTimerChan <- baseTime + client1.AdvanceTo(uiStateTimerComplete) + if n := len(client1.outbox); n != 1 { + t.Fatalf("Message was deleted while in grace period") } client2.testTimerChan <- baseTime client2.AdvanceTo(uiStateTimerComplete) - if n := len(client2.inbox); n != 1 { t.Fatalf("Message was deleted while in grace period") } - client2.nowFunc = func() time.Time { - return baseTime.Add(messageLifetime + messageGraceTime + 20*time.Second) + bumpTime(client1, messageLifetime+messageGraceTime+20*time.Second) + if n := len(client1.outbox); n != 1 { + t.Fatalf("Message deleted while selected") } - - client2.testTimerChan <- baseTime - client2.AdvanceTo(uiStateTimerComplete) - + bumpTime(client2, messageLifetime+messageGraceTime+20*time.Second) if n := len(client2.inbox); n != 1 { t.Fatalf("Message deleted while selected") } - client2.gui.events <- Click{ - name: "compose", + elsewhere := func(client *TestClient) { + client.gui.events <- Click{ + name: "compose", + } + client.AdvanceTo(uiStateCompose) + + client.testTimerChan <- baseTime + client.AdvanceTo(uiStateTimerComplete) } - client2.AdvanceTo(uiStateCompose) - client2.testTimerChan <- baseTime - client2.AdvanceTo(uiStateTimerComplete) + elsewhere(client1) + elsewhere(client2) + if n := len(client1.outbox); n != 0 { + t.Fatalf("Message not deleted") + } if n := len(client2.inbox); n != 0 { t.Fatalf("Message not deleted") } diff --git a/client/disk.go b/client/disk.go index 8406609..48a5c3a 100644 --- a/client/disk.go +++ b/client/disk.go @@ -188,10 +188,12 @@ func (c *client) unmarshal(state *disk.State) error { for _, m := range state.Outbox { msg := &queuedMessage{ - id: *m.Id, - to: *m.To, - server: *m.Server, - created: time.Unix(*m.Created, 0), + id: *m.Id, + to: *m.To, + server: *m.Server, + created: time.Unix(*m.Created, 0), + retained: m.GetRetained(), + exposureTime: now, } c.registerId(msg.id) if len(m.Message) > 0 { @@ -328,7 +330,7 @@ func (c *client) marshal() []byte { var outbox []*disk.Outbox for _, msg := range c.outbox { - if time.Since(msg.created) > messageLifetime { + if time.Since(msg.created) > messageLifetime && !msg.retained { continue } m := &disk.Outbox{ @@ -337,6 +339,7 @@ func (c *client) marshal() []byte { Server: proto.String(msg.server), Created: proto.Int64(msg.created.Unix()), Revocation: proto.Bool(msg.revocation), + Retained: proto.Bool(msg.retained), } if msg.message != nil { if m.Message, err = proto.Marshal(msg.message); err != nil { diff --git a/client/disk/client.pb.go b/client/disk/client.pb.go index 1f29160..e316d8d 100644 --- a/client/disk/client.pb.go +++ b/client/disk/client.pb.go @@ -1,65 +1,89 @@ // Code generated by protoc-gen-go. -// source: github.com/agl/pond/client/disk/client.proto +// source: disk/client.proto // DO NOT EDIT! +/* +Package disk is a generated protocol buffer package. + +It is generated from these files: + disk/client.proto + +It has these top-level messages: + Header + Contact + RatchetState + Inbox + Outbox + Draft + State +*/ package disk import proto "github.com/golang/protobuf/proto" -import json "encoding/json" import math "math" import protos "github.com/agl/pond/protos" -// Reference proto, json, and math imports to suppress error if they are not otherwise used. +// Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal -var _ = &json.SyntaxError{} var _ = math.Inf +// Header is placed at the beginning of the state file and is *unencrypted and +// unauthenticated*. Its purpose is only to describe how to decrypt the +// remainder of the file. type Header struct { - NonceSmearCopies *int32 `protobuf:"varint,1,opt,name=nonce_smear_copies,def=1365" json:"nonce_smear_copies,omitempty"` - KdfSalt []byte `protobuf:"bytes,2,opt,name=kdf_salt" json:"kdf_salt,omitempty"` - Scrypt *Header_SCrypt `protobuf:"bytes,3,opt,name=scrypt" json:"scrypt,omitempty"` - TpmNvram *Header_TPM `protobuf:"bytes,4,opt,name=tpm_nvram" json:"tpm_nvram,omitempty"` - NoErasureStorage *bool `protobuf:"varint,5,opt,name=no_erasure_storage" json:"no_erasure_storage,omitempty"` - XXX_unrecognized []byte `json:"-"` + // nonce_smear_copies contains the number of copies of the nonce that + // follow the header. Each copy of the nonce is different and, XORed + // together, they result in the real nonce. The intent is that this may + // make recovery of old state files more difficult on HDDs. + NonceSmearCopies *int32 `protobuf:"varint,1,opt,name=nonce_smear_copies,def=1365" json:"nonce_smear_copies,omitempty"` + // kdf_salt contains the salt for the KDF function. + KdfSalt []byte `protobuf:"bytes,2,opt,name=kdf_salt" json:"kdf_salt,omitempty"` + Scrypt *Header_SCrypt `protobuf:"bytes,3,opt,name=scrypt" json:"scrypt,omitempty"` + TpmNvram *Header_TPM `protobuf:"bytes,4,opt,name=tpm_nvram" json:"tpm_nvram,omitempty"` + // no_erasure_storage exists to signal that there is no erasure storage + // for this state file, as opposed to the state file using a method + // that isn't recognised by the client. + NoErasureStorage *bool `protobuf:"varint,5,opt,name=no_erasure_storage" json:"no_erasure_storage,omitempty"` + XXX_unrecognized []byte `json:"-"` } -func (this *Header) Reset() { *this = Header{} } -func (this *Header) String() string { return proto.CompactTextString(this) } -func (*Header) ProtoMessage() {} +func (m *Header) Reset() { *m = Header{} } +func (m *Header) String() string { return proto.CompactTextString(m) } +func (*Header) ProtoMessage() {} const Default_Header_NonceSmearCopies int32 = 1365 -func (this *Header) GetNonceSmearCopies() int32 { - if this != nil && this.NonceSmearCopies != nil { - return *this.NonceSmearCopies +func (m *Header) GetNonceSmearCopies() int32 { + if m != nil && m.NonceSmearCopies != nil { + return *m.NonceSmearCopies } return Default_Header_NonceSmearCopies } -func (this *Header) GetKdfSalt() []byte { - if this != nil { - return this.KdfSalt +func (m *Header) GetKdfSalt() []byte { + if m != nil { + return m.KdfSalt } return nil } -func (this *Header) GetScrypt() *Header_SCrypt { - if this != nil { - return this.Scrypt +func (m *Header) GetScrypt() *Header_SCrypt { + if m != nil { + return m.Scrypt } return nil } -func (this *Header) GetTpmNvram() *Header_TPM { - if this != nil { - return this.TpmNvram +func (m *Header) GetTpmNvram() *Header_TPM { + if m != nil { + return m.TpmNvram } return nil } -func (this *Header) GetNoErasureStorage() bool { - if this != nil && this.NoErasureStorage != nil { - return *this.NoErasureStorage +func (m *Header) GetNoErasureStorage() bool { + if m != nil && m.NoErasureStorage != nil { + return *m.NoErasureStorage } return false } @@ -71,47 +95,48 @@ type Header_SCrypt struct { XXX_unrecognized []byte `json:"-"` } -func (this *Header_SCrypt) Reset() { *this = Header_SCrypt{} } -func (this *Header_SCrypt) String() string { return proto.CompactTextString(this) } -func (*Header_SCrypt) ProtoMessage() {} +func (m *Header_SCrypt) Reset() { *m = Header_SCrypt{} } +func (m *Header_SCrypt) String() string { return proto.CompactTextString(m) } +func (*Header_SCrypt) ProtoMessage() {} const Default_Header_SCrypt_N int32 = 32768 const Default_Header_SCrypt_R int32 = 16 const Default_Header_SCrypt_P int32 = 1 -func (this *Header_SCrypt) GetN() int32 { - if this != nil && this.N != nil { - return *this.N +func (m *Header_SCrypt) GetN() int32 { + if m != nil && m.N != nil { + return *m.N } return Default_Header_SCrypt_N } -func (this *Header_SCrypt) GetR() int32 { - if this != nil && this.R != nil { - return *this.R +func (m *Header_SCrypt) GetR() int32 { + if m != nil && m.R != nil { + return *m.R } return Default_Header_SCrypt_R } -func (this *Header_SCrypt) GetP() int32 { - if this != nil && this.P != nil { - return *this.P +func (m *Header_SCrypt) GetP() int32 { + if m != nil && m.P != nil { + return *m.P } return Default_Header_SCrypt_P } +// TPM contains information about an erasure key stored in TPM NVRAM. type Header_TPM struct { Index *uint32 `protobuf:"varint,1,req,name=index" json:"index,omitempty"` XXX_unrecognized []byte `json:"-"` } -func (this *Header_TPM) Reset() { *this = Header_TPM{} } -func (this *Header_TPM) String() string { return proto.CompactTextString(this) } -func (*Header_TPM) ProtoMessage() {} +func (m *Header_TPM) Reset() { *m = Header_TPM{} } +func (m *Header_TPM) String() string { return proto.CompactTextString(m) } +func (*Header_TPM) ProtoMessage() {} -func (this *Header_TPM) GetIndex() uint32 { - if this != nil && this.Index != nil { - return *this.Index +func (m *Header_TPM) GetIndex() uint32 { + if m != nil && m.Index != nil { + return *m.Index } return 0 } @@ -142,162 +167,162 @@ type Contact struct { XXX_unrecognized []byte `json:"-"` } -func (this *Contact) Reset() { *this = Contact{} } -func (this *Contact) String() string { return proto.CompactTextString(this) } -func (*Contact) ProtoMessage() {} +func (m *Contact) Reset() { *m = Contact{} } +func (m *Contact) String() string { return proto.CompactTextString(m) } +func (*Contact) ProtoMessage() {} const Default_Contact_IsPending bool = false -func (this *Contact) GetId() uint64 { - if this != nil && this.Id != nil { - return *this.Id +func (m *Contact) GetId() uint64 { + if m != nil && m.Id != nil { + return *m.Id } return 0 } -func (this *Contact) GetName() string { - if this != nil && this.Name != nil { - return *this.Name +func (m *Contact) GetName() string { + if m != nil && m.Name != nil { + return *m.Name } return "" } -func (this *Contact) GetGroupKey() []byte { - if this != nil { - return this.GroupKey +func (m *Contact) GetGroupKey() []byte { + if m != nil { + return m.GroupKey } return nil } -func (this *Contact) GetSupportedVersion() int32 { - if this != nil && this.SupportedVersion != nil { - return *this.SupportedVersion +func (m *Contact) GetSupportedVersion() int32 { + if m != nil && m.SupportedVersion != nil { + return *m.SupportedVersion } return 0 } -func (this *Contact) GetKeyExchangeBytes() []byte { - if this != nil { - return this.KeyExchangeBytes +func (m *Contact) GetKeyExchangeBytes() []byte { + if m != nil { + return m.KeyExchangeBytes } return nil } -func (this *Contact) GetPandaKeyExchange() []byte { - if this != nil { - return this.PandaKeyExchange +func (m *Contact) GetPandaKeyExchange() []byte { + if m != nil { + return m.PandaKeyExchange } return nil } -func (this *Contact) GetPandaError() string { - if this != nil && this.PandaError != nil { - return *this.PandaError +func (m *Contact) GetPandaError() string { + if m != nil && m.PandaError != nil { + return *m.PandaError } return "" } -func (this *Contact) GetTheirGroup() []byte { - if this != nil { - return this.TheirGroup +func (m *Contact) GetTheirGroup() []byte { + if m != nil { + return m.TheirGroup } return nil } -func (this *Contact) GetMyGroupKey() []byte { - if this != nil { - return this.MyGroupKey +func (m *Contact) GetMyGroupKey() []byte { + if m != nil { + return m.MyGroupKey } return nil } -func (this *Contact) GetGeneration() uint32 { - if this != nil && this.Generation != nil { - return *this.Generation +func (m *Contact) GetGeneration() uint32 { + if m != nil && m.Generation != nil { + return *m.Generation } return 0 } -func (this *Contact) GetTheirServer() string { - if this != nil && this.TheirServer != nil { - return *this.TheirServer +func (m *Contact) GetTheirServer() string { + if m != nil && m.TheirServer != nil { + return *m.TheirServer } return "" } -func (this *Contact) GetTheirPub() []byte { - if this != nil { - return this.TheirPub +func (m *Contact) GetTheirPub() []byte { + if m != nil { + return m.TheirPub } return nil } -func (this *Contact) GetTheirIdentityPublic() []byte { - if this != nil { - return this.TheirIdentityPublic +func (m *Contact) GetTheirIdentityPublic() []byte { + if m != nil { + return m.TheirIdentityPublic } return nil } -func (this *Contact) GetRevokedUs() bool { - if this != nil && this.RevokedUs != nil { - return *this.RevokedUs +func (m *Contact) GetRevokedUs() bool { + if m != nil && m.RevokedUs != nil { + return *m.RevokedUs } return false } -func (this *Contact) GetLastPrivate() []byte { - if this != nil { - return this.LastPrivate +func (m *Contact) GetLastPrivate() []byte { + if m != nil { + return m.LastPrivate } return nil } -func (this *Contact) GetCurrentPrivate() []byte { - if this != nil { - return this.CurrentPrivate +func (m *Contact) GetCurrentPrivate() []byte { + if m != nil { + return m.CurrentPrivate } return nil } -func (this *Contact) GetTheirLastPublic() []byte { - if this != nil { - return this.TheirLastPublic +func (m *Contact) GetTheirLastPublic() []byte { + if m != nil { + return m.TheirLastPublic } return nil } -func (this *Contact) GetTheirCurrentPublic() []byte { - if this != nil { - return this.TheirCurrentPublic +func (m *Contact) GetTheirCurrentPublic() []byte { + if m != nil { + return m.TheirCurrentPublic } return nil } -func (this *Contact) GetRatchet() *RatchetState { - if this != nil { - return this.Ratchet +func (m *Contact) GetRatchet() *RatchetState { + if m != nil { + return m.Ratchet } return nil } -func (this *Contact) GetPreviousTags() []*Contact_PreviousTag { - if this != nil { - return this.PreviousTags +func (m *Contact) GetPreviousTags() []*Contact_PreviousTag { + if m != nil { + return m.PreviousTags } return nil } -func (this *Contact) GetEvents() []*Contact_Event { - if this != nil { - return this.Events +func (m *Contact) GetEvents() []*Contact_Event { + if m != nil { + return m.Events } return nil } -func (this *Contact) GetIsPending() bool { - if this != nil && this.IsPending != nil { - return *this.IsPending +func (m *Contact) GetIsPending() bool { + if m != nil && m.IsPending != nil { + return *m.IsPending } return Default_Contact_IsPending } @@ -308,20 +333,20 @@ type Contact_PreviousTag struct { XXX_unrecognized []byte `json:"-"` } -func (this *Contact_PreviousTag) Reset() { *this = Contact_PreviousTag{} } -func (this *Contact_PreviousTag) String() string { return proto.CompactTextString(this) } -func (*Contact_PreviousTag) ProtoMessage() {} +func (m *Contact_PreviousTag) Reset() { *m = Contact_PreviousTag{} } +func (m *Contact_PreviousTag) String() string { return proto.CompactTextString(m) } +func (*Contact_PreviousTag) ProtoMessage() {} -func (this *Contact_PreviousTag) GetTag() []byte { - if this != nil { - return this.Tag +func (m *Contact_PreviousTag) GetTag() []byte { + if m != nil { + return m.Tag } return nil } -func (this *Contact_PreviousTag) GetExpired() int64 { - if this != nil && this.Expired != nil { - return *this.Expired +func (m *Contact_PreviousTag) GetExpired() int64 { + if m != nil && m.Expired != nil { + return *m.Expired } return 0 } @@ -332,20 +357,20 @@ type Contact_Event struct { XXX_unrecognized []byte `json:"-"` } -func (this *Contact_Event) Reset() { *this = Contact_Event{} } -func (this *Contact_Event) String() string { return proto.CompactTextString(this) } -func (*Contact_Event) ProtoMessage() {} +func (m *Contact_Event) Reset() { *m = Contact_Event{} } +func (m *Contact_Event) String() string { return proto.CompactTextString(m) } +func (*Contact_Event) ProtoMessage() {} -func (this *Contact_Event) GetTime() int64 { - if this != nil && this.Time != nil { - return *this.Time +func (m *Contact_Event) GetTime() int64 { + if m != nil && m.Time != nil { + return *m.Time } return 0 } -func (this *Contact_Event) GetMessage() string { - if this != nil && this.Message != nil { - return *this.Message +func (m *Contact_Event) GetMessage() string { + if m != nil && m.Message != nil { + return *m.Message } return "" } @@ -371,125 +396,125 @@ type RatchetState struct { XXX_unrecognized []byte `json:"-"` } -func (this *RatchetState) Reset() { *this = RatchetState{} } -func (this *RatchetState) String() string { return proto.CompactTextString(this) } -func (*RatchetState) ProtoMessage() {} +func (m *RatchetState) Reset() { *m = RatchetState{} } +func (m *RatchetState) String() string { return proto.CompactTextString(m) } +func (*RatchetState) ProtoMessage() {} -func (this *RatchetState) GetRootKey() []byte { - if this != nil { - return this.RootKey +func (m *RatchetState) GetRootKey() []byte { + if m != nil { + return m.RootKey } return nil } -func (this *RatchetState) GetSendHeaderKey() []byte { - if this != nil { - return this.SendHeaderKey +func (m *RatchetState) GetSendHeaderKey() []byte { + if m != nil { + return m.SendHeaderKey } return nil } -func (this *RatchetState) GetRecvHeaderKey() []byte { - if this != nil { - return this.RecvHeaderKey +func (m *RatchetState) GetRecvHeaderKey() []byte { + if m != nil { + return m.RecvHeaderKey } return nil } -func (this *RatchetState) GetNextSendHeaderKey() []byte { - if this != nil { - return this.NextSendHeaderKey +func (m *RatchetState) GetNextSendHeaderKey() []byte { + if m != nil { + return m.NextSendHeaderKey } return nil } -func (this *RatchetState) GetNextRecvHeaderKey() []byte { - if this != nil { - return this.NextRecvHeaderKey +func (m *RatchetState) GetNextRecvHeaderKey() []byte { + if m != nil { + return m.NextRecvHeaderKey } return nil } -func (this *RatchetState) GetSendChainKey() []byte { - if this != nil { - return this.SendChainKey +func (m *RatchetState) GetSendChainKey() []byte { + if m != nil { + return m.SendChainKey } return nil } -func (this *RatchetState) GetRecvChainKey() []byte { - if this != nil { - return this.RecvChainKey +func (m *RatchetState) GetRecvChainKey() []byte { + if m != nil { + return m.RecvChainKey } return nil } -func (this *RatchetState) GetSendRatchetPrivate() []byte { - if this != nil { - return this.SendRatchetPrivate +func (m *RatchetState) GetSendRatchetPrivate() []byte { + if m != nil { + return m.SendRatchetPrivate } return nil } -func (this *RatchetState) GetRecvRatchetPublic() []byte { - if this != nil { - return this.RecvRatchetPublic +func (m *RatchetState) GetRecvRatchetPublic() []byte { + if m != nil { + return m.RecvRatchetPublic } return nil } -func (this *RatchetState) GetSendCount() uint32 { - if this != nil && this.SendCount != nil { - return *this.SendCount +func (m *RatchetState) GetSendCount() uint32 { + if m != nil && m.SendCount != nil { + return *m.SendCount } return 0 } -func (this *RatchetState) GetRecvCount() uint32 { - if this != nil && this.RecvCount != nil { - return *this.RecvCount +func (m *RatchetState) GetRecvCount() uint32 { + if m != nil && m.RecvCount != nil { + return *m.RecvCount } return 0 } -func (this *RatchetState) GetPrevSendCount() uint32 { - if this != nil && this.PrevSendCount != nil { - return *this.PrevSendCount +func (m *RatchetState) GetPrevSendCount() uint32 { + if m != nil && m.PrevSendCount != nil { + return *m.PrevSendCount } return 0 } -func (this *RatchetState) GetRatchet() bool { - if this != nil && this.Ratchet != nil { - return *this.Ratchet +func (m *RatchetState) GetRatchet() bool { + if m != nil && m.Ratchet != nil { + return *m.Ratchet } return false } -func (this *RatchetState) GetV2() bool { - if this != nil && this.V2 != nil { - return *this.V2 +func (m *RatchetState) GetV2() bool { + if m != nil && m.V2 != nil { + return *m.V2 } return false } -func (this *RatchetState) GetPrivate0() []byte { - if this != nil { - return this.Private0 +func (m *RatchetState) GetPrivate0() []byte { + if m != nil { + return m.Private0 } return nil } -func (this *RatchetState) GetPrivate1() []byte { - if this != nil { - return this.Private1 +func (m *RatchetState) GetPrivate1() []byte { + if m != nil { + return m.Private1 } return nil } -func (this *RatchetState) GetSavedKeys() []*RatchetState_SavedKeys { - if this != nil { - return this.SavedKeys +func (m *RatchetState) GetSavedKeys() []*RatchetState_SavedKeys { + if m != nil { + return m.SavedKeys } return nil } @@ -500,20 +525,20 @@ type RatchetState_SavedKeys struct { XXX_unrecognized []byte `json:"-"` } -func (this *RatchetState_SavedKeys) Reset() { *this = RatchetState_SavedKeys{} } -func (this *RatchetState_SavedKeys) String() string { return proto.CompactTextString(this) } -func (*RatchetState_SavedKeys) ProtoMessage() {} +func (m *RatchetState_SavedKeys) Reset() { *m = RatchetState_SavedKeys{} } +func (m *RatchetState_SavedKeys) String() string { return proto.CompactTextString(m) } +func (*RatchetState_SavedKeys) ProtoMessage() {} -func (this *RatchetState_SavedKeys) GetHeaderKey() []byte { - if this != nil { - return this.HeaderKey +func (m *RatchetState_SavedKeys) GetHeaderKey() []byte { + if m != nil { + return m.HeaderKey } return nil } -func (this *RatchetState_SavedKeys) GetMessageKeys() []*RatchetState_SavedKeys_MessageKey { - if this != nil { - return this.MessageKeys +func (m *RatchetState_SavedKeys) GetMessageKeys() []*RatchetState_SavedKeys_MessageKey { + if m != nil { + return m.MessageKeys } return nil } @@ -525,27 +550,27 @@ type RatchetState_SavedKeys_MessageKey struct { XXX_unrecognized []byte `json:"-"` } -func (this *RatchetState_SavedKeys_MessageKey) Reset() { *this = RatchetState_SavedKeys_MessageKey{} } -func (this *RatchetState_SavedKeys_MessageKey) String() string { return proto.CompactTextString(this) } -func (*RatchetState_SavedKeys_MessageKey) ProtoMessage() {} +func (m *RatchetState_SavedKeys_MessageKey) Reset() { *m = RatchetState_SavedKeys_MessageKey{} } +func (m *RatchetState_SavedKeys_MessageKey) String() string { return proto.CompactTextString(m) } +func (*RatchetState_SavedKeys_MessageKey) ProtoMessage() {} -func (this *RatchetState_SavedKeys_MessageKey) GetNum() uint32 { - if this != nil && this.Num != nil { - return *this.Num +func (m *RatchetState_SavedKeys_MessageKey) GetNum() uint32 { + if m != nil && m.Num != nil { + return *m.Num } return 0 } -func (this *RatchetState_SavedKeys_MessageKey) GetKey() []byte { - if this != nil { - return this.Key +func (m *RatchetState_SavedKeys_MessageKey) GetKey() []byte { + if m != nil { + return m.Key } return nil } -func (this *RatchetState_SavedKeys_MessageKey) GetCreationTime() int64 { - if this != nil && this.CreationTime != nil { - return *this.CreationTime +func (m *RatchetState_SavedKeys_MessageKey) GetCreationTime() int64 { + if m != nil && m.CreationTime != nil { + return *m.CreationTime } return 0 } @@ -562,64 +587,64 @@ type Inbox struct { XXX_unrecognized []byte `json:"-"` } -func (this *Inbox) Reset() { *this = Inbox{} } -func (this *Inbox) String() string { return proto.CompactTextString(this) } -func (*Inbox) ProtoMessage() {} +func (m *Inbox) Reset() { *m = Inbox{} } +func (m *Inbox) String() string { return proto.CompactTextString(m) } +func (*Inbox) ProtoMessage() {} const Default_Inbox_Retained bool = false -func (this *Inbox) GetId() uint64 { - if this != nil && this.Id != nil { - return *this.Id +func (m *Inbox) GetId() uint64 { + if m != nil && m.Id != nil { + return *m.Id } return 0 } -func (this *Inbox) GetFrom() uint64 { - if this != nil && this.From != nil { - return *this.From +func (m *Inbox) GetFrom() uint64 { + if m != nil && m.From != nil { + return *m.From } return 0 } -func (this *Inbox) GetReceivedTime() int64 { - if this != nil && this.ReceivedTime != nil { - return *this.ReceivedTime +func (m *Inbox) GetReceivedTime() int64 { + if m != nil && m.ReceivedTime != nil { + return *m.ReceivedTime } return 0 } -func (this *Inbox) GetAcked() bool { - if this != nil && this.Acked != nil { - return *this.Acked +func (m *Inbox) GetAcked() bool { + if m != nil && m.Acked != nil { + return *m.Acked } return false } -func (this *Inbox) GetMessage() []byte { - if this != nil { - return this.Message +func (m *Inbox) GetMessage() []byte { + if m != nil { + return m.Message } return nil } -func (this *Inbox) GetRead() bool { - if this != nil && this.Read != nil { - return *this.Read +func (m *Inbox) GetRead() bool { + if m != nil && m.Read != nil { + return *m.Read } return false } -func (this *Inbox) GetSealed() []byte { - if this != nil { - return this.Sealed +func (m *Inbox) GetSealed() []byte { + if m != nil { + return m.Sealed } return nil } -func (this *Inbox) GetRetained() bool { - if this != nil && this.Retained != nil { - return *this.Retained +func (m *Inbox) GetRetained() bool { + if m != nil && m.Retained != nil { + return *m.Retained } return Default_Inbox_Retained } @@ -634,76 +659,86 @@ type Outbox struct { Request []byte `protobuf:"bytes,7,opt,name=request" json:"request,omitempty"` Acked *int64 `protobuf:"varint,8,opt,name=acked" json:"acked,omitempty"` Revocation *bool `protobuf:"varint,9,opt,name=revocation" json:"revocation,omitempty"` + Retained *bool `protobuf:"varint,10,opt,name=retained,def=0" json:"retained,omitempty"` XXX_unrecognized []byte `json:"-"` } -func (this *Outbox) Reset() { *this = Outbox{} } -func (this *Outbox) String() string { return proto.CompactTextString(this) } -func (*Outbox) ProtoMessage() {} +func (m *Outbox) Reset() { *m = Outbox{} } +func (m *Outbox) String() string { return proto.CompactTextString(m) } +func (*Outbox) ProtoMessage() {} + +const Default_Outbox_Retained bool = false -func (this *Outbox) GetId() uint64 { - if this != nil && this.Id != nil { - return *this.Id +func (m *Outbox) GetId() uint64 { + if m != nil && m.Id != nil { + return *m.Id } return 0 } -func (this *Outbox) GetTo() uint64 { - if this != nil && this.To != nil { - return *this.To +func (m *Outbox) GetTo() uint64 { + if m != nil && m.To != nil { + return *m.To } return 0 } -func (this *Outbox) GetServer() string { - if this != nil && this.Server != nil { - return *this.Server +func (m *Outbox) GetServer() string { + if m != nil && m.Server != nil { + return *m.Server } return "" } -func (this *Outbox) GetCreated() int64 { - if this != nil && this.Created != nil { - return *this.Created +func (m *Outbox) GetCreated() int64 { + if m != nil && m.Created != nil { + return *m.Created } return 0 } -func (this *Outbox) GetSent() int64 { - if this != nil && this.Sent != nil { - return *this.Sent +func (m *Outbox) GetSent() int64 { + if m != nil && m.Sent != nil { + return *m.Sent } return 0 } -func (this *Outbox) GetMessage() []byte { - if this != nil { - return this.Message +func (m *Outbox) GetMessage() []byte { + if m != nil { + return m.Message } return nil } -func (this *Outbox) GetRequest() []byte { - if this != nil { - return this.Request +func (m *Outbox) GetRequest() []byte { + if m != nil { + return m.Request } return nil } -func (this *Outbox) GetAcked() int64 { - if this != nil && this.Acked != nil { - return *this.Acked +func (m *Outbox) GetAcked() int64 { + if m != nil && m.Acked != nil { + return *m.Acked } return 0 } -func (this *Outbox) GetRevocation() bool { - if this != nil && this.Revocation != nil { - return *this.Revocation +func (m *Outbox) GetRevocation() bool { + if m != nil && m.Revocation != nil { + return *m.Revocation } return false } +func (m *Outbox) GetRetained() bool { + if m != nil && m.Retained != nil { + return *m.Retained + } + return Default_Outbox_Retained +} + type Draft struct { Id *uint64 `protobuf:"fixed64,1,req,name=id" json:"id,omitempty"` Created *int64 `protobuf:"varint,2,req,name=created" json:"created,omitempty"` @@ -715,55 +750,55 @@ type Draft struct { XXX_unrecognized []byte `json:"-"` } -func (this *Draft) Reset() { *this = Draft{} } -func (this *Draft) String() string { return proto.CompactTextString(this) } -func (*Draft) ProtoMessage() {} +func (m *Draft) Reset() { *m = Draft{} } +func (m *Draft) String() string { return proto.CompactTextString(m) } +func (*Draft) ProtoMessage() {} -func (this *Draft) GetId() uint64 { - if this != nil && this.Id != nil { - return *this.Id +func (m *Draft) GetId() uint64 { + if m != nil && m.Id != nil { + return *m.Id } return 0 } -func (this *Draft) GetCreated() int64 { - if this != nil && this.Created != nil { - return *this.Created +func (m *Draft) GetCreated() int64 { + if m != nil && m.Created != nil { + return *m.Created } return 0 } -func (this *Draft) GetTo() uint64 { - if this != nil && this.To != nil { - return *this.To +func (m *Draft) GetTo() uint64 { + if m != nil && m.To != nil { + return *m.To } return 0 } -func (this *Draft) GetBody() string { - if this != nil && this.Body != nil { - return *this.Body +func (m *Draft) GetBody() string { + if m != nil && m.Body != nil { + return *m.Body } return "" } -func (this *Draft) GetInReplyTo() uint64 { - if this != nil && this.InReplyTo != nil { - return *this.InReplyTo +func (m *Draft) GetInReplyTo() uint64 { + if m != nil && m.InReplyTo != nil { + return *m.InReplyTo } return 0 } -func (this *Draft) GetAttachments() []*protos.Message_Attachment { - if this != nil { - return this.Attachments +func (m *Draft) GetAttachments() []*protos.Message_Attachment { + if m != nil { + return m.Attachments } return nil } -func (this *Draft) GetDetachments() []*protos.Message_Detachment { - if this != nil { - return this.Detachments +func (m *Draft) GetDetachments() []*protos.Message_Detachment { + if m != nil { + return m.Detachments } return nil } @@ -785,97 +820,97 @@ type State struct { XXX_unrecognized []byte `json:"-"` } -func (this *State) Reset() { *this = State{} } -func (this *State) String() string { return proto.CompactTextString(this) } -func (*State) ProtoMessage() {} +func (m *State) Reset() { *m = State{} } +func (m *State) String() string { return proto.CompactTextString(m) } +func (*State) ProtoMessage() {} -func (this *State) GetIdentity() []byte { - if this != nil { - return this.Identity +func (m *State) GetIdentity() []byte { + if m != nil { + return m.Identity } return nil } -func (this *State) GetPublic() []byte { - if this != nil { - return this.Public +func (m *State) GetPublic() []byte { + if m != nil { + return m.Public } return nil } -func (this *State) GetPrivate() []byte { - if this != nil { - return this.Private +func (m *State) GetPrivate() []byte { + if m != nil { + return m.Private } return nil } -func (this *State) GetServer() string { - if this != nil && this.Server != nil { - return *this.Server +func (m *State) GetServer() string { + if m != nil && m.Server != nil { + return *m.Server } return "" } -func (this *State) GetGroup() []byte { - if this != nil { - return this.Group +func (m *State) GetGroup() []byte { + if m != nil { + return m.Group } return nil } -func (this *State) GetGroupPrivate() []byte { - if this != nil { - return this.GroupPrivate +func (m *State) GetGroupPrivate() []byte { + if m != nil { + return m.GroupPrivate } return nil } -func (this *State) GetPreviousGroupPrivateKeys() []*State_PreviousGroup { - if this != nil { - return this.PreviousGroupPrivateKeys +func (m *State) GetPreviousGroupPrivateKeys() []*State_PreviousGroup { + if m != nil { + return m.PreviousGroupPrivateKeys } return nil } -func (this *State) GetGeneration() uint32 { - if this != nil && this.Generation != nil { - return *this.Generation +func (m *State) GetGeneration() uint32 { + if m != nil && m.Generation != nil { + return *m.Generation } return 0 } -func (this *State) GetLastErasureStorageTime() int64 { - if this != nil && this.LastErasureStorageTime != nil { - return *this.LastErasureStorageTime +func (m *State) GetLastErasureStorageTime() int64 { + if m != nil && m.LastErasureStorageTime != nil { + return *m.LastErasureStorageTime } return 0 } -func (this *State) GetContacts() []*Contact { - if this != nil { - return this.Contacts +func (m *State) GetContacts() []*Contact { + if m != nil { + return m.Contacts } return nil } -func (this *State) GetInbox() []*Inbox { - if this != nil { - return this.Inbox +func (m *State) GetInbox() []*Inbox { + if m != nil { + return m.Inbox } return nil } -func (this *State) GetOutbox() []*Outbox { - if this != nil { - return this.Outbox +func (m *State) GetOutbox() []*Outbox { + if m != nil { + return m.Outbox } return nil } -func (this *State) GetDrafts() []*Draft { - if this != nil { - return this.Drafts +func (m *State) GetDrafts() []*Draft { + if m != nil { + return m.Drafts } return nil } @@ -887,27 +922,27 @@ type State_PreviousGroup struct { XXX_unrecognized []byte `json:"-"` } -func (this *State_PreviousGroup) Reset() { *this = State_PreviousGroup{} } -func (this *State_PreviousGroup) String() string { return proto.CompactTextString(this) } -func (*State_PreviousGroup) ProtoMessage() {} +func (m *State_PreviousGroup) Reset() { *m = State_PreviousGroup{} } +func (m *State_PreviousGroup) String() string { return proto.CompactTextString(m) } +func (*State_PreviousGroup) ProtoMessage() {} -func (this *State_PreviousGroup) GetGroup() []byte { - if this != nil { - return this.Group +func (m *State_PreviousGroup) GetGroup() []byte { + if m != nil { + return m.Group } return nil } -func (this *State_PreviousGroup) GetGroupPrivate() []byte { - if this != nil { - return this.GroupPrivate +func (m *State_PreviousGroup) GetGroupPrivate() []byte { + if m != nil { + return m.GroupPrivate } return nil } -func (this *State_PreviousGroup) GetExpired() int64 { - if this != nil && this.Expired != nil { - return *this.Expired +func (m *State_PreviousGroup) GetExpired() int64 { + if m != nil && m.Expired != nil { + return *m.Expired } return 0 } diff --git a/client/disk/client.proto b/client/disk/client.proto index ff47104..00a6559 100644 --- a/client/disk/client.proto +++ b/client/disk/client.proto @@ -125,6 +125,7 @@ message Outbox { optional bytes request = 7; optional int64 acked = 8; optional bool revocation = 9; + optional bool retained = 10 [ default = false ]; }; message Draft { diff --git a/client/erasure_gui.go b/client/erasure_gui.go new file mode 100644 index 0000000..016fada --- /dev/null +++ b/client/erasure_gui.go @@ -0,0 +1,16 @@ +// +build !nogui,!linux + +// Any build options that posses their own createErasureStorage should be excluded above. + +package main + +import ( + "github.com/agl/pond/client/disk" +) + +func (c *guiClient) createErasureStorage(pw string, stateFile *disk.StateFile) error { + c.gui.Actions() <- UIState{uiStateErasureStorage} + c.gui.Signal() + + return c.client.createErasureStorage(pw, stateFile) +} diff --git a/client/gui.go b/client/gui.go index 7d96a5f..5798921 100644 --- a/client/gui.go +++ b/client/gui.go @@ -173,7 +173,7 @@ RestartInboxIteration: RestartOutboxIteration: for { for _, msg := range c.outbox { - if msg.id != currentMsgId && now.Sub(msg.created) > messageLifetime { + if msg.id != currentMsgId && !msg.retained && now.Sub(msg.created) > messageLifetime && now.Sub(msg.exposureTime) > messageGraceTime { if msg.revocation || len(msg.message.Body) > 0 { c.outboxUI.Remove(msg.id) } @@ -1726,6 +1726,15 @@ func (c *guiClient) showOutbox(id uint64) interface{} { text: "Delete", }}, }, + { + {1, 1, CheckButton{ + widgetBase: widgetBase{ + name: "retain", + }, + checked: msg.retained, + text: "Retain", + }}, + }, }, } @@ -1749,7 +1758,12 @@ func (c *guiClient) showOutbox(id uint64) interface{} { return event } - if click, ok := event.(Click); ok && click.name == "abort" { + click, ok := event.(Click) + if !ok { + continue + } + switch { + case click.name == "abort": c.queueMutex.Lock() indexOfMessage := c.indexOfQueuedMessage(msg) if indexOfMessage == -1 || msg.sending { @@ -1775,9 +1789,7 @@ func (c *guiClient) showOutbox(id uint64) interface{} { c.drafts[draft.id] = draft c.save() return c.composeUI(draft, nil) - } - - if click, ok := event.(Click); ok && click.name == "delete" { + case click.name == "delete": c.deleteOutboxMsg(msg.id) // Also find and delete any empty acks for this message. for _, inboxMsg := range c.inbox { @@ -1794,6 +1806,15 @@ func (c *guiClient) showOutbox(id uint64) interface{} { c.gui.Actions() <- UIState{uiStateMain} c.gui.Signal() return nil + case click.name == "retain": + msg.retained = click.checks["retain"] + if !msg.retained { + msg.exposureTime = c.Now() + } + c.save() + // no c.updateInboxBackgroundColor(msg) so only needed for testing + c.gui.Actions() <- UIState{uiStateOutbox} + c.gui.Signal() } if !haveSentTime && !msg.sent.IsZero() { diff --git a/gogoprotoc.sh b/gogoprotoc.sh new file mode 100755 index 0000000..dd6ab99 --- /dev/null +++ b/gogoprotoc.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# +# Find gogoprotobuf at https://code.google.com/p/gogoprotobuf/ +# +# Install gogoprotobuf using : +# go get code.google.com/p/gogoprotobuf/{proto,protoc-gen-gogo,gogoproto} +# +exec protoc --proto_path=$GOPATH/src:. --gogo_out=. $* diff --git a/goprotoc.sh b/goprotoc.sh new file mode 100755 index 0000000..630fe63 --- /dev/null +++ b/goprotoc.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# +protoc --proto_path=$GOPATH/src:. --go_out=. $* +# +# We must replace : +# import protos "github.com/agl/pond/protos" +# by : import protos "github.com/agl/pond/protos/pond.pb" +# +perl -p -i~ -e 's/(import protos \"github.com\/agl\/pond\/protos)\/pond.pb\"/$1\"/' disk/client.pb.go +