Initial commit.
commit
b210f39f07
|
@ -0,0 +1,3 @@
|
|||
[submodule "bsdiff"]
|
||||
path = bsdiff
|
||||
url = https://github.com/mendsley/bsdiff.git
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 7d70d8f4ff48345bc76e314c9d98da91f78873fa
|
|
@ -0,0 +1,31 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/icedream/go-bsdiff"
|
||||
)
|
||||
|
||||
func must(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
func main() {
|
||||
oldFile, err := os.Open("test.txt")
|
||||
must(err)
|
||||
|
||||
newFile, err := os.Open("test_new.txt")
|
||||
must(err)
|
||||
|
||||
patchFile, err := os.Create("test.patch")
|
||||
must(err)
|
||||
|
||||
must(bsdiff.Diff(oldFile, newFile, patchFile))
|
||||
|
||||
log.Println("Done.")
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package bsdiff
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/dsnet/compress/bzip2"
|
||||
"github.com/icedream/go-bsdiff/internal/native"
|
||||
)
|
||||
|
||||
func Diff(oldReader, newReader io.Reader, patchWriter io.Writer) (err error) {
|
||||
oldBytes, err := ioutil.ReadAll(oldReader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newBytes, err := ioutil.ReadAll(newReader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = writeHeader(patchWriter, uint64(len(newBytes))); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Compression
|
||||
bz2Writer, err := bzip2.NewWriter(patchWriter, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer bz2Writer.Close()
|
||||
|
||||
return native.Diff(oldBytes, newBytes, bz2Writer)
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
#include "cgo.h"
|
||||
|
||||
#include "bsdiff.h"
|
||||
#include "bspatch.h"
|
||||
|
||||
extern int cgo_write_buffer(int bufferIndex, void* buf, int size);
|
||||
|
||||
int cgo_write(struct bsdiff_stream* stream,
|
||||
const void* buf, int size) {
|
||||
struct buffer_table_index* bufferEntry;
|
||||
|
||||
bufferEntry = (struct buffer_table_index*)stream->opaque;
|
||||
|
||||
return cgo_write_buffer(bufferEntry->index, (void*)buf, size);
|
||||
}
|
||||
|
||||
extern int cgo_read_buffer(int bufferIndex, void* buf, int size);
|
||||
|
||||
int cgo_read(const struct bspatch_stream* stream,
|
||||
void* buf, int size) {
|
||||
struct buffer_table_index* bufferEntry;
|
||||
|
||||
bufferEntry = (struct buffer_table_index*)stream->opaque;
|
||||
|
||||
return cgo_read_buffer(bufferEntry->index, buf, size) ;
|
||||
}
|
||||
|
||||
int bsdiff_cgo(uint8_t* oldptr, int64_t oldsize,
|
||||
uint8_t* newptr, int64_t newsize,
|
||||
int bufferIndex)
|
||||
{
|
||||
struct bsdiff_stream stream;
|
||||
stream.malloc = malloc;
|
||||
stream.free = free;
|
||||
stream.write = cgo_write;
|
||||
|
||||
struct buffer_table_index bufferEntry;
|
||||
bufferEntry.index = bufferIndex;
|
||||
stream.opaque = &bufferEntry;
|
||||
|
||||
return bsdiff(oldptr, oldsize, newptr, newsize, &stream);
|
||||
}
|
||||
|
||||
int bspatch_cgo(uint8_t* oldptr, int64_t oldsize,
|
||||
uint8_t* newptr, int64_t newsize,
|
||||
int bufferIndex)
|
||||
{
|
||||
struct bspatch_stream stream;
|
||||
stream.read = cgo_read;
|
||||
|
||||
struct buffer_table_index bufferEntry;
|
||||
bufferEntry.index = bufferIndex;
|
||||
stream.opaque = &bufferEntry;
|
||||
|
||||
return bspatch(oldptr, oldsize, newptr, newsize, &stream);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
#include <stdlib.h>
|
||||
#include "stdint.h"
|
||||
|
||||
struct buffer_table_index
|
||||
{
|
||||
int index;
|
||||
};
|
||||
|
||||
int bsdiff_cgo(uint8_t* oldptr, int64_t oldsize,
|
||||
uint8_t* newptr, int64_t newsize,
|
||||
int bufferIndex);
|
||||
|
||||
int bspatch_cgo(uint8_t* oldptr, int64_t oldsize,
|
||||
uint8_t* newptr, int64_t newsize,
|
||||
int bufferIndex);
|
|
@ -0,0 +1,43 @@
|
|||
package native
|
||||
|
||||
/*
|
||||
#include "bspatch.h"
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//export cgo_read_buffer
|
||||
func cgo_read_buffer(bufferIndex C.int,
|
||||
bufPtr unsafe.Pointer, length C.int) C.int {
|
||||
goLength := int(length)
|
||||
|
||||
if goLength == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
sourceBuffer := readers.Get(int(bufferIndex))
|
||||
targetBuffer := cPtrToSlice(bufPtr, goLength)
|
||||
|
||||
errCode := 0
|
||||
offset := 0
|
||||
for offset < goLength {
|
||||
n, err := sourceBuffer.Read(targetBuffer)
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
log.Println("cgo_read_buffer failed:", err)
|
||||
errCode = 1
|
||||
break
|
||||
}
|
||||
|
||||
offset += n
|
||||
targetBuffer = targetBuffer[n:]
|
||||
}
|
||||
|
||||
return C.int(errCode)
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package native
|
||||
|
||||
/*
|
||||
#include "bsdiff.h"
|
||||
*/
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
//export cgo_write_buffer
|
||||
func cgo_write_buffer(bufferIndex C.int,
|
||||
dataPtr unsafe.Pointer, size C.int) C.int {
|
||||
buffer := writers.Get(int(bufferIndex))
|
||||
errCode := 0
|
||||
if _, err := buffer.Write(cPtrToSlice(dataPtr, int(size))); err != nil {
|
||||
errCode = 1
|
||||
}
|
||||
return C.int(errCode)
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package native
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I../../bsdiff
|
||||
|
||||
#include "bsdiff.h"
|
||||
#include "cgo.h"
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
func Diff(oldbytes, newbytes []byte, patch io.Writer) (err error) {
|
||||
oldptr, oldsize := bytesToUint8PtrAndSize(oldbytes)
|
||||
newptr, newsize := bytesToUint8PtrAndSize(newbytes)
|
||||
|
||||
bufferIndex := writers.Add(patch)
|
||||
|
||||
errCode := int(C.bsdiff_cgo(oldptr, oldsize, newptr, newsize, C.int(bufferIndex)))
|
||||
if errCode != 0 {
|
||||
err = errors.New("bsdiff failed")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
#include "bsdiff.c"
|
||||
#include "bspatch.c"
|
|
@ -0,0 +1,31 @@
|
|||
package native
|
||||
|
||||
/*
|
||||
#include <stdint.h>
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
writers = writerTable{}
|
||||
readers = readerTable{}
|
||||
)
|
||||
|
||||
func bytesToUint8PtrAndSize(bytes []byte) (ptr *C.uint8_t, size C.int64_t) {
|
||||
ptr = (*C.uint8_t)(unsafe.Pointer(&bytes[0]))
|
||||
size = C.int64_t(int64(len(bytes)))
|
||||
return
|
||||
}
|
||||
|
||||
func cPtrToSlice(ptr unsafe.Pointer, size int) []byte {
|
||||
var slice []byte
|
||||
sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&slice))
|
||||
sliceHeader.Cap = size
|
||||
sliceHeader.Len = size
|
||||
sliceHeader.Data = uintptr(ptr)
|
||||
|
||||
return slice
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package native
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I../../bsdiff
|
||||
|
||||
#include "bspatch.h"
|
||||
#include "cgo.h"
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func Patch(oldbytes, newbytes []byte, patch io.Reader) (err error) {
|
||||
oldptr, oldsize := bytesToUint8PtrAndSize(oldbytes)
|
||||
newptr, newsize := bytesToUint8PtrAndSize(newbytes)
|
||||
|
||||
bufferIndex := readers.Add(patch)
|
||||
|
||||
errCode := int(C.bspatch_cgo(oldptr, oldsize, newptr, newsize, C.int(bufferIndex)))
|
||||
if errCode != 0 {
|
||||
err = errors.New("bspatch failed with code " + strconv.Itoa(errCode))
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package native
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type readerTable struct {
|
||||
nextIndex int
|
||||
table map[int]io.Reader
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func (bt *readerTable) Add(reader io.Reader) (index int) {
|
||||
bt.mutex.Lock()
|
||||
defer bt.mutex.Unlock()
|
||||
|
||||
if bt.table == nil {
|
||||
bt.table = map[int]io.Reader{}
|
||||
}
|
||||
|
||||
index = bt.nextIndex
|
||||
bt.table[index] = reader
|
||||
|
||||
// TODO - Handle int overflow
|
||||
|
||||
bt.nextIndex++
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (bt *readerTable) Get(index int) io.Reader {
|
||||
bt.mutex.Lock()
|
||||
defer bt.mutex.Unlock()
|
||||
|
||||
return bt.table[index]
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package native
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type writerTable struct {
|
||||
nextIndex int
|
||||
table map[int]io.Writer
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func (bt *writerTable) Add(writer io.Writer) (index int) {
|
||||
bt.mutex.Lock()
|
||||
defer bt.mutex.Unlock()
|
||||
|
||||
if bt.table == nil {
|
||||
bt.table = map[int]io.Writer{}
|
||||
}
|
||||
|
||||
index = bt.nextIndex
|
||||
bt.table[index] = writer
|
||||
|
||||
// TODO - Handle int overflow
|
||||
|
||||
bt.nextIndex++
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (bt *writerTable) Get(index int) io.Writer {
|
||||
bt.mutex.Lock()
|
||||
defer bt.mutex.Unlock()
|
||||
|
||||
return bt.table[index]
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package bsdiff
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidMagic = errors.New("Invalid magic")
|
||||
|
||||
sizeEncoding = binary.BigEndian
|
||||
|
||||
magicText = []byte("ENDSLEY/BSDIFF43")
|
||||
)
|
||||
|
||||
func writeHeader(w io.Writer, size uint64) (err error) {
|
||||
if _, err = w.Write(magicText); err != nil {
|
||||
return
|
||||
}
|
||||
err = binary.Write(w, sizeEncoding, size)
|
||||
return
|
||||
}
|
||||
|
||||
func readHeader(r io.Reader) (size uint64, err error) {
|
||||
magicBuf := make([]byte, len(magicText))
|
||||
n, err := r.Read(magicBuf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if n < len(magicText) {
|
||||
err = ErrInvalidMagic
|
||||
return
|
||||
}
|
||||
|
||||
err = binary.Read(r, sizeEncoding, &size)
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package bsdiff
|
||||
|
||||
import (
|
||||
"compress/bzip2"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/icedream/go-bsdiff/internal/native"
|
||||
)
|
||||
|
||||
func Patch(oldReader io.Reader, newWriter io.Writer, patchReader io.Reader) (err error) {
|
||||
oldBytes, err := ioutil.ReadAll(oldReader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
newLen, err := readHeader(patchReader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newBytes := make([]byte, newLen)
|
||||
|
||||
log.Printf("Going to create a file of %d bytes.", newLen)
|
||||
|
||||
// Decompression
|
||||
bz2Reader := bzip2.NewReader(patchReader)
|
||||
|
||||
err = native.Patch(oldBytes, newBytes, bz2Reader)
|
||||
|
||||
newWriter.Write(newBytes)
|
||||
return
|
||||
}
|
Loading…
Reference in New Issue