-
Notifications
You must be signed in to change notification settings - Fork 2
/
radium.go
142 lines (116 loc) · 3.02 KB
/
radium.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
package radium
import (
"context"
"fmt"
"sync"
"github.com/sirupsen/logrus"
)
// Default registered strategies
const (
Strategy1st = "1st"
StrategyConcurrent = "concurrent"
)
// New initializes an instance of radium
func New(cache Cache, logger Logger) *Instance {
if cache == nil {
// only useful in case of server or clipboard mode
cache = &defaultCache{
mu: &sync.Mutex{},
data: map[string][]Article{},
}
}
if logger == nil {
logger = defaultLogger{
logger: logrus.New(),
}
}
ins := &Instance{}
ins.cache = cache
ins.Logger = logger
ins.strategies = map[string]Strategy{
Strategy1st: NewNthResult(1, ins.Logger),
StrategyConcurrent: NewConcurrent(ins.Logger),
}
return ins
}
// Instance represents an instance of radium
type Instance struct {
Logger
sources []RegisteredSource
strategies map[string]Strategy
cache Cache
}
// RegisterStrategy adds a new source to the query sources
func (ins *Instance) RegisterStrategy(name string, strategy Strategy) {
if ins.strategies == nil {
ins.strategies = map[string]Strategy{}
}
ins.strategies[name] = strategy
}
// RegisterSource adds a new source to the query sources
func (ins *Instance) RegisterSource(name string, src Source) error {
for _, entry := range ins.sources {
if name == entry.Name {
return fmt.Errorf("source with given name already exists")
}
}
ins.sources = append(ins.sources, RegisteredSource{
Name: name,
Source: src,
})
return nil
}
// GetSources returns a list of registered sources
func (ins Instance) GetSources() []RegisteredSource {
return ins.sources
}
// Search using given query and return results if any
func (ins Instance) Search(ctx context.Context, query Query, strategyName string) ([]Article, error) {
if err := query.Validate(); err != nil {
return nil, err
}
if rs := ins.findInCache(query); rs != nil && len(rs) > 0 {
ins.Infof("cache hit for '%s'", query.Text)
return rs, nil
}
ins.Infof("cache miss for '%s'", query.Text)
strategy, exists := ins.strategies[strategyName]
if !exists {
return nil, fmt.Errorf("no such strategy: %s", strategyName)
}
results, err := strategy.Execute(ctx, query, ins.sources)
if err != nil {
return nil, err
}
go ins.performCaching(query, results)
return results, nil
}
func (ins Instance) findInCache(query Query) []Article {
if ins.cache == nil {
return nil
}
rs, err := ins.cache.Search(context.Background(), query)
if err != nil {
ins.Warnf("failed to search in cache: %s", err)
return nil
}
return rs
}
func (ins Instance) performCaching(query Query, results []Article) {
if ins.cache == nil {
return
}
if err := ins.cache.Set(query, results); err != nil {
ins.Warnf("failed to cache result: %s", err)
}
}
// Source implementation is responsible for providing
// external data source to query for results.
type Source interface {
Search(ctx context.Context, q Query) ([]Article, error)
}
// RegisteredSource embeds given Source along with the registered name.
type RegisteredSource struct {
Name string
Source
}