shutdown callback
[oweals/gnunet.git] / src / fs / fs_download.c
index 8194ad9f59ce5fcc0bf9cb5470c119c98acf8e6d..92c4b831d5b06b58990fa9d59a167645cacf91f9 100644 (file)
  * @author Christian Grothoff
  *
  * TODO:
- * - handle recursive downloads (need directory & 
- *   fs-level download-parallelism management)
  * - location URI suppport (can wait, easy)
- * - check if blocks exist already (can wait, easy)
+ * - different priority for scheduling probe downloads?
  * - check if iblocks can be computed from existing blocks (can wait, hard)
- * - persistence (can wait)
  */
 #include "platform.h"
 #include "gnunet_constants.h"
@@ -140,21 +137,23 @@ compute_dblock_offset (uint64_t offset,
 
 
 /**
- * Fill in all of the generic fields for 
- * a download event.
+ * Fill in all of the generic fields for a download event and call the
+ * callback.
  *
  * @param pi structure to fill in
  * @param dc overall download context
  */
-static void
-make_download_status (struct GNUNET_FS_ProgressInfo *pi,
-                     struct GNUNET_FS_DownloadContext *dc)
+void
+GNUNET_FS_download_make_status_ (struct GNUNET_FS_ProgressInfo *pi,
+                                struct GNUNET_FS_DownloadContext *dc)
 {
   pi->value.download.dc = dc;
   pi->value.download.cctx
     = dc->client_info;
   pi->value.download.pctx
     = (dc->parent == NULL) ? NULL : dc->parent->client_info;
+  pi->value.download.sctx
+    = (dc->search == NULL) ? NULL : dc->search->client_info;
   pi->value.download.uri 
     = dc->uri;
   pi->value.download.filename
@@ -171,6 +170,13 @@ make_download_status (struct GNUNET_FS_ProgressInfo *pi,
     = GNUNET_TIME_calculate_eta (dc->start_time,
                                 dc->completed,
                                 dc->length);
+  pi->value.download.is_active = (dc->client == NULL) ? GNUNET_NO : GNUNET_YES;
+  if (0 == (dc->options & GNUNET_FS_DOWNLOAD_IS_PROBE))
+    dc->client_info = dc->h->upcb (dc->h->upcb_cls,
+                                  pi);
+  else
+    dc->client_info = GNUNET_FS_search_probe_progress_ (NULL,
+                                                       pi);
 }
 
 /**
@@ -269,6 +275,7 @@ schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
   char block[DBLOCK_SIZE];
   GNUNET_HashCode key;
   struct ProcessResultClosure prc;
+  struct GNUNET_DISK_FileHandle *fh;
 
 #if DEBUG_DOWNLOAD
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -297,18 +304,23 @@ schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
                                     &chk->query,
                                     sm,
                                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
-
+  fh = NULL;
   if ( (dc->old_file_size > off) &&
-       (dc->handle != NULL) &&
+       (dc->filename != NULL) )    
+    fh = GNUNET_DISK_file_open (dc->filename,
+                               GNUNET_DISK_OPEN_READ,
+                               GNUNET_DISK_PERM_NONE);    
+  if ( (fh != NULL) &&
        (off  == 
-       GNUNET_DISK_file_seek (dc->handle,
+       GNUNET_DISK_file_seek (fh,
                               off,
                               GNUNET_DISK_SEEK_SET) ) &&
        (len == 
-       GNUNET_DISK_file_read (dc->handle,
+       GNUNET_DISK_file_read (fh,
                               block,
                               len)) )
     {
+      GNUNET_CRYPTO_hash (block, len, &key);
       if (0 == memcmp (&key,
                       &chk->key,
                       sizeof (GNUNET_HashCode)))
@@ -319,15 +331,23 @@ schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
          GNUNET_HashCode query;
 
          GNUNET_CRYPTO_hash_to_aes_key (&key, &sk, &iv);
-         GNUNET_CRYPTO_aes_encrypt (block, len,
-                                    &sk,
-                                    &iv,
-                                    enc);
+         if (-1 == GNUNET_CRYPTO_aes_encrypt (block, len,
+                                              &sk,
+                                              &iv,
+                                              enc))
+           {
+             GNUNET_break (0);
+             goto do_download;
+           }
          GNUNET_CRYPTO_hash (enc, len, &query);
          if (0 == memcmp (&query,
                           &chk->query,
                           sizeof (GNUNET_HashCode)))
            {
+#if DEBUG_DOWNLOAD
+             GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                         "Matching block already present, no need for download!\n");
+#endif
              /* already got it! */
              prc.dc = dc;
              prc.data = enc;
@@ -345,9 +365,13 @@ schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
            {
              GNUNET_break_op (0);
            }
+         GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh));
          return;
        }
     }
