finish publish shutdown code
[oweals/gnunet.git] / src / fs / fs_publish.c
1 /*
2      This file is part of GNUnet.
3      (C) 2009 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 2, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file fs/fs_publish.c
23  * @brief publish a file or directory in GNUnet
24  * @see http://gnunet.org/encoding.php3
25  * @author Krista Bennett
26  * @author Christian Grothoff
27  *
28  * TODO:
29  * - KBlocks
30  * - SBlocks
31  * - indexing support
32  * - code-sharing with unindex (can wait)
33  * - persistence support (can wait)
34  * - datastore reservation support (optimization)
35  */
36
37 #include "platform.h"
38 #include "gnunet_constants.h"
39 #include "gnunet_util_lib.h"
40 #include "gnunet_fs_service.h"
41 #include "fs.h"
42
43 #define DEBUG_PUBLISH GNUNET_YES
44
45 /**
46  * Main function that performs the upload.
47  * @param cls "struct GNUNET_FS_PublishContext" identifies the upload
48  * @param tc task context
49  */
50 static void
51 do_upload (void *cls,
52            const struct GNUNET_SCHEDULER_TaskContext *tc);
53
54
55 /**
56  * Context for "ds_put_cont".
57  */
58 struct PutContCtx
59 {
60   /**
61    * Publishing context for which the datastore
62    * PUT request was executed.
63    */
64   struct GNUNET_FS_PublishContext *sc;
65
66   /**
67    * Specific file with the block.
68    */
69   struct GNUNET_FS_FileInformation *p;
70
71   /**
72    * Function to run next, if any (can be NULL).
73    */
74   GNUNET_SCHEDULER_Task cont;
75 };
76
77
78 /**
79  * Fill in all of the generic fields for 
80  * a publish event.
81  *
82  * @param pc structure to fill in
83  * @param sc overall publishing context
84  * @param p file information for the file being published
85  */
86 static void
87 make_publish_status (struct GNUNET_FS_ProgressInfo *pi,
88                      struct GNUNET_FS_PublishContext *sc,
89                      const struct GNUNET_FS_FileInformation *p)
90 {
91   pi->value.publish.sc = sc;
92   pi->value.publish.fi = p;
93   pi->value.publish.cctx
94     = p->client_info;
95   pi->value.publish.pctx
96     = (NULL == p->dir) ? NULL : p->dir->client_info;
97   pi->value.publish.size
98     = (p->is_directory) ? p->data.dir.dir_size : p->data.file.file_size;
99   pi->value.publish.eta 
100     = GNUNET_TIME_calculate_eta (p->start_time,
101                                  p->publish_offset,
102                                  pi->value.publish.size);
103   pi->value.publish.duration = GNUNET_TIME_absolute_get_duration (p->start_time);
104   pi->value.publish.completed = p->publish_offset;
105   pi->value.publish.anonymity = p->anonymity;
106 }
107
108
109 /**
110  * Cleanup the publish context, we're done
111  * with it.
112  *
113  * @param pc struct to clean up after
114  */
115 static void
116 publish_cleanup (struct GNUNET_FS_PublishContext *sc)
117 {
118   GNUNET_FS_file_information_destroy (sc->fi, NULL, NULL);
119   GNUNET_FS_namespace_delete (sc->namespace, GNUNET_NO);
120   GNUNET_free_non_null (sc->nid);  
121   GNUNET_free_non_null (sc->nuid);
122   GNUNET_DATASTORE_disconnect (sc->dsh, GNUNET_NO);
123   GNUNET_free (sc);
124 }
125
126
127 /**
128  * Function called by the datastore API with
129  * the result from the PUT request.
130  *
131  * @param cls our closure
132  * @param success GNUNET_OK on success
133  * @param msg error message (or NULL)
134  */
135 static void
136 ds_put_cont (void *cls,
137              int success,
138              const char *msg)
139 {
140   struct PutContCtx *pcc = cls;
141   struct GNUNET_FS_ProgressInfo pi;
142
143   if (GNUNET_SYSERR == pcc->sc->in_network_wait)
144     {
145       /* we were aborted in the meantime,
146          finish shutdown! */
147       publish_cleanup (pcc->sc);
148       return;
149     }
150   GNUNET_assert (GNUNET_YES == pcc->sc->in_network_wait);
151   pcc->sc->in_network_wait = GNUNET_NO;
152   if (GNUNET_OK != success)
153     {
154       GNUNET_asprintf (&pcc->p->emsg, 
155                        _("Upload failed: %s"),
156                        msg);
157       GNUNET_FS_file_information_sync (pcc->p);
158       pi.status = GNUNET_FS_STATUS_PUBLISH_ERROR;
159       make_publish_status (&pi, pcc->sc, pcc->p);
160       pi.value.publish.eta = GNUNET_TIME_UNIT_FOREVER_REL;
161       pi.value.publish.specifics.error.message = pcc->p->emsg;
162       pcc->p->client_info
163         = pcc->sc->h->upcb (pcc->sc->h->upcb_cls,
164                             &pi);
165       return;
166     }
167   GNUNET_FS_file_information_sync (pcc->p);
168   if (NULL != pcc->cont)
169     pcc->sc->upload_task 
170       = GNUNET_SCHEDULER_add_delayed (pcc->sc->h->sched,
171                                       GNUNET_NO,
172                                       GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
173                                       GNUNET_SCHEDULER_NO_TASK,
174                                       GNUNET_TIME_UNIT_ZERO,
175                                       pcc->cont,
176                                       pcc->sc);
177   GNUNET_free (pcc);
178 }
179
180
181 /**
182  * We need to publish a specific block.  Do it.  Then continue with
183  * the main task.
184  *
185  * @param sc overall upload data
186  * @param p file that the block belongs to (needed for options!)
187  * @param query what the block should be indexed under
188  * @param blk encoded block to publish
189  * @param blk_size size of the block
190  * @param blk_type type of the block
191  * @param cont function to run when done
192  */
193 static void
194 publish_block (struct GNUNET_FS_PublishContext *sc,
195                struct GNUNET_FS_FileInformation *p,
196                const GNUNET_HashCode *query,
197                const void* blk,
198                uint16_t blk_size,
199                uint32_t blk_type,
200                GNUNET_SCHEDULER_Task cont)
201 {
202   struct PutContCtx * dpc_cls;
203
204   dpc_cls = GNUNET_malloc(sizeof(struct PutContCtx));
205   dpc_cls->cont = cont;
206   dpc_cls->sc = sc;
207   dpc_cls->p = p;
208   GNUNET_assert (GNUNET_NO == sc->in_network_wait);
209   sc->in_network_wait = GNUNET_YES;
210   GNUNET_DATASTORE_put (sc->dsh,
211                         sc->rid,
212                         query,
213                         blk_size,
214                         blk,
215                         blk_type,
216                         p->priority,
217                         p->anonymity,
218                         p->expirationTime,
219                         GNUNET_CONSTANTS_SERVICE_TIMEOUT,
220                         &ds_put_cont,
221                         dpc_cls);
222 }
223
224
225 /**
226  * Generate the callback that signals clients
227  * that a file (or directory) has been completely
228  * published.
229  *
230  * @param p the completed upload
231  * @param sc context of the publication
232  */
233 static void 
234 signal_publish_completion (struct GNUNET_FS_FileInformation *p,
235                            struct GNUNET_FS_PublishContext *sc)
236 {
237   struct GNUNET_FS_ProgressInfo pi;
238   
239   pi.status = GNUNET_FS_STATUS_PUBLISH_COMPLETED;
240   make_publish_status (&pi, sc, p);
241   pi.value.publish.eta = GNUNET_TIME_UNIT_ZERO;
242   pi.value.publish.specifics.completed.chk_uri = p->chk_uri;
243   p->client_info
244     = sc->h->upcb (sc->h->upcb_cls,
245                   &pi);
246 }
247
248
249 /**
250  * We are almost done publishing the structure,
251  * add SBlocks (if needed).
252  *
253  * @param sc overall upload data
254  */
255 static void
256 publish_sblock (struct GNUNET_FS_PublishContext *sc)
257 {
258   struct GNUNET_FS_FileInformation *p;
259   p = sc->fi;
260
261   // FIXME: build sblock & call publish_block!
262   
263   // FIXME: continuation should
264   // be releasing the datastore reserve
265   // (once implemented)
266   // FIXME: finally, signal overall completion
267   signal_publish_completion (p, sc);
268 }
269
270
271 /**
272  * We have uploaded a file or directory; now publish
273  * the KBlocks in the global keyword space so that
274  * it can be found.  Then continue with the
275  * main task.
276  *
277  * @param sc overall upload data
278  * @param p specific file or directory for which kblocks
279  *          should be created
280  */
281 static void
282 publish_kblocks (struct GNUNET_FS_PublishContext *sc,
283                  struct GNUNET_FS_FileInformation *p)
284 {
285   // FIXME: build all kblocks
286   // call publish_block on each
287
288
289   GNUNET_FS_file_information_sync (p);
290   if (NULL != p->dir)
291     signal_publish_completion (p, sc);
292
293   // last continuation should then call the main continuation again
294 }
295
296
297 /**
298  * Compute the depth of the CHK tree.
299  *
300  * @param flen file length for which to compute the depth
301  * @return depth of the tree
302  */
303 static unsigned int
304 compute_depth (uint64_t flen)
305 {
306   unsigned int treeDepth;
307   uint64_t fl;
308
309   treeDepth = 1;
310   fl = GNUNET_FS_DBLOCK_SIZE;
311   while (fl < flen)
312     {
313       treeDepth++;
314       if (fl * GNUNET_FS_CHK_PER_INODE < fl)
315         {
316           /* integer overflow, this is a HUGE file... */
317           return treeDepth;
318         }
319       fl = fl * GNUNET_FS_CHK_PER_INODE;
320     }
321   return treeDepth;
322 }
323
324
325 /**
326  * Compute the size of the current IBlock.
327  *
328  * @param height height of the IBlock in the tree (aka overall
329  *               number of tree levels minus depth); 0 == DBlock
330  * @param offset current offset in the overall file
331  * @return size of the corresponding IBlock
332  */
333 static uint16_t 
334 compute_iblock_size (unsigned int height,
335                      uint64_t offset)
336 {
337   unsigned int ret;
338   unsigned int i;
339   uint64_t mod;
340   uint64_t bds;
341
342   GNUNET_assert (height > 0);
343   bds = GNUNET_FS_DBLOCK_SIZE; /* number of bytes each CHK at level "i"
344                                   corresponds to */
345   for (i=0;i<height;i++)
346     bds *= GNUNET_FS_CHK_PER_INODE;
347   mod = offset % bds;
348   if (0 == mod)
349     {
350       /* we were triggered at the end of a full block */
351       ret = GNUNET_FS_CHK_PER_INODE;
352     }
353   else
354     {
355       /* we were triggered at the end of the file */
356       bds /= GNUNET_FS_CHK_PER_INODE;
357       ret = mod / bds;
358       if (0 != mod % bds)
359         ret++; 
360     }
361   return (uint16_t) (ret * sizeof(struct ContentHashKey));
362 }
363
364
365 /**
366  * Compute the offset of the CHK for the
367  * current block in the IBlock above.
368  *
369  * @param height height of the IBlock in the tree (aka overall
370  *               number of tree levels minus depth); 0 == DBlock
371  * @param offset current offset in the overall file
372  * @return (array of CHKs') offset in the above IBlock
373  */
374 static unsigned int
375 compute_chk_offset (unsigned int height,
376                     uint64_t offset)
377 {
378   uint64_t bds;
379   unsigned int ret;
380   unsigned int i;
381
382   bds = GNUNET_FS_DBLOCK_SIZE; /* number of bytes each CHK at level "i"
383                                   corresponds to */
384   for (i=0;i<height;i++)
385     bds *= GNUNET_FS_CHK_PER_INODE;
386   GNUNET_assert (0 == (offset % bds));
387   ret = offset / bds;
388   return ret % GNUNET_FS_CHK_PER_INODE; 
389 }
390
391
392 /**
393  * We are uploading a file or directory; load (if necessary) the next
394  * block into memory, encrypt it and send it to the FS service.  Then
395  * continue with the main task.
396  *
397  * @param sc overall upload data
398  * @param p specific file or directory for which kblocks
399  *          should be created
400  */
401 static void
402 publish_content (struct GNUNET_FS_PublishContext *sc,
403                  struct GNUNET_FS_FileInformation *p)
404 {
405   struct GNUNET_FS_ProgressInfo pi;
406   struct ContentHashKey *mychk;
407   const void *pt_block;
408   uint16_t pt_size;
409   char *emsg;
410   char iob[GNUNET_FS_DBLOCK_SIZE];
411   char enc[GNUNET_FS_DBLOCK_SIZE];
412   struct GNUNET_CRYPTO_AesSessionKey sk;
413   struct GNUNET_CRYPTO_AesInitializationVector iv;
414   uint64_t size;
415   unsigned int off;
416   struct GNUNET_FS_DirectoryBuilder *db;
417   struct GNUNET_FS_FileInformation *dirpos;
418   void *raw_data;
419   char *dd;
420
421   // FIXME: figure out how to share this code
422   // with unindex!
423   size = (p->is_directory) ? p->data.dir.dir_size : p->data.file.file_size;
424   if (NULL == p->chk_tree)
425     {
426       if (p->is_directory)
427         {
428           db = GNUNET_FS_directory_builder_create (p->meta);
429           dirpos = p->data.dir.entries;
430           while (NULL != dirpos)
431             {
432               if (dirpos->is_directory)
433                 {
434                   raw_data = dirpos->data.dir.dir_data;
435                   dirpos->data.dir.dir_data = NULL;
436                 }
437               else
438                 {
439                   raw_data = NULL;
440                   if ( (dirpos->data.file.file_size < GNUNET_FS_MAX_INLINE_SIZE) &&
441                        (dirpos->data.file.file_size > 0) )
442                     {
443                       raw_data = GNUNET_malloc (dirpos->data.file.file_size);
444                       emsg = NULL;
445                       if (dirpos->data.file.file_size !=
446                           dirpos->data.file.reader (dirpos->data.file.reader_cls,
447                                                     0,
448                                                     dirpos->data.file.file_size,
449                                                     raw_data,
450                                                     &emsg))
451                         {
452                           GNUNET_free_non_null (emsg);
453                           GNUNET_free (raw_data);
454                           raw_data = NULL;
455                         } 
456                     }
457                 }
458               GNUNET_FS_directory_builder_add (db,
459                                                dirpos->chk_uri,
460                                                dirpos->meta,
461                                                raw_data);
462               GNUNET_free_non_null (raw_data);
463               dirpos = dirpos->next;
464             }
465           GNUNET_FS_directory_builder_finish (db,
466                                               &p->data.dir.dir_size,
467                                               &p->data.dir.dir_data);
468           size = p->data.dir.dir_size;
469         }
470       p->chk_tree_depth = compute_depth (size);
471       p->chk_tree = GNUNET_malloc (p->chk_tree_depth * 
472                                    sizeof (struct ContentHashKey) *
473                                    GNUNET_FS_CHK_PER_INODE);
474       p->current_depth = p->chk_tree_depth;
475     }
476   if (p->current_depth == p->chk_tree_depth)
477     {
478       if (p->is_directory)
479         {
480           pt_size = GNUNET_MIN(GNUNET_FS_DBLOCK_SIZE,
481                                p->data.dir.dir_size - p->publish_offset);
482           dd = p->data.dir.dir_data;
483           pt_block = &dd[p->publish_offset];
484         }
485       else
486         {
487           pt_size = GNUNET_MIN(GNUNET_FS_DBLOCK_SIZE,
488                                p->data.file.file_size - p->publish_offset);
489           emsg = NULL;
490           if (pt_size !=
491               p->data.file.reader (p->data.file.reader_cls,
492                                    p->publish_offset,
493                                    pt_size,
494                                    iob,
495                                    &emsg))
496             {
497               GNUNET_asprintf (&p->emsg, 
498                                _("Upload failed: %s"),
499                                emsg);
500               GNUNET_free (emsg);
501               GNUNET_FS_file_information_sync (p);
502               pi.status = GNUNET_FS_STATUS_PUBLISH_ERROR;
503               make_publish_status (&pi, sc, p);
504               pi.value.publish.eta = GNUNET_TIME_UNIT_FOREVER_REL;
505               pi.value.publish.specifics.error.message = p->emsg;
506               p->client_info
507                 = sc->h->upcb (sc->h->upcb_cls,
508                                &pi);
509               /* continue with main (to propagate error up) */
510               sc->upload_task 
511                 = GNUNET_SCHEDULER_add_delayed (sc->h->sched,
512                                                 GNUNET_NO,
513                                                 GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
514                                                 GNUNET_SCHEDULER_NO_TASK,
515                                                 GNUNET_TIME_UNIT_ZERO,
516                                                 &do_upload,
517                                                 sc);
518               return;
519             }
520           pt_block = iob;
521         }
522     }
523   else
524     {
525       pt_size = compute_iblock_size (p->chk_tree_depth - p->current_depth,
526                                      p->publish_offset); 
527       pt_block = &p->chk_tree[p->current_depth *
528                               GNUNET_FS_CHK_PER_INODE];
529     }
530   off = compute_chk_offset (p->chk_tree_depth - p->current_depth,
531                             p->publish_offset);
532   mychk = &p->chk_tree[(p->current_depth-1)*GNUNET_FS_CHK_PER_INODE+off];
533   GNUNET_CRYPTO_hash (pt_block, pt_size, &mychk->key);
534   GNUNET_CRYPTO_hash_to_aes_key (&mychk->key, &sk, &iv);
535   GNUNET_CRYPTO_aes_encrypt (pt_block,
536                              pt_size,
537                              &sk,
538                              &iv,
539                              enc);
540   // NOTE: this call (and progress below) is all that really differs
541   // between publish/unindex!  Parameterize & move this code!
542   // FIXME: something around here would need to change
543   // for indexing!
544   publish_block (sc, p, 
545                  &mychk->query,
546                  enc, 
547                  pt_size, 
548                  (p->current_depth == p->chk_tree_depth) 
549                  ? GNUNET_DATASTORE_BLOCKTYPE_DBLOCK 
550                  : GNUNET_DATASTORE_BLOCKTYPE_IBLOCK,
551                  &do_upload);
552   if (p->current_depth == p->chk_tree_depth)
553     {
554       pi.status = GNUNET_FS_STATUS_PUBLISH_PROGRESS;
555       make_publish_status (&pi, sc, p);
556       pi.value.publish.specifics.progress.data = pt_block;
557       pi.value.publish.specifics.progress.offset = p->publish_offset;
558       pi.value.publish.specifics.progress.data_len = pt_size;
559       p->client_info 
560         = sc->h->upcb (sc->h->upcb_cls,
561                        &pi);
562     }
563   GNUNET_CRYPTO_hash (enc, pt_size, &mychk->query);
564   if (p->current_depth == p->chk_tree_depth) 
565     { 
566       p->publish_offset += pt_size;
567       if ( (p->publish_offset == size) ||
568            (0 == p->publish_offset % (GNUNET_FS_CHK_PER_INODE * GNUNET_FS_DBLOCK_SIZE) ) )
569         p->current_depth--;
570     }
571   else
572     {
573       if ( (off == GNUNET_FS_CHK_PER_INODE) ||
574            (p->publish_offset == size) )
575         p->current_depth--;
576       else
577         p->current_depth = p->chk_tree_depth;
578     }
579   if (0 == p->current_depth)
580     {
581       p->chk_uri = GNUNET_malloc (sizeof(struct GNUNET_FS_Uri));
582       p->chk_uri->type = chk;
583       p->chk_uri->data.chk.chk = p->chk_tree[0];
584       p->chk_uri->data.chk.file_length = size;
585       GNUNET_free (p->chk_tree);
586       p->chk_tree = NULL;
587     }
588 }
589
590
591 /**
592  * Main function that performs the upload.
593  * @param cls "struct GNUNET_FS_PublishContext" identifies the upload
594  * @param tc task context
595  */
596 static void
597 do_upload (void *cls,
598            const struct GNUNET_SCHEDULER_TaskContext *tc)
599 {
600   struct GNUNET_FS_PublishContext *sc = cls;
601   struct GNUNET_FS_ProgressInfo pi;
602   struct GNUNET_FS_FileInformation *p;
603   char *fn;
604
605   sc->upload_task = GNUNET_SCHEDULER_NO_TASK;  
606   p = sc->fi_pos;
607   if (NULL == p)
608     {
609       /* upload of entire hierarchy complete,
610          publish namespace entries */
611       publish_sblock (sc);
612       return;
613     }
614   if (NULL != p->emsg)
615     {
616       /* error with current file, abort all
617          related files as well! */
618       while (NULL != p->dir)
619         {
620           fn = GNUNET_CONTAINER_meta_data_get_by_type (p->meta,
621                                                        EXTRACTOR_FILENAME);
622           p = p->dir;
623           GNUNET_asprintf (&p->emsg, 
624                            _("Recursive upload failed at `%s'"),
625                            fn);
626           GNUNET_free (fn);
627           GNUNET_FS_file_information_sync (p);
628           pi.status = GNUNET_FS_STATUS_PUBLISH_ERROR;
629           make_publish_status (&pi, sc, p);
630           pi.value.publish.eta = GNUNET_TIME_UNIT_FOREVER_REL;
631           pi.value.publish.specifics.error.message = p->emsg;
632           p->client_info
633             = sc->h->upcb (sc->h->upcb_cls,
634                            &pi);
635         }
636       return;
637     }
638   if (NULL != p->chk_uri)
639     {
640       /* move on to next file */
641       if (NULL != p->next)
642         sc->fi_pos = p->next;
643       else
644         sc->fi_pos = p->dir;
645       /* upload of "p" complete, publish KBlocks! */
646       publish_kblocks (sc, p);
647       return;
648     }
649   if ( (!p->is_directory) &&
650        (p->data.file.do_index) )
651     {
652       // FIXME: need to pre-compute hash over
653       // the entire file and ask FS to prepare
654       // for indexing!
655       return;
656     }
657   publish_content (sc, p);
658 }
659
660
661 /**
662  * Signal the FS's progress function that we are starting
663  * an upload.
664  *
665  * @param cls closure (of type "struct GNUNET_FS_PublishContext*")
666  * @param fi the entry in the publish-structure
667  * @param length length of the file or directory
668  * @param meta metadata for the file or directory (can be modified)
669  * @param uri pointer to the keywords that will be used for this entry (can be modified)
670  * @param anonymity pointer to selected anonymity level (can be modified)
671  * @param priority pointer to selected priority (can be modified)
672  * @param expirationTime pointer to selected expiration time (can be modified)
673  * @param client_info pointer to client context set upon creation (can be modified)
674  * @return GNUNET_OK to continue (always)
675  */
676 static int
677 fip_signal_start(void *cls,
678                  struct GNUNET_FS_FileInformation *fi,
679                  uint64_t length,
680                  struct GNUNET_CONTAINER_MetaData *meta,
681                  struct GNUNET_FS_Uri **uri,
682                  unsigned int *anonymity,
683                  unsigned int *priority,
684                  struct GNUNET_TIME_Absolute *expirationTime,
685                  void **client_info)
686 {
687   struct GNUNET_FS_PublishContext *sc = cls;
688   struct GNUNET_FS_ProgressInfo pi;
689
690   pi.status = GNUNET_FS_STATUS_PUBLISH_START;
691   make_publish_status (&pi, sc, fi);
692   *client_info = sc->h->upcb (sc->h->upcb_cls,
693                               &pi);
694   return GNUNET_OK;
695 }
696
697
698 /**
699  * Publish a file or directory.
700  *
701  * @param h handle to the file sharing subsystem
702  * @param ctx initial value to use for the '*ctx'
703  *        in the callback (for the GNUNET_FS_STATUS_PUBLISH_START event).
704  * @param fi information about the file or directory structure to publish
705  * @param namespace namespace to publish the file in, NULL for no namespace
706  * @param nid identifier to use for the publishd content in the namespace
707  *        (can be NULL, must be NULL if namespace is NULL)
708  * @param nuid update-identifier that will be used for future updates 
709  *        (can be NULL, must be NULL if namespace or nid is NULL)
710  * @return context that can be used to control the publish operation
711  */
712 struct GNUNET_FS_PublishContext *
713 GNUNET_FS_publish_start (struct GNUNET_FS_Handle *h,
714                          void *ctx,
715                          struct GNUNET_FS_FileInformation *fi,
716                          struct GNUNET_FS_Namespace *namespace,
717                          const char *nid,
718                          const char *nuid)
719 {
720   struct GNUNET_FS_PublishContext *ret;
721   struct GNUNET_FS_FileInformation *p;
722   struct GNUNET_DATASTORE_Handle *dsh;
723
724   dsh = GNUNET_DATASTORE_connect (h->cfg,
725                                   h->sched);
726   if (NULL == dsh)
727     return NULL;
728   ret = GNUNET_malloc (sizeof (struct GNUNET_FS_PublishContext));
729   ret->dsh = dsh;
730   ret->h = h;
731   ret->client_ctx = ctx;
732   ret->fi = fi;
733   ret->namespace = namespace;
734   if (namespace != NULL)
735     {
736       namespace->rc++;
737       GNUNET_assert (NULL != nid);
738       ret->nid = GNUNET_strdup (nid);
739       if (NULL != nuid)
740         ret->nuid = GNUNET_strdup (nuid);
741     }
742   // FIXME: make upload persistent!
743
744   /* signal start */
745   GNUNET_FS_file_information_inspect (ret->fi,
746                                       &fip_signal_start,
747                                       ret);
748   /* find first leaf, DFS */
749   p = ret->fi;
750   while ( (p->is_directory) &&
751           (NULL != p->data.dir.entries) )
752     p = p->data.dir.entries;          
753   ret->fi_pos = p;
754
755   // FIXME: calculate space needed for "fi"
756   // and reserve as first task (then trigger
757   // "do_upload" from that continuation)!
758   ret->upload_task 
759     = GNUNET_SCHEDULER_add_delayed (h->sched,
760                                     GNUNET_NO,
761                                     GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
762                                     GNUNET_SCHEDULER_NO_TASK,
763                                     GNUNET_TIME_UNIT_ZERO,
764                                     &do_upload,
765                                     ret);
766   return ret;
767 }
768
769
770 /**
771  * Signal the FS's progress function that we are stopping
772  * an upload.
773  *
774  * @param cls closure (of type "struct GNUNET_FS_PublishContext*")
775  * @param fi the entry in the publish-structure
776  * @param length length of the file or directory
777  * @param meta metadata for the file or directory (can be modified)
778  * @param uri pointer to the keywords that will be used for this entry (can be modified)
779  * @param anonymity pointer to selected anonymity level (can be modified)
780  * @param priority pointer to selected priority (can be modified)
781  * @param expirationTime pointer to selected expiration time (can be modified)
782  * @param client_info pointer to client context set upon creation (can be modified)
783  * @return GNUNET_OK to continue (always)
784  */
785 static int
786 fip_signal_stop(void *cls,
787                 struct GNUNET_FS_FileInformation *fi,
788                 uint64_t length,
789                 struct GNUNET_CONTAINER_MetaData *meta,
790                 struct GNUNET_FS_Uri **uri,
791                 unsigned int *anonymity,
792                 unsigned int *priority,
793                 struct GNUNET_TIME_Absolute *expirationTime,
794                 void **client_info)
795 {
796   struct GNUNET_FS_PublishContext*sc = cls;
797   struct GNUNET_FS_ProgressInfo pi;
798
799   pi.status = GNUNET_FS_STATUS_PUBLISH_STOPPED;
800   make_publish_status (&pi, sc, fi);
801   GNUNET_break (NULL ==
802                 sc->h->upcb (sc->h->upcb_cls,
803                              &pi));
804   *client_info = NULL;
805   return GNUNET_OK;
806 }
807
808
809 /**
810  * Stop an upload.  Will abort incomplete uploads (but 
811  * not remove blocks that have already been publishd) or
812  * simply clean up the state for completed uploads.
813  *
814  * @param sc context for the upload to stop
815  */
816 void 
817 GNUNET_FS_publish_stop (struct GNUNET_FS_PublishContext *sc)
818 {
819   if (GNUNET_SCHEDULER_NO_TASK != sc->upload_task)
820     GNUNET_SCHEDULER_cancel (sc->h->sched, sc->upload_task);
821   // FIXME: remove from persistence DB (?) --- think more about
822   //        shutdown / persistent-resume APIs!!!
823   GNUNET_FS_file_information_inspect (sc->fi,
824                                       &fip_signal_stop,
825                                       sc);
826   if (GNUNET_YES == sc->in_network_wait)
827     {
828       sc->in_network_wait = GNUNET_SYSERR;
829       return;
830     }
831   publish_cleanup (sc);
832 }
833
834
835 /* end of fs_publish.c */