aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Chabowski <kevin@kch42.de>2013-03-27 14:18:35 +0100
committerKevin Chabowski <kevin@kch42.de>2013-03-27 14:18:35 +0100
commitba81e091aac2efe014111f64164d54ec92e8e549 (patch)
tree9bdca77a5f1c92f2891eff3decbebf7f35974690
parent2d56d29ba5c20a582119c1bbfa48f2dbcbb07aaf (diff)
downloadgolibrsync-ba81e091aac2efe014111f64164d54ec92e8e549.tar.gz
golibrsync-ba81e091aac2efe014111f64164d54ec92e8e549.tar.bz2
golibrsync-ba81e091aac2efe014111f64164d54ec92e8e549.zip
Implemented patching
-rw-r--r--librsync/librsync.go67
-rw-r--r--librsync/signature_test.go20
2 files changed, 86 insertions, 1 deletions
diff --git a/librsync/librsync.go b/librsync/librsync.go
index 5d51c10..e154da6 100644
--- a/librsync/librsync.go
+++ b/librsync/librsync.go
@@ -108,7 +108,25 @@ func (job *Job) Close() error {
return nil
}
+// For errors in callbacks
+type jobInternalPanic struct {
+ err error
+}
+
+func (jp jobInternalPanic) Error() string { return jp.err.Error() }
+
func jobIter(job *C.rs_job_t, rsbufs *C.rs_buffers_t) (running bool, err error) {
+ defer func() {
+ r := recover()
+ jp, ok := r.(jobInternalPanic)
+ if !ok {
+ panic(r)
+ }
+
+ running = false
+ err = jp.err
+ }()
+
switch res := C.rs_job_iter(job, rsbufs); res {
case C.RS_DONE:
case C.RS_BLOCKED:
@@ -243,3 +261,52 @@ func NewDeltaGen(sig Signature, input io.Reader) (job *Job, err error) {
return
}
+
+// Patcher is a job with additional hidden data for patching.
+//
+// IMPORTANT: You still need to Close() this!
+type Patcher struct {
+ *Job
+ base io.ReaderAt
+ buf []byte
+}
+
+func _patch_callback(_patcher unsafe.Pointer, pos C.rs_long_t, len *C.size_t, _buf *unsafe.Pointer) C.rs_result {
+ patcher := (*Patcher)(_patcher)
+
+ patcher.buf = make([]byte, int(*len))
+ n, err := patcher.base.ReadAt(patcher.buf, int64(pos))
+ if n < int(*len) {
+ if err != io.EOF {
+ panic(jobInternalPanic{err})
+ } else {
+ return C.RS_INPUT_ENDED
+ }
+ }
+ *len = C.size_t(n)
+ *_buf = unsafe.Pointer(&(patcher.buf[0]))
+
+ return C.RS_DONE
+}
+
+var patch_callback = _patch_callback // So we can use the `&` operator in NewPatcher
+
+func NewPatcher(delta io.Reader, base io.ReaderAt) (job *Patcher, err error) {
+ _job, e := newJob(delta)
+ if e != nil {
+ err = e
+ return
+ }
+
+ job = &Patcher{
+ Job: _job,
+ base: base}
+
+ job.job = C.rs_patch_begin((*C.rs_copy_cb)(unsafe.Pointer(&patch_callback)), unsafe.Pointer(job))
+ if job.job == nil {
+ job.Close()
+ return nil, errors.New("rs_patch_begin failed")
+ }
+
+ return
+}
diff --git a/librsync/signature_test.go b/librsync/signature_test.go
index 152fd12..e0fd50f 100644
--- a/librsync/signature_test.go
+++ b/librsync/signature_test.go
@@ -76,5 +76,23 @@ func TestSignatureDeltaPatch(t *testing.T) {
}
}
- // TODO: Test patching
+ // Apply Patch
+ patchres := new(bytes.Buffer)
+ patcher, err := NewPatcher(deltabuf, orig)
+ if err != nil {
+ t.Fatalf("could not create a patcher: %s", err)
+ }
+ defer patcher.Close()
+
+ if _, err = io.Copy(patchres, patcher); err != nil {
+ t.Fatalf("Applying the patch failed: %s", err)
+ }
+
+ if !bytes.Equal(patchres.Bytes(), testdata.Mutation()) {
+ if path, err := dump(patchres); err == nil {
+ t.Fatalf("patch result and mutation are not equal. Result dumped to %s", path)
+ } else {
+ t.Fatalf("patch result and mutation are not equal. Could not dump result: %s", err)
+ }
+ }
}