otremblay / diffstore

No Description

main.go3.3KB

package main

import (
	"bytes"
	"encoding/gob"
	"errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"

	"github.com/boltdb/bolt"
	"github.com/kr/binarydist"
)

var ErrDiffNotFound = errors.New("Diff not found for key provided")

func main() {
	bd, err := bolt.Open(os.Args[1], 0666, nil)
	if err != nil {
		panic(err)
	}
	r := http.NewServeMux()
	r.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) {
		rw.Header().Add("Access-Control-Allow-Origin", "*")
		rw.Header().Add("Access-Control-Expose-Headers", "X-DocVersion")
		rw.Header().Add("Access-Control-Allow-Headers", "X-DocVersion")
		obj := req.URL.Path
		if obj != "" {
			version := -1
			if v := req.Header.Get("X-Docversion"); v != "" {
				if i, err := strconv.Atoi(v); err == nil {
					version = i
					fmt.Println(version)
				}

			}

			if req.Method == "GET" {
				bd.View(func(t *bolt.Tx) error {
					b := t.Bucket([]byte("diffs"))
					if b == nil {
						return ErrDiffNotFound
					}
					val := b.Get([]byte(obj))
					if val == nil {
						return ErrDiffNotFound
					}
					var diff_bytes = bytes.NewBuffer(val)
					dec := gob.NewDecoder(diff_bytes)
					var d *diff
					err = dec.Decode(&d)
					if err != nil {
						return err
					}
					out, v, err := d.Apply(version)
					if err != nil {
						fmt.Println(err)
						return err
					}
					rw.Header().Add("X-Docversion", strconv.Itoa(v))

					fmt.Fprintf(rw, string(out))
					return err
				})
			}
			if req.Method == "POST" {
				bd.Update(func(t *bolt.Tx) error {
					obj_address := []byte(obj)
					b, err := t.CreateBucketIfNotExists([]byte("diffs"))
					if err != nil {
						return err
					}
					existing := b.Get(obj_address)
					if existing == nil {
						original := &diff{}
						var out bytes.Buffer
						io.Copy(&out, req.Body)

						original.Patch = out.Bytes()
						var bb bytes.Buffer
						enc := gob.NewEncoder(&bb)
						enc.Encode(original)
						return b.Put(obj_address, bb.Bytes())
					}

					diff_bytes := bytes.NewBuffer(existing)
					diff_dec := gob.NewDecoder(diff_bytes)
					var d *diff
					diff_dec.Decode(&d)
					applied_diff, _, err := d.Apply(-1)
					if err != nil {
						return err
					}
					var new_patch bytes.Buffer
					err = binarydist.Diff(bytes.NewBuffer(applied_diff), req.Body, &new_patch)
					if err != nil {
						return err
					}
					d.Append(&diff{Patch: new_patch.Bytes()})
					var out bytes.Buffer
					enc := gob.NewEncoder(&out)
					enc.Encode(d)
					err = b.Put(obj_address, out.Bytes())
					return err
				})
			}
		}
	})

	err = http.ListenAndServe(":8081", r)
	if err != nil {
		panic(err)
	}
}

type diff struct {
	Patch    []byte
	Nextdiff *diff
}

func (d *diff) String() string {
	s2 := ""
	if d.Nextdiff != nil {
		s2 = d.Nextdiff.String()
	}
	return fmt.Sprintf("%s %s", string(d.Patch), s2)
}

func (d *diff) Apply(version int) ([]byte, int, error) {
	b := bytes.NewBuffer(d.Patch)
	i := 0
	for nd := d.Nextdiff; nd != nil && (version == -1 || i < version); nd = nd.Nextdiff {
		bp := bytes.NewBuffer(nd.Patch)
		var bn bytes.Buffer
		err := binarydist.Patch(b, &bn, bp)
		if err != nil {
			return nil, -1, err
		}
		b = &bn
		i++
	}
	return b.Bytes(), i, nil
}

func (d *diff) Append(d2 *diff) {
	od := d
	for nd := od.Nextdiff; nd != nil; nd = nd.Nextdiff {
		od = nd
	}
	od.Nextdiff = d2
}