github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/pkg/fs/recent.go (about)

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