1package storage 2 3import ( 4 "fmt" 5 6 "github.com/keybase/client/go/chat/globals" 7 "github.com/keybase/client/go/chat/utils" 8 "github.com/keybase/client/go/libkb" 9 "github.com/keybase/client/go/protocol/chat1" 10 "github.com/keybase/client/go/protocol/gregor1" 11 "golang.org/x/net/context" 12) 13 14const readOutboxVersion = 1 15 16type ReadOutboxRecord struct { 17 ID chat1.OutboxID 18 ConvID chat1.ConversationID 19 MsgID chat1.MessageID 20} 21 22type diskReadOutbox struct { 23 Version int `codec:"V"` 24 Records []ReadOutboxRecord `codec:"O"` 25} 26 27type ReadOutbox struct { 28 globals.Contextified 29 utils.DebugLabeler 30 *baseBox 31 32 uid gregor1.UID 33} 34 35func NewReadOutbox(g *globals.Context, uid gregor1.UID) *ReadOutbox { 36 return &ReadOutbox{ 37 Contextified: globals.NewContextified(g), 38 DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "ReadOutbox", false), 39 baseBox: newBaseBox(g), 40 uid: uid, 41 } 42} 43 44func (o *ReadOutbox) dbKey() libkb.DbKey { 45 return libkb.DbKey{ 46 Typ: libkb.DBChatOutbox, 47 Key: fmt.Sprintf("rob:%s", o.uid), 48 } 49} 50 51func (o *ReadOutbox) clear(ctx context.Context) Error { 52 err := o.G().LocalChatDb.Delete(o.dbKey()) 53 if err != nil { 54 return NewInternalError(ctx, o.DebugLabeler, "error clearing read outbox: uid: %s err: %s", 55 o.uid, err) 56 } 57 return nil 58} 59 60func (o *ReadOutbox) readStorage(ctx context.Context) (res diskReadOutbox) { 61 if memobox := readOutboxMemCache.Get(o.uid); memobox != nil { 62 o.Debug(ctx, "hit in memory cache") 63 res = *memobox 64 } else { 65 found, ierr := o.readDiskBox(ctx, o.dbKey(), &res) 66 if ierr != nil { 67 if _, ok := ierr.(libkb.LoginRequiredError); !ok { 68 o.maybeNuke(NewInternalError(ctx, o.DebugLabeler, ierr.Error()), o.dbKey()) 69 } 70 return diskReadOutbox{Version: readOutboxVersion} 71 } 72 if !found { 73 return diskReadOutbox{Version: readOutboxVersion} 74 } 75 readOutboxMemCache.Put(o.uid, &res) 76 } 77 if res.Version != readOutboxVersion { 78 o.Debug(ctx, "on disk version not equal to program version, clearing: disk :%d program: %d", 79 res.Version, readOutboxVersion) 80 if cerr := o.clear(ctx); cerr != nil { 81 return diskReadOutbox{Version: readOutboxVersion} 82 } 83 return diskReadOutbox{Version: readOutboxVersion} 84 } 85 return res 86} 87 88func (o *ReadOutbox) writeStorage(ctx context.Context, obox diskReadOutbox) (err Error) { 89 if ierr := o.writeDiskBox(ctx, o.dbKey(), obox); ierr != nil { 90 return NewInternalError(ctx, o.DebugLabeler, "error writing outbox: err: %s", ierr) 91 } 92 readOutboxMemCache.Put(o.uid, &obox) 93 return nil 94} 95 96func (o *ReadOutbox) PushRead(ctx context.Context, convID chat1.ConversationID, msgID chat1.MessageID) (err Error) { 97 locks.ReadOutbox.Lock() 98 defer locks.ReadOutbox.Unlock() 99 obox := o.readStorage(ctx) 100 id, ierr := NewOutboxID() 101 if ierr != nil { 102 return NewInternalError(ctx, o.DebugLabeler, "failed to generate id: %s", ierr) 103 } 104 obox.Records = append(obox.Records, ReadOutboxRecord{ 105 ID: id, 106 ConvID: convID, 107 MsgID: msgID, 108 }) 109 return o.writeStorage(ctx, obox) 110} 111 112func (o *ReadOutbox) GetRecords(ctx context.Context) (res []ReadOutboxRecord, err Error) { 113 locks.ReadOutbox.Lock() 114 defer locks.ReadOutbox.Unlock() 115 obox := o.readStorage(ctx) 116 return obox.Records, nil 117} 118 119func (o *ReadOutbox) RemoveRecord(ctx context.Context, id chat1.OutboxID) Error { 120 locks.ReadOutbox.Lock() 121 defer locks.ReadOutbox.Unlock() 122 obox := o.readStorage(ctx) 123 var newrecs []ReadOutboxRecord 124 for _, rec := range obox.Records { 125 if !rec.ID.Eq(&id) { 126 newrecs = append(newrecs, rec) 127 } 128 } 129 obox.Records = newrecs 130 return o.writeStorage(ctx, obox) 131} 132