This repository has been archived by the owner on Oct 3, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 74
/
images.go
165 lines (149 loc) · 4.14 KB
/
images.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
161
162
163
164
165
package db
import (
"errors"
"time"
"github.com/bakape/meguca/common"
"github.com/bakape/meguca/imager/assets"
"github.com/bakape/meguca/util"
r "github.com/dancannon/gorethink"
)
const (
// Time it takes for an image allocation token to expire
tokenTimeout = time.Minute
)
var (
// Update associate post count on an image document
incrementImageRefCount = map[string]r.Term{
"posts": r.Row.Field("posts").Add(1),
}
// ErrInvalidToken occurs, when trying to retrieve an image with an
// non-existent token. The token might have expired (60 to 119 seconds) or
// the client could have provided an invalid token to begin with.
ErrInvalidToken = errors.New("invalid image token")
)
// Document for registering a token corresponding to a client's right to
// allocate an image in its post
type allocationToken struct {
SHA1 string
Expires time.Time `gorethink:"expires"`
}
// FindImageThumb searches for an existing image with the specified hash and
// returns it, if it exists. Otherwise, returns an empty struct. To ensure the
// image is not deallocated by another thread/process, the reference counter
// of the image will be incremented.
func FindImageThumb(hash string) (img common.ImageCommon, err error) {
query := GetImage(hash).
Update(incrementImageRefCount, r.UpdateOpts{ReturnChanges: true}).
Field("changes").
Field("new_val").
Without("posts").
Default(nil)
err = One(query, &img)
return
}
// NewImageToken inserts a new expiring image token document into the DB and
// returns it's ID
func NewImageToken(SHA1 string) (code int, token string, err error) {
q := r.
Table("imageTokens").
Insert(allocationToken{
SHA1: SHA1,
Expires: time.Now().Add(tokenTimeout),
}).
Field("generated_keys").
AtIndex(0)
err = One(q, &token)
if err != nil {
code = 500
} else {
code = 200
}
return
}
// UseImageToken deletes a document from the "imageTokens" table and uses and
// returns the Image document from the "images" table, the token was created
// for. If no token exists, returns ErrInvalidToken.
func UseImageToken(id string) (img common.ImageCommon, err error) {
q := r.
Table("imageTokens").
Get(id).
Delete(r.DeleteOpts{ReturnChanges: true}).
Field("changes").
AtIndex(0).
Field("old_val").
Pluck("SHA1").
Merge(r.
Table("images").
Get(r.Row.Field("SHA1")).
Without("posts"),
).
Default(nil)
err = One(q, &img)
if err == r.ErrEmptyResult {
err = ErrInvalidToken
}
return
}
// DeallocateImage decrements the image's reference counter. If the counter
// would become zero, the image entry is immediately deleted along with its
// file assets.
func DeallocateImage(id string) error {
query := GetImage(id).
Replace(
func(doc r.Term) r.Term {
return r.Branch(
doc.Field("posts").Eq(1),
nil,
doc.Merge(map[string]r.Term{
"posts": doc.Field("posts").Sub(1),
}),
)
},
r.ReplaceOpts{ReturnChanges: true},
).
Field("changes").
Field("old_val").
Pluck("posts", "fileType")
var res struct {
Posts int
FileType, ThumbType uint8
}
if err := One(query, &res); err != nil {
return err
}
if res.Posts == 1 {
if err := assets.Delete(id, res.FileType, res.ThumbType); err != nil {
return err
}
}
return nil
}
// AllocateImage allocates an image's file resources to their respective served
// directories and write its data to the database
func AllocateImage(src, thumb []byte, img common.ImageCommon) error {
err := assets.Write(img.SHA1, img.FileType, img.ThumbType, src, thumb)
if err != nil {
return cleanUpFailedAllocation(img, err)
}
// TODO: Account for race condition, when the same image is uploaded at the
// same time by multiple clients.
query := r.
Table("images").
Insert(common.ProtoImage{
ImageCommon: img,
Posts: 1,
})
err = Write(query)
if err != nil {
return cleanUpFailedAllocation(img, err)
}
return nil
}
// Delete any dangling image files in case of a failed image allocation
func cleanUpFailedAllocation(img common.ImageCommon, err error) error {
delErr := assets.Delete(img.SHA1, img.FileType, img.ThumbType)
if delErr != nil {
err = util.WrapError(err.Error(), delErr)
}
return err
}