1
0
mirror of https://github.com/woodpecker-ci/woodpecker.git synced 2024-12-18 08:26:45 +02:00
woodpecker/vendor/github.com/djherbis/fscache/server.go
2016-04-12 17:27:24 -07:00

207 lines
3.5 KiB
Go

package fscache
import (
"bytes"
"errors"
"fmt"
"io"
"net"
)
// ListenAndServe hosts a Cache for access via NewRemote
func ListenAndServe(c Cache, addr string) error {
return (&server{c: c}).ListenAndServe(addr)
}
// NewRemote returns a Cache run via ListenAndServe
func NewRemote(raddr string) Cache {
return &remote{raddr: raddr}
}
type server struct {
c Cache
}
func (s *server) ListenAndServe(addr string) error {
l, err := net.Listen("tcp", addr)
if err != nil {
return err
}
for {
c, err := l.Accept()
if err != nil {
return err
}
go s.Serve(c)
}
}
const (
actionGet = iota
actionRemove = iota
actionExists = iota
actionClean = iota
)
func getKey(r io.Reader) string {
dec := newDecoder(r)
buf := bytes.NewBufferString("")
io.Copy(buf, dec)
return buf.String()
}
func sendKey(w io.Writer, key string) {
enc := newEncoder(w)
enc.Write([]byte(key))
enc.Close()
}
func (s *server) Serve(c net.Conn) {
var action int
fmt.Fscanf(c, "%d\n", &action)
switch action {
case actionGet:
s.get(c, getKey(c))
case actionRemove:
s.c.Remove(getKey(c))
case actionExists:
s.exists(c, getKey(c))
case actionClean:
s.c.Clean()
}
}
func (s *server) exists(c net.Conn, key string) {
if s.c.Exists(key) {
fmt.Fprintf(c, "%d\n", 1)
} else {
fmt.Fprintf(c, "%d\n", 0)
}
}
func (s *server) get(c net.Conn, key string) {
r, w, err := s.c.Get(key)
if err != nil {
return // handle this better
}
defer r.Close()
if w != nil {
go func() {
fmt.Fprintf(c, "%d\n", 1)
io.Copy(w, newDecoder(c))
w.Close()
}()
} else {
fmt.Fprintf(c, "%d\n", 0)
}
enc := newEncoder(c)
io.Copy(enc, r)
enc.Close()
}
type remote struct {
raddr string
}
func (rmt *remote) Get(key string) (r ReadAtCloser, w io.WriteCloser, err error) {
c, err := net.Dial("tcp", rmt.raddr)
if err != nil {
return nil, nil, err
}
fmt.Fprintf(c, "%d\n", actionGet)
sendKey(c, key)
var i int
fmt.Fscanf(c, "%d\n", &i)
var ch chan struct{}
switch i {
case 0:
ch = make(chan struct{}) // close net.Conn on reader close
case 1:
ch = make(chan struct{}, 1) // two closes before net.Conn close
w = &safeCloser{
c: c,
ch: ch,
w: newEncoder(c),
}
default:
return nil, nil, errors.New("bad bad bad")
}
r = &safeCloser{
c: c,
ch: ch,
r: newDecoder(c),
}
return r, w, nil
}
type safeCloser struct {
c net.Conn
ch chan<- struct{}
r ReadAtCloser
w io.WriteCloser
}
func (s *safeCloser) ReadAt(p []byte, off int64) (int, error) {
return s.r.ReadAt(p, off)
}
func (s *safeCloser) Read(p []byte) (int, error) { return s.r.Read(p) }
func (s *safeCloser) Write(p []byte) (int, error) { return s.w.Write(p) }
// Close only closes the underlying connection when ch is full.
func (s *safeCloser) Close() (err error) {
if s.r != nil {
err = s.r.Close()
} else if s.w != nil {
err = s.w.Close()
}
select {
case s.ch <- struct{}{}:
return err
default:
return s.c.Close()
}
}
func (rmt *remote) Exists(key string) bool {
c, err := net.Dial("tcp", rmt.raddr)
if err != nil {
return false
}
fmt.Fprintf(c, "%d\n", actionExists)
sendKey(c, key)
var i int
fmt.Fscanf(c, "%d\n", &i)
return i == 1
}
func (rmt *remote) Remove(key string) error {
c, err := net.Dial("tcp", rmt.raddr)
if err != nil {
return err
}
fmt.Fprintf(c, "%d\n", actionRemove)
sendKey(c, key)
return nil
}
func (rmt *remote) Clean() error {
c, err := net.Dial("tcp", rmt.raddr)
if err != nil {
return err
}
fmt.Fprintf(c, "%d\n", actionClean)
return nil
}