+ do_download:
+  if (fh != NULL)
+    GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh));
   if (depth < dc->treedepth)
     {
       // FIXME: try if we could
@@ -359,12 +383,18 @@ schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
 
   if ( (dc->th == NULL) &&
        (dc->client != NULL) )
-    dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
-                                                 sizeof (struct SearchMessage),
-                                                 GNUNET_CONSTANTS_SERVICE_TIMEOUT,
-                                                 GNUNET_NO,
-                                                 &transmit_download_request,
-                                                 dc);
+    {
+#if DEBUG_DOWNLOAD
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 "Asking for transmission to FS service\n");
+#endif
+      dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
+                                                   sizeof (struct SearchMessage),
+                                                   GNUNET_CONSTANTS_SERVICE_TIMEOUT,
+                                                   GNUNET_NO,
+                                                   &transmit_download_request,
+                                                   dc);
+    }
 }
 
 
@@ -624,11 +654,10 @@ check_completed (struct GNUNET_FS_DownloadContext *dc)
       pos = pos->next;
     }
   dc->has_finished = GNUNET_YES;
+  GNUNET_FS_download_sync_ (dc);
   /* signal completion */
   pi.status = GNUNET_FS_STATUS_DOWNLOAD_COMPLETED;
-  make_download_status (&pi, dc);
-  dc->client_info = dc->h->upcb (dc->h->upcb_cls,
-                                &pi);
+  GNUNET_FS_download_make_status_ (&pi, dc);
   if (dc->parent != NULL)
     check_completed (dc->parent);  
 }
@@ -807,6 +836,24 @@ trigger_recursive_download (void *cls,
 }
 
 
+/**
+ * Free entries in the map.
+ *
+ * @param cls unused (NULL)
+ * @param key unused
+ * @param entry entry of type "struct DownloadRequest" which is freed
+ * @return GNUNET_OK
+ */
+static int
+free_entry (void *cls,
+           const GNUNET_HashCode *key,
+           void *entry)
+{
+  GNUNET_free (entry);
+  return GNUNET_OK;
+}
+
+
 /**
  * Iterator over entries in the pending requests in the 'active' map for the
  * reply that we just got.
@@ -823,6 +870,9 @@ process_result_with_request (void *cls,
 {
   struct ProcessResultClosure *prc = cls;
   struct DownloadRequest *sm = value;
+  struct DownloadRequest *ppos;
+  struct DownloadRequest *pprev;
+  struct GNUNET_DISK_FileHandle *fh;
   struct GNUNET_FS_DownloadContext *dc = prc->dc;
   struct GNUNET_CRYPTO_AesSessionKey skey;
   struct GNUNET_CRYPTO_AesInitializationVector iv;
@@ -833,13 +883,13 @@ process_result_with_request (void *cls,
   size_t app;
   int i;
   struct ContentHashKey *chk;
-  char *emsg;
 
+  fh = NULL;
   bs = GNUNET_FS_tree_calculate_block_size (GNUNET_ntohll (dc->uri->data.chk.file_length),
                                            dc->treedepth,
                                            sm->offset,
                                            sm->depth);
-    if (prc->size != bs)
+  if (prc->size != bs)
     {
 #if DEBUG_DOWNLOAD
       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -848,90 +898,107 @@ process_result_with_request (void *cls,
                  prc->size);
 #endif
       dc->emsg = GNUNET_strdup ("Internal error or bogus download URI");
-      /* signal error */
-      pi.status = GNUNET_FS_STATUS_DOWNLOAD_ERROR;
-      make_download_status (&pi, dc);
-      pi.value.download.specifics.error.message = dc->emsg;
-      dc->client_info = dc->h->upcb (dc->h->upcb_cls,
-                                    &pi);
-      /* abort all pending requests */
-      if (NULL != dc->th)
-       {
-         GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
-         dc->th = NULL;
-       }
-      GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
-      dc->client = NULL;
-      return GNUNET_NO;
+      goto signal_error;
     }
   GNUNET_assert (GNUNET_YES ==
                 GNUNET_CONTAINER_multihashmap_remove (dc->active,
                                                       &prc->query,
                                                       sm));
+  /* if this request is on the pending list, remove it! */
+  pprev = NULL;
+  ppos = dc->pending;
+  while (ppos != NULL)
+    {
+      if (ppos == sm)
+       {
+         if (pprev == NULL)
+           dc->pending = ppos->next;
+         else
+           pprev->next = ppos->next;
+         break;
+       }
+      pprev = ppos;
+      ppos = ppos->next;
+    }
   GNUNET_CRYPTO_hash_to_aes_key (&sm->chk.key, &skey, &iv);
