1// +build linux darwin 2 3/* 4Copyright 2013 The Perkeep Authors 5 6Licensed under the Apache License, Version 2.0 (the "License"); 7you may not use this file except in compliance with the License. 8You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12Unless required by applicable law or agreed to in writing, software 13distributed under the License is distributed on an "AS IS" BASIS, 14WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15See the License for the specific language governing permissions and 16limitations under the License. 17*/ 18 19package fs 20 21import ( 22 "context" 23 "os" 24 "path/filepath" 25 "strings" 26 "sync" 27 "time" 28 29 "perkeep.org/pkg/blob" 30 "perkeep.org/pkg/search" 31 32 "bazil.org/fuse" 33 "bazil.org/fuse/fs" 34) 35 36// recentDir implements fuse.Node and is a directory of recent 37// permanodes' files, for permanodes with a camliContent pointing to a 38// "file". 39type recentDir struct { 40 fs *CamliFileSystem 41 42 mu sync.Mutex 43 ents map[string]*search.DescribedBlob // filename to blob meta 44 modTime map[string]time.Time // filename to permanode modtime 45 lastReaddir time.Time 46 lastNames []string 47} 48 49var ( 50 _ fs.Node = (*recentDir)(nil) 51 _ fs.HandleReadDirAller = (*recentDir)(nil) 52 _ fs.NodeStringLookuper = (*recentDir)(nil) 53) 54 55func (n *recentDir) Attr(ctx context.Context, a *fuse.Attr) error { 56 a.Mode = os.ModeDir | 0500 57 a.Uid = uint32(os.Getuid()) 58 a.Gid = uint32(os.Getgid()) 59 return nil 60} 61 62const recentSearchInterval = 10 * time.Second 63 64func (n *recentDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { 65 var ents []fuse.Dirent 66 67 n.mu.Lock() 68 defer n.mu.Unlock() 69 if n.lastReaddir.After(time.Now().Add(-recentSearchInterval)) { 70 Logger.Printf("fs.recent: ReadDirAll from cache") 71 for _, name := range n.lastNames { 72 ents = append(ents, fuse.Dirent{Name: name}) 73 } 74 return ents, nil 75 } 76 77 Logger.Printf("fs.recent: ReadDirAll, doing search") 78 79 n.ents = make(map[string]*search.DescribedBlob) 80 n.modTime = make(map[string]time.Time) 81 82 req := &search.RecentRequest{N: 100} 83 res, err := n.fs.client.GetRecentPermanodes(ctx, req) 84 if err != nil { 85 Logger.Printf("fs.recent: GetRecentPermanodes error in ReadDirAll: %v", err) 86 return nil, fuse.EIO 87 } 88 89 n.lastNames = nil 90 for _, ri := range res.Recent { 91 modTime := ri.ModTime.Time() 92 meta := res.Meta.Get(ri.BlobRef) 93 if meta == nil || meta.Permanode == nil { 94 continue 95 } 96 cc, ok := blob.Parse(meta.Permanode.Attr.Get("camliContent")) 97 if !ok { 98 continue 99 } 100 ccMeta := res.Meta.Get(cc) 101 if ccMeta == nil { 102 continue 103 } 104 var name string 105 switch { 106 case ccMeta.File != nil: 107 name = ccMeta.File.FileName 108 if mt := ccMeta.File.Time; !mt.IsAnyZero() { 109 modTime = mt.Time() 110 } 111 case ccMeta.Dir != nil: 112 name = ccMeta.Dir.FileName 113 default: 114 continue 115 } 116 if name == "" || n.ents[name] != nil { 117 ext := filepath.Ext(name) 118 if ext == "" && ccMeta.File != nil && strings.HasSuffix(ccMeta.File.MIMEType, "image/jpeg") { 119 ext = ".jpg" 120 } 121 name = strings.TrimPrefix(ccMeta.BlobRef.String(), ccMeta.BlobRef.HashName()+"-")[:10] + ext 122 if n.ents[name] != nil { 123 continue 124 } 125 } 126 n.ents[name] = ccMeta 127 n.modTime[name] = modTime 128 Logger.Printf("fs.recent: name %q = %v (at %v -> %v)", name, ccMeta.BlobRef, ri.ModTime.Time(), modTime) 129 n.lastNames = append(n.lastNames, name) 130 ents = append(ents, fuse.Dirent{ 131 Name: name, 132 }) 133 } 134 Logger.Printf("fs.recent returning %d entries", len(ents)) 135 n.lastReaddir = time.Now() 136 return ents, nil 137} 138 139func (n *recentDir) Lookup(ctx context.Context, name string) (fs.Node, error) { 140 n.mu.Lock() 141 defer n.mu.Unlock() 142 if n.ents == nil { 143 // Odd case: a Lookup before a Readdir. Force a readdir to 144 // seed our map. Mostly hit just during development. 145 n.mu.Unlock() // release, since ReadDirAll will acquire 146 n.ReadDirAll(ctx) 147 n.mu.Lock() 148 } 149 db := n.ents[name] 150 Logger.Printf("fs.recent: Lookup(%q) = %v", name, db) 151 if db == nil { 152 return nil, fuse.ENOENT 153 } 154 nod := &node{ 155 fs: n.fs, 156 blobref: db.BlobRef, 157 pnodeModTime: n.modTime[name], 158 } 159 return nod, nil 160} 161