Home Download Docs Code Community
     1	//go:build linux
     2	
     3	/*
     4	Copyright 2014 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		"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	// versionsDir implements fuse.Node containing all roots. Within this node,
    38	// - a directory permanode is represented with a fuse directory (roVersionsDir)
    39	// - a file permanode is represented with a fuse directory (roFileVersionsDir)
    40	// - a file version is represented with a fuse file (roFileVersion)
    41	// In this way you can navigate to a file at a specific point in time.
    42	// Basically is like `at` magic folder but the path (not the date) goes first.
    43	// It is read-only.
    44	type versionsDir struct {
    45		fs *CamliFileSystem
    46	
    47		mu        sync.Mutex
    48		lastQuery time.Time
    49		m         map[string]blob.Ref // ent name => permanode
    50		children  map[string]fs.Node  // ent name => child 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	// requires n.mu is held
   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		// Roots
   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		// Remove any children objects we have mapped that are no
   171		// longer relevant.
   172		for name := range n.children {
   173			if !currentRoots[name] {
   174				delete(n.children, name)
   175			}
   176		}
   177	
   178		// Importers (mapped as roots for now)
   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	}
Website layout inspired by memcached.
Content by the authors.