get_file.c revision 299742
1/*
2 * get_file.c :  entry point for update RA functions for ra_serf
3 *
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
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
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
20 *    under the License.
21 * ====================================================================
22 */
23
24
25
26#define APR_WANT_STRFUNC
27#include <apr_version.h>
28#include <apr_want.h>
29
30#include <apr_uri.h>
31
32#include <serf.h>
33
34#include "svn_private_config.h"
35#include "svn_hash.h"
36#include "svn_pools.h"
37#include "svn_ra.h"
38#include "svn_delta.h"
39#include "svn_path.h"
40#include "svn_props.h"
41
42#include "private/svn_dep_compat.h"
43#include "private/svn_string_private.h"
44
45#include "ra_serf.h"
46#include "../libsvn_ra/ra_loader.h"
47
48
49
50
51/*
52 * This structure represents a single request to GET (fetch) a file with
53 * its associated Serf session/connection.
54 */
55typedef struct stream_ctx_t {
56
57  /* The handler representing this particular fetch.  */
58  svn_ra_serf__handler_t *handler;
59
60  /* Have we read our response headers yet? */
61  svn_boolean_t read_headers;
62
63  svn_boolean_t using_compression;
64
65  /* This flag is set when our response is aborted before we reach the
66   * end and we decide to requeue this request.
67   */
68  svn_boolean_t aborted_read;
69  apr_off_t aborted_read_size;
70
71  /* This is the amount of data that we have read so far. */
72  apr_off_t read_size;
73
74  /* If we're writing this file to a stream, this will be non-NULL. */
75  svn_stream_t *result_stream;
76
77} stream_ctx_t;
78
79
80
81/** Routines called when we are fetching a file */
82
83static svn_error_t *
84headers_fetch(serf_bucket_t *headers,
85              void *baton,
86              apr_pool_t *pool /* request pool */,
87              apr_pool_t *scratch_pool)
88{
89  stream_ctx_t *fetch_ctx = baton;
90
91  if (fetch_ctx->using_compression)
92    {
93      serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip");
94    }
95
96  return SVN_NO_ERROR;
97}
98
99static svn_error_t *
100cancel_fetch(serf_request_t *request,
101             serf_bucket_t *response,
102             int status_code,
103             void *baton)
104{
105  stream_ctx_t *fetch_ctx = baton;
106
107  /* Uh-oh.  Our connection died on us.
108   *
109   * The core ra_serf layer will requeue our request - we just need to note
110   * that we got cut off in the middle of our song.
111   */
112  if (!response)
113    {
114      /* If we already started the fetch and opened the file handle, we need
115       * to hold subsequent read() ops until we get back to where we were
116       * before the close and we can then resume the textdelta() calls.
117       */
118      if (fetch_ctx->read_headers)
119        {
120          if (!fetch_ctx->aborted_read && fetch_ctx->read_size)
121            {
122              fetch_ctx->aborted_read = TRUE;
123              fetch_ctx->aborted_read_size = fetch_ctx->read_size;
124            }
125          fetch_ctx->read_size = 0;
126        }
127
128      return SVN_NO_ERROR;
129    }
130
131  /* We have no idea what went wrong. */
132  SVN_ERR_MALFUNCTION();
133}
134
135
136/* Helper svn_ra_serf__get_file(). Attempts to fetch file contents
137 * using SESSION->wc_callbacks->get_wc_contents() if sha1 property is
138 * present in PROPS.
139 *
140 * Sets *FOUND_P to TRUE if file contents was successfuly fetched.
141 *
142 * Performs all temporary allocations in POOL.
143 */
144static svn_error_t *
145try_get_wc_contents(svn_boolean_t *found_p,
146                    svn_ra_serf__session_t *session,
147                    const char *sha1_checksum_prop,
148                    svn_stream_t *dst_stream,
149                    apr_pool_t *pool)
150{
151  svn_checksum_t *checksum;
152  svn_stream_t *wc_stream;
153  svn_error_t *err;
154
155  /* No contents found by default. */
156  *found_p = FALSE;
157
158  if (!session->wc_callbacks->get_wc_contents
159      || sha1_checksum_prop == NULL)
160    {
161      /* Nothing to do. */
162      return SVN_NO_ERROR;
163    }
164
165  SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
166                                 sha1_checksum_prop, pool));
167
168  err = session->wc_callbacks->get_wc_contents(
169          session->wc_callback_baton, &wc_stream, checksum, pool);
170
171  if (err)
172    {
173      svn_error_clear(err);
174
175      /* Ignore errors for now. */
176      return SVN_NO_ERROR;
177    }
178
179  if (wc_stream)
180    {
181        SVN_ERR(svn_stream_copy3(wc_stream,
182                                 svn_stream_disown(dst_stream, pool),
183                                 NULL, NULL, pool));
184      *found_p = TRUE;
185    }
186
187  return SVN_NO_ERROR;
188}
189
190/* -----------------------------------------------------------------------
191   svn_ra_get_file() specific */
192
193/* Implements svn_ra_serf__response_handler_t */
194static svn_error_t *
195handle_stream(serf_request_t *request,
196              serf_bucket_t *response,
197              void *handler_baton,
198              apr_pool_t *pool)
199{
200  stream_ctx_t *fetch_ctx = handler_baton;
201  apr_status_t status;
202
203  if (fetch_ctx->handler->sline.code != 200)
204    return svn_error_trace(svn_ra_serf__unexpected_status(fetch_ctx->handler));
205
206  while (1)
207    {
208      const char *data;
209      apr_size_t len;
210
211      status = serf_bucket_read(response, 8000, &data, &len);
212      if (SERF_BUCKET_READ_ERROR(status))
213        {
214          return svn_ra_serf__wrap_err(status, NULL);
215        }
216
217      fetch_ctx->read_size += len;
218
219      if (fetch_ctx->aborted_read)
220        {
221          apr_off_t skip;
222
223          /* We haven't caught up to where we were before. */
224          if (fetch_ctx->read_size < fetch_ctx->aborted_read_size)
225            {
226              /* Eek.  What did the file shrink or something? */
227              if (APR_STATUS_IS_EOF(status))
228                {
229                  SVN_ERR_MALFUNCTION();
230                }
231
232              /* Skip on to the next iteration of this loop. */
233              if (APR_STATUS_IS_EAGAIN(status))
234                {
235                  return svn_ra_serf__wrap_err(status, NULL);
236                }
237              continue;
238            }
239
240          /* Woo-hoo.  We're back. */
241          fetch_ctx->aborted_read = FALSE;
242
243          /* Increment data and len by the difference. */
244          skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size);
245          data += skip;
246          len -= (apr_size_t)skip;
247        }
248
249      if (len)
250        {
251          apr_size_t written_len;
252
253          written_len = len;
254
255          SVN_ERR(svn_stream_write(fetch_ctx->result_stream, data,
256                                   &written_len));
257        }
258
259      if (status)
260        {
261          return svn_ra_serf__wrap_err(status, NULL);
262        }
263    }
264  /* not reached */
265}
266
267/* Baton for get_file_prop_cb */
268struct file_prop_baton_t
269{
270  apr_pool_t *result_pool;
271  svn_node_kind_t kind;
272  apr_hash_t *props;
273  const char *sha1_checksum;
274};
275
276/* Implements svn_ra_serf__prop_func_t for svn_ra_serf__get_file */
277static svn_error_t *
278get_file_prop_cb(void *baton,
279                 const char *path,
280                 const char *ns,
281                 const char *name,
282                 const svn_string_t *value,
283                 apr_pool_t *scratch_pool)
284{
285  struct file_prop_baton_t *fb = baton;
286  const char *svn_name;
287
288  if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0)
289    {
290      const char *val = value->data;
291
292      if (strcmp(val, "collection") == 0)
293        fb->kind = svn_node_dir;
294      else
295        fb->kind = svn_node_file;
296
297      return SVN_NO_ERROR;
298    }
299  else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0
300           && strcmp(name, "sha1-checksum") == 0)
301    {
302      fb->sha1_checksum = apr_pstrdup(fb->result_pool, value->data);
303    }
304
305  if (!fb->props)
306    return SVN_NO_ERROR;
307
308  svn_name = svn_ra_serf__svnname_from_wirename(ns, name, fb->result_pool);
309  if (svn_name)
310    {
311      svn_hash_sets(fb->props, svn_name,
312                    svn_string_dup(value, fb->result_pool));
313    }
314  return SVN_NO_ERROR;
315}
316
317svn_error_t *
318svn_ra_serf__get_file(svn_ra_session_t *ra_session,
319                      const char *path,
320                      svn_revnum_t revision,
321                      svn_stream_t *stream,
322                      svn_revnum_t *fetched_rev,
323                      apr_hash_t **props,
324                      apr_pool_t *pool)
325{
326  svn_ra_serf__session_t *session = ra_session->priv;
327  const char *fetch_url;
328  const svn_ra_serf__dav_props_t *which_props;
329  svn_ra_serf__handler_t *propfind_handler;
330  struct file_prop_baton_t fb;
331
332  /* Fetch properties. */
333
334  fetch_url = svn_path_url_add_component2(session->session_url.path, path, pool);
335
336  /* The simple case is if we want HEAD - then a GET on the fetch_url is fine.
337   *
338   * Otherwise, we need to get the baseline version for this particular
339   * revision and then fetch that file.
340   */
341  if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
342    {
343      SVN_ERR(svn_ra_serf__get_stable_url(&fetch_url, fetched_rev,
344                                          session,
345                                          fetch_url, revision,
346                                          pool, pool));
347      revision = SVN_INVALID_REVNUM;
348    }
349  /* REVISION is always SVN_INVALID_REVNUM  */
350  SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
351
352  if (props)
353      which_props = all_props;
354  else if (stream && session->wc_callbacks->get_wc_contents)
355      which_props = type_and_checksum_props;
356  else
357      which_props = check_path_props;
358
359  fb.result_pool = pool;
360  fb.props = props ? apr_hash_make(pool) : NULL;
361  fb.kind = svn_node_unknown;
362  fb.sha1_checksum = NULL;
363
364  SVN_ERR(svn_ra_serf__create_propfind_handler(&propfind_handler, session,
365                                               fetch_url, SVN_INVALID_REVNUM,
366                                               "0", which_props,
367                                               get_file_prop_cb, &fb,
368                                               pool));
369
370  SVN_ERR(svn_ra_serf__context_run_one(propfind_handler, pool));
371
372  /* Verify that resource type is not collection. */
373  if (fb.kind != svn_node_file)
374    {
375      return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
376                              _("Can't get text contents of a directory"));
377    }
378
379  if (props)
380    *props = fb.props;
381
382  if (stream)
383    {
384      svn_boolean_t found;
385      SVN_ERR(try_get_wc_contents(&found, session, fb.sha1_checksum, stream, pool));
386
387      /* No contents found in the WC, let's fetch from server. */
388      if (!found)
389        {
390          stream_ctx_t *stream_ctx;
391          svn_ra_serf__handler_t *handler;
392
393          /* Create the fetch context. */
394          stream_ctx = apr_pcalloc(pool, sizeof(*stream_ctx));
395          stream_ctx->result_stream = stream;
396          stream_ctx->using_compression = session->using_compression;
397
398          handler = svn_ra_serf__create_handler(session, pool);
399
400          handler->method = "GET";
401          handler->path = fetch_url;
402
403          handler->custom_accept_encoding = TRUE;
404          handler->no_dav_headers = TRUE;
405
406          handler->header_delegate = headers_fetch;
407          handler->header_delegate_baton = stream_ctx;
408
409          handler->response_handler = handle_stream;
410          handler->response_baton = stream_ctx;
411
412          handler->response_error = cancel_fetch;
413          handler->response_error_baton = stream_ctx;
414
415          stream_ctx->handler = handler;
416
417          SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
418
419          if (handler->sline.code != 200)
420            return svn_error_trace(svn_ra_serf__unexpected_status(handler));
421        }
422    }
423
424  return SVN_NO_ERROR;
425}
426