2 This file is part of GNUnet.
3 (C) 2001, 2002, 2003, 2004, 2005, 2006, 2008 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 helper methods (which do the real work).
23 * @author Christian Grothoff
27 #include "gnunet_fs_service.h"
30 #define DEBUG_DOWNLOAD GNUNET_YES
35 * Download parts of a file. Note that this will store
36 * the blocks at the respective offset in the given file. Also, the
37 * download is still using the blocking of the underlying FS
38 * encoding. As a result, the download may *write* outside of the
39 * given boundaries (if offset and length do not match the 32k FS
40 * block boundaries). <p>
42 * This function should be used to focus a download towards a
43 * particular portion of the file (optimization), not to strictly
44 * limit the download to exactly those bytes.
46 * @param h handle to the file sharing subsystem
47 * @param uri the URI of the file (determines what to download); CHK or LOC URI
48 * @param filename where to store the file, maybe NULL (then no file is
49 * created on disk and data must be grabbed from the callbacks)
50 * @param offset at what offset should we start the download (typically 0)
51 * @param length how many bytes should be downloaded starting at offset
52 * @param anonymity anonymity level to use for the download
53 * @param no_temporaries set to GNUNET_YES to disallow generation of temporary files
54 * @param recursive should this be a recursive download (useful for directories
55 * to automatically trigger download of files in the directories)
56 * @param parent parent download to associate this download with (use NULL
57 * for top-level downloads; useful for manually-triggered recursive downloads)
58 * @return context that can be used to control this download
60 struct GNUNET_FS_DownloadContext *
61 GNUNET_FS_file_download_start (struct GNUNET_FS_Handle *h,
62 const struct GNUNET_FS_Uri *uri,
64 unsigned long long offset,
65 unsigned long long length,
66 unsigned int anonymity,
69 struct GNUNET_FS_DownloadContext *parent)
75 * Stop a download (aborts if download is incomplete).
77 * @param rm handle for the download
78 * @param do_delete delete files of incomplete downloads
81 GNUNET_FS_file_download_stop (struct GNUNET_FS_DownloadContext *rm,
92 * Node-specific data (not shared, keep small!). 152 bytes.
93 * Nodes are kept in a doubly-linked list.
98 * Pointer to shared data between all nodes (request manager,
99 * progress data, etc.).
101 struct GNUNET_ECRS_DownloadContext *ctx;
104 * Previous entry in DLL.
114 * What is the GNUNET_EC_ContentHashKey for this block?
116 GNUNET_EC_ContentHashKey chk;
119 * At what offset (on the respective level!) is this
122 unsigned long long offset;
125 * 0 for dblocks, >0 for iblocks.
132 * @brief structure that keeps track of currently pending requests for
135 * Handle to the state of a request manager. Here we keep track of
136 * which queries went out with which priorities and which nodes in
137 * the merkle-tree are waiting for the replies.
139 struct GNUNET_ECRS_DownloadContext
143 * Total number of bytes in the file.
145 unsigned long long total;
148 * Number of bytes already obtained
150 unsigned long long completed;
153 * Starting-offset in file (for partial download)
155 unsigned long long offset;
158 * Length of the download (starting at offset).
160 unsigned long long length;
163 * Time download was started.
165 GNUNET_CronTime startTime;
168 * Doubly linked list of all pending requests (head)
173 * Doubly linked list of all pending requests (tail)
178 * FSLIB context for issuing requests.
180 struct GNUNET_FS_SearchContext *sctx;
183 * Context for error reporting.
185 struct GNUNET_GE_Context *ectx;
188 * Configuration information.
190 struct GNUNET_GC_Configuration *cfg;
198 * Do we exclusively own this sctx?
208 * Main thread running the operation.
210 struct GNUNET_ThreadHandle *main;
213 * Function to call when we make progress.
215 GNUNET_ECRS_DownloadProgressCallback dpcb;
218 * Extra argument to dpcb.
223 * Identity of the peer having the content, or all-zeros
224 * if we don't know of such a peer.
226 GNUNET_PeerIdentity target;
229 * Abort? Flag that can be set at any time
230 * to abort the RM as soon as possible. Set
231 * to GNUNET_YES during orderly shutdown,
232 * set to GNUNET_SYSERR on error.
237 * Do we have a specific peer from which we download
243 * Desired anonymity level for the download.
245 unsigned int anonymityLevel;
248 * The depth of the file-tree.
250 unsigned int treedepth;
255 content_receive_callback (const GNUNET_HashCode * query,
256 const GNUNET_DatastoreValue * reply, void *cls,
257 unsigned long long uid);
261 * Close the files and free the associated resources.
263 * @param self reference to the download context
266 free_request_manager (struct GNUNET_ECRS_DownloadContext *rm)
270 if (rm->abortFlag == GNUNET_NO)
271 rm->abortFlag = GNUNET_YES;
272 if (rm->my_sctx == GNUNET_YES)
273 GNUNET_FS_destroy_search_context (rm->sctx);
275 GNUNET_FS_suspend_search_context (rm->sctx);
276 while (rm->head != NULL)
279 GNUNET_DLL_remove (rm->head, rm->tail, pos);
280 if (rm->my_sctx != GNUNET_YES)
281 GNUNET_FS_stop_search (rm->sctx, &content_receive_callback, pos);
284 if (rm->my_sctx != GNUNET_YES)
285 GNUNET_FS_resume_search_context (rm->sctx);
286 GNUNET_GE_ASSERT (NULL, rm->tail == NULL);
289 if (rm->main != NULL)
290 GNUNET_thread_release_self (rm->main);
291 GNUNET_free_non_null (rm->filename);
299 * @param self reference to the download context
300 * @param level level in the tree to read/write at
301 * @param pos position where to read or write
302 * @param buf where to read from or write to
303 * @param len how many bytes to read or write
304 * @return number of bytes read, GNUNET_SYSERR on error
307 read_from_files (struct GNUNET_ECRS_DownloadContext *self,
309 unsigned long long pos, void *buf, unsigned int len)
311 if ((level > 0) || (self->handle == -1))
312 return GNUNET_SYSERR;
313 LSEEK (self->handle, pos, SEEK_SET);
314 return READ (self->handle, buf, len);
320 * @param self reference to the download context
321 * @param level level in the tree to write to
322 * @param pos position where to write
323 * @param buf where to write to
324 * @param len how many bytes to write
325 * @return number of bytes written, GNUNET_SYSERR on error
328 write_to_files (struct GNUNET_ECRS_DownloadContext *self,
330 unsigned long long pos, void *buf, unsigned int len)
335 return len; /* lie -- no more temps */
336 if (self->handle == -1)
338 LSEEK (self->handle, pos, SEEK_SET);
339 ret = WRITE (self->handle, buf, len);
341 GNUNET_GE_LOG_STRERROR_FILE (self->ectx,
342 GNUNET_GE_ERROR | GNUNET_GE_BULK |
343 GNUNET_GE_USER, "write", self->filename);
348 * Queue a request for execution.
350 * @param rm the request manager struct from createRequestManager
351 * @param node the node to call once a reply is received
354 add_request (struct Node *node)
356 struct GNUNET_ECRS_DownloadContext *rm = node->ctx;
358 GNUNET_DLL_insert (rm->head, rm->tail, node);
359 GNUNET_FS_start_search (rm->sctx,
360 rm->have_target == GNUNET_NO ? NULL : &rm->target,
361 GNUNET_ECRS_BLOCKTYPE_DATA, 1,
364 &content_receive_callback, node);
368 signal_abort (struct GNUNET_ECRS_DownloadContext *rm, const char *msg)
370 rm->abortFlag = GNUNET_SYSERR;
371 if ((rm->head != NULL) && (rm->dpcb != NULL))
372 rm->dpcb (rm->length + 1, 0, 0, 0, msg, 0, rm->dpcbClosure);
373 GNUNET_thread_stop_sleep (rm->main);
379 * @param self the request manager struct from createRequestManager
380 * @param node the block for which the request is canceled
383 delete_node (struct Node *node)
385 struct GNUNET_ECRS_DownloadContext *rm = node->ctx;
387 GNUNET_DLL_remove (rm->head, rm->tail, node);
389 if (rm->head == NULL)
390 GNUNET_thread_stop_sleep (rm->main);
394 * Compute how many bytes of data are stored in
398 get_node_size (const struct Node *node)
402 unsigned long long rsize;
403 unsigned long long spos;
404 unsigned long long epos;
406 GNUNET_GE_ASSERT (node->ctx->ectx, node->offset < node->ctx->total);
407 if (node->level == 0)
409 ret = GNUNET_ECRS_DBLOCK_SIZE;
410 if (node->offset + (unsigned long long) ret > node->ctx->total)
411 ret = (unsigned int) (node->ctx->total - node->offset);
413 GNUNET_GE_LOG (node->ctx->rm->ectx,
414 GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER,
415 "Node at offset %llu and level %d has size %u\n",
416 node->offset, node->level, ret);
420 rsize = GNUNET_ECRS_DBLOCK_SIZE;
421 for (i = 0; i < node->level - 1; i++)
422 rsize *= GNUNET_ECRS_CHK_PER_INODE;
423 spos = rsize * (node->offset / sizeof (GNUNET_EC_ContentHashKey));
424 epos = spos + rsize * GNUNET_ECRS_CHK_PER_INODE;
425 if (epos > node->ctx->total)
426 epos = node->ctx->total;
427 ret = (epos - spos) / rsize;
428 if (ret * rsize < epos - spos)
429 ret++; /* need to round up! */
431 GNUNET_GE_LOG (node->ctx->rm->ectx,
432 GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER,
433 "Node at offset %llu and level %d has size %u\n",
434 node->offset, node->level,
435 ret * sizeof (GNUNET_EC_ContentHashKey));
437 return ret * sizeof (GNUNET_EC_ContentHashKey);
441 * Notify client about progress.
444 notify_client_about_progress (const struct Node *node,
445 const char *data, unsigned int size)
447 struct GNUNET_ECRS_DownloadContext *rm = node->ctx;
450 if ((rm->abortFlag != GNUNET_NO) || (node->level != 0))
452 rm->completed += size;
453 eta = GNUNET_get_time ();
454 if (rm->completed > 0)
455 eta = (GNUNET_CronTime) (rm->startTime +
456 (((double) (eta - rm->startTime) /
457 (double) rm->completed)) *
458 (double) rm->length);
459 if (rm->dpcb != NULL)
460 rm->dpcb (rm->length,
461 rm->completed, eta, node->offset, data, size, rm->dpcbClosure);
466 * DOWNLOAD children of this GNUNET_EC_IBlock.
468 * @param node the node for which the children should be downloaded
469 * @param data data for the node
470 * @param size size of data
472 static void iblock_download_children (const struct Node *node,
473 const char *data, unsigned int size);
476 * Check if self block is already present on the drive. If the block
477 * is a dblock and present, the ProgressModel is notified. If the
478 * block is present and it is an iblock, downloading the children is
481 * Also checks if the block is within the range of blocks
482 * that we are supposed to download. If not, the method
483 * returns as if the block is present but does NOT signal
486 * @param node that is checked for presence
487 * @return GNUNET_YES if present, GNUNET_NO if not.
490 check_node_present (const struct Node *node)
498 size = get_node_size (node);
499 /* first check if node is within range.
500 For now, keeping it simple, we only do
501 this for level-0 nodes */
502 if ((node->level == 0) &&
503 ((node->offset + size < node->ctx->offset) ||
504 (node->offset >= node->ctx->offset + node->ctx->length)))
506 data = GNUNET_malloc (size);
508 res = read_from_files (node->ctx, node->level, node->offset, data, size);
511 GNUNET_hash (data, size, &hc);
512 if (0 == memcmp (&hc, &node->chk.key, sizeof (GNUNET_HashCode)))
514 notify_client_about_progress (node, data, size);
516 iblock_download_children (node, data, size);
525 * DOWNLOAD children of this GNUNET_EC_IBlock.
527 * @param node the node that should be downloaded
530 iblock_download_children (const struct Node *node,
531 const char *data, unsigned int size)
533 struct GNUNET_GE_Context *ectx = node->ctx->ectx;
536 unsigned int childcount;
537 const GNUNET_EC_ContentHashKey *chks;
538 unsigned int levelSize;
539 unsigned long long baseOffset;
541 GNUNET_GE_ASSERT (ectx, node->level > 0);
542 childcount = size / sizeof (GNUNET_EC_ContentHashKey);
543 if (size != childcount * sizeof (GNUNET_EC_ContentHashKey))
545 GNUNET_GE_BREAK (ectx, 0);
548 if (node->level == 1)
550 levelSize = GNUNET_ECRS_DBLOCK_SIZE;
552 node->offset / sizeof (GNUNET_EC_ContentHashKey) *
553 GNUNET_ECRS_DBLOCK_SIZE;
558 sizeof (GNUNET_EC_ContentHashKey) * GNUNET_ECRS_CHK_PER_INODE;
559 baseOffset = node->offset * GNUNET_ECRS_CHK_PER_INODE;
561 chks = (const GNUNET_EC_ContentHashKey *) data;
562 for (i = 0; i < childcount; i++)
564 child = GNUNET_malloc (sizeof (struct Node));
565 child->ctx = node->ctx;
566 child->chk = chks[i];
567 child->offset = baseOffset + i * levelSize;
568 GNUNET_GE_ASSERT (ectx, child->offset < node->ctx->total);
569 child->level = node->level - 1;
570 GNUNET_GE_ASSERT (ectx, (child->level != 0) ||
571 ((child->offset % GNUNET_ECRS_DBLOCK_SIZE) == 0));
572 if (GNUNET_NO == check_node_present (child))
575 GNUNET_free (child); /* done already! */
581 * Decrypts a given data block
583 * @param data represents the data block
584 * @param hashcode represents the key concatenated with the initial
585 * value used in the alg
586 * @param result where to store the result (encrypted block)
587 * @returns GNUNET_OK on success, GNUNET_SYSERR on error
590 decrypt_content (const char *data,
591 unsigned int size, const GNUNET_HashCode * hashcode,
594 GNUNET_AES_InitializationVector iv;
595 GNUNET_AES_SessionKey skey;
597 /* get key and init value from the GNUNET_HashCode */
598 GNUNET_hash_to_AES_key (hashcode, &skey, &iv);
599 return GNUNET_AES_decrypt (&skey, data, size, &iv, result);
603 * We received a GNUNET_EC_ContentHashKey reply for a block. Decrypt. Note
604 * that the caller (fslib) has already aquired the
605 * RM lock (we sometimes aquire it again in callees,
606 * mostly because our callees could be also be theoretically
607 * called from elsewhere).
609 * @param cls the node for which the reply is given, freed in
611 * @param query the query for which reply is the answer
612 * @param reply the reply
613 * @return GNUNET_OK if the reply was valid, GNUNET_SYSERR on error
616 content_receive_callback (const GNUNET_HashCode * query,
617 const GNUNET_DatastoreValue * reply, void *cls,
618 unsigned long long uid)
620 struct Node *node = cls;
621 struct GNUNET_ECRS_DownloadContext *rm = node->ctx;
622 struct GNUNET_GE_Context *ectx = rm->ectx;
627 if (rm->abortFlag != GNUNET_NO)
628 return GNUNET_SYSERR;
629 GNUNET_GE_ASSERT (ectx,
630 0 == memcmp (query, &node->chk.query,
631 sizeof (GNUNET_HashCode)));
632 size = ntohl (reply->size) - sizeof (GNUNET_DatastoreValue);
633 if ((size <= sizeof (GNUNET_EC_DBlock)) ||
634 (size - sizeof (GNUNET_EC_DBlock) != get_node_size (node)))
636 GNUNET_GE_BREAK (ectx, 0);
637 return GNUNET_SYSERR; /* invalid size! */
639 size -= sizeof (GNUNET_EC_DBlock);
640 data = GNUNET_malloc (size);
642 decrypt_content ((const char *)
643 &((const GNUNET_EC_DBlock *) &reply[1])[1], size,
644 &node->chk.key, data))
645 GNUNET_GE_ASSERT (ectx, 0);
646 GNUNET_hash (data, size, &hc);
647 if (0 != memcmp (&hc, &node->chk.key, sizeof (GNUNET_HashCode)))
650 GNUNET_GE_BREAK (ectx, 0);
652 _("Decrypted content does not match key. "
653 "This is either a bug or a maliciously inserted "
654 "file. Download aborted.\n"));
655 return GNUNET_SYSERR;
657 if (size != write_to_files (rm, node->level, node->offset, data, size))
659 GNUNET_GE_LOG_STRERROR (ectx,
660 GNUNET_GE_ERROR | GNUNET_GE_ADMIN |
661 GNUNET_GE_USER | GNUNET_GE_BULK, "WRITE");
662 signal_abort (rm, _("IO error."));
663 return GNUNET_SYSERR;
665 notify_client_about_progress (node, data, size);
667 iblock_download_children (node, data, size);
669 /* request satisfied, stop requesting! */
676 * Helper function to sanitize filename
677 * and create necessary directories.
680 get_real_download_filename (struct GNUNET_GE_Context *ectx,
681 const char *filename)
688 if ((filename[strlen (filename) - 1] == '/') ||
689 (filename[strlen (filename) - 1] == '\\'))
692 GNUNET_malloc (strlen (filename) + strlen (GNUNET_DIRECTORY_EXT));
693 strcpy (realFN, filename);
694 realFN[strlen (filename) - 1] = '\0';
695 strcat (realFN, GNUNET_DIRECTORY_EXT);
699 realFN = GNUNET_strdup (filename);
701 path = GNUNET_malloc (strlen (realFN) * strlen (GNUNET_DIRECTORY_EXT) + 1);
702 strcpy (path, realFN);
706 if (*pos == DIR_SEPARATOR)
709 if ((0 == STAT (path, &buf)) && (!S_ISDIR (buf.st_mode)))
711 *pos = DIR_SEPARATOR;
712 memmove (pos + strlen (GNUNET_DIRECTORY_EXT),
715 GNUNET_DIRECTORY_EXT, strlen (GNUNET_DIRECTORY_EXT));
716 pos += strlen (GNUNET_DIRECTORY_EXT);
720 *pos = DIR_SEPARATOR;
725 GNUNET_free (realFN);
729 /* ***************** main method **************** */
733 * Download parts of a file. Note that this will store
734 * the blocks at the respective offset in the given file.
735 * Also, the download is still using the blocking of the
736 * underlying ECRS encoding. As a result, the download
737 * may *write* outside of the given boundaries (if offset
738 * and length do not match the 32k ECRS block boundaries).
741 * This function should be used to focus a download towards a
742 * particular portion of the file (optimization), not to strictly
743 * limit the download to exactly those bytes.
745 * @param uri the URI of the file (determines what to download)
746 * @param filename where to store the file
747 * @param no_temporaries set to GNUNET_YES to disallow generation of temporary files
748 * @param start starting offset
749 * @param length length of the download (starting at offset)
751 struct GNUNET_ECRS_DownloadContext *
752 GNUNET_ECRS_file_download_partial_start (struct GNUNET_GE_Context *ectx,
753 struct GNUNET_GC_Configuration *cfg,
754 struct GNUNET_FS_SearchContext *sc,
755 const struct GNUNET_ECRS_URI *uri,
756 const char *filename,
757 unsigned long long offset,
758 unsigned long long length,
759 unsigned int anonymityLevel,
761 GNUNET_ECRS_DownloadProgressCallback
762 dpcb, void *dpcbClosure)
764 struct GNUNET_ECRS_DownloadContext *rm;
769 if ((!GNUNET_ECRS_uri_test_chk (uri)) && (!GNUNET_ECRS_uri_test_loc (uri)))
771 GNUNET_GE_BREAK (ectx, 0);
774 rm = GNUNET_malloc (sizeof (struct GNUNET_ECRS_DownloadContext));
775 memset (rm, 0, sizeof (struct GNUNET_ECRS_DownloadContext));
778 rm->sctx = GNUNET_FS_create_search_context (ectx, cfg);
779 if (rm->sctx == NULL)
784 rm->my_sctx = GNUNET_YES;
789 rm->my_sctx = GNUNET_NO;
793 rm->startTime = GNUNET_get_time ();
794 rm->anonymityLevel = anonymityLevel;
798 rm->dpcbClosure = dpcbClosure;
799 rm->main = GNUNET_thread_get_self ();
800 rm->total = GNUNET_ntohll (uri->data.fi.file_length);
802 filename != NULL ? get_real_download_filename (ectx, filename) : NULL;
804 if ((rm->filename != NULL) &&
806 GNUNET_disk_directory_create_for_file (ectx, rm->filename)))
808 free_request_manager (rm);
813 if (rm->filename != NULL)
815 ret = GNUNET_disk_file_open (ectx,
817 O_CREAT | O_WRONLY | O_TRUNC,
821 free_request_manager (rm);
826 dpcb (0, 0, rm->startTime, 0, NULL, 0, dpcbClosure);
827 free_request_manager (rm);
830 rm->treedepth = GNUNET_ECRS_compute_depth (rm->total);
831 if ((NULL != rm->filename) &&
832 ((0 == STAT (rm->filename, &buf))
833 && ((size_t) buf.st_size > rm->total)))
835 /* if exists and oversized, truncate */
836 if (truncate (rm->filename, rm->total) != 0)
838 GNUNET_GE_LOG_STRERROR_FILE (ectx,
839 GNUNET_GE_ERROR | GNUNET_GE_ADMIN |
840 GNUNET_GE_BULK, "truncate",
842 free_request_manager (rm);
846 if (rm->filename != NULL)
848 rm->handle = GNUNET_disk_file_open (ectx,
854 free_request_manager (rm);
860 if (GNUNET_ECRS_uri_test_loc (uri))
862 GNUNET_hash (&uri->data.loc.peer, sizeof (GNUNET_RSA_PublicKey),
863 &rm->target.hashPubKey);
864 rm->have_target = GNUNET_YES;
866 top = GNUNET_malloc (sizeof (struct Node));
867 memset (top, 0, sizeof (struct Node));
869 top->chk = uri->data.fi.chk;
871 top->level = rm->treedepth;
872 if (GNUNET_NO == check_node_present (top))
880 GNUNET_ECRS_file_download_partial_stop (struct GNUNET_ECRS_DownloadContext
886 free_request_manager (rm);
887 if (ret == GNUNET_NO)
888 ret = GNUNET_OK; /* normal termination */
893 * Download parts of a file. Note that this will store
894 * the blocks at the respective offset in the given file.
895 * Also, the download is still using the blocking of the
896 * underlying ECRS encoding. As a result, the download
897 * may *write* outside of the given boundaries (if offset
898 * and length do not match the 32k ECRS block boundaries).
901 * This function should be used to focus a download towards a
902 * particular portion of the file (optimization), not to strictly
903 * limit the download to exactly those bytes.
905 * @param uri the URI of the file (determines what to download)
906 * @param filename where to store the file
907 * @param no_temporaries set to GNUNET_YES to disallow generation of temporary files
908 * @param start starting offset
909 * @param length length of the download (starting at offset)
912 GNUNET_ECRS_file_download_partial (struct GNUNET_GE_Context *ectx,
913 struct GNUNET_GC_Configuration *cfg,
914 const struct GNUNET_ECRS_URI *uri,
915 const char *filename,
916 unsigned long long offset,
917 unsigned long long length,
918 unsigned int anonymityLevel,
920 GNUNET_ECRS_DownloadProgressCallback dpcb,
922 GNUNET_ECRS_TestTerminate tt,
925 struct GNUNET_ECRS_DownloadContext *rm;
930 rm = GNUNET_ECRS_file_download_partial_start (ectx,
941 return GNUNET_SYSERR;
942 while ((GNUNET_OK == tt (ttClosure)) &&
943 (GNUNET_YES != GNUNET_shutdown_test ()) &&
944 (rm->abortFlag == GNUNET_NO) && (rm->head != NULL))
945 GNUNET_thread_sleep (5 * GNUNET_CRON_SECONDS);
946 ret = GNUNET_ECRS_file_download_partial_stop (rm);
951 * Download a file (simplified API).
953 * @param uri the URI of the file (determines what to download)
954 * @param filename where to store the file
957 GNUNET_ECRS_file_download (struct GNUNET_GE_Context *ectx,
958 struct GNUNET_GC_Configuration *cfg,
959 const struct GNUNET_ECRS_URI *uri,
960 const char *filename,
961 unsigned int anonymityLevel,
962 GNUNET_ECRS_DownloadProgressCallback dpcb,
963 void *dpcbClosure, GNUNET_ECRS_TestTerminate tt,
966 return GNUNET_ECRS_file_download_partial (ectx,
971 GNUNET_ECRS_uri_get_file_size
972 (uri), anonymityLevel, GNUNET_NO,
973 dpcb, dpcbClosure, tt, ttClosure);
978 /* end of fs_download.c */