-
Notifications
You must be signed in to change notification settings - Fork 11
/
rater.go
123 lines (101 loc) · 2.54 KB
/
rater.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
// Copyright 2014 The Too Authors. All rights reserved.
package too
import (
"fmt"
"github.com/garyburd/redigo/redis"
)
type Rater struct {
e *Engine
kind string
memberships map[User][]Item
}
type BatchRaterOp struct {
User User
Items []Item
}
func (r Rater) Batch(ops []BatchRaterOp, updateSimilarsAndSuggestions bool) error {
// Disable Auto Update, Suggestions and Similiars will be updated later
r.e.DisableAutoUpdateSimilarsAndSuggestions()
// Cache memberships to be used into redis transaction
r.memberships = make(map[User][]Item, 0)
r.cacheMemberships(ops)
// Start a transaction
r.e.c.Send("MULTI")
for _, op := range ops {
for _, item := range op.Items {
err := r.Add(op.User, item)
if err != nil {
// Rollback if found error
r.e.c.Send("DISCARD")
return err
}
}
}
// Commit the transaction
r.e.c.Do("EXEC")
// After finished, update Suggestions and Similiars
if updateSimilarsAndSuggestions {
for _, op := range ops {
r.e.Update(op.User)
}
}
r.e.EnableAutoUpdateSimilarsAndSuggestions()
return nil
}
func (r Rater) cacheMemberships(ops []BatchRaterOp) {
for _, op := range ops {
for _, item := range op.Items {
r.e.c.Send("WATCH", r.memberKey(item))
yes, err := r.userIsMember(op.User, item, false)
if yes && err != nil {
r.memberships[op.User] = append(r.memberships[op.User], item)
}
}
}
}
func (r Rater) isCachedInMembership(user User, item Item) bool {
for _, _item := range r.memberships[user] {
if _item == item {
return true
}
}
return false
}
func (r Rater) userIsMember(user User, item Item, useCache bool) (bool, error) {
yes, err := redis.Bool(r.e.c.Do("SISMEMBER", r.memberKey(item), user))
if err != nil && useCache {
return r.isCachedInMembership(user, item), nil
}
return yes, err
}
func (r Rater) memberKey(item Item) string {
return fmt.Sprintf("%s:%s:%s", r.e.class, item, r.kind)
}
// Add adds a rating by user for item
func (r Rater) Add(user User, item Item) error {
yes, err := r.userIsMember(user, item, true)
if err != nil {
return err
}
if !yes {
_, err = r.e.c.Do("ZINCRBY", fmt.Sprintf("%s:mosts:%s", r.e.class, r.kind), 1, item)
if err != nil {
return err
}
}
_, err = r.e.c.Do("SADD", fmt.Sprintf("%s:%s:%s", r.e.class, user, r.kind), item)
if err != nil {
return err
}
_, err = r.e.c.Do("SADD", fmt.Sprintf("%s:%s:%s", r.e.class, item, r.kind), user)
if err != nil {
return err
}
if r.e.autoUpdateSimilarsAndSuggestions {
err = r.e.Update(user)
}
if err != nil {
return err
}
return nil
}