2 This file is part of GNUnet.
3 (C) 2001, 2002, 2003, 2004, 2005, 2006, 2008, 2009 Christian Grothoff (and other contributing authors)
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.
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.
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.
21 * @file fs/fs_download.c
22 * @brief download methods
23 * @author Christian Grothoff
26 * - handle recursive downloads (need directory &
27 * fs-level download-parallelism management)
28 * - location URI suppport (can wait, easy)
29 * - check if blocks exist already (can wait, easy)
30 * - check if iblocks can be computed from existing blocks (can wait, hard)
31 * - persistence (can wait)
34 #include "gnunet_constants.h"
35 #include "gnunet_fs_service.h"
39 #define DEBUG_DOWNLOAD GNUNET_NO
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).
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
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
62 compute_disk_offset (uint64_t fsize,
65 unsigned int treedepth)
68 uint64_t lsize; /* what is the size of all IBlocks for depth "i"? */
69 uint64_t loff; /* where do IBlocks for depth "i" start? */
70 unsigned int ioff; /* which IBlock corresponds to "off" at depth "i"? */
72 if (depth == treedepth)
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--)
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;
88 return loff + ioff * sizeof (struct ContentHashKey);
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
97 * @param offset the offset of the first
98 * DBLOCK in the subtree of the
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
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)
109 compute_dblock_offset (uint64_t offset,
111 unsigned int treedepth,
115 uint64_t lsize; /* what is the size of the sum of all DBlocks
116 that a CHK at depth i corresponds to? */
118 if (depth == treedepth)
121 for (i=treedepth-1;i>depth;i--)
122 lsize *= CHK_PER_INODE;
123 return offset + k * lsize;
128 * Fill in all of the generic fields for
131 * @param pi structure to fill in
132 * @param dc overall download context
135 make_download_status (struct GNUNET_FS_ProgressInfo *pi,
136 struct GNUNET_FS_DownloadContext *dc)
138 pi->value.download.dc = dc;
139 pi->value.download.cctx
141 pi->value.download.pctx
142 = (dc->parent == NULL) ? NULL : dc->parent->client_info;
143 pi->value.download.uri
145 pi->value.download.filename
147 pi->value.download.size
149 pi->value.download.duration
150 = GNUNET_TIME_absolute_get_duration (dc->start_time);
151 pi->value.download.completed
153 pi->value.download.anonymity
155 pi->value.download.eta
156 = GNUNET_TIME_calculate_eta (dc->start_time,
162 * We're ready to transmit a search request to the
163 * file-sharing service. Do it. If there is
164 * more than one request pending, try to send
165 * multiple or request another transmission.
168 * @param size number of bytes available in buf
169 * @param buf where the callee should write the message
170 * @return number of bytes written to buf
173 transmit_download_request (void *cls,
179 * Schedule the download of the specified
182 * @param dc overall download this block belongs to
183 * @param chk content-hash-key of the block
184 * @param offset offset of the block in the file
185 * (for IBlocks, the offset is the lowest
186 * offset of any DBlock in the subtree under
188 * @param depth depth of the block, 0 is the root of the tree
191 schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
192 const struct ContentHashKey *chk,
196 struct DownloadRequest *sm;
200 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
201 "Scheduling download at offset %llu and depth %u for `%s'\n",
202 (unsigned long long) offset,
204 GNUNET_h2s (&chk->query));
206 off = compute_disk_offset (GNUNET_ntohll (dc->uri->data.chk.file_length),
210 if ( (dc->old_file_size > off) &&
211 (dc->handle != NULL) &&
213 GNUNET_DISK_file_seek (dc->handle,
215 GNUNET_DISK_SEEK_SET) ) )
217 // FIXME: check if block exists on disk!
218 // (read block, encode, compare with
219 // query; if matches, simply return)
221 if (depth < dc->treedepth)
223 // FIXME: try if we could
224 // reconstitute this IBLOCK
225 // from the existing blocks on disk (can wait)
226 // (read block(s), encode, compare with
227 // query; if matches, simply return)
229 sm = GNUNET_malloc (sizeof (struct DownloadRequest));
233 sm->is_pending = GNUNET_YES;
234 sm->next = dc->pending;
236 GNUNET_CONTAINER_multihashmap_put (dc->active,
239 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
241 if ( (dc->th == NULL) &&
242 (dc->client != NULL) )
243 dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
244 sizeof (struct SearchMessage),
245 GNUNET_CONSTANTS_SERVICE_TIMEOUT,
247 &transmit_download_request,
254 * We've lost our connection with the FS service.
255 * Re-establish it and re-transmit all of our
258 * @param dc download context that is having trouble
261 try_reconnect (struct GNUNET_FS_DownloadContext *dc);
265 * Compute how many bytes of data should be stored in
266 * the specified node.
268 * @param fsize overall file size
269 * @param totaldepth depth of the entire tree
270 * @param offset offset of the node
271 * @param depth depth of the node
272 * @return number of bytes stored in this node
275 calculate_block_size (uint64_t fsize,
276 unsigned int totaldepth,
286 GNUNET_assert (offset < fsize);
287 if (depth == totaldepth)
290 if (offset + ret > fsize)
291 ret = (size_t) (fsize - offset);
296 for (i = totaldepth-1; i > depth; i--)
297 rsize *= CHK_PER_INODE;
298 epos = offset + rsize * CHK_PER_INODE;
299 GNUNET_assert (epos > offset);
302 /* round up when computing #CHKs in our IBlock */
303 chks = (epos - offset + rsize - 1) / rsize;
304 GNUNET_assert (chks <= CHK_PER_INODE);
305 return chks * sizeof (struct ContentHashKey);
310 * Closure for iterator processing results.
312 struct ProcessResultClosure
318 GNUNET_HashCode query;
321 * Data found in P2P network.
326 * Our download context.
328 struct GNUNET_FS_DownloadContext *dc;
331 * Number of bytes in data.
344 * Iterator over entries in the pending requests in the 'active' map for the
345 * reply that we just got.
347 * @param cls closure (our 'struct ProcessResultClosure')
348 * @param key query for the given value / request
349 * @param value value in the hash map (a 'struct DownloadRequest')
350 * @return GNUNET_YES (we should continue to iterate); unless serious error
353 process_result_with_request (void *cls,
354 const GNUNET_HashCode * key,
357 struct ProcessResultClosure *prc = cls;
358 struct DownloadRequest *sm = value;
359 struct GNUNET_FS_DownloadContext *dc = prc->dc;
360 struct GNUNET_CRYPTO_AesSessionKey skey;
361 struct GNUNET_CRYPTO_AesInitializationVector iv;
363 struct GNUNET_FS_ProgressInfo pi;
367 struct ContentHashKey *chk;
370 if (prc->size != calculate_block_size (GNUNET_ntohll (dc->uri->data.chk.file_length),
376 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
377 "Internal error or bogus download URI (expected %u bytes, got %u)\n",
378 calculate_block_size (GNUNET_ntohll (dc->uri->data.chk.file_length),
384 dc->emsg = GNUNET_strdup ("Internal error or bogus download URI");
386 pi.status = GNUNET_FS_STATUS_DOWNLOAD_ERROR;
387 make_download_status (&pi, dc);
388 pi.value.download.specifics.error.message = dc->emsg;
389 dc->client_info = dc->h->upcb (dc->h->upcb_cls,
391 /* abort all pending requests */
394 GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
397 GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
401 GNUNET_assert (GNUNET_YES ==
402 GNUNET_CONTAINER_multihashmap_remove (dc->active,
405 GNUNET_CRYPTO_hash_to_aes_key (&sm->chk.key, &skey, &iv);
406 GNUNET_CRYPTO_aes_decrypt (prc->data,
412 if ( (NULL != dc->handle) &&
413 ( (sm->depth == dc->treedepth) ||
414 (0 == (dc->options & GNUNET_FS_DOWNLOAD_NO_TEMPORARIES)) ) )
416 off = compute_disk_offset (GNUNET_ntohll (dc->uri->data.chk.file_length),
422 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
423 "Saving decrypted block to disk at offset %llu\n",
424 (unsigned long long) off);
427 GNUNET_DISK_file_seek (dc->handle,
429 GNUNET_DISK_SEEK_SET) ) )
430 GNUNET_asprintf (&emsg,
431 _("Failed to seek to offset %llu in file `%s': %s\n"),
432 (unsigned long long) off,
435 else if (prc->size !=
436 GNUNET_DISK_file_write (dc->handle,
439 GNUNET_asprintf (&emsg,
440 _("Failed to write block of %u bytes at offset %llu in file `%s': %s\n"),
441 (unsigned int) prc->size,
442 (unsigned long long) off,
448 // FIXME: make persistent
451 pi.status = GNUNET_FS_STATUS_DOWNLOAD_ERROR;
452 make_download_status (&pi, dc);
453 pi.value.download.specifics.error.message = emsg;
454 dc->client_info = dc->h->upcb (dc->h->upcb_cls,
457 /* abort all pending requests */
460 GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
463 GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
469 if (sm->depth == dc->treedepth)
472 if (sm->offset < dc->offset)
474 /* starting offset begins in the middle of pt,
475 do not count first bytes as progress */
476 GNUNET_assert (app > (dc->offset - sm->offset));
477 app -= (dc->offset - sm->offset);
479 if (sm->offset + prc->size > dc->offset + dc->length)
481 /* end of block is after relevant range,
482 do not count last bytes as progress */
483 GNUNET_assert (app > (sm->offset + prc->size) - (dc->offset + dc->length));
484 app -= (sm->offset + prc->size) - (dc->offset + dc->length);
486 dc->completed += app;
489 pi.status = GNUNET_FS_STATUS_DOWNLOAD_PROGRESS;
490 make_download_status (&pi, dc);
491 pi.value.download.specifics.progress.data = pt;
492 pi.value.download.specifics.progress.offset = sm->offset;
493 pi.value.download.specifics.progress.data_len = prc->size;
494 pi.value.download.specifics.progress.depth = sm->depth;
495 dc->client_info = dc->h->upcb (dc->h->upcb_cls,
497 GNUNET_assert (dc->completed <= dc->length);
498 if (dc->completed == dc->length)
501 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
502 "Download completed, truncating file to desired length %llu\n",
503 (unsigned long long) GNUNET_ntohll (dc->uri->data.chk.file_length));
505 /* truncate file to size (since we store IBlocks at the end) */
506 if (dc->handle != NULL)
508 GNUNET_DISK_file_close (dc->handle);
510 if (0 != truncate (dc->filename,
511 GNUNET_ntohll (dc->uri->data.chk.file_length)))
512 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
516 /* signal completion */
517 pi.status = GNUNET_FS_STATUS_DOWNLOAD_COMPLETED;
518 make_download_status (&pi, dc);
519 dc->client_info = dc->h->upcb (dc->h->upcb_cls,
521 GNUNET_assert (sm->depth == dc->treedepth);
523 // FIXME: make persistent
524 if (sm->depth == dc->treedepth)
530 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
531 "Triggering downloads of children (this block was at depth %u and offset %llu)\n",
533 (unsigned long long) sm->offset);
535 GNUNET_assert (0 == (prc->size % sizeof(struct ContentHashKey)));
536 chk = (struct ContentHashKey*) pt;
537 for (i=(prc->size / sizeof(struct ContentHashKey))-1;i>=0;i--)
539 off = compute_dblock_offset (sm->offset,
543 if ( (off + DBLOCK_SIZE >= dc->offset) &&
544 (off < dc->offset + dc->length) )
545 schedule_block_download (dc,
556 * Process a download result.
558 * @param dc our download context
559 * @param type type of the result
560 * @param data the (encrypted) response
561 * @param size size of data
564 process_result (struct GNUNET_FS_DownloadContext *dc,
569 struct ProcessResultClosure prc;
575 GNUNET_CRYPTO_hash (data, size, &prc.query);
577 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
578 "Received result for query `%s' from `%s'-service\n",
579 GNUNET_h2s (&prc.query),
582 GNUNET_CONTAINER_multihashmap_get_multiple (dc->active,
584 &process_result_with_request,
590 * Type of a function to call when we receive a message
594 * @param msg message received, NULL on timeout or fatal error
597 receive_results (void *cls,
598 const struct GNUNET_MessageHeader * msg)
600 struct GNUNET_FS_DownloadContext *dc = cls;
601 const struct PutMessage *cm;
604 if ( (NULL == msg) ||
605 (ntohs (msg->type) != GNUNET_MESSAGE_TYPE_FS_PUT) ||
606 (sizeof (struct PutMessage) > ntohs(msg->size)) )
608 GNUNET_break (msg == NULL);
612 msize = ntohs(msg->size);
613 cm = (const struct PutMessage*) msg;
617 msize - sizeof (struct PutMessage));
618 if (dc->client == NULL)
619 return; /* fatal error */
620 /* continue receiving */
621 GNUNET_CLIENT_receive (dc->client,
624 GNUNET_TIME_UNIT_FOREVER_REL);
630 * We're ready to transmit a search request to the
631 * file-sharing service. Do it. If there is
632 * more than one request pending, try to send
633 * multiple or request another transmission.
636 * @param size number of bytes available in buf
637 * @param buf where the callee should write the message
638 * @return number of bytes written to buf
641 transmit_download_request (void *cls,
645 struct GNUNET_FS_DownloadContext *dc = cls;
647 struct SearchMessage *sm;
655 GNUNET_assert (size >= sizeof (struct SearchMessage));
658 while ( (dc->pending != NULL) &&
659 (size > msize + sizeof (struct SearchMessage)) )
662 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
663 "Transmitting download request for `%s' to `%s'-service\n",
664 GNUNET_h2s (&dc->pending->chk.query),
667 memset (sm, 0, sizeof (struct SearchMessage));
668 sm->header.size = htons (sizeof (struct SearchMessage));
669 sm->header.type = htons (GNUNET_MESSAGE_TYPE_FS_START_SEARCH);
670 if (dc->pending->depth == dc->treedepth)
671 sm->type = htonl (GNUNET_DATASTORE_BLOCKTYPE_DBLOCK);
673 sm->type = htonl (GNUNET_DATASTORE_BLOCKTYPE_IBLOCK);
674 sm->anonymity_level = htonl (dc->anonymity);
675 sm->target = dc->target.hashPubKey;
676 sm->query = dc->pending->chk.query;
677 dc->pending->is_pending = GNUNET_NO;
678 dc->pending = dc->pending->next;
679 msize += sizeof (struct SearchMessage);
682 if (dc->pending != NULL)
683 dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
684 sizeof (struct SearchMessage),
685 GNUNET_CONSTANTS_SERVICE_TIMEOUT,
687 &transmit_download_request,
694 * Reconnect to the FS service and transmit our queries NOW.
696 * @param cls our download context
700 do_reconnect (void *cls,
701 const struct GNUNET_SCHEDULER_TaskContext *tc)
703 struct GNUNET_FS_DownloadContext *dc = cls;
704 struct GNUNET_CLIENT_Connection *client;
706 dc->task = GNUNET_SCHEDULER_NO_TASK;
707 client = GNUNET_CLIENT_connect (dc->h->sched,
712 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
713 "Connecting to `%s'-service failed, will try again.\n",
719 dc->th = GNUNET_CLIENT_notify_transmit_ready (client,
720 sizeof (struct SearchMessage),
721 GNUNET_CONSTANTS_SERVICE_TIMEOUT,
723 &transmit_download_request,
725 GNUNET_CLIENT_receive (client,
728 GNUNET_TIME_UNIT_FOREVER_REL);
733 * Add entries that are not yet pending back to the pending list.
735 * @param cls our download context
737 * @param entry entry of type "struct DownloadRequest"
741 retry_entry (void *cls,
742 const GNUNET_HashCode *key,
745 struct GNUNET_FS_DownloadContext *dc = cls;
746 struct DownloadRequest *dr = entry;
748 if (! dr->is_pending)
750 dr->next = dc->pending;
751 dr->is_pending = GNUNET_YES;
759 * We've lost our connection with the FS service.
760 * Re-establish it and re-transmit all of our
763 * @param dc download context that is having trouble
766 try_reconnect (struct GNUNET_FS_DownloadContext *dc)
769 if (NULL != dc->client)
773 GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
776 GNUNET_CONTAINER_multihashmap_iterate (dc->active,
779 GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
783 = GNUNET_SCHEDULER_add_delayed (dc->h->sched,
784 GNUNET_TIME_UNIT_SECONDS,
791 * Download parts of a file. Note that this will store
792 * the blocks at the respective offset in the given file. Also, the
793 * download is still using the blocking of the underlying FS
794 * encoding. As a result, the download may *write* outside of the
795 * given boundaries (if offset and length do not match the 32k FS
796 * block boundaries). <p>
798 * This function should be used to focus a download towards a
799 * particular portion of the file (optimization), not to strictly
800 * limit the download to exactly those bytes.
802 * @param h handle to the file sharing subsystem
803 * @param uri the URI of the file (determines what to download); CHK or LOC URI
804 * @param meta known metadata for the file (can be NULL)
805 * @param filename where to store the file, maybe NULL (then no file is
806 * created on disk and data must be grabbed from the callbacks)
807 * @param offset at what offset should we start the download (typically 0)
808 * @param length how many bytes should be downloaded starting at offset
809 * @param anonymity anonymity level to use for the download
810 * @param options various options
811 * @param cctx initial value for the client context for this download
812 * @param parent parent download to associate this download with (use NULL
813 * for top-level downloads; useful for manually-triggered recursive downloads)
814 * @return context that can be used to control this download
816 struct GNUNET_FS_DownloadContext *
817 GNUNET_FS_download_start (struct GNUNET_FS_Handle *h,
818 const struct GNUNET_FS_Uri *uri,
819 const struct GNUNET_CONTAINER_MetaData *meta,
820 const char *filename,
824 enum GNUNET_FS_DownloadOptions options,
826 struct GNUNET_FS_DownloadContext *parent)
828 struct GNUNET_FS_ProgressInfo pi;
829 struct GNUNET_FS_DownloadContext *dc;
830 struct GNUNET_CLIENT_Connection *client;
832 GNUNET_assert (GNUNET_FS_uri_test_chk (uri));
833 if ( (offset + length < offset) ||
834 (offset + length > uri->data.chk.file_length) )
839 client = GNUNET_CLIENT_connect (h->sched,
844 // FIXME: add support for "loc" URIs!
846 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
847 "Starting download `%s' of %llu bytes\n",
849 (unsigned long long) length);
851 dc = GNUNET_malloc (sizeof(struct GNUNET_FS_DownloadContext));
855 dc->uri = GNUNET_FS_uri_dup (uri);
856 dc->meta = GNUNET_CONTAINER_meta_data_duplicate (meta);
857 dc->client_info = cctx;
858 dc->start_time = GNUNET_TIME_absolute_get ();
859 if (NULL != filename)
861 dc->filename = GNUNET_strdup (filename);
862 if (GNUNET_YES == GNUNET_DISK_file_test (filename))
863 GNUNET_DISK_file_size (filename,
866 dc->handle = GNUNET_DISK_file_open (filename,
867 GNUNET_DISK_OPEN_READWRITE |
868 GNUNET_DISK_OPEN_CREATE,
869 GNUNET_DISK_PERM_USER_READ |
870 GNUNET_DISK_PERM_USER_WRITE |
871 GNUNET_DISK_PERM_GROUP_READ |
872 GNUNET_DISK_PERM_OTHER_READ);
873 if (dc->handle == NULL)
875 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
876 _("Download failed: could not open file `%s': %s\n"),
879 GNUNET_CONTAINER_meta_data_destroy (dc->meta);
880 GNUNET_FS_uri_destroy (dc->uri);
881 GNUNET_free (dc->filename);
882 GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
887 // FIXME: set "dc->target" for LOC uris!
890 dc->anonymity = anonymity;
891 dc->options = options;
892 dc->active = GNUNET_CONTAINER_multihashmap_create (2 * (length / DBLOCK_SIZE));
893 dc->treedepth = GNUNET_FS_compute_depth (GNUNET_ntohll(dc->uri->data.chk.file_length));
895 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
896 "Download tree has depth %u\n",
899 // FIXME: make persistent
900 schedule_block_download (dc,
901 &dc->uri->data.chk.chk,
903 1 /* 0 == CHK, 1 == top */);
904 GNUNET_CLIENT_receive (client,
907 GNUNET_TIME_UNIT_FOREVER_REL);
908 pi.status = GNUNET_FS_STATUS_DOWNLOAD_START;
909 make_download_status (&pi, dc);
910 pi.value.download.specifics.start.meta = meta;
911 dc->client_info = dc->h->upcb (dc->h->upcb_cls,
919 * Free entries in the map.
921 * @param cls unused (NULL)
923 * @param entry entry of type "struct DownloadRequest" which is freed
927 free_entry (void *cls,
928 const GNUNET_HashCode *key,
937 * Stop a download (aborts if download is incomplete).
939 * @param dc handle for the download
940 * @param do_delete delete files of incomplete downloads
943 GNUNET_FS_download_stop (struct GNUNET_FS_DownloadContext *dc,
946 struct GNUNET_FS_ProgressInfo pi;
948 // FIXME: make unpersistent
949 pi.status = GNUNET_FS_STATUS_DOWNLOAD_STOPPED;
950 make_download_status (&pi, dc);
951 dc->client_info = dc->h->upcb (dc->h->upcb_cls,
954 if (GNUNET_SCHEDULER_NO_TASK != dc->task)
955 GNUNET_SCHEDULER_cancel (dc->h->sched,
959 GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
962 if (NULL != dc->client)
963 GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
964 GNUNET_CONTAINER_multihashmap_iterate (dc->active,
967 GNUNET_CONTAINER_multihashmap_destroy (dc->active);
968 if (dc->filename != NULL)
970 if (NULL != dc->handle)
971 GNUNET_DISK_file_close (dc->handle);
972 if ( (dc->completed != dc->length) &&
973 (GNUNET_YES == do_delete) )
975 if (0 != UNLINK (dc->filename))
976 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
980 GNUNET_free (dc->filename);
982 GNUNET_CONTAINER_meta_data_destroy (dc->meta);
983 GNUNET_FS_uri_destroy (dc->uri);
987 /* end of fs_download.c */