github.com/holochain/holochain-proto@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  }