fs.go 3.89 KB
Newer Older
1
// Package fs is a simple Datastore implementation that stores keys
2
// as directories and files, mirroring the key. That is, the key
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// "/foo/bar" is stored as file "PATH/foo/bar/.dsobject".
//
// This means key some segments will not work. For example, the
// following keys will result in unwanted behavior:
//
//     - "/foo/./bar"
//     - "/foo/../bar"
//     - "/foo\x00bar"
//
// Keys that only differ in case may be confused with each other on
// case insensitive file systems, for example in OS X.
//
// This package is intended for exploratory use, where the user would
// examine the file system manually, and should only be used with
// human-friendly, trusted keys. You have been warned.
18
package examples
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
19 20 21 22

import (
	"fmt"
	"io/ioutil"
23
	"log"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
24 25 26 27
	"os"
	"path/filepath"
	"strings"

Jeromy's avatar
Jeromy committed
28 29
	ds "github.com/ipfs/go-datastore"
	query "github.com/ipfs/go-datastore/query"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
30 31
)

32 33
var ObjectKeySuffix = ".dsobject"

Tommi Virtanen's avatar
Tommi Virtanen committed
34
// Datastore uses a uses a file per key to store values.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
type Datastore struct {
	path string
}

// NewDatastore returns a new fs Datastore at given `path`
func NewDatastore(path string) (ds.Datastore, error) {
	if !isDir(path) {
		return nil, fmt.Errorf("Failed to find directory at: %v (file? perms?)", path)
	}

	return &Datastore{path: path}, nil
}

// KeyFilename returns the filename associated with `key`
func (d *Datastore) KeyFilename(key ds.Key) string {
50
	return filepath.Join(d.path, key.String(), ObjectKeySuffix)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
51 52 53
}

// Put stores the given value.
54
func (d *Datastore) Put(key ds.Key, value []byte) (err error) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
55 56 57 58 59 60 61 62
	fn := d.KeyFilename(key)

	// mkdirall above.
	err = os.MkdirAll(filepath.Dir(fn), 0755)
	if err != nil {
		return err
	}

63
	return ioutil.WriteFile(fn, value, 0666)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
64 65 66
}

// Get returns the value for given key
67
func (d *Datastore) Get(key ds.Key) (value []byte, err error) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
68 69 70 71 72 73 74 75 76 77 78 79 80
	fn := d.KeyFilename(key)
	if !isFile(fn) {
		return nil, ds.ErrNotFound
	}

	return ioutil.ReadFile(fn)
}

// Has returns whether the datastore has a value for a given key
func (d *Datastore) Has(key ds.Key) (exists bool, err error) {
	return ds.GetBackedHas(d, key)
}

Steven Allen's avatar
Steven Allen committed
81 82 83 84
func (d *Datastore) GetSize(key ds.Key) (size int, err error) {
	return ds.GetBackedSize(d, key)
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
85 86 87 88 89 90 91 92 93 94
// Delete removes the value for given key
func (d *Datastore) Delete(key ds.Key) (err error) {
	fn := d.KeyFilename(key)
	if !isFile(fn) {
		return ds.ErrNotFound
	}

	return os.Remove(fn)
}

95
// Query implements Datastore.Query
96
func (d *Datastore) Query(q query.Query) (query.Results, error) {
97

98
	results := make(chan query.Result)
99 100 101 102 103 104 105 106 107 108 109

	walkFn := func(path string, info os.FileInfo, err error) error {
		// remove ds path prefix
		if strings.HasPrefix(path, d.path) {
			path = path[len(d.path):]
		}

		if !info.IsDir() {
			if strings.HasSuffix(path, ObjectKeySuffix) {
				path = path[:len(path)-len(ObjectKeySuffix)]
			}
110
			var result query.Result
111
			key := ds.NewKey(path)
112 113 114 115 116
			result.Entry.Key = key.String()
			if !q.KeysOnly {
				result.Entry.Value, result.Error = d.Get(key)
			}
			results <- result
117 118 119 120 121 122
		}
		return nil
	}

	go func() {
		filepath.Walk(d.path, walkFn)
123
		close(results)
124
	}()
125 126
	r := query.ResultsWithChan(q, results)
	r = query.NaiveQueryApply(q, r)
127 128 129
	return r, nil
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
// isDir returns whether given path is a directory
func isDir(path string) bool {
	finfo, err := os.Stat(path)
	if err != nil {
		return false
	}

	return finfo.IsDir()
}

// isFile returns whether given path is a file
func isFile(path string) bool {
	finfo, err := os.Stat(path)
	if err != nil {
		return false
	}

	return !finfo.IsDir()
}
Jeromy's avatar
Jeromy committed
149 150 151 152 153 154 155 156

func (d *Datastore) Close() error {
	return nil
}

func (d *Datastore) Batch() (ds.Batch, error) {
	return ds.NewBasicBatch(d), nil
}
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172

// DiskUsage returns the disk size used by the datastore in bytes.
func (d *Datastore) DiskUsage() (uint64, error) {
	var du uint64
	err := filepath.Walk(d.path, func(p string, f os.FileInfo, err error) error {
		if err != nil {
			log.Println(err)
			return err
		}
		if f != nil {
			du += uint64(f.Size())
		}
		return nil
	})
	return du, err
}