2 * text-delta.c -- Internal text delta representation
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
28 #include <apr_general.h> /* for APR_INLINE */
29 #include <apr_md5.h> /* for, um...MD5 stuff */
31 #include "svn_delta.h"
33 #include "svn_pools.h"
34 #include "svn_checksum.h"
39 /* Text delta stream descriptor. */
41 struct svn_txdelta_stream_t {
42 /* Copied from parameters to svn_txdelta_stream_create. */
44 svn_txdelta_next_window_fn_t next_window;
45 svn_txdelta_md5_digest_fn_t md5_digest;
48 /* Delta stream baton. */
49 struct txdelta_baton {
50 /* These are copied from parameters passed to svn_txdelta. */
55 svn_boolean_t more_source; /* FALSE if source stream hit EOF. */
56 svn_boolean_t more; /* TRUE if there are more data in the pool. */
57 svn_filesize_t pos; /* Offset of next read in source file. */
58 char *buf; /* Buffer for input data. */
60 svn_checksum_ctx_t *context; /* If not NULL, the context for computing
62 svn_checksum_t *checksum; /* If non-NULL, the checksum of TARGET. */
64 apr_pool_t *result_pool; /* For results (e.g. checksum) */
68 /* Target-push stream descriptor. */
71 /* These are copied from parameters passed to svn_txdelta_target_push. */
73 svn_txdelta_window_handler_t wh;
79 svn_filesize_t source_offset;
80 apr_size_t source_len;
81 svn_boolean_t source_done;
82 apr_size_t target_len;
86 /* Text delta applicator. */
89 /* These are copied from parameters passed to svn_txdelta_apply. */
93 /* Private data. Between calls, SBUF contains the data from the
94 * last window's source view, as specified by SBUF_OFFSET and
95 * SBUF_LEN. The contents of TBUF are not interesting between
97 apr_pool_t *pool; /* Pool to allocate data from */
98 char *sbuf; /* Source buffer */
99 apr_size_t sbuf_size; /* Allocated source buffer space */
100 svn_filesize_t sbuf_offset; /* Offset of SBUF data in source stream */
101 apr_size_t sbuf_len; /* Length of SBUF data */
102 char *tbuf; /* Target buffer */
103 apr_size_t tbuf_size; /* Allocated target buffer space */
105 apr_md5_ctx_t md5_context; /* Leads to result_digest below. */
106 unsigned char *result_digest; /* MD5 digest of resultant fulltext;
107 must point to at least APR_MD5_DIGESTSIZE
110 const char *error_info; /* Optional extra info for error returns. */
115 svn_txdelta_window_t *
116 svn_txdelta__make_window(const svn_txdelta__ops_baton_t *build_baton,
119 svn_txdelta_window_t *window;
120 svn_string_t *new_data = apr_palloc(pool, sizeof(*new_data));
122 window = apr_palloc(pool, sizeof(*window));
123 window->sview_offset = 0;
124 window->sview_len = 0;
125 window->tview_len = 0;
127 window->num_ops = build_baton->num_ops;
128 window->src_ops = build_baton->src_ops;
129 window->ops = build_baton->ops;
131 /* just copy the fields over, rather than alloc/copying into a whole new
132 svn_string_t structure. */
133 /* ### would be much nicer if window->new_data were not a ptr... */
134 new_data->data = build_baton->new_data->data;
135 new_data->len = build_baton->new_data->len;
136 window->new_data = new_data;
142 /* Compute and return a delta window using the xdelta algorithm on
143 DATA, which contains SOURCE_LEN bytes of source data and TARGET_LEN
144 bytes of target data. SOURCE_OFFSET gives the offset of the source
145 data, and is simply copied into the window's sview_offset field. */
146 static svn_txdelta_window_t *
147 compute_window(const char *data, apr_size_t source_len, apr_size_t target_len,
148 svn_filesize_t source_offset, apr_pool_t *pool)
150 svn_txdelta__ops_baton_t build_baton = { 0 };
151 svn_txdelta_window_t *window;
153 /* Compute the delta operations. */
154 build_baton.new_data = svn_stringbuf_create_empty(pool);
157 svn_txdelta__insert_op(&build_baton, svn_txdelta_new, 0, target_len, data,
160 svn_txdelta__xdelta(&build_baton, data, source_len, target_len, pool);
162 /* Create and return the delta window. */
163 window = svn_txdelta__make_window(&build_baton, pool);
164 window->sview_offset = source_offset;
165 window->sview_len = source_len;
166 window->tview_len = target_len;
172 svn_txdelta_window_t *
173 svn_txdelta_window_dup(const svn_txdelta_window_t *window,
176 svn_txdelta__ops_baton_t build_baton = { 0 };
177 svn_txdelta_window_t *new_window;
178 const apr_size_t ops_size = (window->num_ops * sizeof(*build_baton.ops));
180 build_baton.num_ops = window->num_ops;
181 build_baton.src_ops = window->src_ops;
182 build_baton.ops_size = window->num_ops;
183 build_baton.ops = apr_palloc(pool, ops_size);
184 memcpy(build_baton.ops, window->ops, ops_size);
185 build_baton.new_data =
186 svn_stringbuf_create_from_string(window->new_data, pool);
188 new_window = svn_txdelta__make_window(&build_baton, pool);
189 new_window->sview_offset = window->sview_offset;
190 new_window->sview_len = window->sview_len;
191 new_window->tview_len = window->tview_len;
195 /* This is a private interlibrary compatibility wrapper. */
196 svn_txdelta_window_t *
197 svn_txdelta__copy_window(const svn_txdelta_window_t *window,
199 svn_txdelta_window_t *
200 svn_txdelta__copy_window(const svn_txdelta_window_t *window,
203 return svn_txdelta_window_dup(window, pool);
207 /* Insert a delta op into a delta window. */
210 svn_txdelta__insert_op(svn_txdelta__ops_baton_t *build_baton,
211 enum svn_delta_action opcode,
214 const char *new_data,
217 svn_txdelta_op_t *op;
219 /* Check if this op can be merged with the previous op. The delta
220 combiner sometimes generates such ops, and this is the obvious
221 place to make the check. */
222 if (build_baton->num_ops > 0)
224 op = &build_baton->ops[build_baton->num_ops - 1];
225 if (op->action_code == opcode
226 && (opcode == svn_txdelta_new
227 || op->offset + op->length == offset))
229 op->length += length;
230 if (opcode == svn_txdelta_new)
231 svn_stringbuf_appendbytes(build_baton->new_data,
237 /* Create space for the new op. */
238 if (build_baton->num_ops == build_baton->ops_size)
240 svn_txdelta_op_t *const old_ops = build_baton->ops;
241 int const new_ops_size = (build_baton->ops_size == 0
242 ? 16 : 2 * build_baton->ops_size);
244 apr_palloc(pool, new_ops_size * sizeof(*build_baton->ops));
246 /* Copy any existing ops into the new array */
248 memcpy(build_baton->ops, old_ops,
249 build_baton->ops_size * sizeof(*build_baton->ops));
250 build_baton->ops_size = new_ops_size;
253 /* Insert the op. svn_delta_source and svn_delta_target are
254 just inserted. For svn_delta_new, the new data must be
255 copied into the window. */
256 op = &build_baton->ops[build_baton->num_ops];
259 case svn_txdelta_source:
260 ++build_baton->src_ops;
262 case svn_txdelta_target:
263 op->action_code = opcode;
267 case svn_txdelta_new:
268 op->action_code = opcode;
269 op->offset = build_baton->new_data->len;
271 svn_stringbuf_appendbytes(build_baton->new_data, new_data, length);
274 assert(!"unknown delta op.");
277 ++build_baton->num_ops;
281 svn_txdelta__remove_copy(svn_txdelta__ops_baton_t *build_baton,
284 svn_txdelta_op_t *op;
287 /* remove ops back to front */
288 while (build_baton->num_ops > 0)
290 op = &build_baton->ops[build_baton->num_ops-1];
292 /* we can't modify svn_txdelta_target ops -> stop there */
293 if (op->action_code == svn_txdelta_target)
296 /* handle the case that we cannot remove the op entirely */
297 if (op->length + len > max_len)
299 /* truncate only insertions. Copies don't benefit
300 from being truncated. */
301 if (op->action_code == svn_txdelta_new)
303 build_baton->new_data->len -= max_len - len;
304 op->length -= max_len - len;
311 /* drop the op entirely */
312 if (op->action_code == svn_txdelta_new)
313 build_baton->new_data->len -= op->length;
316 --build_baton->num_ops;
324 /* Generic delta stream functions. */
326 svn_txdelta_stream_t *
327 svn_txdelta_stream_create(void *baton,
328 svn_txdelta_next_window_fn_t next_window,
329 svn_txdelta_md5_digest_fn_t md5_digest,
332 svn_txdelta_stream_t *stream = apr_palloc(pool, sizeof(*stream));
334 stream->baton = baton;
335 stream->next_window = next_window;
336 stream->md5_digest = md5_digest;
342 svn_txdelta_next_window(svn_txdelta_window_t **window,
343 svn_txdelta_stream_t *stream,
346 return stream->next_window(window, stream->baton, pool);
349 const unsigned char *
350 svn_txdelta_md5_digest(svn_txdelta_stream_t *stream)
352 return stream->md5_digest(stream->baton);
358 txdelta_next_window(svn_txdelta_window_t **window,
362 struct txdelta_baton *b = baton;
363 apr_size_t source_len = SVN_DELTA_WINDOW_SIZE;
364 apr_size_t target_len = SVN_DELTA_WINDOW_SIZE;
366 /* Read the source stream. */
369 SVN_ERR(svn_stream_read_full(b->source, b->buf, &source_len));
370 b->more_source = (source_len == SVN_DELTA_WINDOW_SIZE);
375 /* Read the target stream. */
376 SVN_ERR(svn_stream_read_full(b->target, b->buf + source_len, &target_len));
377 b->pos += source_len;
381 /* No target data? We're done; return the final window. */
382 if (b->context != NULL)
383 SVN_ERR(svn_checksum_final(&b->checksum, b->context, b->result_pool));
389 else if (b->context != NULL)
390 SVN_ERR(svn_checksum_update(b->context, b->buf + source_len, target_len));
392 *window = compute_window(b->buf, source_len, target_len,
393 b->pos - source_len, pool);
400 static const unsigned char *
401 txdelta_md5_digest(void *baton)
403 struct txdelta_baton *b = baton;
404 /* If there are more windows for this stream, the digest has not yet
409 /* If checksumming has not been activated, there will be no digest. */
410 if (b->context == NULL)
413 /* The checksum should be there. */
414 return b->checksum->digest;
419 svn_txdelta_run(svn_stream_t *source,
420 svn_stream_t *target,
421 svn_txdelta_window_handler_t handler,
423 svn_checksum_kind_t checksum_kind,
424 svn_checksum_t **checksum,
425 svn_cancel_func_t cancel_func,
427 apr_pool_t *result_pool,
428 apr_pool_t *scratch_pool)
430 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
431 struct txdelta_baton tb = { 0 };
432 svn_txdelta_window_t *window;
436 tb.more_source = TRUE;
439 tb.buf = apr_palloc(scratch_pool, 2 * SVN_DELTA_WINDOW_SIZE);
440 tb.result_pool = result_pool;
442 if (checksum != NULL)
443 tb.context = svn_checksum_ctx_create(checksum_kind, scratch_pool);
447 /* free the window (if any) */
448 svn_pool_clear(iterpool);
450 /* read in a single delta window */
451 SVN_ERR(txdelta_next_window(&window, &tb, iterpool));
453 /* shove it at the handler */
454 SVN_ERR((*handler)(window, handler_baton));
457 SVN_ERR(cancel_func(cancel_baton));
459 while (window != NULL);
461 svn_pool_destroy(iterpool);
463 if (checksum != NULL)
464 *checksum = tb.checksum; /* should be there! */
471 svn_txdelta2(svn_txdelta_stream_t **stream,
472 svn_stream_t *source,
473 svn_stream_t *target,
474 svn_boolean_t calculate_checksum,
477 struct txdelta_baton *b = apr_pcalloc(pool, sizeof(*b));
481 b->more_source = TRUE;
483 b->buf = apr_palloc(pool, 2 * SVN_DELTA_WINDOW_SIZE);
484 b->context = calculate_checksum
485 ? svn_checksum_ctx_create(svn_checksum_md5, pool)
487 b->result_pool = pool;
489 *stream = svn_txdelta_stream_create(b, txdelta_next_window,
490 txdelta_md5_digest, pool);
494 svn_txdelta(svn_txdelta_stream_t **stream,
495 svn_stream_t *source,
496 svn_stream_t *target,
499 svn_txdelta2(stream, source, target, TRUE, pool);
504 /* Functions for implementing a "target push" delta. */
506 /* This is the write handler for a target-push delta stream. It reads
507 * source data, buffers target data, and fires off delta windows when
508 * the target data buffer is full. */
510 tpush_write_handler(void *baton, const char *data, apr_size_t *len)
512 struct tpush_baton *tb = baton;
513 apr_size_t chunk_len, data_len = *len;
514 apr_pool_t *pool = svn_pool_create(tb->pool);
515 svn_txdelta_window_t *window;
519 svn_pool_clear(pool);
521 /* Make sure we're all full up on source data, if possible. */
522 if (tb->source_len == 0 && !tb->source_done)
524 tb->source_len = SVN_DELTA_WINDOW_SIZE;
525 SVN_ERR(svn_stream_read_full(tb->source, tb->buf, &tb->source_len));
526 if (tb->source_len < SVN_DELTA_WINDOW_SIZE)
527 tb->source_done = TRUE;
530 /* Copy in the target data, up to SVN_DELTA_WINDOW_SIZE. */
531 chunk_len = SVN_DELTA_WINDOW_SIZE - tb->target_len;
532 if (chunk_len > data_len)
533 chunk_len = data_len;
534 memcpy(tb->buf + tb->source_len + tb->target_len, data, chunk_len);
536 data_len -= chunk_len;
537 tb->target_len += chunk_len;
539 /* If we're full of target data, compute and fire off a window. */
540 if (tb->target_len == SVN_DELTA_WINDOW_SIZE)
542 window = compute_window(tb->buf, tb->source_len, tb->target_len,
543 tb->source_offset, pool);
544 SVN_ERR(tb->wh(window, tb->whb));
545 tb->source_offset += tb->source_len;
551 svn_pool_destroy(pool);
556 /* This is the close handler for a target-push delta stream. It sends
557 * a final window if there is any buffered target data, and then sends
558 * a NULL window signifying the end of the window stream. */
560 tpush_close_handler(void *baton)
562 struct tpush_baton *tb = baton;
563 svn_txdelta_window_t *window;
565 /* Send a final window if we have any residual target data. */
566 if (tb->target_len > 0)
568 window = compute_window(tb->buf, tb->source_len, tb->target_len,
569 tb->source_offset, tb->pool);
570 SVN_ERR(tb->wh(window, tb->whb));
573 /* Send a final NULL window signifying the end. */
574 return tb->wh(NULL, tb->whb);
579 svn_txdelta_target_push(svn_txdelta_window_handler_t handler,
580 void *handler_baton, svn_stream_t *source,
583 struct tpush_baton *tb;
584 svn_stream_t *stream;
586 /* Initialize baton. */
587 tb = apr_palloc(pool, sizeof(*tb));
590 tb->whb = handler_baton;
592 tb->buf = apr_palloc(pool, 2 * SVN_DELTA_WINDOW_SIZE);
593 tb->source_offset = 0;
595 tb->source_done = FALSE;
598 /* Create and return writable stream. */
599 stream = svn_stream_create(tb, pool);
600 svn_stream_set_write(stream, tpush_write_handler);
601 svn_stream_set_close(stream, tpush_close_handler);
607 /* Functions for applying deltas. */
609 /* Ensure that BUF has enough space for VIEW_LEN bytes. */
610 static APR_INLINE svn_error_t *
611 size_buffer(char **buf, apr_size_t *buf_size,
612 apr_size_t view_len, apr_pool_t *pool)
614 if (view_len > *buf_size)
617 if (*buf_size < view_len)
618 *buf_size = view_len;
619 SVN_ERR_ASSERT(APR_ALIGN_DEFAULT(*buf_size) >= *buf_size);
620 *buf = apr_palloc(pool, *buf_size);
626 /* Copy LEN bytes from SOURCE to TARGET. Unlike memmove() or memcpy(),
627 * create repeating patterns if the source and target ranges overlap.
628 * Return a pointer to the first byte after the copied target range. */
629 static APR_INLINE char *
630 patterning_copy(char *target, const char *source, apr_size_t len)
632 /* If the source and target overlap, repeat the overlapping pattern
633 in the target buffer. Always copy from the source buffer because
634 presumably it will be in the L1 cache after the first iteration
635 and doing this should avoid pipeline stalls due to write/read
637 const apr_size_t overlap = target - source;
638 while (len > overlap)
640 memcpy(target, source, overlap);
645 /* Copy any remaining source pattern. */
648 memcpy(target, source, len);
656 svn_txdelta_apply_instructions(svn_txdelta_window_t *window,
657 const char *sbuf, char *tbuf,
660 const svn_txdelta_op_t *op;
663 /* Nothing to do for empty buffers.
664 * This check allows for NULL TBUF in that case. */
668 for (op = window->ops; op < window->ops + window->num_ops; op++)
670 const apr_size_t buf_len = (op->length < *tlen - tpos
671 ? op->length : *tlen - tpos);
673 /* Check some invariants common to all instructions. */
674 assert(tpos + op->length <= window->tview_len);
676 switch (op->action_code)
678 case svn_txdelta_source:
679 /* Copy from source area. */
681 assert(op->offset + op->length <= window->sview_len);
682 memcpy(tbuf + tpos, sbuf + op->offset, buf_len);
685 case svn_txdelta_target:
686 /* Copy from target area. We can't use memcpy() or the like
687 * since we need a specific semantics for overlapping copies:
688 * they must result in repeating patterns.
689 * Note that most copies won't have overlapping source and
690 * target ranges (they are just a result of self-compressed
691 * data) but a small percentage will. */
692 assert(op->offset < tpos);
693 patterning_copy(tbuf + tpos, tbuf + op->offset, buf_len);
696 case svn_txdelta_new:
697 /* Copy from window new area. */
698 assert(op->offset + op->length <= window->new_data->len);
700 window->new_data->data + op->offset,
705 assert(!"Invalid delta instruction code");
710 return; /* The buffer is full. */
713 /* Check that we produced the right amount of data. */
714 assert(tpos == window->tview_len);
718 /* Apply WINDOW to the streams given by APPL. */
720 apply_window(svn_txdelta_window_t *window, void *baton)
722 struct apply_baton *ab = (struct apply_baton *) baton;
728 /* We're done; just clean up. */
729 if (ab->result_digest)
730 apr_md5_final(ab->result_digest, &(ab->md5_context));
732 err = svn_stream_close(ab->target);
733 svn_pool_destroy(ab->pool);
738 /* Make sure the source view didn't slide backwards. */
739 SVN_ERR_ASSERT(window->sview_len == 0
740 || (window->sview_offset >= ab->sbuf_offset
741 && (window->sview_offset + window->sview_len
742 >= ab->sbuf_offset + ab->sbuf_len)));
744 /* Make sure there's enough room in the target buffer. */
745 SVN_ERR(size_buffer(&ab->tbuf, &ab->tbuf_size, window->tview_len, ab->pool));
747 /* Prepare the source buffer for reading from the input stream. */
748 if (window->sview_offset != ab->sbuf_offset
749 || window->sview_len > ab->sbuf_size)
751 char *old_sbuf = ab->sbuf;
753 /* Make sure there's enough room. */
754 SVN_ERR(size_buffer(&ab->sbuf, &ab->sbuf_size, window->sview_len,
757 /* If the existing view overlaps with the new view, copy the
758 * overlap to the beginning of the new buffer. */
759 if ( (apr_size_t)ab->sbuf_offset + ab->sbuf_len
760 > (apr_size_t)window->sview_offset)
763 (apr_size_t)(window->sview_offset - ab->sbuf_offset);
764 memmove(ab->sbuf, old_sbuf + start, ab->sbuf_len - start);
765 ab->sbuf_len -= start;
769 ab->sbuf_offset = window->sview_offset;
772 /* Read the remainder of the source view into the buffer. */
773 if (ab->sbuf_len < window->sview_len)
775 len = window->sview_len - ab->sbuf_len;
776 err = svn_stream_read_full(ab->source, ab->sbuf + ab->sbuf_len, &len);
777 if (err == SVN_NO_ERROR && len != window->sview_len - ab->sbuf_len)
778 err = svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL,
779 "Delta source ended unexpectedly");
780 if (err != SVN_NO_ERROR)
782 ab->sbuf_len = window->sview_len;
785 /* Apply the window instructions to the source view to generate
787 len = window->tview_len;
788 svn_txdelta_apply_instructions(window, ab->sbuf, ab->tbuf, &len);
789 SVN_ERR_ASSERT(len == window->tview_len);
791 /* Write out the output. */
793 /* Just update the context here. */
794 if (ab->result_digest)
795 apr_md5_update(&(ab->md5_context), ab->tbuf, len);
797 return svn_stream_write(ab->target, ab->tbuf, &len);
802 svn_txdelta_apply(svn_stream_t *source,
803 svn_stream_t *target,
804 unsigned char *result_digest,
805 const char *error_info,
807 svn_txdelta_window_handler_t *handler,
808 void **handler_baton)
810 apr_pool_t *subpool = svn_pool_create(pool);
811 struct apply_baton *ab;
813 ab = apr_palloc(subpool, sizeof(*ab));
823 ab->result_digest = result_digest;
826 apr_md5_init(&(ab->md5_context));
829 ab->error_info = apr_pstrdup(subpool, error_info);
831 ab->error_info = NULL;
833 *handler = apply_window;
839 /* Convenience routines */
842 svn_txdelta_send_string(const svn_string_t *string,
843 svn_txdelta_window_handler_t handler,
847 svn_txdelta_window_t window = { 0 };
850 /* Build a single `new' op */
851 op.action_code = svn_txdelta_new;
853 op.length = string->len;
855 /* Build a single window containing a ptr to the string. */
856 window.tview_len = string->len;
859 window.new_data = string;
861 /* Push the one window at the handler. */
862 SVN_ERR((*handler)(&window, handler_baton));
864 /* Push a NULL at the handler, because we're done. */
865 return (*handler)(NULL, handler_baton);
868 svn_error_t *svn_txdelta_send_stream(svn_stream_t *stream,
869 svn_txdelta_window_handler_t handler,
871 unsigned char *digest,
874 svn_txdelta_window_t delta_window = { 0 };
875 svn_txdelta_op_t delta_op;
876 svn_string_t window_data;
877 char read_buf[SVN__STREAM_CHUNK_SIZE + 1];
878 svn_checksum_ctx_t *md5_checksum_ctx;
881 md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
885 apr_size_t read_len = SVN__STREAM_CHUNK_SIZE;
887 SVN_ERR(svn_stream_read_full(stream, read_buf, &read_len));
891 window_data.data = read_buf;
892 window_data.len = read_len;
894 delta_op.action_code = svn_txdelta_new;
896 delta_op.length = read_len;
898 delta_window.tview_len = read_len;
899 delta_window.num_ops = 1;
900 delta_window.ops = &delta_op;
901 delta_window.new_data = &window_data;
903 SVN_ERR(handler(&delta_window, handler_baton));
906 SVN_ERR(svn_checksum_update(md5_checksum_ctx, read_buf, read_len));
908 if (read_len < SVN__STREAM_CHUNK_SIZE)
911 SVN_ERR(handler(NULL, handler_baton));
915 svn_checksum_t *md5_checksum;
917 SVN_ERR(svn_checksum_final(&md5_checksum, md5_checksum_ctx, pool));
918 memcpy(digest, md5_checksum->digest, APR_MD5_DIGESTSIZE);
924 svn_error_t *svn_txdelta_send_txstream(svn_txdelta_stream_t *txstream,
925 svn_txdelta_window_handler_t handler,
929 svn_txdelta_window_t *window;
931 /* create a pool just for the windows */
932 apr_pool_t *wpool = svn_pool_create(pool);
936 /* free the window (if any) */
937 svn_pool_clear(wpool);
939 /* read in a single delta window */
940 SVN_ERR(svn_txdelta_next_window(&window, txstream, wpool));
942 /* shove it at the handler */
943 SVN_ERR((*handler)(window, handler_baton));
945 while (window != NULL);
947 svn_pool_destroy(wpool);
953 svn_txdelta_send_contents(const unsigned char *contents,
955 svn_txdelta_window_handler_t handler,
959 svn_string_t new_data;
960 svn_txdelta_op_t op = { svn_txdelta_new, 0, 0 };
961 svn_txdelta_window_t window = { 0, 0, 0, 1, 0 };
963 window.new_data = &new_data;
965 /* send CONTENT as a series of max-sized windows */
968 /* stuff next chunk into the window */
969 window.tview_len = len < SVN_DELTA_WINDOW_SIZE
971 : SVN_DELTA_WINDOW_SIZE;
972 op.length = window.tview_len;
973 new_data.len = window.tview_len;
974 new_data.data = (const char*)contents;
976 /* update remaining */
977 contents += window.tview_len;
978 len -= window.tview_len;
980 /* shove it at the handler */
981 SVN_ERR((*handler)(&window, handler_baton));
984 /* indicate end of stream */
985 SVN_ERR((*handler)(NULL, handler_baton));