github.com/metacurrency/holochain@v0.1.0-alpha-26.0.20200915073418-5c83169c9b5b/utils.go (about) 1 // Copyright (C) 2013-2017, The MetaCurrency Project (Eric Harris-Braun, Arthur Brock, et. al.) 2 // Use of this source code is governed by GPLv3 found in the LICENSE file 3 //---------------------------------------------------------------------------------------- 4 // non exported utility functions for Holochain package 5 6 package holochain 7 8 import ( 9 "bytes" 10 "encoding/gob" 11 "encoding/json" 12 "errors" 13 "fmt" 14 "io" 15 "io/ioutil" 16 "os" 17 "path/filepath" 18 "strings" 19 "unicode" 20 21 "github.com/BurntSushi/toml" 22 "github.com/ghodss/yaml" 23 "github.com/lestrrat/go-jsschema" 24 "github.com/lestrrat/go-jsval/builder" 25 // "sync" 26 "time" 27 ) 28 29 const ( 30 OS_READ = 04 31 OS_WRITE = 02 32 OS_EX = 01 33 OS_USER_SHIFT = 6 34 OS_GROUP_SHIFT = 3 35 OS_OTH_SHIFT = 0 36 37 OS_USER_R = OS_READ << OS_USER_SHIFT 38 OS_USER_W = OS_WRITE << OS_USER_SHIFT 39 OS_USER_X = OS_EX << OS_USER_SHIFT 40 OS_USER_RW = OS_USER_R | OS_USER_W 41 OS_USER_RWX = OS_USER_RW | OS_USER_X 42 43 OS_GROUP_R = OS_READ << OS_GROUP_SHIFT 44 OS_GROUP_W = OS_WRITE << OS_GROUP_SHIFT 45 OS_GROUP_X = OS_EX << OS_GROUP_SHIFT 46 OS_GROUP_RW = OS_GROUP_R | OS_GROUP_W 47 OS_GROUP_RWX = OS_GROUP_RW | OS_GROUP_X 48 49 OS_OTH_R = OS_READ << OS_OTH_SHIFT 50 OS_OTH_W = OS_WRITE << OS_OTH_SHIFT 51 OS_OTH_X = OS_EX << OS_OTH_SHIFT 52 OS_OTH_RW = OS_OTH_R | OS_OTH_W 53 OS_OTH_RWX = OS_OTH_RW | OS_OTH_X 54 55 OS_ALL_R = OS_USER_R | OS_GROUP_R | OS_OTH_R 56 OS_ALL_W = OS_USER_W | OS_GROUP_W | OS_OTH_W 57 OS_ALL_X = OS_USER_X | OS_GROUP_X | OS_OTH_X 58 OS_ALL_RW = OS_ALL_R | OS_ALL_W 59 OS_ALL_RWX = OS_ALL_RW | OS_GROUP_X 60 ) 61 62 func writeToml(path string, file string, data interface{}, overwrite bool) error { 63 p := filepath.Join(path, file) 64 if !overwrite && FileExists(p) { 65 return mkErr(path + " already exists") 66 } 67 f, err := os.Create(p) 68 if err != nil { 69 return err 70 } 71 72 defer f.Close() 73 enc := toml.NewEncoder(f) 74 err = enc.Encode(data) 75 return err 76 } 77 78 func WriteFile(data []byte, pathParts ...string) error { 79 p := filepath.Join(pathParts...) 80 if FileExists(p) { 81 return mkErr(p + " already exists") 82 } 83 f, err := os.Create(p) 84 if err != nil { 85 return err 86 } 87 defer f.Close() 88 89 l, err := f.Write(data) 90 if err != nil { 91 return err 92 } 93 94 if l != len(data) { 95 return mkErr("unable to write all data") 96 } 97 f.Sync() 98 return err 99 } 100 101 func ReadFile(pathParts ...string) (data []byte, err error) { 102 p := filepath.Join(pathParts...) 103 data, err = ioutil.ReadFile(p) 104 return data, err 105 } 106 107 func mkErr(err string) error { 108 return errors.New("holochain: " + err) 109 } 110 111 func DirExists(pathParts ...string) bool { 112 path := filepath.Join(pathParts...) 113 info, err := os.Stat(path) 114 return err == nil && info.Mode().IsDir() 115 } 116 117 func FileExists(pathParts ...string) bool { 118 path := filepath.Join(pathParts...) 119 info, err := os.Stat(path) 120 if err != nil { 121 return false 122 } 123 return info.Mode().IsRegular() 124 } 125 126 func FileSize(pathParts ...string) int64 { 127 path := filepath.Join(pathParts...) 128 info, err := os.Stat(path) 129 if err != nil { 130 return 0 131 } 132 return info.Size() 133 } 134 135 func filePerms(pathParts ...string) (perms os.FileMode, err error) { 136 var fi os.FileInfo 137 fi, err = os.Stat(filepath.Join(pathParts...)) 138 if err != nil { 139 return 140 } 141 perms = fi.Mode().Perm() 142 return 143 } 144 145 // CopyDir recursively copies a directory tree, attempting to preserve permissions. 146 // Source directory must exist, destination directory must *not* exist. 147 func CopyDir(source string, dest string) (err error) { 148 149 // get properties of source dir 150 fi, err := os.Stat(source) 151 if err != nil { 152 return err 153 } 154 155 if !fi.IsDir() { 156 return errors.New("Source is not a directory") 157 } 158 159 // ensure dest dir does not already exist 160 161 _, err = os.Open(dest) 162 if !os.IsNotExist(err) { 163 return fmt.Errorf("Destination (%s) already exists", dest) 164 } 165 166 // create dest dir 167 168 err = os.MkdirAll(dest, fi.Mode()) 169 if err != nil { 170 return err 171 } 172 173 entries, err := ioutil.ReadDir(source) 174 175 for _, entry := range entries { 176 177 sfp := filepath.Join(source, entry.Name()) 178 dfp := filepath.Join(dest, entry.Name()) 179 if entry.IsDir() { 180 err = CopyDir(sfp, dfp) 181 if err != nil { 182 return err 183 } 184 } else { 185 // perform copy 186 err = CopyFile(sfp, dfp) 187 if err != nil { 188 return err 189 } 190 } 191 192 } 193 return 194 } 195 196 // CopyFile copies file source to destination dest. 197 func CopyFile(source string, dest string) (err error) { 198 sf, err := os.Open(source) 199 if err != nil { 200 return err 201 } 202 defer sf.Close() 203 df, err := os.Create(dest) 204 if err != nil { 205 return err 206 } 207 defer df.Close() 208 _, err = io.Copy(df, sf) 209 if err == nil { 210 var si os.FileInfo 211 si, err = os.Stat(source) 212 if err == nil { 213 err = os.Chmod(dest, si.Mode()) 214 } 215 } 216 return 217 } 218 219 // Encode encodes data to the writer according to the given format 220 func Encode(writer io.Writer, format string, data interface{}) (err error) { 221 switch format { 222 case "toml": 223 enc := toml.NewEncoder(writer) 224 err = enc.Encode(data) 225 226 case "json": 227 enc := json.NewEncoder(writer) 228 enc.SetIndent("", " ") 229 err = enc.Encode(data) 230 231 case "yml": 232 fallthrough 233 case "yaml": 234 y, e := yaml.Marshal(data) 235 if e != nil { 236 err = e 237 return 238 } 239 n, e := writer.Write(y) 240 if e != nil { 241 err = e 242 return 243 } 244 if n != len(y) { 245 err = errors.New("unable to write all bytes while encoding") 246 } 247 248 default: 249 err = errors.New("unknown encoding format: " + format) 250 } 251 return 252 } 253 254 // Decode extracts data from the reader according to the type 255 func Decode(reader io.Reader, format string, data interface{}) (err error) { 256 switch format { 257 case "toml": 258 _, err = toml.DecodeReader(reader, data) 259 case "json": 260 dec := json.NewDecoder(reader) 261 err = dec.Decode(data) 262 case "yml": 263 fallthrough 264 case "yaml": 265 y, e := ioutil.ReadAll(reader) 266 if e != nil { 267 err = e 268 return 269 } 270 err = yaml.Unmarshal(y, data) 271 default: 272 err = errors.New("unknown encoding format: " + format) 273 } 274 return 275 } 276 277 // DecodeFile decodes a file based on the extension and the data type 278 func DecodeFile(data interface{}, pathParts ...string) (err error) { 279 file := filepath.Join(pathParts...) 280 format := EncodingFormat(file) 281 if format == "" { 282 err = fmt.Errorf("unknown encoding format: %s", file) 283 return 284 } 285 var f *os.File 286 f, err = os.Open(file) 287 if err != nil { 288 return 289 } 290 defer f.Close() 291 err = Decode(f, format, data) 292 if err != nil { 293 return 294 } 295 return 296 } 297 298 // EncodingFormat returns the files format if supported otherwise "" 299 func EncodingFormat(file string) (f string) { 300 s := strings.Split(file, ".") 301 f = s[len(s)-1] 302 if f == "json" || f == "yml" || f == "yaml" || f == "toml" { 303 return 304 } 305 f = "" 306 return 307 } 308 309 // ByteEncoder encodes anything using gob 310 func ByteEncoder(data interface{}) (b []byte, err error) { 311 var buf bytes.Buffer 312 enc := gob.NewEncoder(&buf) 313 err = enc.Encode(data) 314 if err != nil { 315 return 316 } 317 b = buf.Bytes() 318 return 319 } 320 321 // ByteDecoder decodes data encoded by ByteEncoder 322 func ByteDecoder(b []byte, to interface{}) (err error) { 323 buf := bytes.NewBuffer(b) 324 dec := gob.NewDecoder(buf) 325 err = dec.Decode(to) 326 return 327 } 328 329 func BuildJSONSchemaValidatorFromFile(path string) (validator *JSONSchemaValidator, err error) { 330 var s *schema.Schema 331 s, err = schema.ReadFile(path) 332 if err != nil { 333 return 334 } 335 336 b := builder.New() 337 var v JSONSchemaValidator 338 v.v, err = b.Build(s) 339 if err == nil { 340 validator = &v 341 } 342 return 343 } 344 345 func BuildJSONSchemaValidatorFromString(input string) (validator *JSONSchemaValidator, err error) { 346 var s *schema.Schema 347 s, err = schema.Read(strings.NewReader(input)) 348 if err != nil { 349 return 350 } 351 b := builder.New() 352 var v JSONSchemaValidator 353 v.v, err = b.Build(s) 354 if err == nil { 355 validator = &v 356 } 357 return 358 } 359 360 // Ticker runs a function on an interval that can be stopped with the returned bool channel 361 func Ticker(interval time.Duration, fn func()) (stopper chan bool) { 362 ticker := time.NewTicker(interval) 363 stopper = make(chan bool, 1) 364 go func() { 365 // var lk sync.RWMutex 366 var stopped bool 367 for { 368 select { 369 case <-ticker.C: 370 // lk.RLock() 371 if !stopped { 372 fn() 373 } 374 // lk.Unlock() 375 case <-stopper: 376 // lk.Lock() 377 stopped = true 378 // lk.Unlock() 379 return 380 } 381 } 382 }() 383 return 384 } 385 386 // PrettyPrintJSON for human reability. 387 func PrettyPrintJSON(b []byte) (string, error) { 388 var out bytes.Buffer 389 err := json.Indent(&out, b, "", " ") 390 391 if err != nil { 392 return "", err 393 } 394 return string(out.Bytes()), nil 395 } 396 397 // EscapeJSONValue removed characters from a JSON value to avoid parsing issues. 398 func EscapeJSONValue(json string) string { 399 cleanStr := strings.Map(func(r rune) rune { 400 if unicode.IsPrint(r) && !unicode.IsControl(r) && !unicode.IsSymbol(r) { 401 return r 402 } 403 return ' ' 404 }, json) 405 406 return strings.Replace(cleanStr, `"`, `\"`, -1) 407 }