fsrepo.go 4.92 KB
Newer Older
1 2 3
package fsrepo

import (
4
	"fmt"
5
	"io"
6 7
	"os"
	"path/filepath"
8

9 10
	repo "github.com/jbenet/go-ipfs/repo"
	common "github.com/jbenet/go-ipfs/repo/common"
11 12
	config "github.com/jbenet/go-ipfs/repo/config"
	util "github.com/jbenet/go-ipfs/util"
13
	debugerror "github.com/jbenet/go-ipfs/util/debugerror"
14 15
)

Brian Tiger Chow's avatar
Brian Tiger Chow committed
16
// FSRepo represents an IPFS FileSystem Repo
17
type FSRepo struct {
18
	state  state
19
	path   string
20
	config *config.Config
21 22
}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
23
// At returns a handle to an FSRepo at the provided |path|.
24 25
func At(path string) *FSRepo {
	return &FSRepo{
26 27
		path:  path,
		state: unopened, // explicitly set for clarity
28 29 30
	}
}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
31
// Init initializes a new FSRepo at the given path with the provided config.
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
func Init(path string, conf *config.Config) error {
	if IsInitialized(path) {
		return nil
	}
	configFilename, err := config.Filename(path)
	if err != nil {
		return err
	}
	if err := writeConfigFile(configFilename, conf); err != nil {
		return err
	}
	return nil
}

// Open returns an error if the repo is not initialized.
47
func (r *FSRepo) Open() error {
48 49 50 51 52 53
	if r.state != unopened {
		return debugerror.Errorf("repo is %s", r.state)
	}
	if !IsInitialized(r.path) {
		return debugerror.New("repo is not initialized")
	}
54
	// check repo path, then check all constituent parts.
55
	// TODO acquire repo lock
56 57 58 59 60
	// TODO if err := initCheckDir(logpath); err != nil { // }
	if err := initCheckDir(r.path); err != nil {
		return err
	}

61 62 63 64
	configFilename, err := config.Filename(r.path)
	if err != nil {
		return err
	}
65
	conf, err := load(configFilename)
66 67 68 69 70
	if err != nil {
		return err
	}
	r.config = conf

71 72 73 74 75 76 77 78 79
	// datastore
	dspath, err := config.DataStorePath("")
	if err != nil {
		return err
	}
	if err := initCheckDir(dspath); err != nil {
		return debugerror.Errorf("datastore: %s", err)
	}

80 81 82 83 84 85 86 87
	logpath, err := config.LogsPath("")
	if err != nil {
		return debugerror.Wrap(err)
	}
	if err := initCheckDir(logpath); err != nil {
		return debugerror.Errorf("logs: %s", err)
	}

88
	r.state = opened
89 90 91
	return nil
}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
92 93
// Config returns the FSRepo's config. Result is undefined if the Repo is not
// Open.
94 95 96 97 98 99 100
func (r *FSRepo) Config() *config.Config {
	if r.state != opened {
		panic(fmt.Sprintln("repo is", r.state))
	}
	return r.config
}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
101
// SetConfig updates the FSRepo's config.
102
func (r *FSRepo) SetConfig(updated *config.Config) error {
103 104 105
	if r.state != opened {
		panic(fmt.Sprintln("repo is", r.state))
	}
106 107 108 109
	configFilename, err := config.Filename(r.path)
	if err != nil {
		return err
	}
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
	// to avoid clobbering user-provided keys, must read the config from disk
	// as a map, write the updated struct values to the map and write the map
	// to disk.
	var mapconf map[string]interface{}
	if err := readConfigFile(configFilename, &mapconf); err != nil {
		return err
	}
	m, err := config.ToMap(updated)
	if err != nil {
		return err
	}
	for k, v := range m {
		mapconf[k] = v
	}
	if err := writeConfigFile(configFilename, mapconf); err != nil {
125 126
		return err
	}
127
	*r.config = *updated // copy so caller cannot modify this private config
128 129 130
	return nil
}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
131
// GetConfigKey retrieves only the value of a particular key.
132
func (r *FSRepo) GetConfigKey(key string) (interface{}, error) {
133 134 135
	if r.state != opened {
		return nil, debugerror.Errorf("repo is %s", r.state)
	}
136 137 138 139 140 141 142 143 144 145 146
	filename, err := config.Filename(r.path)
	if err != nil {
		return nil, err
	}
	var cfg map[string]interface{}
	if err := readConfigFile(filename, &cfg); err != nil {
		return nil, err
	}
	return common.MapGetKV(cfg, key)
}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
147
// SetConfigKey writes the value of a particular key.
148
func (r *FSRepo) SetConfigKey(key string, value interface{}) error {
149 150 151
	if r.state != opened {
		return debugerror.Errorf("repo is %s", r.state)
	}
152 153 154 155 156 157 158 159 160 161 162 163 164 165
	filename, err := config.Filename(r.path)
	if err != nil {
		return err
	}
	var mapconf map[string]interface{}
	if err := readConfigFile(filename, &mapconf); err != nil {
		return err
	}
	if err := common.MapSetKV(mapconf, key, value); err != nil {
		return err
	}
	if err := writeConfigFile(filename, mapconf); err != nil {
		return err
	}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
166
	conf, err := config.FromMap(mapconf)
167 168 169 170 171 172
	if err != nil {
		return err
	}
	return r.SetConfig(conf)
}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
173
// Close closes the FSRepo, releasing held resources.
174
func (r *FSRepo) Close() error {
175 176 177
	if r.state != opened {
		return debugerror.Errorf("repo is %s", r.state)
	}
178 179 180 181
	return nil // TODO release repo lock
}

var _ io.Closer = &FSRepo{}
182
var _ repo.Interface = &FSRepo{}
183

184 185
// IsInitialized returns true if the repo is initialized at provided |path|.
func IsInitialized(path string) bool {
186 187 188 189 190 191 192 193 194
	configFilename, err := config.Filename(path)
	if err != nil {
		return false
	}
	if !util.FileExists(configFilename) {
		return false
	}
	return true
}
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209

// initCheckDir ensures the directory exists and is writable
func initCheckDir(path string) error {
	// Construct the path if missing
	if err := os.MkdirAll(path, os.ModePerm); err != nil {
		return err
	}
	// Check the directory is writeable
	if f, err := os.Create(filepath.Join(path, "._check_writeable")); err == nil {
		os.Remove(f.Name())
	} else {
		return debugerror.New("'" + path + "' is not writeable")
	}
	return nil
}