-
Notifications
You must be signed in to change notification settings - Fork 1
/
filecache.go
154 lines (130 loc) · 2.88 KB
/
filecache.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
package main
import (
"fmt"
"log"
"os"
"path"
"regexp"
"time"
)
type FileCache struct {
Path string
RecvChannel chan fcTask
Closed bool
FileRefs []FileRef
}
type SearchResult struct {
FileRefs []FileRef
}
type fcTask struct {
action string // actions can be refresh or search
args []interface{}
}
func (fc *FileCache) daemon() {
for {
task, ok := <-fc.RecvChannel
if !ok {
// the channel has been closed. just exit
return
}
switch {
case task.action == "refresh":
fc.doRefresh()
case task.action == "search":
regex := task.args[0].(string)
responseChannel := task.args[1].(chan []FileRef)
fc.doSearch(regex, responseChannel)
}
}
}
func (fc *FileCache) doRefreshDirectory(realPath string, webPath string) {
file, err := os.Open(realPath)
defer file.Close()
if err != nil {
log.Println(err.Error())
return
}
fi, err := file.Readdir(-1)
if err != nil {
log.Println(err.Error())
return
}
for _, f := range fi {
fr := MakeFileRef(webPath, f)
fc.FileRefs = append(fc.FileRefs, fr)
if f.IsDir() {
fc.doRefreshDirectory(path.Join(realPath, f.Name()), path.Join(webPath, f.Name()))
}
}
}
func (fc *FileCache) doRefresh() {
fc.FileRefs = []FileRef{}
fc.doRefreshDirectory(fc.Path, "/")
}
func (fc *FileCache) doSearch(regex string, responseChannel chan []FileRef) {
re, err := regexp.Compile(regex)
if err != nil {
// if it failed just close the response channel to indicate that
// it failed
close(responseChannel)
}
matchedFiles := []FileRef{}
for _, f := range fc.FileRefs {
if re.MatchString(f.Path) {
matchedFiles = append(matchedFiles, f)
}
}
responseChannel <- matchedFiles
close(responseChannel)
}
func (fc *FileCache) refresh() {
task := fcTask{}
task.action = "refresh"
fc.RecvChannel <- task
}
func NewFileCache(path string) *FileCache {
fc := FileCache{}
fc.Path = path
fc.RecvChannel = make(chan fcTask)
go fc.daemon()
fc.refresh()
// start the refresh daemon. just pings the filecache
// daemon every minute to refresh its cache
go func() {
for {
// if the channel is closed return
if fc.Closed {
return
}
time.Sleep(time.Minute)
fc.refresh()
}
}()
return &fc
}
func (fc *FileCache) Search(regex string) ([]FileRef, error) {
task := fcTask{}
task.action = "search"
filerefChannel := make(chan []FileRef)
task.args = []interface{}{regex, filerefChannel}
fc.RecvChannel <- task
timeout := make(chan bool, 1)
go func() {
// timeout after 5 seconds
time.Sleep(5 * time.Second)
}()
select {
case resRefs, ok := <-filerefChannel:
if !ok {
return nil, fmt.Errorf("Search \"%s\" failed", regex)
}
return resRefs, nil
case <-timeout:
return nil, fmt.Errorf("Search \"%s\" timed out", regex)
}
return nil, fmt.Errorf("Search \"%s\" failed. FileCache daemon not running")
}
func (fc *FileCache) Close() {
fc.Closed = true
close(fc.RecvChannel)
}