-
Notifications
You must be signed in to change notification settings - Fork 12
/
resources.go
160 lines (138 loc) · 4.91 KB
/
resources.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package clientdb
import (
"errors"
"os"
"path"
"path/filepath"
"time"
"github.com/companyzero/bisonrelay/client/clientintf"
"github.com/companyzero/bisonrelay/rpc"
)
// NewPagesSession starts a new session for fetching related pages.
func (db *DB) NewPagesSession(tx ReadWriteTx) (clientintf.PagesSessionID, error) {
baseDir := filepath.Join(db.root, pageSessionsDir)
last, err := pageSessDirPattern.Last(baseDir)
if err != nil {
return 0, err
}
id := clientintf.PagesSessionID(last.ID + 1)
sessDir := filepath.Join(baseDir, pageSessDirPattern.FilenameFor(uint64(id)))
err = os.MkdirAll(sessDir, 0o700)
return id, err
}
// StoreResourceRequest stores the specified requested resource. This generates
// a random tag for the request, which is set in the passed request Tag field.
func (db *DB) StoreResourceRequest(tx ReadWriteTx, uid UserID,
sess, parentPage clientintf.PagesSessionID, req *rpc.RMFetchResource) error {
userReqsDir := filepath.Join(db.root, inboundDir, uid.String(), reqResourcesDir)
// Generate an unused tag for this user.
tag := rpc.ResourceTag(db.mustRandomUint64())
filename := path.Join(userReqsDir, tag.String())
for fileExists(filename) {
tag = rpc.ResourceTag(db.mustRandomUint64())
filename = path.Join(userReqsDir, tag.String())
}
req.Tag = tag
rr := ResourceRequest{
UID: uid,
SesssionID: sess,
ParentPage: parentPage,
Request: *req,
Timestamp: time.Now(),
}
if err := db.saveJsonFile(filename, rr); err != nil {
return err
}
// Update the overview of the session with the new request.
overv, err := db.readResourcesSessionOverview(sess)
if err != nil {
return err
}
overv.appendRequest(uid, tag)
if err := db.saveResourcesSessionOverview(sess, &overv); err != nil {
return err
}
return nil
}
// readResourceRequest returns the resource request corresponding to the
// specified tag.
func (db *DB) readResourceRequest(tx ReadTx, uid UserID,
tag rpc.ResourceTag) (ResourceRequest, error) {
dir := filepath.Join(db.root, inboundDir, uid.String(), reqResourcesDir)
filename := path.Join(dir, tag.String())
var res ResourceRequest
err := db.readJsonFile(filename, &res)
return res, err
}
// removeResourceRequest deletes the request with the corresponding tag.
func (db *DB) removeResourceRequest(tx ReadWriteTx, uid UserID, tag rpc.ResourceTag) error {
dir := filepath.Join(db.root, inboundDir, uid.String(), reqResourcesDir)
filename := path.Join(dir, tag.String())
return removeIfExists(filename)
}
// readResourcesSessionOverview reads the overview data of a pages session. It
// returns a new, empty overview if one does not exist for the session.
func (db *DB) readResourcesSessionOverview(sessID clientintf.PagesSessionID) (PageSessionOverview, error) {
sessionDir := filepath.Join(db.root, pageSessionsDir, sessID.String())
fname := filepath.Join(sessionDir, pageSessionOverviewFile)
var res PageSessionOverview
err := db.readJsonFile(fname, &res)
if err != nil && !errors.Is(err, ErrNotFound) {
return res, err
}
return res, nil
}
func (db *DB) saveResourcesSessionOverview(sessID clientintf.PagesSessionID, overview *PageSessionOverview) error {
sessionDir := filepath.Join(db.root, pageSessionsDir, sessID.String())
fname := filepath.Join(sessionDir, pageSessionOverviewFile)
return db.saveJsonFile(fname, overview)
}
// StoreFetchedResource removes an existing request sent to the specified
// user with the tag, and stores the resulting fetched response.
func (db *DB) StoreFetchedResource(tx ReadWriteTx, uid UserID, tag rpc.ResourceTag,
reply rpc.RMFetchResourceReply) (FetchedResource, PageSessionOverview, error) {
var fr FetchedResource
var sess PageSessionOverview
// Double check request exists.
req, err := db.readResourceRequest(tx, uid, tag)
if err != nil {
return fr, sess, err
}
sessionDir := filepath.Join(db.root, pageSessionsDir, pageSessDirPattern.FilenameFor(uint64(req.SesssionID)))
last, err := pageFnamePattern.Last(sessionDir)
if err != nil {
return fr, sess, err
}
pageID := last.ID + 1
// Store the fetched resource.
fr = FetchedResource{
UID: uid,
SessionID: req.SesssionID,
ParentPage: req.ParentPage,
PageID: clientintf.PagesSessionID(pageID),
RequestTS: req.Timestamp,
ResponseTS: time.Now(),
Request: req.Request,
Response: reply,
}
fname := filepath.Join(sessionDir, pageFnamePattern.FilenameFor(pageID))
err = db.saveJsonFile(fname, fr)
if err != nil {
return fr, sess, err
}
// Update the overview of this session, adding the new page.
sess, err = db.readResourcesSessionOverview(req.SesssionID)
if err != nil {
return fr, sess, err
}
sess.removeRequest(uid, tag)
sess.append(req.ParentPage, clientintf.PagesSessionID(pageID))
if err := db.saveResourcesSessionOverview(req.SesssionID, &sess); err != nil {
return fr, sess, err
}
// Finally, remove the old request.
if err := db.removeResourceRequest(tx, uid, tag); err != nil {
return fr, sess, err
}
return fr, sess, nil
}