-  GNUNET_CRYPTO_aes_decrypt (prc->data,
-                            prc->size,
-                            &skey,
-                            &iv,
-                            pt);
+  if (-1 == GNUNET_CRYPTO_aes_decrypt (prc->data,
+                                      prc->size,
+                                      &skey,
+                                      &iv,
+                                      pt))
+    {
+      GNUNET_break (0);
+      dc->emsg = GNUNET_strdup ("internal error decrypting content");
+      goto signal_error;
+    }
   off = compute_disk_offset (GNUNET_ntohll (dc->uri->data.chk.file_length),
                             sm->offset,
                             sm->depth,
                             dc->treedepth);
   /* save to disk */
   if ( ( GNUNET_YES == prc->do_store) &&
-       (NULL != dc->handle) &&
+       ( (dc->filename != NULL) ||
+        (is_recursive_download (dc)) ) &&
+       ( (sm->depth == dc->treedepth) ||
+        (0 == (dc->options & GNUNET_FS_DOWNLOAD_NO_TEMPORARIES)) ) )
+    {
+      fh = GNUNET_DISK_file_open (dc->filename != NULL 
+                                 ? dc->filename 
+                                 : dc->temp_filename, 
+                                 GNUNET_DISK_OPEN_READWRITE | 
+                                 GNUNET_DISK_OPEN_CREATE,
+                                 GNUNET_DISK_PERM_USER_READ |
+                                 GNUNET_DISK_PERM_USER_WRITE |
+                                 GNUNET_DISK_PERM_GROUP_READ |
+                                 GNUNET_DISK_PERM_OTHER_READ);
+    }
+  if ( (NULL == fh) &&
+       (GNUNET_YES == prc->do_store) &&
+       ( (dc->filename != NULL) ||
+        (is_recursive_download (dc)) ) &&
        ( (sm->depth == dc->treedepth) ||
         (0 == (dc->options & GNUNET_FS_DOWNLOAD_NO_TEMPORARIES)) ) )
     {
-      emsg = NULL;
+      GNUNET_asprintf (&dc->emsg,
+                      _("Download failed: could not open file `%s': %s\n"),
+                      dc->filename,
+                      STRERROR (errno));
+      goto signal_error;
+    }
+  if (fh != NULL)
+    {
 #if DEBUG_DOWNLOAD
       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                  "Saving decrypted block to disk at offset %llu\n",
                  (unsigned long long) off);
 #endif
       if ( (off  != 
-           GNUNET_DISK_file_seek (dc->handle,
+           GNUNET_DISK_file_seek (fh,
                                   off,
                                   GNUNET_DISK_SEEK_SET) ) )
-       GNUNET_asprintf (&emsg,
-                        _("Failed to seek to offset %llu in file `%s': %s\n"),
-                        (unsigned long long) off,
-                        dc->filename,
-                        STRERROR (errno));
-      else if (prc->size !=
-              GNUNET_DISK_file_write (dc->handle,
-                                      pt,
-                                      prc->size))
-       GNUNET_asprintf (&emsg,
-                        _("Failed to write block of %u bytes at offset %llu in file `%s': %s\n"),
-                        (unsigned int) prc->size,
-                        (unsigned long long) off,
-                        dc->filename,
-                        STRERROR (errno));
-      if (NULL != emsg)
        {
-         dc->emsg = emsg;
-         // FIXME: make persistent
-
-         /* signal error */
-         pi.status = GNUNET_FS_STATUS_DOWNLOAD_ERROR;
-         make_download_status (&pi, dc);
-         pi.value.download.specifics.error.message = emsg;
-         dc->client_info = dc->h->upcb (dc->h->upcb_cls,
-                                        &pi);
-
-         /* abort all pending requests */
-         if (NULL != dc->th)
-           {
-             GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
-             dc->th = NULL;
-           }
-         GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
-         dc->client = NULL;
-         GNUNET_free (sm);
-         return GNUNET_NO;
+         GNUNET_asprintf (&dc->emsg,
+                          _("Failed to seek to offset %llu in file `%s': %s\n"),
+                          (unsigned long long) off,
+                          dc->filename,
+                          STRERROR (errno));
+         goto signal_error;
+       }
+      if (prc->size !=
+         GNUNET_DISK_file_write (fh,
+                                 pt,
+                                 prc->size))
+       {
+         GNUNET_asprintf (&dc->emsg,
+                          _("Failed to write block of %u bytes at offset %llu in file `%s': %s\n"),
+                          (unsigned int) prc->size,
+                          (unsigned long long) off,
+                          dc->filename,
+                          STRERROR (errno));
+         goto signal_error;
        }
+      GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh));
+      fh = NULL;
     }
   if (sm->depth == dc->treedepth) 
     {
@@ -964,13 +1031,11 @@ process_result_with_request (void *cls,
            
     }
   pi.status = GNUNET_FS_STATUS_DOWNLOAD_PROGRESS;
-  make_download_status (&pi, dc);
   pi.value.download.specifics.progress.data = pt;
   pi.value.download.specifics.progress.offset = sm->offset;
   pi.value.download.specifics.progress.data_len = prc->size;
   pi.value.download.specifics.progress.depth = sm->depth;
-  dc->client_info = dc->h->upcb (dc->h->upcb_cls,
-                                &pi);
+  GNUNET_FS_download_make_status_ (&pi, dc);
   GNUNET_assert (dc->completed <= dc->length);
   if (dc->completed == dc->length)
     {
@@ -980,34 +1045,34 @@ process_result_with_request (void *cls,
                  (unsigned long long) GNUNET_ntohll (dc->uri->data.chk.file_length));
 #endif
       /* truncate file to size (since we store IBlocks at the end) */
-      if (dc->handle != NULL)
+      if (dc->filename != NULL)
        {
-         GNUNET_DISK_file_close (dc->handle);
-         dc->handle = NULL;
          if (0 != truncate (dc->filename,
                             GNUNET_ntohll (dc->uri->data.chk.file_length)))
            GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
                                      "truncate",
                                      dc->filename);
        }
-
+      if (dc->job_queue != NULL)
+       {
+         GNUNET_FS_dequeue_ (dc->job_queue);
+         dc->job_queue = NULL;
+       }
       if (is_recursive_download (dc))
        full_recursive_download (dc);
       if (dc->child_head == NULL)
        {
          /* signal completion */
          pi.status = GNUNET_FS_STATUS_DOWNLOAD_COMPLETED;
-         make_download_status (&pi, dc);
-         dc->client_info = dc->h->upcb (dc->h->upcb_cls,
-                                        &pi);
+         GNUNET_FS_download_make_status_ (&pi, dc);
          if (dc->parent != NULL)
            check_completed (dc->parent);
        }
       GNUNET_assert (sm->depth == dc->treedepth);
     }
