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