Persist workspace configuration across restarts. The manifests are stored under ~/.config/slothfs/manifests. This is a boon to people that restart slothfs often, such as its developers. It also guarantees that state survives machine reboots. Change-Id: I5faa074a55920de2c4992aca66f7319832f233b4
diff --git a/README.md b/README.md index e46734f..2aefb36 100644 --- a/README.md +++ b/README.md
@@ -59,6 +59,9 @@ A more elaborate configuration file is included as `android.json`. +By default, slothfs loads the configuration from =$HOME/.config/slothfs/clone.json=. + + DISCLAIMER ==========
diff --git a/cmd/slothfs-multifs/main.go b/cmd/slothfs-multifs/main.go index 03e9214..5dbad99 100644 --- a/cmd/slothfs-multifs/main.go +++ b/cmd/slothfs-multifs/main.go
@@ -32,7 +32,7 @@ gitilesURL := flag.String("gitiles", "", "gitiles URL. If unset, derive from manifest location.") cacheDir := flag.String("cache", filepath.Join(os.Getenv("HOME"), ".cache", "slothfs"), "cache dir") debug := flag.Bool("debug", false, "print debug info") - config := flag.String("config", "", "JSON file configuring what repositories should be cloned.") + config := flag.String("config", filepath.Join(os.Getenv("HOME"), ".config", "slothfs"), "directory with configuration files.") cookieJarPath := flag.String("cookies", "", "path to cURL-style cookie jar file.") agent := flag.String("agent", "slothfs-multifs", "gitiles User-Agent string to use.") flag.Parse() @@ -69,7 +69,8 @@ opts := fs.MultiFSOptions{} if *config != "" { - configContents, err := ioutil.ReadFile(*config) + cloneJS := filepath.Join(*config, "clone.json") + configContents, err := ioutil.ReadFile(cloneJS) if err != nil { log.Fatal(err) } @@ -77,6 +78,11 @@ if err != nil { log.Fatal(err) } + + opts.ManifestDir = filepath.Join(*config, "manifests") + if err := os.MkdirAll(opts.ManifestDir, 0755); err != nil { + log.Fatal(err) + } } root := fs.NewMultiFS(service, cache, opts)
diff --git a/fs/api.go b/fs/api.go index 6bfb0d4..fd860ba 100644 --- a/fs/api.go +++ b/fs/api.go
@@ -49,6 +49,8 @@ // MultiFSOptions holds options for a file system with multiple manifests. type MultiFSOptions struct { + // ManifestDir stores configured manifest files. + ManifestDir string // RepoCloneOption matches against the Path field of the // repository within a manifest.
diff --git a/fs/gitilesfs_test.go b/fs/gitilesfs_test.go index 9d17b30..a749287 100644 --- a/fs/gitilesfs_test.go +++ b/fs/gitilesfs_test.go
@@ -758,3 +758,62 @@ t.Errorf("Lstat(%s): got %v, want error", wsDir, fi) } } + +func TestMultiFSManifestDir(t *testing.T) { + fix, err := newTestFixture() + if err != nil { + t.Fatalf("newTestFixture: %v", err) + } + defer fix.cleanup() + + mfDir := filepath.Join(fix.dir, "manifests") + if err := os.MkdirAll(mfDir, 0755); err != nil { + t.Fatalf("MkdirAll: %v", err) + } + + xmlFile := filepath.Join(mfDir, "ws") + if err := ioutil.WriteFile(xmlFile, []byte(testManifestXML), 0644); err != nil { + t.Errorf("WriteFile(%s): %v", xmlFile, err) + } + + opts := MultiFSOptions{ + ManifestDir: mfDir, + } + fs := NewMultiFS(fix.service, fix.cache, opts) + + if err := fix.mount(fs); err != nil { + t.Fatalf("mount: %v", err) + } + + wsDir := filepath.Join(fix.mntDir, "ws") + if _, err := os.Lstat(wsDir); err != nil { + t.Fatalf("Lstat(%s): %v", wsDir) + } + + if err := os.Remove(filepath.Join(fix.mntDir, "config", "ws")); err != nil { + t.Fatalf("Remove(config link): %v", err) + } + + if fi, err := os.Lstat(filepath.Join(mfDir, "ws")); err == nil { + t.Errorf("'ws' still in manifest dir: %v", fi) + } + + f, err := ioutil.TempFile("", "") + if err != nil { + t.Fatalf("TempFile: %v", err) + } + if err := ioutil.WriteFile(f.Name(), []byte(testManifestXML), 0644); err != nil { + t.Errorf("WriteFile(%s): %v", xmlFile, err) + } + + configName := filepath.Join(fix.mntDir, "config", "ws2") + if err := os.Symlink(f.Name(), configName); err != nil { + t.Fatalf("Symlink(%s): %v", xmlFile, err) + } + + // XML file appears again. + xmlFile = filepath.Join(mfDir, "ws2") + if _, err := os.Stat(xmlFile); err != nil { + t.Errorf("Stat(%s): %v", xmlFile, err) + } +}
diff --git a/fs/multifs.go b/fs/multifs.go index e67a4fd..3c72c05 100644 --- a/fs/multifs.go +++ b/fs/multifs.go
@@ -17,6 +17,7 @@ import ( "io/ioutil" "log" + "os" "path/filepath" "github.com/google/slothfs/cache" @@ -35,12 +36,35 @@ gitiles *gitiles.Service } +func (c *configNode) configureWorkspaces() error { + if c.root.options.ManifestDir == "" { + return nil + } + fs, err := filepath.Glob(filepath.Join(c.root.options.ManifestDir, "*")) + if err != nil || len(fs) == 0 { + return err + } + + log.Println("configuring workspaces...") + for _, f := range fs { + _, code := c.Symlink(filepath.Base(f), f, nil) + log.Printf("manifest %s: %v", f, code) + } + return nil +} + func (r *multiManifestFSRoot) OnMount(fsConn *nodefs.FileSystemConnector) { r.fsConn = fsConn - r.Inode().NewChild("config", true, &configNode{ + + cfg := &configNode{ Node: nodefs.NewDefaultNode(), root: r, - }) + } + r.Inode().NewChild("config", true, cfg) + + if err := cfg.configureWorkspaces(); err != nil { + log.Println("configureWorkspaces: %v", err) + } } func (r *configNode) Deletable() bool { return false } @@ -98,6 +122,10 @@ // the Unlink method, will VFS already knows about the // deletion once we return OK. + if dir := c.root.options.ManifestDir; dir != "" { + os.Remove(filepath.Join(dir, name)) + } + return fuse.OK } @@ -145,6 +173,27 @@ } child.NewChild("ERROR", false, &dataNode{nodefs.NewDefaultNode(), []byte(err.Error())}) + } else { + if dir := c.root.options.ManifestDir; dir != "" { + for { + f, err := ioutil.TempFile(dir, "") + if err != nil { + break + } + + _, err = f.Write(mfBytes) + if err != nil { + break + } + + if err := f.Close(); err != nil { + break + } + + os.Rename(f.Name(), filepath.Join(dir, name)) + break + } + } } c.root.fsConn.EntryNotify(c.root.Inode(), name)