-  // FIXME: make persistent
   if (sm->depth == dc->treedepth) 
     {
+      GNUNET_FS_download_sync_ (dc);
       GNUNET_free (sm);      
       return GNUNET_YES;
     }
@@ -1033,7 +1098,30 @@ process_result_with_request (void *cls,
                                 sm->depth + 1);
     }
   GNUNET_free (sm);
+  GNUNET_FS_download_sync_ (dc);
   return GNUNET_YES;
+
+ signal_error:
+  if (fh != NULL)
+    GNUNET_DISK_file_close (fh);
+  pi.status = GNUNET_FS_STATUS_DOWNLOAD_ERROR;
+  pi.value.download.specifics.error.message = dc->emsg;
+  GNUNET_FS_download_make_status_ (&pi, dc);
+  /* abort all pending requests */
+  if (NULL != dc->th)
+    {
+      GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
+      dc->th = NULL;
+    }
+  GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
+  GNUNET_CONTAINER_multihashmap_iterate (dc->active,
+                                        &free_entry,
+                                        NULL);
+  dc->pending = NULL;
+  dc->client = NULL;
+  GNUNET_free (sm);
+  GNUNET_FS_download_sync_ (dc);
+  return GNUNET_NO;
 }
 
 
@@ -1135,6 +1223,10 @@ transmit_download_request (void *cls,
   dc->th = NULL;
   if (NULL == buf)
     {
+#if DEBUG_DOWNLOAD
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 "Transmitting download request failed, trying to reconnect\n");
+#endif
       try_reconnect (dc);
       return 0;
     }
