Home Download Docs Code Community
     1	//go:build linux
     2	
     3	/*
     4	Copyright 2013 The Perkeep Authors
     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		"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".
    39	type 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	
    49	var (
    50		_ fs.Node               = (*recentDir)(nil)
    51		_ fs.HandleReadDirAller = (*recentDir)(nil)
    52		_ fs.NodeStringLookuper = (*recentDir)(nil)
    53	)
    54	
    55	func (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	
    62	const recentSearchInterval = 10 * time.Second
    63	
    64	func (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, handleEIOorEINTR(err)
    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	
   139	func (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			refresh := func() error {
   146				n.mu.Unlock() // release, since ReadDirAll will acquire
   147				defer n.mu.Lock()
   148	
   149				_, err := n.ReadDirAll(ctx)
   150				return err
   151			}
   152			if err := refresh(); err != nil {
   153				return nil, err
   154			}
   155		}
   156		db := n.ents[name]
   157		Logger.Printf("fs.recent: Lookup(%q) = %v", name, db)
   158		if db == nil {
   159			return nil, fuse.ENOENT
   160		}
   161		nod := &node{
   162			fs:           n.fs,
   163			blobref:      db.BlobRef,
   164			pnodeModTime: n.modTime[name],
   165		}
   166		return nod, nil
   167	}
Website layout inspired by memcached.
Content by the authors.