1
2
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
18
19 package fs
20
21 import (
22 "context"
23 "os"
24 "strings"
25 "sync"
26 "time"
27
28 "bazil.org/fuse"
29 "bazil.org/fuse/fs"
30 "go4.org/syncutil"
31 "perkeep.org/pkg/blob"
32 "perkeep.org/pkg/search"
33 )
34
35 const versionsRefreshTime = 1 * time.Minute
36
37
38
39
40
41
42
43
44 type versionsDir struct {
45 fs *CamliFileSystem
46
47 mu sync.Mutex
48 lastQuery time.Time
49 m map[string]blob.Ref
50 children map[string]fs.Node
51 }
52
53 func (n *versionsDir) isRO() bool {
54 return true
55 }
56
57 func (n *versionsDir) dirMode() os.FileMode {
58 if n.isRO() {
59 return 0500
60 }
61 return 0700
62 }
63
64 func (n *versionsDir) Attr(ctx context.Context, a *fuse.Attr) error {
65 a.Mode = os.ModeDir | n.dirMode()
66 a.Uid = uint32(os.Getuid())
67 a.Gid = uint32(os.Getgid())
68 return nil
69 }
70
71 func (n *versionsDir) ReadDir(ctx context.Context) ([]fuse.Dirent, error) {
72 n.mu.Lock()
73 defer n.mu.Unlock()
74 if err := n.condRefresh(ctx); err != nil {
75 return nil, handleEIOorEINTR(err)
76 }
77 var ents []fuse.Dirent
78 for name := range n.m {
79 ents = append(ents, fuse.Dirent{Name: name})
80 }
81 Logger.Printf("fs.versions.ReadDir() -> %v", ents)
82 return ents, nil
83 }
84
85 func (n *versionsDir) Lookup(ctx context.Context, name string) (fs.Node, error) {
86 Logger.Printf("fs.versions: Lookup(%q)", name)
87 n.mu.Lock()
88 defer n.mu.Unlock()
89 if err := n.condRefresh(ctx); err != nil {
90 return nil, handleEIOorEINTR(err)
91 }
92 br := n.m[name]
93 if !br.Valid() {
94 return nil, fuse.ENOENT
95 }
96
97 nod, ok := n.children[name]
98
99 if ok {
100 return nod, nil
101 }
102
103 nod = newROVersionsDir(n.fs, br, name)
104
105 n.children[name] = nod
106
107 return nod, nil
108 }
109
110
111 func (n *versionsDir) condRefresh(ctx context.Context) error {
112 if n.lastQuery.After(time.Now().Add(-versionsRefreshTime)) {
113 return nil
114 }
115 Logger.Printf("fs.versions: querying")
116
117 var rootRes, impRes *search.WithAttrResponse
118 var grp syncutil.Group
119 grp.Go(func() (err error) {
120 rootRes, err = n.fs.client.GetPermanodesWithAttr(ctx, &search.WithAttrRequest{N: 100, Attr: "camliRoot"})
121 return
122 })
123 grp.Go(func() (err error) {
124 impRes, err = n.fs.client.GetPermanodesWithAttr(ctx, &search.WithAttrRequest{N: 100, Attr: "camliImportRoot"})
125 return
126 })
127 if err := grp.Err(); err != nil {
128 Logger.Printf("fs.versions: GetRecentPermanodes error in ReadDir: %v", err)
129 return err
130 }
131
132 n.m = make(map[string]blob.Ref)
133 if n.children == nil {
134 n.children = make(map[string]fs.Node)
135 }
136
137 dr := &search.DescribeRequest{
138 Depth: 1,
139 }
140 for _, wi := range rootRes.WithAttr {
141 dr.BlobRefs = append(dr.BlobRefs, wi.Permanode)
142 }
143 for _, wi := range impRes.WithAttr {
144 dr.BlobRefs = append(dr.BlobRefs, wi.Permanode)
145 }
146 if len(dr.BlobRefs) == 0 {
147 return nil
148 }
149
150 dres, err := n.fs.client.Describe(ctx, dr)
151 if err != nil {
152 Logger.Printf("Describe failure: %v", err)
153 return err
154 }
155
156
157 currentRoots := map[string]bool{}
158 for _, wi := range rootRes.WithAttr {
159 pn := wi.Permanode
160 db := dres.Meta[pn.String()]
161 if db != nil && db.Permanode != nil {
162 name := db.Permanode.Attr.Get("camliRoot")
163 if name != "" {
164 currentRoots[name] = true
165 n.m[name] = pn
166 }
167 }
168 }
169
170
171
172 for name := range n.children {
173 if !currentRoots[name] {
174 delete(n.children, name)
175 }
176 }
177
178
179 for _, wi := range impRes.WithAttr {
180 pn := wi.Permanode
181 db := dres.Meta[pn.String()]
182 if db != nil && db.Permanode != nil {
183 name := db.Permanode.Attr.Get("camliImportRoot")
184 if name != "" {
185 name = strings.Replace(name, ":", "-", -1)
186 name = strings.Replace(name, "/", "-", -1)
187 n.m["importer-"+name] = pn
188 }
189 }
190 }
191
192 n.lastQuery = time.Now()
193 return nil
194 }