@@ -1153,6 +1245,10 @@ transmit_download_request (void *cls,
       memset (sm, 0, sizeof (struct SearchMessage));
       sm->header.size = htons (sizeof (struct SearchMessage));
       sm->header.type = htons (GNUNET_MESSAGE_TYPE_FS_START_SEARCH);
+      if (0 != (dc->options & GNUNET_FS_DOWNLOAD_OPTION_LOOPBACK_ONLY))
+       sm->options = htonl (1);
+      else
+       sm->options = htonl (0);      
       if (dc->pending->depth == dc->treedepth)
        sm->type = htonl (GNUNET_BLOCK_TYPE_DBLOCK);
       else
@@ -1254,6 +1350,10 @@ try_reconnect (struct GNUNET_FS_DownloadContext *dc)
   
   if (NULL != dc->client)
     {
+#if DEBUG_DOWNLOAD
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 "Moving all requests back to pending list\n");
+#endif
       if (NULL != dc->th)
        {
          GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
@@ -1265,6 +1365,10 @@ try_reconnect (struct GNUNET_FS_DownloadContext *dc)
       GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
       dc->client = NULL;
     }
+#if DEBUG_DOWNLOAD
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Will try to reconnect in 1s\n");
+#endif
   dc->task
     = GNUNET_SCHEDULER_add_delayed (dc->h->sched,
                                    GNUNET_TIME_UNIT_SECONDS,
@@ -1273,6 +1377,116 @@ try_reconnect (struct GNUNET_FS_DownloadContext *dc)
 }
 
 
+
+/**
+ * We're allowed to ask the FS service for our blocks.  Start the download.
+ *
+ * @param cls the 'struct GNUNET_FS_DownloadContext'
+ * @param client handle to use for communcation with FS (we must destroy it!)
+ */
+static void
+activate_fs_download (void *cls,
+                     struct GNUNET_CLIENT_Connection *client)
+{
+  struct GNUNET_FS_DownloadContext *dc = cls;
+  struct GNUNET_FS_ProgressInfo pi;
+
+  GNUNET_assert (NULL != client);
+  GNUNET_assert (dc->client == NULL);
+  GNUNET_assert (dc->th == NULL);
+  dc->client = client;
+  GNUNET_CLIENT_receive (client,
+                        &receive_results,
+                        dc,
+                        GNUNET_TIME_UNIT_FOREVER_REL);
+  pi.status = GNUNET_FS_STATUS_DOWNLOAD_ACTIVE;
+  GNUNET_FS_download_make_status_ (&pi, dc);
+  GNUNET_CONTAINER_multihashmap_iterate (dc->active,
+                                        &retry_entry,
+                                        dc);
+  dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
+                                               sizeof (struct SearchMessage),
+                                               GNUNET_CONSTANTS_SERVICE_TIMEOUT,
+                                               GNUNET_NO,
+                                               &transmit_download_request,
+                                               dc);    
+}
+
+
+/**
+ * We must stop to ask the FS service for our blocks.  Pause the download.
+ *
+ * @param cls the 'struct GNUNET_FS_DownloadContext'
+ */
+static void
+deactivate_fs_download (void *cls)
+{
+  struct GNUNET_FS_DownloadContext *dc = cls;
+  struct GNUNET_FS_ProgressInfo pi;
+  
+  if (NULL != dc->th)
+    {
+      GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
+      dc->th = NULL;
+    }
+  if (NULL != dc->client)
+    {
+      GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
+      dc->client = NULL;
+    }
+  pi.status = GNUNET_FS_STATUS_DOWNLOAD_INACTIVE;
+  GNUNET_FS_download_make_status_ (&pi, dc);
+}
+
+
+/**
+ * Create SUSPEND event for the given download operation
+ * and then clean up our state (without stop signal).
+ *
+ * @param cls the 'struct GNUNET_FS_DownloadContext' to signal for
+ */
+void
+GNUNET_FS_download_signal_suspend_ (void *cls)
+{
+  struct GNUNET_FS_DownloadContext *dc = cls;
+  struct GNUNET_FS_ProgressInfo pi;
+  
+  if (dc->top != NULL)
+    GNUNET_FS_end_top (dc->h, dc->top);
+  while (NULL != dc->child_head)
+    GNUNET_FS_download_signal_suspend_ (dc->child_head);  
+  if (dc->search != NULL)
+    {
+      dc->search->download = NULL;
+      dc->search = NULL;
+    }
+  if (dc->job_queue != NULL)
+    {
+      GNUNET_FS_dequeue_ (dc->job_queue);
+      dc->job_queue = NULL;
+    }
+  if (dc->parent != NULL)
+    GNUNET_CONTAINER_DLL_remove (dc->parent->child_head,
+                                dc->parent->child_tail,
+                                dc);  
+  pi.status = GNUNET_FS_STATUS_DOWNLOAD_SUSPEND;
+  GNUNET_FS_download_make_status_ (&pi, dc);
+  if (GNUNET_SCHEDULER_NO_TASK != dc->task)
+    GNUNET_SCHEDULER_cancel (dc->h->sched,
+                            dc->task);
+  GNUNET_CONTAINER_multihashmap_iterate (dc->active,
+                                        &free_entry,
+                                        NULL);
+  GNUNET_CONTAINER_multihashmap_destroy (dc->active);
+  GNUNET_free_non_null (dc->filename);
+  GNUNET_CONTAINER_meta_data_destroy (dc->meta);
+  GNUNET_FS_uri_destroy (dc->uri);
+  GNUNET_free_non_null (dc->temp_filename);
+  GNUNET_free_non_null (dc->serialization);
+  GNUNET_free (dc);
+}
+
+
 /**
  * Download parts of a file.  Note that this will store
  * the blocks at the respective offset in the given file.  Also, the
@@ -1318,7 +1532,6 @@ GNUNET_FS_download_start (struct GNUNET_FS_Handle *h,
 {
   struct GNUNET_FS_ProgressInfo pi;
   struct GNUNET_FS_DownloadContext *dc;
-  struct GNUNET_CLIENT_Connection *client;
 
   GNUNET_assert (GNUNET_FS_uri_test_chk (uri));
   if ( (offset + length < offset) ||
@@ -1327,7 +1540,6 @@ GNUNET_FS_download_start (struct GNUNET_FS_Handle *h,
       GNUNET_break (0);
       return NULL;
     }
-  // FIXME: add support for "loc" URIs!
 #if DEBUG_DOWNLOAD
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Starting download `%s' of %llu bytes\n",
@@ -1354,27 +1566,11 @@ GNUNET_FS_download_start (struct GNUNET_FS_Handle *h,
        GNUNET_DISK_file_size (filename,
                               &dc->old_file_size,
                               GNUNET_YES);
-      dc->handle = GNUNET_DISK_file_open (filename, 
-                                         GNUNET_DISK_OPEN_READWRITE | 
-                                         GNUNET_DISK_OPEN_CREATE,
-                                         GNUNET_DISK_PERM_USER_READ |
-                                         GNUNET_DISK_PERM_USER_WRITE |
-                                         GNUNET_DISK_PERM_GROUP_READ |
-                                         GNUNET_DISK_PERM_OTHER_READ);
-      if (dc->handle == NULL)
-       {
-         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                     _("Download failed: could not open file `%s': %s\n"),
-                     dc->filename,
-                     STRERROR (errno));
-         GNUNET_CONTAINER_meta_data_destroy (dc->meta);
-         GNUNET_FS_uri_destroy (dc->uri);
-         GNUNET_free (dc->filename);
-         GNUNET_free (dc);
-         return NULL;
-       }
     }
-  // FIXME: set "dc->target" for LOC uris!
+  if (GNUNET_FS_uri_test_loc (dc->uri))
+    GNUNET_assert (GNUNET_OK ==
+                  GNUNET_FS_uri_loc_get_peer_identity (dc->uri,
+                                                       &dc->target));
   dc->offset = offset;
   dc->length = length;
   dc->anonymity = anonymity;
@@ -1395,46 +1591,164 @@ GNUNET_FS_download_start (struct GNUNET_FS_Handle *h,
              "Download tree has depth %u\n",
              dc->treedepth);
 #endif
-  // FIXME: make persistent
-  
-  // FIXME: bound parallelism here!
-  client = GNUNET_CLIENT_connect (h->sched,
-                                 "fs",
-                                 h->cfg);
-  dc->client = client;
+  if (parent == NULL)
+    {
+      dc->top = GNUNET_FS_make_top (dc->h,
+                                   &GNUNET_FS_download_signal_suspend_,
+                                   dc);
+    }
+  pi.status = GNUNET_FS_STATUS_DOWNLOAD_START;
+  pi.value.download.specifics.start.meta = meta;
+  GNUNET_FS_download_make_status_ (&pi, dc);
   schedule_block_download (dc, 
                           &dc->uri->data.chk.chk,
                           0, 
-                          1 /* 0 == CHK, 1 == top */);
-  GNUNET_CLIENT_receive (client,
-                        &receive_results,
-                        dc,
-                        GNUNET_TIME_UNIT_FOREVER_REL);
-  pi.status = GNUNET_FS_STATUS_DOWNLOAD_START;
-  make_download_status (&pi, dc);
-  pi.value.download.specifics.start.meta = meta;
-  dc->client_info = dc->h->upcb (dc->h->upcb_cls,
-                                &pi);
-
+                          1 /* 0 == CHK, 1 == top */); 
+  GNUNET_FS_download_sync_ (dc);
+  GNUNET_FS_download_start_downloading_ (dc);
   return dc;
 }
 
 
 /**
- * Free entries in the map.
+ * Download parts of a file based on a search result.  The download
+ * will be associated with the search result (and the association
+ * will be preserved when serializing/deserializing the state).
+ * If the search is stopped, the download will not be aborted but
+ * be 'promoted' to a stand-alone download.
  *
- * @param cls unused (NULL)
- * @param key unused
- * @param entry entry of type "struct DownloadRequest" which is freed
- * @return GNUNET_OK
+ * As with the other download function, this will store
+ * the blocks at the respective offset in the given file.  Also, the
+ * download is still using the blocking of the underlying FS
+ * encoding.  As a result, the download may *write* outside of the
+ * given boundaries (if offset and length do not match the 32k FS
+ * block boundaries). <p>
+ *
+ * The given range can be used to focus a download towards a
+ * particular portion of the file (optimization), not to strictly
+ * limit the download to exactly those bytes.
+ *
+ * @param h handle to the file sharing subsystem
+ * @param sr the search result to use for the download (determines uri and
+ *        meta data and associations)
+ * @param filename where to store the file, maybe NULL (then no file is
+ *        created on disk and data must be grabbed from the callbacks)
+ * @param tempname where to store temporary file data, not used if filename is non-NULL;
+ *        can be NULL (in which case we will pick a name if needed); the temporary file
+ *        may already exist, in which case we will try to use the data that is there and
+ *        if it is not what is desired, will overwrite it
+ * @param offset at what offset should we start the download (typically 0)
+ * @param length how many bytes should be downloaded starting at offset
+ * @param anonymity anonymity level to use for the download
+ * @param options various download options
+ * @param cctx initial value for the client context for this download
+ * @return context that can be used to control this download
  */
