diff options
Diffstat (limited to 'subversion/libsvn_ra_serf/get_file.c')
-rw-r--r-- | subversion/libsvn_ra_serf/get_file.c | 425 |
1 files changed, 425 insertions, 0 deletions
diff --git a/subversion/libsvn_ra_serf/get_file.c b/subversion/libsvn_ra_serf/get_file.c new file mode 100644 index 0000000..cb63b7d --- /dev/null +++ b/subversion/libsvn_ra_serf/get_file.c @@ -0,0 +1,425 @@ +/* + * get_file.c : entry point for update RA functions for ra_serf + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#define APR_WANT_STRFUNC +#include <apr_version.h> +#include <apr_want.h> + +#include <apr_uri.h> + +#include <serf.h> + +#include "svn_private_config.h" +#include "svn_hash.h" +#include "svn_pools.h" +#include "svn_ra.h" +#include "svn_delta.h" +#include "svn_path.h" +#include "svn_props.h" + +#include "private/svn_dep_compat.h" +#include "private/svn_string_private.h" + +#include "ra_serf.h" +#include "../libsvn_ra/ra_loader.h" + + + + +/* + * This structure represents a single request to GET (fetch) a file with + * its associated Serf session/connection. + */ +typedef struct stream_ctx_t { + + /* The handler representing this particular fetch. */ + svn_ra_serf__handler_t *handler; + + /* Have we read our response headers yet? */ + svn_boolean_t read_headers; + + svn_boolean_t using_compression; + + /* This flag is set when our response is aborted before we reach the + * end and we decide to requeue this request. + */ + svn_boolean_t aborted_read; + apr_off_t aborted_read_size; + + /* This is the amount of data that we have read so far. */ + apr_off_t read_size; + + /* If we're writing this file to a stream, this will be non-NULL. */ + svn_stream_t *result_stream; + +} stream_ctx_t; + + + +/** Routines called when we are fetching a file */ + +static svn_error_t * +headers_fetch(serf_bucket_t *headers, + void *baton, + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) +{ + stream_ctx_t *fetch_ctx = baton; + + if (fetch_ctx->using_compression) + { + serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip"); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +cancel_fetch(serf_request_t *request, + serf_bucket_t *response, + int status_code, + void *baton) +{ + stream_ctx_t *fetch_ctx = baton; + + /* Uh-oh. Our connection died on us. + * + * The core ra_serf layer will requeue our request - we just need to note + * that we got cut off in the middle of our song. + */ + if (!response) + { + /* If we already started the fetch and opened the file handle, we need + * to hold subsequent read() ops until we get back to where we were + * before the close and we can then resume the textdelta() calls. + */ + if (fetch_ctx->read_headers) + { + if (!fetch_ctx->aborted_read && fetch_ctx->read_size) + { + fetch_ctx->aborted_read = TRUE; + fetch_ctx->aborted_read_size = fetch_ctx->read_size; + } + fetch_ctx->read_size = 0; + } + + return SVN_NO_ERROR; + } + + /* We have no idea what went wrong. */ + SVN_ERR_MALFUNCTION(); +} + + +/* Helper svn_ra_serf__get_file(). Attempts to fetch file contents + * using SESSION->wc_callbacks->get_wc_contents() if sha1 property is + * present in PROPS. + * + * Sets *FOUND_P to TRUE if file contents was successfuly fetched. + * + * Performs all temporary allocations in POOL. + */ +static svn_error_t * +try_get_wc_contents(svn_boolean_t *found_p, + svn_ra_serf__session_t *session, + const char *sha1_checksum_prop, + svn_stream_t *dst_stream, + apr_pool_t *pool) +{ + svn_checksum_t *checksum; + svn_stream_t *wc_stream; + svn_error_t *err; + + /* No contents found by default. */ + *found_p = FALSE; + + if (!session->wc_callbacks->get_wc_contents + || sha1_checksum_prop == NULL) + { + /* Nothing to do. */ + return SVN_NO_ERROR; + } + + SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, + sha1_checksum_prop, pool)); + + err = session->wc_callbacks->get_wc_contents( + session->wc_callback_baton, &wc_stream, checksum, pool); + + if (err) + { + svn_error_clear(err); + + /* Ignore errors for now. */ + return SVN_NO_ERROR; + } + + if (wc_stream) + { + SVN_ERR(svn_stream_copy3(wc_stream, + svn_stream_disown(dst_stream, pool), + NULL, NULL, pool)); + *found_p = TRUE; + } + + return SVN_NO_ERROR; +} + +/* ----------------------------------------------------------------------- + svn_ra_get_file() specific */ + +/* Implements svn_ra_serf__response_handler_t */ +static svn_error_t * +handle_stream(serf_request_t *request, + serf_bucket_t *response, + void *handler_baton, + apr_pool_t *pool) +{ + stream_ctx_t *fetch_ctx = handler_baton; + apr_status_t status; + + if (fetch_ctx->handler->sline.code != 200) + return svn_error_trace(svn_ra_serf__unexpected_status(fetch_ctx->handler)); + + while (1) + { + const char *data; + apr_size_t len; + + status = serf_bucket_read(response, 8000, &data, &len); + if (SERF_BUCKET_READ_ERROR(status)) + { + return svn_ra_serf__wrap_err(status, NULL); + } + + fetch_ctx->read_size += len; + + if (fetch_ctx->aborted_read) + { + apr_off_t skip; + + /* We haven't caught up to where we were before. */ + if (fetch_ctx->read_size < fetch_ctx->aborted_read_size) + { + /* Eek. What did the file shrink or something? */ + if (APR_STATUS_IS_EOF(status)) + { + SVN_ERR_MALFUNCTION(); + } + + /* Skip on to the next iteration of this loop. */ + if (APR_STATUS_IS_EAGAIN(status)) + { + return svn_ra_serf__wrap_err(status, NULL); + } + continue; + } + + /* Woo-hoo. We're back. */ + fetch_ctx->aborted_read = FALSE; + + /* Increment data and len by the difference. */ + skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size); + data += skip; + len -= (apr_size_t)skip; + } + + if (len) + { + apr_size_t written_len; + + written_len = len; + + SVN_ERR(svn_stream_write(fetch_ctx->result_stream, data, + &written_len)); + } + + if (status) + { + return svn_ra_serf__wrap_err(status, NULL); + } + } + /* not reached */ +} + +/* Baton for get_file_prop_cb */ +struct file_prop_baton_t +{ + apr_pool_t *result_pool; + svn_node_kind_t kind; + apr_hash_t *props; + const char *sha1_checksum; +}; + +/* Implements svn_ra_serf__prop_func_t for svn_ra_serf__get_file */ +static svn_error_t * +get_file_prop_cb(void *baton, + const char *path, + const char *ns, + const char *name, + const svn_string_t *value, + apr_pool_t *scratch_pool) +{ + struct file_prop_baton_t *fb = baton; + const char *svn_name; + + if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0) + { + const char *val = value->data; + + if (strcmp(val, "collection") == 0) + fb->kind = svn_node_dir; + else + fb->kind = svn_node_file; + + return SVN_NO_ERROR; + } + else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0 + && strcmp(name, "sha1-checksum") == 0) + { + fb->sha1_checksum = apr_pstrdup(fb->result_pool, value->data); + } + + if (!fb->props) + return SVN_NO_ERROR; + + svn_name = svn_ra_serf__svnname_from_wirename(ns, name, fb->result_pool); + if (svn_name) + { + svn_hash_sets(fb->props, svn_name, + svn_string_dup(value, fb->result_pool)); + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_serf__get_file(svn_ra_session_t *ra_session, + const char *path, + svn_revnum_t revision, + svn_stream_t *stream, + svn_revnum_t *fetched_rev, + apr_hash_t **props, + apr_pool_t *pool) +{ + svn_ra_serf__session_t *session = ra_session->priv; + const char *fetch_url; + const svn_ra_serf__dav_props_t *which_props; + svn_ra_serf__handler_t *propfind_handler; + struct file_prop_baton_t fb; + + /* Fetch properties. */ + + fetch_url = svn_path_url_add_component2(session->session_url.path, path, pool); + + /* The simple case is if we want HEAD - then a GET on the fetch_url is fine. + * + * Otherwise, we need to get the baseline version for this particular + * revision and then fetch that file. + */ + if (SVN_IS_VALID_REVNUM(revision) || fetched_rev) + { + SVN_ERR(svn_ra_serf__get_stable_url(&fetch_url, fetched_rev, + session, + fetch_url, revision, + pool, pool)); + revision = SVN_INVALID_REVNUM; + } + /* REVISION is always SVN_INVALID_REVNUM */ + SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision)); + + if (props) + which_props = all_props; + else if (stream && session->wc_callbacks->get_wc_contents) + which_props = type_and_checksum_props; + else + which_props = check_path_props; + + fb.result_pool = pool; + fb.props = props ? apr_hash_make(pool) : NULL; + fb.kind = svn_node_unknown; + fb.sha1_checksum = NULL; + + SVN_ERR(svn_ra_serf__create_propfind_handler(&propfind_handler, session, + fetch_url, SVN_INVALID_REVNUM, + "0", which_props, + get_file_prop_cb, &fb, + pool)); + + SVN_ERR(svn_ra_serf__context_run_one(propfind_handler, pool)); + + /* Verify that resource type is not collection. */ + if (fb.kind != svn_node_file) + { + return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL, + _("Can't get text contents of a directory")); + } + + if (props) + *props = fb.props; + + if (stream) + { + svn_boolean_t found; + SVN_ERR(try_get_wc_contents(&found, session, fb.sha1_checksum, stream, pool)); + + /* No contents found in the WC, let's fetch from server. */ + if (!found) + { + stream_ctx_t *stream_ctx; + svn_ra_serf__handler_t *handler; + + /* Create the fetch context. */ + stream_ctx = apr_pcalloc(pool, sizeof(*stream_ctx)); + stream_ctx->result_stream = stream; + stream_ctx->using_compression = session->using_compression; + + handler = svn_ra_serf__create_handler(session, pool); + + handler->method = "GET"; + handler->path = fetch_url; + + handler->custom_accept_encoding = TRUE; + handler->no_dav_headers = TRUE; + + handler->header_delegate = headers_fetch; + handler->header_delegate_baton = stream_ctx; + + handler->response_handler = handle_stream; + handler->response_baton = stream_ctx; + + handler->response_error = cancel_fetch; + handler->response_error_baton = stream_ctx; + + stream_ctx->handler = handler; + + SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); + + if (handler->sline.code != 200) + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); + } + } + + return SVN_NO_ERROR; +} |