work on downloading
[oweals/gnunet.git] / src / fs / fs_download.c
1 /*
2      This file is part of GNUnet.
3      (C) 2001, 2002, 2003, 2004, 2005, 2006, 2008, 2009 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 2, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20 /**
21  * @file fs/fs_download.c
22  * @brief download methods
23  * @author Christian Grothoff
24  *
25  * TODO:
26  * - location URI suppport (can wait, easy)
27  * - check if blocks exist already (can wait, easy)
28  * - handle recursive downloads (need directory & 
29  *   fs-level download-parallelism management, can wait)
30  * - check if iblocks can be computed from existing blocks (can wait, hard)
31  * - persistence (can wait)
32  */
33 #include "platform.h"
34 #include "gnunet_constants.h"
35 #include "gnunet_fs_service.h"
36 #include "fs.h"
37 #include "fs_tree.h"
38
39 #define DEBUG_DOWNLOAD GNUNET_YES
40
41 /**
42  * We're storing the IBLOCKS after the
43  * DBLOCKS on disk (so that we only have
44  * to truncate the file once we're done).
45  *
46  * Given the offset of a block (with respect
47  * to the DBLOCKS) and its depth, return the
48  * offset where we would store this block
49  * in the file.
50
51  * 
52  * @param fsize overall file size
53  * @param off offset of the block in the file
54  * @param depth depth of the block in the tree
55  * @param treedepth maximum depth of the tree
56  * @return off for DBLOCKS (depth == treedepth),
57  *         otherwise an offset past the end
58  *         of the file that does not overlap
59  *         with the range for any other block
60  */
61 static uint64_t
62 compute_disk_offset (uint64_t fsize,
63                      uint64_t off,
64                      unsigned int depth,
65                      unsigned int treedepth)
66 {
67   unsigned int i;
68   uint64_t lsize; /* what is the size of all IBlocks for level "i"? */
69   uint64_t loff; /* where do IBlocks for level "i" start? */
70   unsigned int ioff; /* which IBlock corresponds to "off" at level "i"? */
71   
72   if (depth == treedepth)
73     return off;
74   /* first IBlocks start at the end of file, rounded up
75      to full DBLOCK_SIZE */
76   loff = ((fsize + DBLOCK_SIZE - 1) / DBLOCK_SIZE) * DBLOCK_SIZE;
77   lsize = ( (fsize + DBLOCK_SIZE-1) / DBLOCK_SIZE) * sizeof (struct ContentHashKey);
78   GNUNET_assert (0 == (off % DBLOCK_SIZE));
79   ioff = (off / DBLOCK_SIZE);
80   for (i=treedepth-1;i>depth;i--)
81     {
82       loff += lsize;
83       lsize = (lsize + CHK_PER_INODE - 1) / CHK_PER_INODE;
84       GNUNET_assert (lsize > 0);
85       GNUNET_assert (0 == (ioff % CHK_PER_INODE));
86       ioff /= CHK_PER_INODE;
87     }
88   return loff + ioff * sizeof (struct ContentHashKey);
89 }
90
91
92 /**
93  * Given a file of the specified treedepth and a block at the given
94  * offset and depth, calculate the offset for the CHK at the given
95  * index.
96  *
97  * @param offset the offset of the first
98  *        DBLOCK in the subtree of the 
99  *        identified IBLOCK
100  * @param depth the depth of the IBLOCK in the tree
101  * @param treedepth overall depth of the tree
102  * @param k which CHK in the IBLOCK are we 
103  *        talking about
104  * @return offset if k=0, otherwise an appropriately
105  *         larger value (i.e., if depth = treedepth-1,
106  *         the returned value should be offset+DBLOCK_SIZE)
107  */
108 static uint64_t
109 compute_dblock_offset (uint64_t offset,
110                        unsigned int depth,
111                        unsigned int treedepth,
112                        unsigned int k)
113 {
114   unsigned int i;
115   uint64_t lsize; /* what is the size of the sum of all DBlocks 
116                      that a CHK at level i corresponds to? */
117
118   if (depth == treedepth)
119     return offset;
120   lsize = DBLOCK_SIZE;
121   for (i=treedepth-1;i>depth;i--)
122     lsize *= CHK_PER_INODE;
123   return offset + i * lsize;
124 }
125
126
127 /**
128  * Fill in all of the generic fields for 
129  * a download event.
130  *
131  * @param pc structure to fill in
132  * @param dc overall download context
133  */
134 static void
135 make_download_status (struct GNUNET_FS_ProgressInfo *pi,
136                       struct GNUNET_FS_DownloadContext *dc)
137 {
138   pi->value.download.dc = dc;
139   pi->value.download.cctx
140     = dc->client_info;
141   pi->value.download.pctx
142     = (dc->parent == NULL) ? NULL : dc->parent->client_info;
143   pi->value.download.uri 
144     = dc->uri;
145   pi->value.download.length
146     = dc->length;
147   pi->value.download.duration
148     = GNUNET_TIME_absolute_get_duration (dc->start_time);
149   pi->value.download.completed
150     = dc->completed;
151   pi->value.download.anonymity
152     = dc->anonymity;
153 }
154
155
156 /**
157  * Schedule the download of the specified
158  * block in the tree.
159  *
160  * @param dc overall download this block belongs to
161  * @param chk content-hash-key of the block
162  * @param offset offset of the block in the file
163  *         (for IBlocks, the offset is the lowest
164  *          offset of any DBlock in the subtree under
165  *          the IBlock)
166  * @param depth depth of the block, 0 is the root of the tree
167  */
168 static void
169 schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
170                          const struct ContentHashKey *chk,
171                          uint64_t offset,
172                          unsigned int depth)
173 {
174   struct DownloadRequest *sm;
175   uint64_t off;
176
177   off = compute_disk_offset (GNUNET_ntohll (dc->uri->data.chk.file_length),
178                              offset,
179                              depth,
180                              dc->treedepth);
181   if ( (dc->old_file_size > off) &&
182        (dc->handle != NULL) &&
183        (off  == 
184         GNUNET_DISK_file_seek (dc->handle,
185                                off,
186                                GNUNET_DISK_SEEK_SET) ) )
187     {
188       // FIXME: check if block exists on disk!
189       // (read block, encode, compare with
190       // query; if matches, simply return)
191     }
192   if (depth < dc->treedepth)
193     {
194       // FIXME: try if we could
195       // reconstitute this IBLOCK
196       // from the existing blocks on disk (can wait)
197       // (read block(s), encode, compare with
198       // query; if matches, simply return)
199     }
200   sm = GNUNET_malloc (sizeof (struct DownloadRequest));
201   sm->chk = *chk;
202   sm->offset = offset;
203   sm->depth = depth;
204   sm->is_pending = GNUNET_YES;
205   sm->next = dc->pending;
206   dc->pending = sm;
207   GNUNET_CONTAINER_multihashmap_put (dc->active,
208                                      &chk->query,
209                                      sm,
210                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
211 }
212
213
214 /**
215  * We've lost our connection with the FS service.
216  * Re-establish it and re-transmit all of our
217  * pending requests.
218  *
219  * @param dc download context that is having trouble
220  */
221 static void
222 try_reconnect (struct GNUNET_FS_DownloadContext *dc);
223
224
225 /**
226  * Compute how many bytes of data should be stored in
227  * the specified node.
228  *
229  * @param fsize overall file size
230  * @param off offset of the node
231  * @param depth depth of the node
232  * @return number of bytes stored in this node
233  */
234 static size_t
235 calculate_block_size (uint64_t fsize,
236                       unsigned int totaldepth,
237                       uint64_t offset,
238                       unsigned int depth)
239 {
240   unsigned int i;
241   size_t ret;
242   uint64_t rsize;
243   uint64_t epos;
244   unsigned int chks;
245
246   GNUNET_assert (offset < fsize);
247   if (depth == totaldepth)
248     {
249       ret = DBLOCK_SIZE;
250       if (offset + ret > fsize)
251         ret = (size_t) (fsize - offset);
252       return ret;
253     }
254
255   rsize = DBLOCK_SIZE;
256   for (i = totaldepth-1; i > depth; i--)
257     rsize *= CHK_PER_INODE;
258   epos = offset + rsize * CHK_PER_INODE;
259   GNUNET_assert (epos > offset);
260   if (epos > fsize)
261     epos = fsize;
262   /* round up when computing #CHKs in our IBlock */
263   chks = (epos - offset + rsize - 1) / rsize;
264   GNUNET_assert (chks <= CHK_PER_INODE);
265   return chks * sizeof (struct ContentHashKey);
266 }
267
268
269 /**
270  * Process a search result.
271  *
272  * @param sc our search context
273  * @param type type of the result
274  * @param data the (encrypted) response
275  * @param size size of data
276  */
277 static void
278 process_result (struct GNUNET_FS_DownloadContext *dc,
279                 uint32_t type,
280                 const void *data,
281                 size_t size)
282 {
283   struct GNUNET_FS_ProgressInfo pi;
284   GNUNET_HashCode query;
285   struct DownloadRequest *sm;
286   struct GNUNET_CRYPTO_AesSessionKey skey;
287   struct GNUNET_CRYPTO_AesInitializationVector iv;
288   char pt[size];
289   uint64_t off;
290   size_t app;
291   unsigned int i;
292   struct ContentHashKey *chk;
293   char *emsg;
294
295   GNUNET_CRYPTO_hash (data, size, &query);
296   sm = GNUNET_CONTAINER_multihashmap_get (dc->active,
297                                           &query);
298   if (NULL == sm)
299     {
300       GNUNET_break (0);
301       return;
302     }
303   if (size != calculate_block_size (GNUNET_ntohll (dc->uri->data.chk.file_length),
304                                     dc->treedepth,
305                                     sm->offset,
306                                     sm->depth))
307     {
308       dc->emsg = GNUNET_strdup ("Internal error or bogus download URI");
309       /* signal error */
310       pi.status = GNUNET_FS_STATUS_DOWNLOAD_ERROR;
311       make_download_status (&pi, dc);
312       pi.value.download.specifics.error.message = dc->emsg;
313       dc->client_info = dc->h->upcb (dc->h->upcb_cls,
314                                      &pi);
315       /* abort all pending requests */
316       GNUNET_CLIENT_disconnect (dc->client);
317       dc->client = NULL;
318       return;
319     }
320   GNUNET_assert (GNUNET_YES ==
321                  GNUNET_CONTAINER_multihashmap_remove (dc->active,
322                                                        &query,
323                                                        sm));
324   GNUNET_CRYPTO_hash_to_aes_key (&sm->chk.key, &skey, &iv);
325   GNUNET_CRYPTO_aes_decrypt (data,
326                              size,
327                              &skey,
328                              &iv,
329                              pt);
330   /* save to disk */
331   if ( (NULL != dc->handle) &&
332        ( (sm->depth == dc->treedepth) ||
333          (0 == (dc->options & GNUNET_FS_DOWNLOAD_NO_TEMPORARIES)) ) )
334     {
335       off = compute_disk_offset (GNUNET_ntohll (dc->uri->data.chk.file_length),
336                                  sm->offset,
337                                  sm->depth,
338                                  dc->treedepth);
339       emsg = NULL;
340       if ( (off  != 
341             GNUNET_DISK_file_seek (dc->handle,
342                                    off,
343                                    GNUNET_DISK_SEEK_SET) ) )
344         GNUNET_asprintf (&emsg,
345                          _("Failed to seek to offset %llu in file `%s': %s\n"),
346                          (unsigned long long) off,
347                          dc->filename,
348                          STRERROR (errno));
349       else if (size !=
350                GNUNET_DISK_file_write (dc->handle,
351                                        pt,
352                                        size))
353         GNUNET_asprintf (&emsg,
354                          _("Failed to write block of %u bytes at offset %llu in file `%s': %s\n"),
355                          (unsigned int) size,
356                          (unsigned long long) off,
357                          dc->filename,
358                          STRERROR (errno));
359       if (NULL != emsg)
360         {
361           dc->emsg = emsg;
362           // FIXME: make persistent
363
364           /* signal error */
365           pi.status = GNUNET_FS_STATUS_DOWNLOAD_ERROR;
366           make_download_status (&pi, dc);
367           pi.value.download.specifics.error.message = emsg;
368           dc->client_info = dc->h->upcb (dc->h->upcb_cls,
369                                          &pi);
370
371           /* abort all pending requests */
372           GNUNET_CLIENT_disconnect (dc->client);
373           dc->client = NULL;
374           return;
375         }
376     }
377   if (sm->depth == dc->treedepth) 
378     {
379       app = size;
380       if (sm->offset < dc->offset)
381         {
382           /* starting offset begins in the middle of pt,
383              do not count first bytes as progress */
384           GNUNET_assert (app > (dc->offset - sm->offset));
385           app -= (dc->offset - sm->offset);       
386         }
387       if (sm->offset + size > dc->offset + dc->length)
388         {
389           /* end of block is after relevant range,
390              do not count last bytes as progress */
391           GNUNET_assert (app > (sm->offset + size) - (dc->offset + dc->length));
392           app -= (sm->offset + size) - (dc->offset + dc->length);
393         }
394       dc->completed += app;
395     }
396
397   pi.status = GNUNET_FS_STATUS_DOWNLOAD_PROGRESS;
398   make_download_status (&pi, dc);
399   pi.value.download.specifics.progress.data = pt;
400   pi.value.download.specifics.progress.offset = sm->offset;
401   pi.value.download.specifics.progress.data_len = size;
402   pi.value.download.specifics.progress.depth = sm->depth;
403   dc->client_info = dc->h->upcb (dc->h->upcb_cls,
404                                  &pi);
405   GNUNET_assert (dc->completed <= dc->length);
406   if (dc->completed == dc->length)
407     {
408       /* truncate file to size (since we store IBlocks at the end) */
409       if (dc->handle != NULL)
410         {
411           GNUNET_DISK_file_close (dc->handle);
412           dc->handle = NULL;
413           if (0 != truncate (dc->filename,
414                              GNUNET_ntohll (dc->uri->data.chk.file_length)))
415             GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
416                                       "truncate",
417                                       dc->filename);
418         }
419       /* signal completion */
420       pi.status = GNUNET_FS_STATUS_DOWNLOAD_COMPLETED;
421       make_download_status (&pi, dc);
422       dc->client_info = dc->h->upcb (dc->h->upcb_cls,
423                                      &pi);
424       GNUNET_assert (sm->depth == dc->treedepth);
425     }
426   // FIXME: make persistent
427   if (sm->depth == dc->treedepth) 
428     return;
429   GNUNET_assert (0 == (size % sizeof(struct ContentHashKey)));
430   chk = (struct ContentHashKey*) pt;
431   for (i=0;i<(size / sizeof(struct ContentHashKey));i++)
432     {
433       off = compute_dblock_offset (sm->offset,
434                                    sm->depth,
435                                    dc->treedepth,
436                                    i);
437       if ( (off + DBLOCK_SIZE >= dc->offset) &&
438            (off < dc->offset + dc->length) ) 
439         schedule_block_download (dc,
440                                  &chk[i],
441                                  off,
442                                  sm->depth + 1);
443     }
444 }
445
446
447 /**
448  * Type of a function to call when we receive a message
449  * from the service.
450  *
451  * @param cls closure
452  * @param msg message received, NULL on timeout or fatal error
453  */
454 static void 
455 receive_results (void *cls,
456                  const struct GNUNET_MessageHeader * msg)
457 {
458   struct GNUNET_FS_DownloadContext *dc = cls;
459   const struct ContentMessage *cm;
460   uint16_t msize;
461
462   if ( (NULL == msg) ||
463        (ntohs (msg->type) != GNUNET_MESSAGE_TYPE_FS_CONTENT) ||
464        (ntohs (msg->size) <= sizeof (struct ContentMessage)) )
465     {
466       try_reconnect (dc);
467       return;
468     }
469   msize = ntohs (msg->size);
470   cm = (const struct ContentMessage*) msg;
471   process_result (dc, 
472                   ntohl (cm->type),
473                   &cm[1],
474                   msize - sizeof (struct ContentMessage));
475   /* continue receiving */
476   GNUNET_CLIENT_receive (dc->client,
477                          &receive_results,
478                          dc,
479                          GNUNET_TIME_UNIT_FOREVER_REL);
480 }
481
482
483
484 /**
485  * We're ready to transmit a search request to the
486  * file-sharing service.  Do it.  If there is 
487  * more than one request pending, try to send 
488  * multiple or request another transmission.
489  *
490  * @param cls closure
491  * @param size number of bytes available in buf
492  * @param buf where the callee should write the message
493  * @return number of bytes written to buf
494  */
495 static size_t
496 transmit_download_request (void *cls,
497                            size_t size, 
498                            void *buf)
499 {
500   struct GNUNET_FS_DownloadContext *dc = cls;
501   size_t msize;
502   struct SearchMessage *sm;
503
504   if (NULL == buf)
505     {
506       try_reconnect (dc);
507       return 0;
508     }
509   GNUNET_assert (size >= sizeof (struct SearchMessage));
510   msize = 0;
511   sm = buf;
512   while ( (dc->pending == NULL) &&
513           (size > msize + sizeof (struct SearchMessage)) )
514     {
515       memset (sm, 0, sizeof (struct SearchMessage));
516       sm->header.size = htons (sizeof (struct SearchMessage));
517       sm->header.type = htons (GNUNET_MESSAGE_TYPE_FS_START_SEARCH);
518       sm->anonymity_level = htonl (dc->anonymity);
519       sm->target = dc->target.hashPubKey;
520       sm->query = dc->pending->chk.query;
521       dc->pending->is_pending = GNUNET_NO;
522       dc->pending = dc->pending->next;
523       msize += sizeof (struct SearchMessage);
524       sm++;
525     }
526   return msize;
527 }
528
529
530 /**
531  * Reconnect to the FS service and transmit
532  * our queries NOW.
533  *
534  * @param cls our download context
535  * @param tc unused
536  */
537 static void
538 do_reconnect (void *cls,
539               const struct GNUNET_SCHEDULER_TaskContext *tc)
540 {
541   struct GNUNET_FS_DownloadContext *dc = cls;
542   struct GNUNET_CLIENT_Connection *client;
543   
544   dc->task = GNUNET_SCHEDULER_NO_TASK;
545   client = GNUNET_CLIENT_connect (dc->h->sched,
546                                   "fs",
547                                   dc->h->cfg);
548   if (NULL == client)
549     {
550       try_reconnect (dc);
551       return;
552     }
553   dc->client = client;
554   GNUNET_CLIENT_notify_transmit_ready (client,
555                                        sizeof (struct SearchMessage),
556                                        GNUNET_CONSTANTS_SERVICE_TIMEOUT,
557                                        &transmit_download_request,
558                                        dc);  
559   GNUNET_CLIENT_receive (client,
560                          &receive_results,
561                          dc,
562                          GNUNET_TIME_UNIT_FOREVER_REL);
563 }
564
565
566 /**
567  * Add entries that are not yet pending back to
568  * the pending list.
569  *
570  * @param cls our download context
571  * @param key unused
572  * @param entry entry of type "struct DownloadRequest"
573  * @return GNUNET_OK
574  */
575 static int
576 retry_entry (void *cls,
577              const GNUNET_HashCode *key,
578              void *entry)
579 {
580   struct GNUNET_FS_DownloadContext *dc = cls;
581   struct DownloadRequest *dr = entry;
582
583   if (! dr->is_pending)
584     {
585       dr->next = dc->pending;
586       dr->is_pending = GNUNET_YES;
587       dc->pending = entry;
588     }
589   return GNUNET_OK;
590 }
591
592
593 /**
594  * We've lost our connection with the FS service.
595  * Re-establish it and re-transmit all of our
596  * pending requests.
597  *
598  * @param dc download context that is having trouble
599  */
600 static void
601 try_reconnect (struct GNUNET_FS_DownloadContext *dc)
602 {
603   
604   if (NULL != dc->client)
605     {
606       GNUNET_CONTAINER_multihashmap_iterate (dc->active,
607                                              &retry_entry,
608                                              dc);
609       GNUNET_CLIENT_disconnect (dc->client);
610       dc->client = NULL;
611     }
612   dc->task
613     = GNUNET_SCHEDULER_add_delayed (dc->h->sched,
614                                     GNUNET_NO,
615                                     GNUNET_SCHEDULER_PRIORITY_IDLE,
616                                     GNUNET_SCHEDULER_NO_TASK,
617                                     GNUNET_TIME_UNIT_SECONDS,
618                                     &do_reconnect,
619                                     dc);
620 }
621
622
623 /**
624  * Download parts of a file.  Note that this will store
625  * the blocks at the respective offset in the given file.  Also, the
626  * download is still using the blocking of the underlying FS
627  * encoding.  As a result, the download may *write* outside of the
628  * given boundaries (if offset and length do not match the 32k FS
629  * block boundaries). <p>
630  *
631  * This function should be used to focus a download towards a
632  * particular portion of the file (optimization), not to strictly
633  * limit the download to exactly those bytes.
634  *
635  * @param h handle to the file sharing subsystem
636  * @param uri the URI of the file (determines what to download); CHK or LOC URI
637  * @param meta known metadata for the file (can be NULL)
638  * @param filename where to store the file, maybe NULL (then no file is
639  *        created on disk and data must be grabbed from the callbacks)
640  * @param offset at what offset should we start the download (typically 0)
641  * @param length how many bytes should be downloaded starting at offset
642  * @param anonymity anonymity level to use for the download
643  * @param options various options
644  * @param parent parent download to associate this download with (use NULL
645  *        for top-level downloads; useful for manually-triggered recursive downloads)
646  * @return context that can be used to control this download
647  */
648 struct GNUNET_FS_DownloadContext *
649 GNUNET_FS_file_download_start (struct GNUNET_FS_Handle *h,
650                                const struct GNUNET_FS_Uri *uri,
651                                const struct GNUNET_CONTAINER_MetaData *meta,
652                                const char *filename,
653                                uint64_t offset,
654                                uint64_t length,
655                                uint32_t anonymity,
656                                enum GNUNET_FS_DownloadOptions options,
657                                struct GNUNET_FS_DownloadContext *parent)
658 {
659   struct GNUNET_FS_ProgressInfo pi;
660   struct GNUNET_FS_DownloadContext *dc;
661   struct GNUNET_CLIENT_Connection *client;
662
663   client = GNUNET_CLIENT_connect (h->sched,
664                                   "fs",
665                                   h->cfg);
666   if (NULL == client)
667     return NULL;
668   // FIXME: add support for "loc" URIs!
669   GNUNET_assert (GNUNET_FS_uri_test_chk (uri));
670   if ( (dc->offset + dc->length < dc->offset) ||
671        (dc->offset + dc->length > uri->data.chk.file_length) )
672     {
673       GNUNET_break (0);
674       return NULL;
675     }
676   dc = GNUNET_malloc (sizeof(struct GNUNET_FS_DownloadContext));
677   dc->h = h;
678   dc->client = client;
679   dc->parent = parent;
680   dc->uri = GNUNET_FS_uri_dup (uri);
681   dc->meta = GNUNET_CONTAINER_meta_data_duplicate (meta);
682   if (NULL != filename)
683     {
684       dc->filename = GNUNET_strdup (filename);
685       if (GNUNET_YES == GNUNET_DISK_file_test (filename))
686         GNUNET_DISK_file_size (filename,
687                                &dc->old_file_size,
688                                GNUNET_YES);
689       dc->handle = GNUNET_DISK_file_open (filename, 
690                                           GNUNET_DISK_OPEN_READWRITE | 
691                                           GNUNET_DISK_OPEN_CREATE,
692                                           GNUNET_DISK_PERM_USER_READ |
693                                           GNUNET_DISK_PERM_USER_WRITE |
694                                           GNUNET_DISK_PERM_GROUP_READ |
695                                           GNUNET_DISK_PERM_OTHER_READ);
696       if (dc->handle == NULL)
697         {
698           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
699                       _("Download failed: could not open file `%s': %s\n"),
700                       dc->filename,
701                       STRERROR (errno));
702           GNUNET_CONTAINER_meta_data_destroy (dc->meta);
703           GNUNET_FS_uri_destroy (dc->uri);
704           GNUNET_free (dc->filename);
705           GNUNET_CLIENT_disconnect (dc->client);
706           GNUNET_free (dc);
707           return NULL;
708         }
709     }
710   // FIXME: set "dc->target" for LOC uris!
711   dc->offset = offset;
712   dc->length = length;
713   dc->anonymity = anonymity;
714   dc->options = options;
715   dc->active = GNUNET_CONTAINER_multihashmap_create (1 + (length / DBLOCK_SIZE));
716   dc->treedepth = GNUNET_FS_compute_depth (GNUNET_ntohll(dc->uri->data.chk.file_length));
717   // FIXME: make persistent
718   schedule_block_download (dc, 
719                            &dc->uri->data.chk.chk,
720                            0, 
721                            0);
722   GNUNET_CLIENT_notify_transmit_ready (client,
723                                        sizeof (struct SearchMessage),
724                                        GNUNET_CONSTANTS_SERVICE_TIMEOUT,
725                                        &transmit_download_request,
726                                        dc);  
727   GNUNET_CLIENT_receive (client,
728                          &receive_results,
729                          dc,
730                          GNUNET_TIME_UNIT_FOREVER_REL);
731   pi.status = GNUNET_FS_STATUS_DOWNLOAD_START;
732   make_download_status (&pi, dc);
733   pi.value.download.specifics.start.meta = meta;
734   dc->client_info = dc->h->upcb (dc->h->upcb_cls,
735                                  &pi);
736
737   return dc;
738 }
739
740
741 /**
742  * Free entries in the map.
743  *
744  * @param cls unused (NULL)
745  * @param key unused
746  * @param entry entry of type "struct DownloadRequest" which is freed
747  * @return GNUNET_OK
748  */
749 static int
750 free_entry (void *cls,
751             const GNUNET_HashCode *key,
752             void *entry)
753 {
754   GNUNET_free (entry);
755   return GNUNET_OK;
756 }
757
758
759 /**
760  * Stop a download (aborts if download is incomplete).
761  *
762  * @param dc handle for the download
763  * @param do_delete delete files of incomplete downloads
764  */
765 void
766 GNUNET_FS_file_download_stop (struct GNUNET_FS_DownloadContext *dc,
767                               int do_delete)
768 {
769   struct GNUNET_FS_ProgressInfo pi;
770
771   // FIXME: make unpersistent  
772   pi.status = GNUNET_FS_STATUS_DOWNLOAD_STOPPED;
773   make_download_status (&pi, dc);
774   dc->client_info = dc->h->upcb (dc->h->upcb_cls,
775                                  &pi);
776
777   if (GNUNET_SCHEDULER_NO_TASK != dc->task)
778     GNUNET_SCHEDULER_cancel (dc->h->sched,
779                              dc->task);
780   if (NULL != dc->client)
781     GNUNET_CLIENT_disconnect (dc->client);
782   GNUNET_CONTAINER_multihashmap_iterate (dc->active,
783                                          &free_entry,
784                                          NULL);
785   GNUNET_CONTAINER_multihashmap_destroy (dc->active);
786   if (dc->filename != NULL)
787     {
788       if (NULL != dc->handle)
789         GNUNET_DISK_file_close (dc->handle);
790       if ( (dc->completed != dc->length) &&
791            (GNUNET_YES == do_delete) )
792         {
793           if (0 != UNLINK (dc->filename))
794             GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
795                                       "unlink",
796                                       dc->filename);
797         }
798       GNUNET_free (dc->filename);
799     }
800   GNUNET_CONTAINER_meta_data_destroy (dc->meta);
801   GNUNET_FS_uri_destroy (dc->uri);
802   GNUNET_free (dc);
803 }
804
805
806
807 #if 0
808
809
810 /**
811  * Check if self block is already present on the drive.  If the block
812  * is a dblock and present, the ProgressModel is notified. If the
813  * block is present and it is an iblock, downloading the children is
814  * triggered.
815  *
816  * Also checks if the block is within the range of blocks
817  * that we are supposed to download.  If not, the method
818  * returns as if the block is present but does NOT signal
819  * progress.
820  *
821  * @param node that is checked for presence
822  * @return GNUNET_YES if present, GNUNET_NO if not.
823  */
824 static int
825 check_node_present (const struct Node *node)
826 {
827   int res;
828   int ret;
829   char *data;
830   unsigned int size;
831   GNUNET_HashCode hc;
832
833   size = get_node_size (node);
834   /* first check if node is within range.
835      For now, keeping it simple, we only do
836      this for level-0 nodes */
837   if ((node->level == 0) &&
838       ((node->offset + size < node->ctx->offset) ||
839        (node->offset >= node->ctx->offset + node->ctx->length)))
840     return GNUNET_YES;
841   data = GNUNET_malloc (size);
842   ret = GNUNET_NO;
843   res = read_from_files (node->ctx, node->level, node->offset, data, size);
844   if (res == size)
845     {
846       GNUNET_hash (data, size, &hc);
847       if (0 == memcmp (&hc, &node->chk.key, sizeof (GNUNET_HashCode)))
848         {
849           notify_client_about_progress (node, data, size);
850           if (node->level > 0)
851             iblock_download_children (node, data, size);
852           ret = GNUNET_YES;
853         }
854     }
855   GNUNET_free (data);
856   return ret;
857 }
858
859 #endif
860
861
862 /* end of fs_download.c */