-static int
-free_entry (void *cls,
-           const GNUNET_HashCode *key,
-           void *entry)
+struct GNUNET_FS_DownloadContext *
+GNUNET_FS_download_start_from_search (struct GNUNET_FS_Handle *h,
+                                     struct GNUNET_FS_SearchResult *sr,
+                                     const char *filename,
+                                     const char *tempname,
+                                     uint64_t offset,
+                                     uint64_t length,
+                                     uint32_t anonymity,
+                                     enum GNUNET_FS_DownloadOptions options,
+                                     void *cctx)
 {
-  GNUNET_free (entry);
-  return GNUNET_OK;
+  struct GNUNET_FS_ProgressInfo pi;
+  struct GNUNET_FS_DownloadContext *dc;
+
+  if ( (sr == NULL) ||
+       (sr->download != NULL) )
+    {
+      GNUNET_break (0);
+      return NULL;
+    }
+  GNUNET_assert (GNUNET_FS_uri_test_chk (sr->uri));
+  if ( (offset + length < offset) ||
+       (offset + length > sr->uri->data.chk.file_length) )
+    {      
+      GNUNET_break (0);
+      return NULL;
+    }
+#if DEBUG_DOWNLOAD
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Starting download `%s' of %llu bytes\n",
+             filename,
+             (unsigned long long) length);
+#endif
+  dc = GNUNET_malloc (sizeof(struct GNUNET_FS_DownloadContext));
+  dc->h = h;
+  dc->search = sr;
+  sr->download = dc;
+  if (sr->probe_ctx != NULL)
+    {
+      GNUNET_FS_download_stop (sr->probe_ctx, GNUNET_YES);
+      sr->probe_ctx = NULL;      
+    }
+  dc->uri = GNUNET_FS_uri_dup (sr->uri);
+  dc->meta = GNUNET_CONTAINER_meta_data_duplicate (sr->meta);
+  dc->client_info = cctx;
+  dc->start_time = GNUNET_TIME_absolute_get ();
+  if (NULL != filename)
+    {
+      dc->filename = GNUNET_strdup (filename);
+      if (GNUNET_YES == GNUNET_DISK_file_test (filename))
+       GNUNET_DISK_file_size (filename,
+                              &dc->old_file_size,
+                              GNUNET_YES);
+    }
+  if (GNUNET_FS_uri_test_loc (dc->uri))
+    GNUNET_assert (GNUNET_OK ==
+                  GNUNET_FS_uri_loc_get_peer_identity (dc->uri,
+                                                       &dc->target));
+  dc->offset = offset;
+  dc->length = length;
+  dc->anonymity = anonymity;
+  dc->options = options;
+  dc->active = GNUNET_CONTAINER_multihashmap_create (1 + 2 * (length / DBLOCK_SIZE));
+  dc->treedepth = GNUNET_FS_compute_depth (GNUNET_ntohll(dc->uri->data.chk.file_length));
+  if ( (filename == NULL) &&
+       (is_recursive_download (dc) ) )
+    {
+      if (tempname != NULL)
+       dc->temp_filename = GNUNET_strdup (tempname);
+      else
+       dc->temp_filename = GNUNET_DISK_mktemp ("gnunet-directory-download-tmp");    
+    }
+
+#if DEBUG_DOWNLOAD
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Download tree has depth %u\n",
+             dc->treedepth);
+#endif
+  pi.status = GNUNET_FS_STATUS_DOWNLOAD_START;
+  pi.value.download.specifics.start.meta = dc->meta;
+  GNUNET_FS_download_make_status_ (&pi, dc);
+  schedule_block_download (dc, 
+                          &dc->uri->data.chk.chk,
+                          0, 
+                          1 /* 0 == CHK, 1 == top */); 
+  GNUNET_FS_download_sync_ (dc);
+  GNUNET_FS_download_start_downloading_ (dc);
+  return dc;  
+}
+
+
+/**
+ * Start the downloading process (by entering the queue).
+ *
+ * @param dc our download context
+ */
+void
+GNUNET_FS_download_start_downloading_ (struct GNUNET_FS_DownloadContext *dc)
+{
+  GNUNET_assert (dc->job_queue == NULL);
+  dc->job_queue = GNUNET_FS_queue_ (dc->h, 
+                                   &activate_fs_download,
+                                   &deactivate_fs_download,
+                                   dc,
+                                   (dc->length + DBLOCK_SIZE-1) / DBLOCK_SIZE);
 }
 
 
@@ -1449,39 +1763,52 @@ GNUNET_FS_download_stop (struct GNUNET_FS_DownloadContext *dc,
                         int do_delete)
 {
   struct GNUNET_FS_ProgressInfo pi;
+  int have_children;
 
+  if (dc->top != NULL)
+    GNUNET_FS_end_top (dc->h, dc->top);
+  if (dc->search != NULL)
+    {
+      dc->search->download = NULL;
+      dc->search = NULL;
+    }
+  if (dc->job_queue != NULL)
+    {
+      GNUNET_FS_dequeue_ (dc->job_queue);
+      dc->job_queue = NULL;
+    }
+  have_children = (NULL != dc->child_head) ? GNUNET_YES : GNUNET_NO;
   while (NULL != dc->child_head)
     GNUNET_FS_download_stop (dc->child_head, 
                             do_delete);
-  // FIXME: make unpersistent  
   if (dc->parent != NULL)
     GNUNET_CONTAINER_DLL_remove (dc->parent->child_head,
                                 dc->parent->child_tail,
-                                dc);
-  
+                                dc);  
+  if (dc->serialization != NULL)
+    GNUNET_FS_remove_sync_file_ (dc->h,
+                                ( (dc->parent != NULL)  || (dc->search != NULL) )
+                                ? GNUNET_FS_SYNC_PATH_CHILD_DOWNLOAD 
+                                : GNUNET_FS_SYNC_PATH_MASTER_DOWNLOAD , 
+                                dc->serialization);
+  if ( (GNUNET_YES == have_children) &&
+       (dc->parent == NULL) )
+    GNUNET_FS_remove_sync_dir_ (dc->h, 
+                               (dc->search != NULL) 
+                               ? GNUNET_FS_SYNC_PATH_CHILD_DOWNLOAD 
+                               : GNUNET_FS_SYNC_PATH_MASTER_DOWNLOAD,
+                               dc->serialization);  
   pi.status = GNUNET_FS_STATUS_DOWNLOAD_STOPPED;
-  make_download_status (&pi, dc);
-  dc->client_info = dc->h->upcb (dc->h->upcb_cls,
-                                &pi);
-
+  GNUNET_FS_download_make_status_ (&pi, dc);
   if (GNUNET_SCHEDULER_NO_TASK != dc->task)
     GNUNET_SCHEDULER_cancel (dc->h->sched,
                             dc->task);
-  if (NULL != dc->th)
-    {
-      GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
-      dc->th = NULL;
-    }
-  if (NULL != dc->client)
-    GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
   GNUNET_CONTAINER_multihashmap_iterate (dc->active,
                                         &free_entry,
                                         NULL);
   GNUNET_CONTAINER_multihashmap_destroy (dc->active);
   if (dc->filename != NULL)
     {
-      if (NULL != dc->handle)
-       GNUNET_DISK_file_close (dc->handle);
       if ( (dc->completed != dc->length) &&
           (GNUNET_YES == do_delete) )
        {
@@ -1502,6 +1829,7 @@ GNUNET_FS_download_stop (struct GNUNET_FS_DownloadContext *dc,
                                  dc->temp_filename);
       GNUNET_free (dc->temp_filename);
     }
+  GNUNET_free_non_null (dc->serialization);
   GNUNET_free (dc);
 }