fix ttl underflow check
[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, 2010 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  * - handle recursive downloads (need directory & 
27  *   fs-level download-parallelism management)
28  * - handle recursive downloads where directory file is
29  *   NOT saved on disk (need temporary file instead then!)
30  * - location URI suppport (can wait, easy)
31  * - check if blocks exist already (can wait, easy)
32  * - check if iblocks can be computed from existing blocks (can wait, hard)
33  * - persistence (can wait)
34  */
35 #include "platform.h"
36 #include "gnunet_constants.h"
37 #include "gnunet_fs_service.h"
38 #include "fs.h"
39 #include "fs_tree.h"
40
41 #define DEBUG_DOWNLOAD GNUNET_YES
42
43 /**
44  * We're storing the IBLOCKS after the DBLOCKS on disk (so that we
45  * only have to truncate the file once we're done).
46  *
47  * Given the offset of a block (with respect to the DBLOCKS) and its
48  * depth, return the offset where we would store this block in the
49  * file.
50  * 
51  * @param fsize overall file size
52  * @param off offset of the block in the file
53  * @param depth depth of the block in the tree
54  * @param treedepth maximum depth of the tree
55  * @return off for DBLOCKS (depth == treedepth),
56  *         otherwise an offset past the end
57  *         of the file that does not overlap
58  *         with the range for any other block
59  */
60 static uint64_t
61 compute_disk_offset (uint64_t fsize,
62                      uint64_t off,
63                      unsigned int depth,
64                      unsigned int treedepth)
65 {
66   unsigned int i;
67   uint64_t lsize; /* what is the size of all IBlocks for depth "i"? */
68   uint64_t loff; /* where do IBlocks for depth "i" start? */
69   unsigned int ioff; /* which IBlock corresponds to "off" at depth "i"? */
70   
71   if (depth == treedepth)
72     return off;
73   /* first IBlocks start at the end of file, rounded up
74      to full DBLOCK_SIZE */
75   loff = ((fsize + DBLOCK_SIZE - 1) / DBLOCK_SIZE) * DBLOCK_SIZE;
76   lsize = ( (fsize + DBLOCK_SIZE-1) / DBLOCK_SIZE) * sizeof (struct ContentHashKey);
77   GNUNET_assert (0 == (off % DBLOCK_SIZE));
78   ioff = (off / DBLOCK_SIZE);
79   for (i=treedepth-1;i>depth;i--)
80     {
81       loff += lsize;
82       lsize = (lsize + CHK_PER_INODE - 1) / CHK_PER_INODE;
83       GNUNET_assert (lsize > 0);
84       GNUNET_assert (0 == (ioff % CHK_PER_INODE));
85       ioff /= CHK_PER_INODE;
86     }
87   return loff + ioff * sizeof (struct ContentHashKey);
88 }
89
90
91 /**
92  * Given a file of the specified treedepth and a block at the given
93  * offset and depth, calculate the offset for the CHK at the given
94  * index.
95  *
96  * @param offset the offset of the first
97  *        DBLOCK in the subtree of the 
98  *        identified IBLOCK
99  * @param depth the depth of the IBLOCK in the tree
100  * @param treedepth overall depth of the tree
101  * @param k which CHK in the IBLOCK are we 
102  *        talking about
103  * @return offset if k=0, otherwise an appropriately
104  *         larger value (i.e., if depth = treedepth-1,
105  *         the returned value should be offset+DBLOCK_SIZE)
106  */
107 static uint64_t
108 compute_dblock_offset (uint64_t offset,
109                        unsigned int depth,
110                        unsigned int treedepth,
111                        unsigned int k)
112 {
113   unsigned int i;
114   uint64_t lsize; /* what is the size of the sum of all DBlocks 
115                      that a CHK at depth i corresponds to? */
116
117   if (depth == treedepth)
118     return offset;
119   lsize = DBLOCK_SIZE;
120   for (i=treedepth-1;i>depth;i--)
121     lsize *= CHK_PER_INODE;
122   return offset + k * lsize;
123 }
124
125
126 /**
127  * Fill in all of the generic fields for 
128  * a download event.
129  *
130  * @param pi structure to fill in
131  * @param dc overall download context
132  */
133 static void
134 make_download_status (struct GNUNET_FS_ProgressInfo *pi,
135                       struct GNUNET_FS_DownloadContext *dc)
136 {
137   pi->value.download.dc = dc;
138   pi->value.download.cctx
139     = dc->client_info;
140   pi->value.download.pctx
141     = (dc->parent == NULL) ? NULL : dc->parent->client_info;
142   pi->value.download.uri 
143     = dc->uri;
144   pi->value.download.filename
145     = dc->filename;
146   pi->value.download.size
147     = dc->length;
148   pi->value.download.duration
149     = GNUNET_TIME_absolute_get_duration (dc->start_time);
150   pi->value.download.completed
151     = dc->completed;
152   pi->value.download.anonymity
153     = dc->anonymity;
154   pi->value.download.eta
155     = GNUNET_TIME_calculate_eta (dc->start_time,
156                                  dc->completed,
157                                  dc->length);
158 }
159
160 /**
161  * We're ready to transmit a search request to the
162  * file-sharing service.  Do it.  If there is 
163  * more than one request pending, try to send 
164  * multiple or request another transmission.
165  *
166  * @param cls closure
167  * @param size number of bytes available in buf
168  * @param buf where the callee should write the message
169  * @return number of bytes written to buf
170  */
171 static size_t
172 transmit_download_request (void *cls,
173                            size_t size, 
174                            void *buf);
175
176
177 /**
178  * Schedule the download of the specified block in the tree.
179  *
180  * @param dc overall download this block belongs to
181  * @param chk content-hash-key of the block
182  * @param offset offset of the block in the file
183  *         (for IBlocks, the offset is the lowest
184  *          offset of any DBlock in the subtree under
185  *          the IBlock)
186  * @param depth depth of the block, 0 is the root of the tree
187  */
188 static void
189 schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
190                          const struct ContentHashKey *chk,
191                          uint64_t offset,
192                          unsigned int depth)
193 {
194   struct DownloadRequest *sm;
195   uint64_t off;
196
197 #if DEBUG_DOWNLOAD
198   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
199               "Scheduling download at offset %llu and depth %u for `%s'\n",
200               (unsigned long long) offset,
201               depth,
202               GNUNET_h2s (&chk->query));
203 #endif
204   off = compute_disk_offset (GNUNET_ntohll (dc->uri->data.chk.file_length),
205                              offset,
206                              depth,
207                              dc->treedepth);
208   if ( (dc->old_file_size > off) &&
209        (dc->handle != NULL) &&
210        (off  == 
211         GNUNET_DISK_file_seek (dc->handle,
212                                off,
213                                GNUNET_DISK_SEEK_SET) ) )
214     {
215       // FIXME: check if block exists on disk!
216       // (read block, encode, compare with
217       // query; if matches, simply return)
218     }
219   if (depth < dc->treedepth)
220     {
221       // FIXME: try if we could
222       // reconstitute this IBLOCK
223       // from the existing blocks on disk (can wait)
224       // (read block(s), encode, compare with
225       // query; if matches, simply return)
226     }
227   sm = GNUNET_malloc (sizeof (struct DownloadRequest));
228   sm->chk = *chk;
229   sm->offset = offset;
230   sm->depth = depth;
231   sm->is_pending = GNUNET_YES;
232   sm->next = dc->pending;
233   dc->pending = sm;
234   GNUNET_CONTAINER_multihashmap_put (dc->active,
235                                      &chk->query,
236                                      sm,
237                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
238
239   if ( (dc->th == NULL) &&
240        (dc->client != NULL) )
241     dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
242                                                   sizeof (struct SearchMessage),
243                                                   GNUNET_CONSTANTS_SERVICE_TIMEOUT,
244                                                   GNUNET_NO,
245                                                   &transmit_download_request,
246                                                   dc);
247 }
248
249
250
251 /**
252  * Suggest a filename based on given metadata.
253  * 
254  * @param md given meta data
255  * @return NULL if meta data is useless for suggesting a filename
256  */
257 char *
258 GNUNET_FS_meta_data_suggest_filename (const struct GNUNET_CONTAINER_MetaData *md)
259 {
260   static const char *mimeMap[][2] = {
261     {"application/bz2", ".bz2"},
262     {"application/gnunet-directory", ".gnd"},
263     {"application/java", ".class"},
264     {"application/msword", ".doc"},
265     {"application/ogg", ".ogg"},
266     {"application/pdf", ".pdf"},
267     {"application/pgp-keys", ".key"},
268     {"application/pgp-signature", ".pgp"},
269     {"application/postscript", ".ps"},
270     {"application/rar", ".rar"},
271     {"application/rtf", ".rtf"},
272     {"application/xml", ".xml"},
273     {"application/x-debian-package", ".deb"},
274     {"application/x-dvi", ".dvi"},
275     {"applixation/x-flac", ".flac"},
276     {"applixation/x-gzip", ".gz"},
277     {"application/x-java-archive", ".jar"},
278     {"application/x-java-vm", ".class"},
279     {"application/x-python-code", ".pyc"},
280     {"application/x-redhat-package-manager", ".rpm"},
281     {"application/x-rpm", ".rpm"},
282     {"application/x-tar", ".tar"},
283     {"application/x-tex-pk", ".pk"},
284     {"application/x-texinfo", ".texinfo"},
285     {"application/x-xcf", ".xcf"},
286     {"application/x-xfig", ".xfig"},
287     {"application/zip", ".zip"},
288     
289     {"audio/midi", ".midi"},
290     {"audio/mpeg", ".mp3"},
291     {"audio/real", ".rm"},
292     {"audio/x-wav", ".wav"},
293     
294     {"image/gif", ".gif"},
295     {"image/jpeg", ".jpg"},
296     {"image/pcx", ".pcx"},
297     {"image/png", ".png"},
298     {"image/tiff", ".tiff"},
299     {"image/x-ms-bmp", ".bmp"},
300     {"image/x-xpixmap", ".xpm"},
301     
302     {"text/css", ".css"},
303     {"text/html", ".html"},
304     {"text/plain", ".txt"},
305     {"text/rtf", ".rtf"},
306     {"text/x-c++hdr", ".h++"},
307     {"text/x-c++src", ".c++"},
308     {"text/x-chdr", ".h"},
309     {"text/x-csrc", ".c"},
310     {"text/x-java", ".java"},
311     {"text/x-moc", ".moc"},
312     {"text/x-pascal", ".pas"},
313     {"text/x-perl", ".pl"},
314     {"text/x-python", ".py"},
315     {"text/x-tex", ".tex"},
316     
317     {"video/avi", ".avi"},
318     {"video/mpeg", ".mpeg"},
319     {"video/quicktime", ".qt"},
320     {"video/real", ".rm"},
321     {"video/x-msvideo", ".avi"},
322     {NULL, NULL},
323   };
324   char *ret;
325   unsigned int i;
326   char *mime;
327   char *base;
328   const char *ext;
329
330   ret = GNUNET_CONTAINER_meta_data_get_by_type (md,
331                                                 EXTRACTOR_METATYPE_FILENAME);
332   if (ret != NULL)
333     return ret;  
334   ext = NULL;
335   mime = GNUNET_CONTAINER_meta_data_get_by_type (md,
336                                                  EXTRACTOR_METATYPE_MIMETYPE);
337   if (mime != NULL)
338     {
339       i = 0;
340       while ( (mimeMap[i][0] != NULL) && 
341               (0 != strcmp (mime, mimeMap[i][0])))
342         i++;
343       if (mimeMap[i][1] == NULL)
344         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG | 
345                     GNUNET_ERROR_TYPE_BULK,
346                     _("Did not find mime type `%s' in extension list.\n"),
347                     mime);
348       else
349         ext = mimeMap[i][1];
350       GNUNET_free (mime);
351     }
352   base = GNUNET_CONTAINER_meta_data_get_first_by_types (md,
353                                                         EXTRACTOR_METATYPE_TITLE,
354                                                         EXTRACTOR_METATYPE_BOOK_TITLE,
355                                                         EXTRACTOR_METATYPE_ORIGINAL_TITLE,
356                                                         EXTRACTOR_METATYPE_PACKAGE_NAME,
357                                                         EXTRACTOR_METATYPE_URL,
358                                                         EXTRACTOR_METATYPE_URI, 
359                                                         EXTRACTOR_METATYPE_DESCRIPTION,
360                                                         EXTRACTOR_METATYPE_ISRC,
361                                                         EXTRACTOR_METATYPE_JOURNAL_NAME,
362                                                         EXTRACTOR_METATYPE_AUTHOR_NAME,
363                                                         EXTRACTOR_METATYPE_SUBJECT,
364                                                         EXTRACTOR_METATYPE_ALBUM,
365                                                         EXTRACTOR_METATYPE_ARTIST,
366                                                         EXTRACTOR_METATYPE_KEYWORDS,
367                                                         EXTRACTOR_METATYPE_COMMENT,
368                                                         EXTRACTOR_METATYPE_UNKNOWN,
369                                                         -1);
370   if ( (base == NULL) &&
371        (ext == NULL) )
372     return NULL;
373   if (base == NULL)
374     return GNUNET_strdup (ext);
375   if (ext == NULL)
376     return base;
377   GNUNET_asprintf (&ret,
378                    "%s%s",
379                    base,
380                    ext);
381   GNUNET_free (base);
382   return ret;
383 }
384
385
386 /**
387  * We've lost our connection with the FS service.
388  * Re-establish it and re-transmit all of our
389  * pending requests.
390  *
391  * @param dc download context that is having trouble
392  */
393 static void
394 try_reconnect (struct GNUNET_FS_DownloadContext *dc);
395
396
397 /**
398  * Compute how many bytes of data should be stored in
399  * the specified node.
400  *
401  * @param fsize overall file size
402  * @param totaldepth depth of the entire tree
403  * @param offset offset of the node
404  * @param depth depth of the node
405  * @return number of bytes stored in this node
406  */
407 static size_t
408 calculate_block_size (uint64_t fsize,
409                       unsigned int totaldepth,
410                       uint64_t offset,
411                       unsigned int depth)
412 {
413   unsigned int i;
414   size_t ret;
415   uint64_t rsize;
416   uint64_t epos;
417   unsigned int chks;
418
419   GNUNET_assert (offset < fsize);
420   if (depth == totaldepth)
421     {
422       ret = DBLOCK_SIZE;
423       if (offset + ret > fsize)
424         ret = (size_t) (fsize - offset);
425       return ret;
426     }
427
428   rsize = DBLOCK_SIZE;
429   for (i = totaldepth-1; i > depth; i--)
430     rsize *= CHK_PER_INODE;
431   epos = offset + rsize * CHK_PER_INODE;
432   GNUNET_assert (epos > offset);
433   if (epos > fsize)
434     epos = fsize;
435   /* round up when computing #CHKs in our IBlock */
436   chks = (epos - offset + rsize - 1) / rsize;
437   GNUNET_assert (chks <= CHK_PER_INODE);
438   return chks * sizeof (struct ContentHashKey);
439 }
440
441
442 /**
443  * Closure for iterator processing results.
444  */
445 struct ProcessResultClosure
446 {
447   
448   /**
449    * Hash of data.
450    */
451   GNUNET_HashCode query;
452
453   /**
454    * Data found in P2P network.
455    */ 
456   const void *data;
457
458   /**
459    * Our download context.
460    */
461   struct GNUNET_FS_DownloadContext *dc;
462                 
463   /**
464    * Number of bytes in data.
465    */
466   size_t size;
467
468   /**
469    * Type of data.
470    */
471   uint32_t type;
472   
473 };
474
475
476 /**
477  * We found an entry in a directory.  Check if the respective child
478  * already exists and if not create the respective child download.
479  *
480  * @param cls the parent download
481  * @param filename name of the file in the directory
482  * @param uri URI of the file (CHK or LOC)
483  * @param meta meta data of the file
484  * @param length number of bytes in data
485  * @param data contents of the file (or NULL if they were not inlined)
486  */
487 static void 
488 trigger_recursive_download (void *cls,
489                             const char *filename,
490                             const struct GNUNET_FS_Uri *uri,
491                             const struct GNUNET_CONTAINER_MetaData *meta,
492                             size_t length,
493                             const void *data)
494 {
495   struct GNUNET_FS_DownloadContext *dc = cls;  
496   struct GNUNET_FS_DownloadContext *cpos;
497   struct GNUNET_DISK_FileHandle *fh;
498   char *fn;
499   char *us;
500   char *ext;
501   char *dn;
502   char *full_name;
503
504   if (NULL == uri)
505     return; /* entry for the directory itself */
506   cpos = dc->child_head;
507   while (cpos != NULL)
508     {
509       if ( (GNUNET_FS_uri_test_equal (uri,
510                                       cpos->uri)) ||
511            ( (filename != NULL) &&
512              (0 == strcmp (cpos->filename,
513                            filename)) ) )
514         break;  
515       cpos = cpos->next;
516     }
517   if (cpos != NULL)
518     return; /* already exists */
519   fn = NULL;
520   if (NULL == filename)
521     {
522       fn = GNUNET_FS_meta_data_suggest_filename (meta);      
523       if (fn == NULL)
524         {
525           us = GNUNET_FS_uri_to_string (uri);
526           fn = GNUNET_strdup (&us [strlen (GNUNET_FS_URI_PREFIX 
527                                            GNUNET_FS_URI_CHK_INFIX)]);
528           GNUNET_free (us);
529         }
530       else if (fn[0] == '.')
531         {
532           ext = fn;
533           us = GNUNET_FS_uri_to_string (uri);
534           GNUNET_asprintf (&fn,
535                            "%s%s",
536                            &us[strlen (GNUNET_FS_URI_PREFIX 
537                                        GNUNET_FS_URI_CHK_INFIX)], ext);
538           GNUNET_free (ext);
539           GNUNET_free (us);
540         }
541       filename = fn;
542     }
543   if (dc->filename == NULL)
544     {
545       full_name = NULL;
546     }
547   else
548     {
549       dn = GNUNET_strdup (dc->filename);
550       GNUNET_break ( (strlen (dn) >= strlen (GNUNET_FS_DIRECTORY_EXT)) &&
551                      (NULL !=
552                       strstr (dn + strlen(dn) - strlen(GNUNET_FS_DIRECTORY_EXT),
553                               GNUNET_FS_DIRECTORY_EXT)) );
554       if ( (strlen (dn) >= strlen (GNUNET_FS_DIRECTORY_EXT)) &&
555            (NULL !=
556             strstr (dn + strlen(dn) - strlen(GNUNET_FS_DIRECTORY_EXT),
557                     GNUNET_FS_DIRECTORY_EXT)) )      
558         dn[strlen(dn) - strlen (GNUNET_FS_DIRECTORY_EXT)] = '\0';      
559       if ( (GNUNET_YES == GNUNET_FS_meta_data_test_for_directory (meta)) &&
560            ( (strlen (filename) < strlen (GNUNET_FS_DIRECTORY_EXT)) ||
561              (NULL ==
562               strstr (filename + strlen(filename) - strlen(GNUNET_FS_DIRECTORY_EXT),
563                       GNUNET_FS_DIRECTORY_EXT)) ) )
564         {
565           GNUNET_asprintf (&full_name,
566                            "%s%s%s%s",
567                            dn,
568                            DIR_SEPARATOR_STR,
569                            filename,
570                            GNUNET_FS_DIRECTORY_EXT);
571         }
572       else
573         {
574           GNUNET_asprintf (&full_name,
575                            "%s%s%s",
576                            dn,
577                            DIR_SEPARATOR_STR,
578                            filename);
579         }
580       GNUNET_free (dn);
581     }
582   if ( (full_name != NULL) &&
583        (GNUNET_OK !=
584         GNUNET_DISK_directory_create_for_file (full_name)) )
585     {
586       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
587                   _("Failed to create directory for recursive download of `%s'\n"),
588                   full_name);
589       GNUNET_free (full_name);
590       GNUNET_free_non_null (fn);
591       return;
592     }
593     
594   if (data != NULL) 
595     {
596       if (full_name != NULL)
597         {
598           fh = GNUNET_DISK_file_open (full_name,
599                                       GNUNET_DISK_OPEN_WRITE | GNUNET_DISK_OPEN_TRUNCATE | GNUNET_DISK_OPEN_CREATE,
600                                       GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE);
601           if (fh == NULL)
602             {
603               GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
604                                         "open",
605                                         full_name);           
606               GNUNET_free (full_name);
607               GNUNET_free_non_null (fn);
608               return;
609             }
610           if (length != 
611               GNUNET_DISK_file_write (fh,
612                                       data,
613                                       length))
614             {
615               GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
616                                         "write",
617                                         full_name);           
618             }
619           GNUNET_DISK_file_close (fh);
620         }
621       else
622         {
623           /* FIXME: generate 'progress' events and move to
624              instant completion! */
625           GNUNET_break (0); // FIXME: not implemented
626         }
627     }
628   GNUNET_FS_download_start (dc->h,
629                             uri,
630                             meta,
631                             full_name,
632                             0,
633                             GNUNET_FS_uri_chk_get_file_size (uri),
634                             dc->anonymity,
635                             dc->options,
636                             NULL,
637                             dc);
638   GNUNET_free_non_null (full_name);
639   GNUNET_free_non_null (fn);
640 }
641
642
643 /**
644  * We're done downloading a directory.  Open the file and
645  * trigger all of the (remaining) child downloads.
646  *
647  * @param dc context of download that just completed
648  */
649 static void
650 full_recursive_download (struct GNUNET_FS_DownloadContext *dc)
651 {
652   size_t size;
653   uint64_t size64;
654   void *data;
655   struct GNUNET_DISK_FileHandle *h;
656   struct GNUNET_DISK_MapHandle *m;
657   
658   size64 = GNUNET_FS_uri_chk_get_file_size (dc->uri);
659   size = (size_t) size64;
660   if (size64 != (uint64_t) size)
661     {
662       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
663                   _("Recursive downloads of directories larger than 4 GB are not supported on 32-bit systems\n"));
664       return;
665     }
666   if (dc->filename != NULL)
667     {
668       h = GNUNET_DISK_file_open (dc->filename,
669                                  GNUNET_DISK_OPEN_READ,
670                                  GNUNET_DISK_PERM_NONE);
671     }
672   else
673     {
674       /* FIXME: need to initialize (and use) temp_filename
675          in various places in order for this assertion to
676          not fail; right now, it will always fail! */
677       GNUNET_assert (dc->temp_filename != NULL);
678       h = GNUNET_DISK_file_open (dc->temp_filename,
679                                  GNUNET_DISK_OPEN_READ,
680                                  GNUNET_DISK_PERM_NONE);
681     }
682   if (h == NULL)
683     return; /* oops */
684   data = GNUNET_DISK_file_map (h, &m, GNUNET_DISK_MAP_TYPE_READ, size);
685   if (data == NULL)
686     {
687       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
688                   _("Directory too large for system address space\n"));
689     }
690   else
691     {
692       GNUNET_FS_directory_list_contents (size,
693                                          data,
694                                          0,
695                                          &trigger_recursive_download,
696                                          dc);         
697       GNUNET_DISK_file_unmap (m);
698     }
699   GNUNET_DISK_file_close (h);
700   if (dc->filename == NULL)
701     {
702       if (0 != UNLINK (dc->temp_filename))
703         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
704                                   "unlink",
705                                   dc->temp_filename);
706       GNUNET_free (dc->temp_filename);
707       dc->temp_filename = NULL;
708     }
709 }
710
711
712 /**
713  * Check if all child-downloads have completed and
714  * if so, signal completion (and possibly recurse to
715  * parent).
716  */
717 static void
718 check_completed (struct GNUNET_FS_DownloadContext *dc)
719 {
720   struct GNUNET_FS_ProgressInfo pi;
721   struct GNUNET_FS_DownloadContext *pos;
722
723   pos = dc->child_head;
724   while (pos != NULL)
725     {
726       if ( (pos->emsg == NULL) &&
727            (pos->completed < pos->length) )
728         return; /* not done yet */
729       if ( (pos->child_head != NULL) &&
730            (pos->has_finished != GNUNET_YES) )
731         return; /* not transitively done yet */
732       pos = pos->next;
733     }
734   dc->has_finished = GNUNET_YES;
735   /* signal completion */
736   pi.status = GNUNET_FS_STATUS_DOWNLOAD_COMPLETED;
737   make_download_status (&pi, dc);
738   dc->client_info = dc->h->upcb (dc->h->upcb_cls,
739                                  &pi);
740   if (dc->parent != NULL)
741     check_completed (dc->parent);  
742 }
743
744
745 /**
746  * Iterator over entries in the pending requests in the 'active' map for the
747  * reply that we just got.
748  *
749  * @param cls closure (our 'struct ProcessResultClosure')
750  * @param key query for the given value / request
751  * @param value value in the hash map (a 'struct DownloadRequest')
752  * @return GNUNET_YES (we should continue to iterate); unless serious error
753  */
754 static int
755 process_result_with_request (void *cls,
756                              const GNUNET_HashCode * key,
757                              void *value)
758 {
759   struct ProcessResultClosure *prc = cls;
760   struct DownloadRequest *sm = value;
761   struct GNUNET_FS_DownloadContext *dc = prc->dc;
762   struct GNUNET_CRYPTO_AesSessionKey skey;
763   struct GNUNET_CRYPTO_AesInitializationVector iv;
764   char pt[prc->size];
765   struct GNUNET_FS_ProgressInfo pi;
766   uint64_t off;
767   size_t app;
768   int i;
769   struct ContentHashKey *chk;
770   char *emsg;
771
772   if (prc->size != calculate_block_size (GNUNET_ntohll (dc->uri->data.chk.file_length),
773                                          dc->treedepth,
774                                          sm->offset,
775                                          sm->depth))
776     {
777 #if DEBUG_DOWNLOAD
778       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
779                   "Internal error or bogus download URI (expected %u bytes, got %u)\n",
780                   calculate_block_size (GNUNET_ntohll (dc->uri->data.chk.file_length),
781                                         dc->treedepth,
782                                         sm->offset,
783                                         sm->depth),
784                   prc->size);
785 #endif
786       dc->emsg = GNUNET_strdup ("Internal error or bogus download URI");
787       /* signal error */
788       pi.status = GNUNET_FS_STATUS_DOWNLOAD_ERROR;
789       make_download_status (&pi, dc);
790       pi.value.download.specifics.error.message = dc->emsg;
791       dc->client_info = dc->h->upcb (dc->h->upcb_cls,
792                                      &pi);
793       /* abort all pending requests */
794       if (NULL != dc->th)
795         {
796           GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
797           dc->th = NULL;
798         }
799       GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
800       dc->client = NULL;
801       return GNUNET_NO;
802     }
803   GNUNET_assert (GNUNET_YES ==
804                  GNUNET_CONTAINER_multihashmap_remove (dc->active,
805                                                        &prc->query,
806                                                        sm));
807   GNUNET_CRYPTO_hash_to_aes_key (&sm->chk.key, &skey, &iv);
808   GNUNET_CRYPTO_aes_decrypt (prc->data,
809                              prc->size,
810                              &skey,
811                              &iv,
812                              pt);
813   off = compute_disk_offset (GNUNET_ntohll (dc->uri->data.chk.file_length),
814                              sm->offset,
815                              sm->depth,
816                              dc->treedepth);
817   /* save to disk */
818   if ( (NULL != dc->handle) &&
819        ( (sm->depth == dc->treedepth) ||
820          (0 == (dc->options & GNUNET_FS_DOWNLOAD_NO_TEMPORARIES)) ) )
821     {
822       emsg = NULL;
823 #if DEBUG_DOWNLOAD
824       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
825                   "Saving decrypted block to disk at offset %llu\n",
826                   (unsigned long long) off);
827 #endif
828       if ( (off  != 
829             GNUNET_DISK_file_seek (dc->handle,
830                                    off,
831                                    GNUNET_DISK_SEEK_SET) ) )
832         GNUNET_asprintf (&emsg,
833                          _("Failed to seek to offset %llu in file `%s': %s\n"),
834                          (unsigned long long) off,
835                          dc->filename,
836                          STRERROR (errno));
837       else if (prc->size !=
838                GNUNET_DISK_file_write (dc->handle,
839                                        pt,
840                                        prc->size))
841         GNUNET_asprintf (&emsg,
842                          _("Failed to write block of %u bytes at offset %llu in file `%s': %s\n"),
843                          (unsigned int) prc->size,
844                          (unsigned long long) off,
845                          dc->filename,
846                          STRERROR (errno));
847       if (NULL != emsg)
848         {
849           dc->emsg = emsg;
850           // FIXME: make persistent
851
852           /* signal error */
853           pi.status = GNUNET_FS_STATUS_DOWNLOAD_ERROR;
854           make_download_status (&pi, dc);
855           pi.value.download.specifics.error.message = emsg;
856           dc->client_info = dc->h->upcb (dc->h->upcb_cls,
857                                          &pi);
858
859           /* abort all pending requests */
860           if (NULL != dc->th)
861             {
862               GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
863               dc->th = NULL;
864             }
865           GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
866           dc->client = NULL;
867           GNUNET_free (sm);
868           return GNUNET_NO;
869         }
870     }
871   if (sm->depth == dc->treedepth) 
872     {
873       app = prc->size;
874       if (sm->offset < dc->offset)
875         {
876           /* starting offset begins in the middle of pt,
877              do not count first bytes as progress */
878           GNUNET_assert (app > (dc->offset - sm->offset));
879           app -= (dc->offset - sm->offset);       
880         }
881       if (sm->offset + prc->size > dc->offset + dc->length)
882         {
883           /* end of block is after relevant range,
884              do not count last bytes as progress */
885           GNUNET_assert (app > (sm->offset + prc->size) - (dc->offset + dc->length));
886           app -= (sm->offset + prc->size) - (dc->offset + dc->length);
887         }
888       dc->completed += app;
889
890       /* do recursive download if option is set and either meta data
891          says it is a directory or if no meta data is given AND filename 
892          ends in '.gnd' (top-level case) */
893       if ( (0 != (dc->options & GNUNET_FS_DOWNLOAD_OPTION_RECURSIVE)) &&
894            ( (GNUNET_YES == GNUNET_FS_meta_data_test_for_directory (dc->meta)) ||
895              ( (dc->meta == NULL) &&
896                ( (NULL == dc->filename) ||             
897                  ( (strlen (dc->filename) >= strlen (GNUNET_FS_DIRECTORY_EXT)) &&
898                    (NULL !=
899                     strstr (dc->filename + strlen(dc->filename) - strlen(GNUNET_FS_DIRECTORY_EXT),
900                             GNUNET_FS_DIRECTORY_EXT)) ) ) ) ) ) 
901         GNUNET_FS_directory_list_contents (prc->size,
902                                            pt,
903                                            off,
904                                            &trigger_recursive_download,
905                                            dc);         
906             
907     }
908   pi.status = GNUNET_FS_STATUS_DOWNLOAD_PROGRESS;
909   make_download_status (&pi, dc);
910   pi.value.download.specifics.progress.data = pt;
911   pi.value.download.specifics.progress.offset = sm->offset;
912   pi.value.download.specifics.progress.data_len = prc->size;
913   pi.value.download.specifics.progress.depth = sm->depth;
914   dc->client_info = dc->h->upcb (dc->h->upcb_cls,
915                                  &pi);
916   GNUNET_assert (dc->completed <= dc->length);
917   if (dc->completed == dc->length)
918     {
919 #if DEBUG_DOWNLOAD
920       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
921                   "Download completed, truncating file to desired length %llu\n",
922                   (unsigned long long) GNUNET_ntohll (dc->uri->data.chk.file_length));
923 #endif
924       /* truncate file to size (since we store IBlocks at the end) */
925       if (dc->handle != NULL)
926         {
927           GNUNET_DISK_file_close (dc->handle);
928           dc->handle = NULL;
929           if (0 != truncate (dc->filename,
930                              GNUNET_ntohll (dc->uri->data.chk.file_length)))
931             GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
932                                       "truncate",
933                                       dc->filename);
934         }
935
936       if ( (0 != (dc->options & GNUNET_FS_DOWNLOAD_OPTION_RECURSIVE)) &&
937            ( (GNUNET_YES == GNUNET_FS_meta_data_test_for_directory (dc->meta)) ||
938              ( (dc->meta == NULL) &&
939                ( (NULL == dc->filename) ||             
940                  ( (strlen (dc->filename) >= strlen (GNUNET_FS_DIRECTORY_EXT)) &&
941                    (NULL !=
942                     strstr (dc->filename + strlen(dc->filename) - strlen(GNUNET_FS_DIRECTORY_EXT),
943                             GNUNET_FS_DIRECTORY_EXT)) ) ) ) ) ) 
944         full_recursive_download (dc);
945       if (dc->child_head == NULL)
946         {
947           /* signal completion */
948           pi.status = GNUNET_FS_STATUS_DOWNLOAD_COMPLETED;
949           make_download_status (&pi, dc);
950           dc->client_info = dc->h->upcb (dc->h->upcb_cls,
951                                          &pi);
952           if (dc->parent != NULL)
953             check_completed (dc->parent);
954         }
955       GNUNET_assert (sm->depth == dc->treedepth);
956     }
957   // FIXME: make persistent
958   if (sm->depth == dc->treedepth) 
959     {
960       GNUNET_free (sm);      
961       return GNUNET_YES;
962     }
963 #if DEBUG_DOWNLOAD
964   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
965               "Triggering downloads of children (this block was at depth %u and offset %llu)\n",
966               sm->depth,
967               (unsigned long long) sm->offset);
968 #endif
969   GNUNET_assert (0 == (prc->size % sizeof(struct ContentHashKey)));
970   chk = (struct ContentHashKey*) pt;
971   for (i=(prc->size / sizeof(struct ContentHashKey))-1;i>=0;i--)
972     {
973       off = compute_dblock_offset (sm->offset,
974                                    sm->depth,
975                                    dc->treedepth,
976                                    i);
977       if ( (off + DBLOCK_SIZE >= dc->offset) &&
978            (off < dc->offset + dc->length) ) 
979         schedule_block_download (dc,
980                                  &chk[i],
981                                  off,
982                                  sm->depth + 1);
983     }
984   GNUNET_free (sm);
985   return GNUNET_YES;
986 }
987
988
989 /**
990  * Process a download result.
991  *
992  * @param dc our download context
993  * @param type type of the result
994  * @param data the (encrypted) response
995  * @param size size of data
996  */
997 static void
998 process_result (struct GNUNET_FS_DownloadContext *dc,
999                 uint32_t type,
1000                 const void *data,
1001                 size_t size)
1002 {
1003   struct ProcessResultClosure prc;
1004
1005   prc.dc = dc;
1006   prc.data = data;
1007   prc.size = size;
1008   prc.type = type;
1009   GNUNET_CRYPTO_hash (data, size, &prc.query);
1010 #if DEBUG_DOWNLOAD
1011   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1012               "Received result for query `%s' from `%s'-service\n",
1013               GNUNET_h2s (&prc.query),
1014               "FS");
1015 #endif
1016   GNUNET_CONTAINER_multihashmap_get_multiple (dc->active,
1017                                               &prc.query,
1018                                               &process_result_with_request,
1019                                               &prc);
1020 }
1021
1022
1023 /**
1024  * Type of a function to call when we receive a message
1025  * from the service.
1026  *
1027  * @param cls closure
1028  * @param msg message received, NULL on timeout or fatal error
1029  */
1030 static void 
1031 receive_results (void *cls,
1032                  const struct GNUNET_MessageHeader * msg)
1033 {
1034   struct GNUNET_FS_DownloadContext *dc = cls;
1035   const struct PutMessage *cm;
1036   uint16_t msize;
1037
1038   if ( (NULL == msg) ||
1039        (ntohs (msg->type) != GNUNET_MESSAGE_TYPE_FS_PUT) ||
1040        (sizeof (struct PutMessage) > ntohs(msg->size)) )
1041     {
1042       GNUNET_break (msg == NULL);       
1043       try_reconnect (dc);
1044       return;
1045     }
1046   msize = ntohs(msg->size);
1047   cm = (const struct PutMessage*) msg;
1048   process_result (dc, 
1049                   ntohl (cm->type),
1050                   &cm[1],
1051                   msize - sizeof (struct PutMessage));
1052   if (dc->client == NULL)
1053     return; /* fatal error */
1054   /* continue receiving */
1055   GNUNET_CLIENT_receive (dc->client,
1056                          &receive_results,
1057                          dc,
1058                          GNUNET_TIME_UNIT_FOREVER_REL);
1059 }
1060
1061
1062
1063 /**
1064  * We're ready to transmit a search request to the
1065  * file-sharing service.  Do it.  If there is 
1066  * more than one request pending, try to send 
1067  * multiple or request another transmission.
1068  *
1069  * @param cls closure
1070  * @param size number of bytes available in buf
1071  * @param buf where the callee should write the message
1072  * @return number of bytes written to buf
1073  */
1074 static size_t
1075 transmit_download_request (void *cls,
1076                            size_t size, 
1077                            void *buf)
1078 {
1079   struct GNUNET_FS_DownloadContext *dc = cls;
1080   size_t msize;
1081   struct SearchMessage *sm;
1082
1083   dc->th = NULL;
1084   if (NULL == buf)
1085     {
1086       try_reconnect (dc);
1087       return 0;
1088     }
1089   GNUNET_assert (size >= sizeof (struct SearchMessage));
1090   msize = 0;
1091   sm = buf;
1092   while ( (dc->pending != NULL) &&
1093           (size > msize + sizeof (struct SearchMessage)) )
1094     {
1095 #if DEBUG_DOWNLOAD
1096       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1097                   "Transmitting download request for `%s' to `%s'-service\n",
1098                   GNUNET_h2s (&dc->pending->chk.query),
1099                   "FS");
1100 #endif
1101       memset (sm, 0, sizeof (struct SearchMessage));
1102       sm->header.size = htons (sizeof (struct SearchMessage));
1103       sm->header.type = htons (GNUNET_MESSAGE_TYPE_FS_START_SEARCH);
1104       if (dc->pending->depth == dc->treedepth)
1105         sm->type = htonl (GNUNET_DATASTORE_BLOCKTYPE_DBLOCK);
1106       else
1107         sm->type = htonl (GNUNET_DATASTORE_BLOCKTYPE_IBLOCK);
1108       sm->anonymity_level = htonl (dc->anonymity);
1109       sm->target = dc->target.hashPubKey;
1110       sm->query = dc->pending->chk.query;
1111       dc->pending->is_pending = GNUNET_NO;
1112       dc->pending = dc->pending->next;
1113       msize += sizeof (struct SearchMessage);
1114       sm++;
1115     }
1116   if (dc->pending != NULL)
1117     dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
1118                                                   sizeof (struct SearchMessage),
1119                                                   GNUNET_CONSTANTS_SERVICE_TIMEOUT,
1120                                                   GNUNET_NO,
1121                                                   &transmit_download_request,
1122                                                   dc); 
1123   return msize;
1124 }
1125
1126
1127 /**
1128  * Reconnect to the FS service and transmit our queries NOW.
1129  *
1130  * @param cls our download context
1131  * @param tc unused
1132  */
1133 static void
1134 do_reconnect (void *cls,
1135               const struct GNUNET_SCHEDULER_TaskContext *tc)
1136 {
1137   struct GNUNET_FS_DownloadContext *dc = cls;
1138   struct GNUNET_CLIENT_Connection *client;
1139   
1140   dc->task = GNUNET_SCHEDULER_NO_TASK;
1141   client = GNUNET_CLIENT_connect (dc->h->sched,
1142                                   "fs",
1143                                   dc->h->cfg);
1144   if (NULL == client)
1145     {
1146       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1147                   "Connecting to `%s'-service failed, will try again.\n",
1148                   "FS");
1149       try_reconnect (dc);
1150       return;
1151     }
1152   dc->client = client;
1153   dc->th = GNUNET_CLIENT_notify_transmit_ready (client,
1154                                                 sizeof (struct SearchMessage),
1155                                                 GNUNET_CONSTANTS_SERVICE_TIMEOUT,
1156                                                 GNUNET_NO,
1157                                                 &transmit_download_request,
1158                                                 dc);  
1159   GNUNET_CLIENT_receive (client,
1160                          &receive_results,
1161                          dc,
1162                          GNUNET_TIME_UNIT_FOREVER_REL);
1163 }
1164
1165
1166 /**
1167  * Add entries that are not yet pending back to the pending list.
1168  *
1169  * @param cls our download context
1170  * @param key unused
1171  * @param entry entry of type "struct DownloadRequest"
1172  * @return GNUNET_OK
1173  */
1174 static int
1175 retry_entry (void *cls,
1176              const GNUNET_HashCode *key,
1177              void *entry)
1178 {
1179   struct GNUNET_FS_DownloadContext *dc = cls;
1180   struct DownloadRequest *dr = entry;
1181
1182   if (! dr->is_pending)
1183     {
1184       dr->next = dc->pending;
1185       dr->is_pending = GNUNET_YES;
1186       dc->pending = entry;
1187     }
1188   return GNUNET_OK;
1189 }
1190
1191
1192 /**
1193  * We've lost our connection with the FS service.
1194  * Re-establish it and re-transmit all of our
1195  * pending requests.
1196  *
1197  * @param dc download context that is having trouble
1198  */
1199 static void
1200 try_reconnect (struct GNUNET_FS_DownloadContext *dc)
1201 {
1202   
1203   if (NULL != dc->client)
1204     {
1205       if (NULL != dc->th)
1206         {
1207           GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
1208           dc->th = NULL;
1209         }
1210       GNUNET_CONTAINER_multihashmap_iterate (dc->active,
1211                                              &retry_entry,
1212                                              dc);
1213       GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
1214       dc->client = NULL;
1215     }
1216   dc->task
1217     = GNUNET_SCHEDULER_add_delayed (dc->h->sched,
1218                                     GNUNET_TIME_UNIT_SECONDS,
1219                                     &do_reconnect,
1220                                     dc);
1221 }
1222
1223
1224 /**
1225  * Download parts of a file.  Note that this will store
1226  * the blocks at the respective offset in the given file.  Also, the
1227  * download is still using the blocking of the underlying FS
1228  * encoding.  As a result, the download may *write* outside of the
1229  * given boundaries (if offset and length do not match the 32k FS
1230  * block boundaries). <p>
1231  *
1232  * This function should be used to focus a download towards a
1233  * particular portion of the file (optimization), not to strictly
1234  * limit the download to exactly those bytes.
1235  *
1236  * @param h handle to the file sharing subsystem
1237  * @param uri the URI of the file (determines what to download); CHK or LOC URI
1238  * @param meta known metadata for the file (can be NULL)
1239  * @param filename where to store the file, maybe NULL (then no file is
1240  *        created on disk and data must be grabbed from the callbacks)
1241  * @param offset at what offset should we start the download (typically 0)
1242  * @param length how many bytes should be downloaded starting at offset
1243  * @param anonymity anonymity level to use for the download
1244  * @param options various options
1245  * @param cctx initial value for the client context for this download
1246  * @param parent parent download to associate this download with (use NULL
1247  *        for top-level downloads; useful for manually-triggered recursive downloads)
1248  * @return context that can be used to control this download
1249  */
1250 struct GNUNET_FS_DownloadContext *
1251 GNUNET_FS_download_start (struct GNUNET_FS_Handle *h,
1252                           const struct GNUNET_FS_Uri *uri,
1253                           const struct GNUNET_CONTAINER_MetaData *meta,
1254                           const char *filename,
1255                           uint64_t offset,
1256                           uint64_t length,
1257                           uint32_t anonymity,
1258                           enum GNUNET_FS_DownloadOptions options,
1259                           void *cctx,
1260                           struct GNUNET_FS_DownloadContext *parent)
1261 {
1262   struct GNUNET_FS_ProgressInfo pi;
1263   struct GNUNET_FS_DownloadContext *dc;
1264   struct GNUNET_CLIENT_Connection *client;
1265
1266   GNUNET_assert (GNUNET_FS_uri_test_chk (uri));
1267   if ( (offset + length < offset) ||
1268        (offset + length > uri->data.chk.file_length) )
1269     {      
1270       GNUNET_break (0);
1271       return NULL;
1272     }
1273   // FIXME: add support for "loc" URIs!
1274 #if DEBUG_DOWNLOAD
1275   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1276               "Starting download `%s' of %llu bytes\n",
1277               filename,
1278               (unsigned long long) length);
1279 #endif
1280   dc = GNUNET_malloc (sizeof(struct GNUNET_FS_DownloadContext));
1281   dc->h = h;
1282   dc->parent = parent;
1283   if (parent != NULL)
1284     {
1285       GNUNET_CONTAINER_DLL_insert (parent->child_head,
1286                                    parent->child_tail,
1287                                    dc);
1288     }
1289   dc->uri = GNUNET_FS_uri_dup (uri);
1290   dc->meta = GNUNET_CONTAINER_meta_data_duplicate (meta);
1291   dc->client_info = cctx;
1292   dc->start_time = GNUNET_TIME_absolute_get ();
1293   if (NULL != filename)
1294     {
1295       dc->filename = GNUNET_strdup (filename);
1296       if (GNUNET_YES == GNUNET_DISK_file_test (filename))
1297         GNUNET_DISK_file_size (filename,
1298                                &dc->old_file_size,
1299                                GNUNET_YES);
1300       dc->handle = GNUNET_DISK_file_open (filename, 
1301                                           GNUNET_DISK_OPEN_READWRITE | 
1302                                           GNUNET_DISK_OPEN_CREATE,
1303                                           GNUNET_DISK_PERM_USER_READ |
1304                                           GNUNET_DISK_PERM_USER_WRITE |
1305                                           GNUNET_DISK_PERM_GROUP_READ |
1306                                           GNUNET_DISK_PERM_OTHER_READ);
1307       if (dc->handle == NULL)
1308         {
1309           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1310                       _("Download failed: could not open file `%s': %s\n"),
1311                       dc->filename,
1312                       STRERROR (errno));
1313           GNUNET_CONTAINER_meta_data_destroy (dc->meta);
1314           GNUNET_FS_uri_destroy (dc->uri);
1315           GNUNET_free (dc->filename);
1316           GNUNET_free (dc);
1317           return NULL;
1318         }
1319     }
1320   // FIXME: set "dc->target" for LOC uris!
1321   dc->offset = offset;
1322   dc->length = length;
1323   dc->anonymity = anonymity;
1324   dc->options = options;
1325   dc->active = GNUNET_CONTAINER_multihashmap_create (1 + 2 * (length / DBLOCK_SIZE));
1326   dc->treedepth = GNUNET_FS_compute_depth (GNUNET_ntohll(dc->uri->data.chk.file_length));
1327 #if DEBUG_DOWNLOAD
1328   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1329               "Download tree has depth %u\n",
1330               dc->treedepth);
1331 #endif
1332   // FIXME: make persistent
1333   
1334   // FIXME: bound parallelism here!
1335   client = GNUNET_CLIENT_connect (h->sched,
1336                                   "fs",
1337                                   h->cfg);
1338   dc->client = client;
1339   schedule_block_download (dc, 
1340                            &dc->uri->data.chk.chk,
1341                            0, 
1342                            1 /* 0 == CHK, 1 == top */);
1343   GNUNET_CLIENT_receive (client,
1344                          &receive_results,
1345                          dc,
1346                          GNUNET_TIME_UNIT_FOREVER_REL);
1347   pi.status = GNUNET_FS_STATUS_DOWNLOAD_START;
1348   make_download_status (&pi, dc);
1349   pi.value.download.specifics.start.meta = meta;
1350   dc->client_info = dc->h->upcb (dc->h->upcb_cls,
1351                                  &pi);
1352
1353   return dc;
1354 }
1355
1356
1357 /**
1358  * Free entries in the map.
1359  *
1360  * @param cls unused (NULL)
1361  * @param key unused
1362  * @param entry entry of type "struct DownloadRequest" which is freed
1363  * @return GNUNET_OK
1364  */
1365 static int
1366 free_entry (void *cls,
1367             const GNUNET_HashCode *key,
1368             void *entry)
1369 {
1370   GNUNET_free (entry);
1371   return GNUNET_OK;
1372 }
1373
1374
1375 /**
1376  * Stop a download (aborts if download is incomplete).
1377  *
1378  * @param dc handle for the download
1379  * @param do_delete delete files of incomplete downloads
1380  */
1381 void
1382 GNUNET_FS_download_stop (struct GNUNET_FS_DownloadContext *dc,
1383                          int do_delete)
1384 {
1385   struct GNUNET_FS_ProgressInfo pi;
1386
1387   while (NULL != dc->child_head)
1388     GNUNET_FS_download_stop (dc->child_head, 
1389                              do_delete);
1390   // FIXME: make unpersistent  
1391   if (dc->parent != NULL)
1392     GNUNET_CONTAINER_DLL_remove (dc->parent->child_head,
1393                                  dc->parent->child_tail,
1394                                  dc);
1395   
1396   pi.status = GNUNET_FS_STATUS_DOWNLOAD_STOPPED;
1397   make_download_status (&pi, dc);
1398   dc->client_info = dc->h->upcb (dc->h->upcb_cls,
1399                                  &pi);
1400
1401   if (GNUNET_SCHEDULER_NO_TASK != dc->task)
1402     GNUNET_SCHEDULER_cancel (dc->h->sched,
1403                              dc->task);
1404   if (NULL != dc->th)
1405     {
1406       GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
1407       dc->th = NULL;
1408     }
1409   if (NULL != dc->client)
1410     GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
1411   GNUNET_CONTAINER_multihashmap_iterate (dc->active,
1412                                          &free_entry,
1413                                          NULL);
1414   GNUNET_CONTAINER_multihashmap_destroy (dc->active);
1415   if (dc->filename != NULL)
1416     {
1417       if (NULL != dc->handle)
1418         GNUNET_DISK_file_close (dc->handle);
1419       if ( (dc->completed != dc->length) &&
1420            (GNUNET_YES == do_delete) )
1421         {
1422           if (0 != UNLINK (dc->filename))
1423             GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
1424                                       "unlink",
1425                                       dc->filename);
1426         }
1427       GNUNET_free (dc->filename);
1428     }
1429   GNUNET_CONTAINER_meta_data_destroy (dc->meta);
1430   GNUNET_FS_uri_destroy (dc->uri);
1431   GNUNET_free (dc);
1432 }
1433
1434 /* end of fs_download.c */