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 000000000000..cb63b7dd438b --- /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; +}  | 
