github.com/code-to-go/safepool.lib@v0.0.0-20221205180519-ee25e63c226e/pool/pool.go (about)

     1  package pool
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"path"
    10  	"time"
    11  
    12  	"github.com/code-to-go/safepool.lib/core"
    13  	"github.com/code-to-go/safepool.lib/security"
    14  	"github.com/code-to-go/safepool.lib/transport"
    15  
    16  	"github.com/godruoyi/go-snowflake"
    17  	"github.com/sirupsen/logrus"
    18  )
    19  
    20  const SafeConfigFile = ".safepool-pool.json"
    21  
    22  var ErrNoExchange = errors.New("no Exchange available")
    23  var ErrInvalidSignature = errors.New("signature is invalid")
    24  var ErrNotTrusted = errors.New("the author is not a trusted user")
    25  var ErrNotAuthorized = errors.New("no authorization for this file")
    26  var ErrAlreadyExist = errors.New("pool already exists")
    27  var ErrInvalidToken = errors.New("provided token is invalid: missing name or configs")
    28  var ErrInvalidConfig = errors.New("provided config is invalid: missing name or configs")
    29  
    30  // type SafeConfig struct {
    31  // 	Version float32
    32  // 	Id      uint64
    33  // }
    34  
    35  type Consumer interface {
    36  	TimeOffset(s *Pool) time.Time
    37  	Accept(s *Pool, h Head) bool
    38  }
    39  
    40  type Pool struct {
    41  	Name      string
    42  	Self      security.Identity
    43  	Consumers []Consumer
    44  
    45  	e           transport.Exchanger
    46  	exchangers  []transport.Exchanger
    47  	masterKeyId uint64
    48  	masterKey   []byte
    49  	lastReplica time.Time
    50  	accessHash  []byte
    51  }
    52  
    53  type Identity struct {
    54  	security.Identity
    55  	//Since is the keyId used when the identity was added to the Pool access
    56  	Since uint64
    57  	//AddedOn is the timestamp when the identity is stored on the local DB
    58  	AddedOn time.Time
    59  }
    60  
    61  type Head struct {
    62  	Id        uint64
    63  	Name      string
    64  	Size      int64
    65  	Hash      []byte
    66  	ModTime   time.Time
    67  	Author    security.Identity
    68  	Signature []byte
    69  	TimeStamp time.Time `json:"-"`
    70  	Meta      []byte
    71  }
    72  
    73  const (
    74  	ID_CREATE       = 0x0
    75  	ID_FORCE_CREATE = 0x1
    76  )
    77  
    78  var ForceCreation = false
    79  var ReplicaPeriod = time.Hour
    80  
    81  type Config struct {
    82  	Name    string
    83  	Configs []transport.Config
    84  }
    85  
    86  func List() []string {
    87  	names, _ := sqlList()
    88  	return names
    89  }
    90  
    91  func Create(self security.Identity, name string) (*Pool, error) {
    92  	configs, err := sqlLoad(name)
    93  	if core.IsErr(err, "unknown pool %s: %v", name) {
    94  		return nil, err
    95  	}
    96  
    97  	s := &Pool{
    98  		Name:        name,
    99  		Self:        self,
   100  		lastReplica: time.Now(),
   101  	}
   102  	err = s.connectSafe(name, configs)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	s.masterKeyId = snowflake.ID()
   108  	s.masterKey = security.GenerateBytesKey(32)
   109  	err = s.sqlSetKey(s.masterKeyId, s.masterKey)
   110  	if core.IsErr(err, "çannot store master encryption key to db: %v") {
   111  		return nil, err
   112  	}
   113  	err = security.SetIdentity(self)
   114  	if core.IsErr(err, "çannot save identity to db: %v") {
   115  		return nil, err
   116  	}
   117  
   118  	err = s.sqlSetIdentity(self, s.masterKeyId)
   119  	if core.IsErr(err, "cannot link identity to save: %v") {
   120  		return nil, err
   121  	}
   122  
   123  	if !ForceCreation {
   124  		_, err = s.e.Stat(path.Join(s.Name, ".access"))
   125  		if err == nil {
   126  			return nil, ErrAlreadyExist
   127  		}
   128  	}
   129  
   130  	err = s.ExportAccessFile(s.e)
   131  	if core.IsErr(err, "cannot export access file: %v") {
   132  		return nil, err
   133  	}
   134  
   135  	return s, err
   136  }
   137  
   138  // Init initialized a domain on the specified exchangers
   139  func Open(self security.Identity, name string) (*Pool, error) {
   140  	configs, err := sqlLoad(name)
   141  	if core.IsErr(err, "unknown pool %s: %v", name) {
   142  		return nil, err
   143  	}
   144  	s := &Pool{
   145  		Name: name,
   146  		Self: self,
   147  	}
   148  	err = s.connectSafe(name, configs)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  
   153  	_, err = s.ImportAccess(s.e)
   154  	return s, err
   155  }
   156  
   157  func (p *Pool) List(afterId uint64, afterTs time.Time) []Head {
   158  	hs, _ := p.list(afterId, afterTs)
   159  	return hs
   160  }
   161  
   162  func (p *Pool) Post(name string, r io.Reader, meta []byte) (Head, error) {
   163  	id := snowflake.ID()
   164  	n := path.Join(p.Name, fmt.Sprintf("%d.body", id))
   165  	hr, err := p.writeFile(n, r)
   166  	if core.IsErr(err, "cannot post file %s to %s: %v", name, p.e) {
   167  		return Head{}, err
   168  	}
   169  
   170  	hash := hr.Hash()
   171  	signature, err := security.Sign(p.Self, hash)
   172  	if core.IsErr(err, "cannot sign file %s.body in %s: %v", name, p.e) {
   173  		return Head{}, err
   174  	}
   175  	h := Head{
   176  		Id:        id,
   177  		Name:      name,
   178  		Size:      hr.Size(),
   179  		Hash:      hash,
   180  		ModTime:   time.Now(),
   181  		Author:    p.Self.Public(),
   182  		Signature: signature,
   183  		Meta:      meta,
   184  	}
   185  	data, err := json.Marshal(h)
   186  	if core.IsErr(err, "cannot marshal header to json: %v") {
   187  		return Head{}, err
   188  	}
   189  
   190  	n = path.Join(p.Name, fmt.Sprintf("%d.head", id))
   191  	_, err = p.writeFile(n, bytes.NewBuffer(data))
   192  	core.IsErr(err, "cannot write header %s.head in %s: %v", name, p.e)
   193  
   194  	return h, nil
   195  }
   196  
   197  func (p *Pool) Get(id uint64, rang *transport.Range, w io.Writer) error {
   198  	headName := path.Join(p.Name, fmt.Sprintf("%d.head", id))
   199  	bodyName := path.Join(p.Name, fmt.Sprintf("%d.body", id))
   200  
   201  	h, err := p.readHead(headName)
   202  	if core.IsErr(err, "cannot read header '%s': %v") {
   203  		return err
   204  	}
   205  
   206  	hr, err := p.readFile(bodyName, rang, w)
   207  	if core.IsErr(err, "cannot read body '%s': %v", bodyName) {
   208  		return err
   209  	}
   210  	hash := hr.Hash()
   211  	if !bytes.Equal(hash, h.Hash) {
   212  		return ErrInvalidSignature
   213  	}
   214  	return nil
   215  }
   216  
   217  func (p *Pool) Close() {
   218  	for _, e := range p.exchangers {
   219  		_ = e.Close()
   220  	}
   221  }
   222  
   223  func (p *Pool) Delete() error {
   224  	for _, e := range p.exchangers {
   225  		err := e.Delete(p.Name)
   226  		if err != nil {
   227  			return err
   228  		}
   229  	}
   230  	return nil
   231  }
   232  
   233  func (p *Pool) Identities() ([]Identity, error) {
   234  	identities, err := p.sqlGetIdentities(false)
   235  	return identities, err
   236  }
   237  
   238  func (p *Pool) Sync() {
   239  	logrus.Infof("poll request on %s", p.Name)
   240  
   241  	if !p.e.Touched(p.Name + "/") {
   242  		return
   243  	}
   244  
   245  	timeOffset := time.Now()
   246  	offsets := map[Consumer]time.Time{}
   247  	for _, c := range p.Consumers {
   248  		o := c.TimeOffset(p)
   249  		offsets[c] = o
   250  		if timeOffset.After(o) {
   251  			timeOffset = o
   252  		}
   253  	}
   254  
   255  	for _, h := range p.List(0, timeOffset) {
   256  		for _, c := range p.Consumers {
   257  			if c.Accept(p, h) {
   258  				break
   259  			}
   260  		}
   261  	}
   262  
   263  	if time.Since(p.lastReplica) > ReplicaPeriod {
   264  		p.replica()
   265  	}
   266  }