/* This file is part of GNUnet (C) 2005, 2006, 2010 Christian Grothoff (and other contributing authors) GNUnet is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNUnet; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /** * @file src/fs/gnunet-fs-gtk_publish-dialog.c * @author Christian Grothoff */ #include "gnunet-fs-gtk_common.h" #include "gnunet-fs-gtk.h" #include "gnunet-fs-gtk_publish-edit-dialog.h" #include #include #define MARKER_DIR_FILE_SIZE "-" /** * Be very verbose when reporting progress (usually bad as it takes more time * to display this than to make progress). */ #define VERBOSE_PROGRESS GNUNET_NO /** * Context we create when we are scanning a directory. */ struct AddDirClientContext; /** * Main handle of the dialog for a publish operation. */ struct MainPublishingDialogContext { /** * Main builder for the publishing dialog. */ GtkBuilder *builder; /** * Builder for the master window (FIXME: remove) */ GtkBuilder *main_window_builder; /** * Handle to the main window of the publishing dialog. */ GtkWindow *master_pubdialog; /** * Selected pseudonym. */ GtkTreeSelection *pseudonym_selection; /** * Model with the list of (our) pseudonyms. */ GtkTreeModel *pseudonym_treemodel; /** * Tree view listing files to be published. */ GtkTreeView *file_info_treeview; /** * Selected file in the 'file_info_treeview' */ GtkTreeSelection *file_info_selection; /** * Model with the list of files to be shared. */ GtkTreeModel *file_info_treemodel; /** * Button to move selected file upwards */ GtkWidget *up_button; /** * Button to move selected file downwards */ GtkWidget *down_button; /** * Button to move selected file left (make sibling of current parent) */ GtkWidget *left_button; /** * Button to move selected file right (make child of predecessor) */ GtkWidget *right_button; /** * Button to delete selected file from the list */ GtkWidget *delete_button; /** * Button to edit meta data of the selected file */ GtkWidget *edit_button; /** * Button to publish all files from the dialog */ GtkWidget *execute_button; /** * Button to abort the publishing operation */ GtkWidget *cancel_button; /** * FIXME: ugly, keep? */ gulong open_directory_handler_id; /** * FIXME: ugly, keep? */ GtkBuilder *open_directory_builder; /** * FIXME: ugly, keep? */ gulong open_file_handler_id; /** * FIXME: ugly, keep? */ GtkBuilder *open_file_builder; /** * Head of linked list of active open-directory operations. */ struct AddDirClientContext *adddir_head; /** * Tail of linked list of active open-directory operations. */ struct AddDirClientContext *adddir_tail; }; /** * Context we create when we are scanning a directory. */ struct AddDirClientContext { /** * This is a doubly-linked list. */ struct AddDirClientContext *next; /** * This is a doubly-linked list. */ struct AddDirClientContext *prev; /** * Handle of the master publish window. */ struct MainPublishingDialogContext *ctx; /** * Builder for the progress dialog that is displayed during the scan. */ GtkBuilder *progress_dialog_builder; /** * The progress dialog itself. */ GtkWidget *progress_dialog; /** * The progress bar of the progress dialog. */ GtkProgressBar *progress_dialog_bar; /** * Text view in the progress dialog (for error messages). */ GtkTextView *progress_dialog_textview; /** * Text buffer of the text view in the progress dialog. */ GtkTextBuffer *progress_dialog_textbuffer; /** * Adjustment (for scrolling) of the text view in the progress dialog. */ GtkAdjustment *textview_vertical_adjustment; /** * Handle to the active directory scanning operation. */ struct GNUNET_FS_DirScanner *ds; /** * Default options to use for sharing when adding files during the scan. */ struct GNUNET_FS_BlockOptions directory_scan_bo; /** * Default "index" option to use for sharing when adding files during the scan. */ int directory_scan_do_index; /** * Number of files that have had their meta data extracted (once done==total * we're finished processing). */ unsigned int done; /** * Total number of files that we have found in the directory structure and that * will need to be processed. */ unsigned int total; }; /* ************************ editing operations inside the master dialog ********************* */ /** * Check if two GtkTreeIters refer to the same element. * * @param tm tree model of the iterators * @param i1 first iterator * @param i2 second iterator * @return GNUNET_YES if they are equal */ static int gtk_tree_iter_equals (GtkTreeModel * tm, GtkTreeIter * i1, GtkTreeIter * i2) { GtkTreePath *p1; GtkTreePath *p2; int ret; p1 = gtk_tree_model_get_path (tm, i1); p2 = gtk_tree_model_get_path (tm, i2); ret = gtk_tree_path_compare (p1, p2); gtk_tree_path_free (p1); gtk_tree_path_free (p2); return (0 == ret) ? GNUNET_YES : GNUNET_NO; } /** * Update selectivity of buttons (up/down/left/right/cancel/execute) in the master dialog. * * @param ctx master dialog to update selectivity for */ static void update_selectivity (struct MainPublishingDialogContext *ctx) { GtkTreeIter iter; GtkTreeIter parent; GtkTreeIter pred; int is_dir; struct GNUNET_FS_FileInformation *fip; int ns_ok; gchar *namespace_id; /* find out if a namespace was selected */ ns_ok = GNUNET_YES; if (gtk_tree_selection_get_selected (ctx->pseudonym_selection, NULL, &iter)) { gtk_tree_model_get (ctx->pseudonym_treemodel, &iter, 2, &namespace_id, -1); if (namespace_id == NULL) ns_ok = GNUNET_NO; else g_free (namespace_id); } /* Don't let the user close the dialog until all scanners are finished and their windows are closed */ /* FIXME: what about open-directory operations? */ if ( (gtk_tree_model_get_iter_first (ctx->file_info_treemodel, &iter)) && (ns_ok == GNUNET_YES) && (ctx->adddir_head == NULL) ) gtk_widget_set_sensitive (ctx->execute_button, TRUE); else gtk_widget_set_sensitive (ctx->execute_button, FALSE); /* if an 'edit' operation is open, don't even allow "cancel" */ /* FIXME: what about open-directory operations? */ if (ctx->adddir_head == NULL) gtk_widget_set_sensitive (ctx->cancel_button, TRUE); else gtk_widget_set_sensitive (ctx->cancel_button, FALSE); /* now for the editing buttons... */ if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter)) { gtk_widget_set_sensitive (ctx->up_button, FALSE); gtk_widget_set_sensitive (ctx->down_button, FALSE); gtk_widget_set_sensitive (ctx->left_button, FALSE); gtk_widget_set_sensitive (ctx->right_button, FALSE); gtk_widget_set_sensitive (ctx->delete_button, FALSE); gtk_widget_set_sensitive (ctx->edit_button, FALSE); return; } gtk_widget_set_sensitive (ctx->delete_button, TRUE); gtk_widget_set_sensitive (ctx->edit_button, TRUE); /* figure out which move operations are currently legal */ GNUNET_assert (gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter)); if (gtk_tree_model_iter_next (ctx->file_info_treemodel, &iter)) gtk_widget_set_sensitive (ctx->down_button, TRUE); else gtk_widget_set_sensitive (ctx->down_button, FALSE); GNUNET_assert (gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter)); if (gtk_tree_model_iter_parent (ctx->file_info_treemodel, &parent, &iter)) { gtk_widget_set_sensitive (ctx->left_button, TRUE); GNUNET_assert (gtk_tree_model_iter_children (ctx->file_info_treemodel, &pred, &parent)); } else { gtk_widget_set_sensitive (ctx->left_button, FALSE); GNUNET_assert (gtk_tree_model_get_iter_first (ctx->file_info_treemodel, &pred)); } /* iterate over 'next' of pred to find out if our * predecessor is a directory! */ is_dir = GNUNET_SYSERR; while (GNUNET_YES != gtk_tree_iter_equals (ctx->file_info_treemodel, &pred, &iter)) { gtk_tree_model_get (ctx->file_info_treemodel, &pred, 5, &fip, -1); is_dir = GNUNET_FS_file_information_is_directory (fip); GNUNET_assert (gtk_tree_model_iter_next (ctx->file_info_treemodel, &pred)); } if (GNUNET_YES == is_dir) gtk_widget_set_sensitive (ctx->right_button, TRUE); else gtk_widget_set_sensitive (ctx->right_button, FALSE); if (GNUNET_SYSERR != is_dir) gtk_widget_set_sensitive (ctx->up_button, TRUE); else gtk_widget_set_sensitive (ctx->up_button, FALSE); } /** * The selection in the file list tree view changed; update the button sensitivity. * * @param ts the changed selection * @param user_data master publishing dialog context of our window */ static void selection_changed_cb (GtkTreeSelection * ts, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; update_selectivity (ctx); } /** * Add an empty directory to the tree model. * * @param ctx master publishing dialog context of our window * @param name name for the directory * @param bo block options * @param iter parent entry, or NULL for top-level addition * @param pos iterator to set to the location of the new element */ static void create_dir_at_iter (struct MainPublishingDialogContext *ctx, const char *name, const struct GNUNET_FS_BlockOptions *bo, GtkTreeIter * iter, GtkTreeIter * pos) { struct GNUNET_FS_FileInformation *fi; GtkTreeRowReference *row_reference; GtkTreePath *path; struct GNUNET_CONTAINER_MetaData *meta; meta = GNUNET_CONTAINER_meta_data_create (); GNUNET_FS_meta_data_make_directory (meta); GNUNET_CONTAINER_meta_data_insert (meta, "", EXTRACTOR_METATYPE_FILENAME, EXTRACTOR_METAFORMAT_UTF8, "text/plain", name, strlen (name) + 1); gtk_tree_store_insert_before (GTK_TREE_STORE (ctx->file_info_treemodel), pos, iter, NULL); path = gtk_tree_model_get_path (ctx->file_info_treemodel, pos); row_reference = gtk_tree_row_reference_new (ctx->file_info_treemodel, path); gtk_tree_path_free (path); fi = GNUNET_FS_file_information_create_empty_directory (GNUNET_FS_GTK_get_fs_handle (), row_reference, NULL, meta, bo, name); GNUNET_CONTAINER_meta_data_destroy (meta); gtk_tree_store_set (GTK_TREE_STORE (ctx->file_info_treemodel), pos, 0, MARKER_DIR_FILE_SIZE, 1, (gboolean) GNUNET_NO, 2, name, 3, (guint) bo->anonymity_level, 4, (guint) bo->content_priority, 5, fi, 6, (guint64) bo->expiration_time.abs_value, 7, (guint) bo->replication_level, -1); update_selectivity (ctx); } /** * Remove the given entry and all of its children from the tree store. * * @param ts tree store to edit * @param root root of the subtree to remove */ static void remove_old_entry (GtkTreeStore * ts, GtkTreeIter * root) { GtkTreeIter child; while (gtk_tree_model_iter_children (GTK_TREE_MODEL (ts), &child, root)) remove_old_entry (ts, &child); gtk_tree_store_remove (ts, root); } /** * Copy an entry in the tree from the 'old' position to the 'new' * position. All of the fields are copied, plain pointers will be * aliased (model will thus be inconsistent until 'remove_old_entry' * is called on the 'old' entry). * * @param ctx main publishing context * @param tm tree model for the move operation * @param old old position (source of the copy operation) * @param newpos destination of the copy operation * @param dsel GNUNET_YES for the top-level operation, * GNUNET_NO for the recursive calls; if GNUNET_YES, * we ensure that the tree view is expanded to cover * the element; the element is also then selected */ static void copy_entry (struct MainPublishingDialogContext *ctx, GtkTreeModel * tm, GtkTreeIter * old, GtkTreeIter * newpos, int dsel) { GtkTreePath *path; GtkTreeIter child; GtkTreeRowReference *rr; /* first, move the data */ { struct GNUNET_FS_FileInformation *fip; gint do_index; gchar *short_fn; guint anonymity_level; guint priority; guint replication_level; guint64 expiration_time_abs; char *fsf; gtk_tree_model_get (tm, old, 0, &fsf, 1, &do_index, 2, &short_fn, 3, &anonymity_level, 4, &priority, 5, &fip, 6, &expiration_time_abs, 7, &replication_level, -1); gtk_tree_store_set (GTK_TREE_STORE (tm), newpos, 0, fsf, 1, do_index, 2, short_fn, 3, anonymity_level, 4, priority, 5, fip, 6, expiration_time_abs, 7, replication_level, -1); g_free (short_fn); g_free (fsf); } /* remember our destination location if needed */ if (dsel == GNUNET_YES) { path = gtk_tree_model_get_path (tm, newpos); rr = gtk_tree_row_reference_new (tm, path); gtk_tree_path_free (path); } else { rr = NULL; } /* recursively move children */ if (gtk_tree_model_iter_children (tm, &child, old)) { do { GtkTreeRowReference *crr; GtkTreeIter cnewpos; path = gtk_tree_model_get_path (tm, &child); crr = gtk_tree_row_reference_new (tm, path); gtk_tree_path_free (path); gtk_tree_store_insert_before (GTK_TREE_STORE (tm), &cnewpos, newpos, NULL); copy_entry (ctx, tm, &child, &cnewpos, GNUNET_NO); path = gtk_tree_row_reference_get_path (crr); gtk_tree_row_reference_free (crr); GNUNET_assert (TRUE == gtk_tree_model_get_iter (tm, &child, path)); gtk_tree_path_free (path); } while (gtk_tree_model_iter_next (tm, &child)); } /* update selection, etc. if applicable */ if (dsel == GNUNET_YES) { path = gtk_tree_row_reference_get_path (rr); gtk_tree_row_reference_free (rr); gtk_tree_view_expand_to_path (ctx->file_info_treeview, path); GNUNET_assert (TRUE == gtk_tree_model_get_iter (tm, newpos, path)); gtk_tree_path_free (path); gtk_tree_selection_select_iter (ctx->file_info_selection, newpos); update_selectivity (ctx); } } /** * User has changed the "update" identifier for the content in * the GtkTreeView. Update the model. * * @param renderer pseudonym renderer that notified us about the edit * @param cpath where the edit happened * @param new_text the new value * @param user_data master publishing dialog context of our window */ void GNUNET_GTK_master_publish_dialog_pseudonym_updates_renderer_edited_cb (GtkCellRendererText * renderer, gchar * cpath, gchar * new_text, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; GtkTreeIter iter; if (! gtk_tree_model_get_iter_from_string (ctx->pseudonym_treemodel, &iter, cpath)) { GNUNET_break (0); return; } gtk_tree_store_set (GTK_TREE_STORE (ctx->pseudonym_treemodel), &iter, 5, new_text, -1); update_selectivity (ctx); } /** * User has changed the "current" identifier for the content in * the GtkTreeView. Update the model. * * @param renderer pseudonym renderer that notified us about the edit * @param cpath where the edit happened * @param new_text the new value * @param user_data master publishing dialog context of our window */ void GNUNET_GTK_master_publish_dialog_pseudonym_identifier_renderer_edited_cb (GtkCellRendererText * renderer, gchar * cpath, gchar * new_text, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; GtkTreeIter iter; if (! gtk_tree_model_get_iter_from_string (ctx->pseudonym_treemodel, &iter, cpath)) { GNUNET_break (0); return; } gtk_tree_store_set (GTK_TREE_STORE (ctx->pseudonym_treemodel), &iter, 2, new_text, -1); update_selectivity (ctx); } /** * User has clicked on the 'right' button to move files in the master * edit dialog tree view. Execute the move. * * @param dummy the button * @param user_data master publishing dialog context of our window */ void GNUNET_GTK_master_publish_dialog_right_button_clicked_cb (GtkWidget * dummy, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; GtkTreeIter iter; GtkTreeIter parent; GtkTreeIter pred; GtkTreeIter prev; GtkTreeIter pos; if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter)) { GNUNET_break (0); return; } if (gtk_tree_model_iter_parent (ctx->file_info_treemodel, &parent, &iter)) GNUNET_assert (gtk_tree_model_iter_children (ctx->file_info_treemodel, &pred, &parent)); else if (! gtk_tree_model_get_iter_first (ctx->file_info_treemodel, &pred)) { GNUNET_break (0); return; } /* iterate over 'next' of pred to find out who our predecessor is! */ memset (&prev, 0, sizeof (GtkTreeIter)); while (GNUNET_YES != gtk_tree_iter_equals (ctx->file_info_treemodel, &pred, &iter)) { prev = pred; GNUNET_assert (gtk_tree_model_iter_next (ctx->file_info_treemodel, &pred)); } gtk_tree_store_insert_before (GTK_TREE_STORE (ctx->file_info_treemodel), &pos, &prev, NULL); if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter)) { GNUNET_break (0); return; } copy_entry (ctx, ctx->file_info_treemodel, &iter, &pos, GNUNET_YES); remove_old_entry (GTK_TREE_STORE (ctx->file_info_treemodel), &iter); } /** * User has clicked on the 'left' button to move files in the master * edit dialog tree view. Execute the move. * * @param dummy the button * @param user_data master publishing dialog context of our window */ void GNUNET_GTK_master_publish_dialog_left_button_clicked_cb (GtkWidget * dummy, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; GtkTreeIter iter; GtkTreeIter parent; GtkTreeIter pos; if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter)) { GNUNET_break (0); return; } if (! gtk_tree_model_iter_parent (ctx->file_info_treemodel, &parent, &iter)) { GNUNET_break (0); return; } gtk_tree_store_insert_after (GTK_TREE_STORE (ctx->file_info_treemodel), &pos, NULL, &parent); if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter)) { GNUNET_break (0); return; } copy_entry (ctx, ctx->file_info_treemodel, &iter, &pos, GNUNET_YES); remove_old_entry (GTK_TREE_STORE (ctx->file_info_treemodel), &iter); } /** * User has clicked on the 'up' button to move files in the master * edit dialog tree view. Execute the move. * * @param dummy the button * @param user_data master publishing dialog context of our window */ void GNUNET_GTK_master_publish_dialog_up_button_clicked_cb (GtkWidget * dummy, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; GtkTreeIter iter; GtkTreeIter parent; GtkTreeIter pred; GtkTreeIter prev; GtkTreeIter *pprev; GtkTreeIter pos; if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter)) { GNUNET_break (0); return; } if (! gtk_tree_model_iter_parent (ctx->file_info_treemodel, &parent, &iter)) { GNUNET_assert (TRUE == gtk_tree_model_iter_children (ctx->file_info_treemodel, &pred, &parent)); pprev = &parent; } else if (! gtk_tree_model_get_iter_first (ctx->file_info_treemodel, &pred)) { GNUNET_break (0); return; } else pprev = NULL; /* iterate over 'next' of pred to find out who our predecessor is! */ while (GNUNET_YES != gtk_tree_iter_equals (ctx->file_info_treemodel, &pred, &iter)) { prev = pred; pprev = &prev; GNUNET_assert (gtk_tree_model_iter_next (ctx->file_info_treemodel, &pred)); } gtk_tree_store_insert_before (GTK_TREE_STORE (ctx->file_info_treemodel), &pos, NULL, pprev); if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter)) { GNUNET_break (0); return; } copy_entry (ctx, ctx->file_info_treemodel, &iter, &pos, GNUNET_YES); remove_old_entry (GTK_TREE_STORE (ctx->file_info_treemodel), &iter); } /** * User has clicked on the 'down' button to move files in the master * edit dialog tree view. Execute the move. * * @param dummy the button * @param user_data master publishing dialog context of our window */ void GNUNET_GTK_master_publish_dialog_down_button_clicked_cb (GtkWidget * dummy, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; GtkTreeIter iter; GtkTreeIter next; GtkTreeIter pos; if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter)) { GNUNET_break (0); return; } if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &next)) { GNUNET_break (0); return; } GNUNET_assert (gtk_tree_model_iter_next (ctx->file_info_treemodel, &next)); gtk_tree_store_insert_after (GTK_TREE_STORE (ctx->file_info_treemodel), &pos, NULL, &next); if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter)) { GNUNET_break (0); return; } copy_entry (ctx, ctx->file_info_treemodel, &iter, &pos, GNUNET_YES); remove_old_entry (GTK_TREE_STORE (ctx->file_info_treemodel), &iter); } /** * User has clicked on the 'new' button to add an empty directory in the master * edit dialog tree view. Add an empty directory. * * @param dummy the button * @param user_data master publishing dialog context of our window */ void GNUNET_GTK_master_publish_dialog_new_button_clicked_cb (GtkWidget * dummy, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; GtkTreeIter iter; GtkTreeIter pos; struct GNUNET_FS_BlockOptions bo; /* FIXME-FEATURE: consider opening a dialog to get anonymity, priority and expiration prior to calling this function (currently we use default values for those). Or getting these values from the configuration. */ bo.anonymity_level = 1; bo.content_priority = 1000; bo.expiration_time = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_YEARS); bo.replication_level = 1; if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter)) create_dir_at_iter (ctx, "unnamed/", &bo, NULL, &pos); else create_dir_at_iter (ctx, "unnamed/", &bo, &iter, &pos); } /** * Free row reference stored in the file information's * client-info pointer. * * @param cls always NULL * @param fi the file information that is being destroyed, unused * @param length length of the file, unused * @param meta meta data, unused * @param uri keyword URI, unused * @param bo publishing options, unused * @param do_index indexing option, unused * @param client_info pointer to the GtkTreeRowReference, freed * @return GNUNET_OK to traverse entire subtree */ static int free_fi_row_reference (void *cls, struct GNUNET_FS_FileInformation *fi, uint64_t length, struct GNUNET_CONTAINER_MetaData *meta, struct GNUNET_FS_Uri **uri, struct GNUNET_FS_BlockOptions *bo, int *do_index, void **client_info) { GtkTreeRowReference *row = *client_info; if (row == NULL) { GNUNET_break (0); return GNUNET_OK; } gtk_tree_row_reference_free (row); *client_info = NULL; return GNUNET_OK; } /** * User has clicked on the 'delete' button to delete a file or directory in the master * edit dialog tree view. Delete the selected entry. * * @param dummy the button * @param user_data master publishing dialog context of our window */ void GNUNET_GTK_master_publish_dialog_delete_button_clicked_cb (GtkWidget * dummy, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; GtkTreeIter iter; struct GNUNET_FS_FileInformation *fip; if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter)) { GNUNET_break (0); return; } gtk_tree_model_get (ctx->file_info_treemodel, &iter, 5, &fip, -1); GNUNET_FS_file_information_destroy (fip, &free_fi_row_reference, NULL); /* FIXME-BUG: the call above frees the row references in the entire subtree; however, with the 'remove' operation below we ONLY delete the top-level entry; we probably want to delete the entire directory subtree here... */ gtk_tree_store_remove (GTK_TREE_STORE (ctx->file_info_treemodel), &iter); update_selectivity (ctx); } /* ******************** progress dialog / import of directories * ********************** */ /** * Close the progress dialog and free its handle. * * @param adcc context for the progress dialog to close */ static void destroy_progress_dialog (struct AddDirClientContext *adcc) { GNUNET_assert (NULL == adcc->ds); gtk_widget_destroy (adcc->progress_dialog); g_object_unref (G_OBJECT (adcc->progress_dialog_builder)); GNUNET_CONTAINER_DLL_remove (adcc->ctx->adddir_head, adcc->ctx->adddir_tail, adcc); update_selectivity (adcc->ctx); GNUNET_free (adcc); } /** * User clicked on the 'cancel' button of the progress dialog. * Cancel the operation. * * @param button the cancel button * @param user_data progress dialog context of our window */ void GNUNET_FS_GTK_progress_dialog_cancel_button_clicked_cb (GtkButton *button, gpointer user_data) { struct AddDirClientContext *adcc = user_data; if (NULL == adcc->ds) { GNUNET_break (0); } else { /* signal the scanner to finish */ GNUNET_FS_directory_scan_abort (adcc->ds); adcc->ds = NULL; } destroy_progress_dialog (adcc); } /** * User attempted to close the progress dialog. Refuse. * * @param widget the widget emitting the event * @param event the event * @param user_data progress dialog context of our window * @return FALSE to refuse to close */ gboolean GNUNET_FS_GTK_progress_dialog_delete_event_cb (GtkWidget *widget, GdkEvent * event, void *cls) { /* Don't allow GTK to kill the window, until the scan is finished */ return FALSE; } /** * Display some additional information in the text area of the * progress dialog. * * @param adcc progress dialog context of our window * @param text text to add */ static void insert_progress_dialog_text (struct AddDirClientContext *adcc, const char *text) { gtk_text_buffer_insert_at_cursor (adcc->progress_dialog_textbuffer, text, -1); gtk_text_view_place_cursor_onscreen (adcc->progress_dialog_textview); gtk_adjustment_set_value (adcc->textview_vertical_adjustment, gtk_adjustment_get_upper (adcc->textview_vertical_adjustment)); } /** * Convert a single item from the scan to an entry in the tree view. * * @param adcc progress dialog context of our window * @param ts tree store to add an item to * @param item scanned item to add * @param parent of the item, can be NULL (for root) * @param sibling predecessor of the item, can be NULL (for first) * @param item_iter entry to set to the added item (OUT) */ static void add_item (struct AddDirClientContext *adcc, GtkTreeStore *ts, struct GNUNET_FS_ShareTreeItem *item, GtkTreeIter *parent, GtkTreeIter *sibling, GtkTreeIter *item_iter) { char *file_size_fancy; struct GNUNET_FS_FileInformation *fi; GtkTreeRowReference *row_reference; GtkTreePath *path; struct stat sbuf; if (0 != stat (item->filename, &sbuf)) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "stat", item->filename); return; } gtk_tree_store_insert_after (ts, item_iter, parent, sibling); path = gtk_tree_model_get_path (GTK_TREE_MODEL (ts), item_iter); row_reference = gtk_tree_row_reference_new (GTK_TREE_MODEL (ts), path); gtk_tree_path_free (path); if (item->is_directory) { /* update meta data mime type (force to be GNUnet-directory) */ if (NULL != item->meta) GNUNET_CONTAINER_meta_data_delete (item->meta, EXTRACTOR_METATYPE_MIMETYPE, NULL, 0); else item->meta = GNUNET_CONTAINER_meta_data_create (); GNUNET_FS_meta_data_make_directory (item->meta); fi = GNUNET_FS_file_information_create_empty_directory (GNUNET_FS_GTK_get_fs_handle (), row_reference, item->ksk_uri, item->meta, &adcc->directory_scan_bo, item->filename); file_size_fancy = GNUNET_strdup (MARKER_DIR_FILE_SIZE); } else { fi = GNUNET_FS_file_information_create_from_file (GNUNET_FS_GTK_get_fs_handle (), row_reference, item->filename, item->ksk_uri, item->meta, adcc->directory_scan_do_index, &adcc->directory_scan_bo); file_size_fancy = GNUNET_STRINGS_byte_size_fancy (sbuf.st_size); } gtk_tree_store_set (ts, item_iter, 0, file_size_fancy, 1, (gboolean) adcc->directory_scan_do_index, 2, item->short_filename, 3, (guint) adcc->directory_scan_bo.anonymity_level, 4, (guint) adcc->directory_scan_bo.content_priority, 5, fi, 6, (guint64) adcc->directory_scan_bo.expiration_time.abs_value, 7, (guint) adcc->directory_scan_bo.replication_level, -1); GNUNET_free (file_size_fancy); } /** * Recursively traverse the share tree and add it to the tree store * * @param adcc progress dialog context of our window * @param toplevel root of the tree to add * @param parent_iter parent of the current entry to add */ static void add_share_items_to_treestore (struct AddDirClientContext *adcc, struct GNUNET_FS_ShareTreeItem *toplevel, GtkTreeIter *parent_iter) { struct MainPublishingDialogContext *ctx = adcc->ctx; GtkTreeStore *ts = GTK_TREE_STORE (ctx->file_info_treemodel); GtkTreeIter *sibling_iter; GtkTreeIter last_added; struct GNUNET_FS_ShareTreeItem *item; sibling_iter = NULL; for (item = toplevel; NULL != item; item = item->next) { add_item (adcc, ts, item, parent_iter, sibling_iter, &last_added); sibling_iter = &last_added; if (item->is_directory) add_share_items_to_treestore (adcc, item->children_head, sibling_iter); } } /** * Progress callback called from the directory scanner with * information about our progress scanning the hierarchy. * * @param cls progress dialog context of our window * @param filename filename this update is about, can be NULL * @param is_directory is this file a directory, SYSERR if not applicable * @param reason kind of progress that was made */ static void directory_scan_cb (void *cls, const char *filename, int is_directory, enum GNUNET_FS_DirScannerProgressUpdateReason reason) { struct AddDirClientContext *adcc = cls; static struct GNUNET_TIME_Absolute last_pulse; char *s; gdouble fraction; switch (reason) { case GNUNET_FS_DIRSCANNER_FILE_START: GNUNET_assert (NULL != filename); if (GNUNET_TIME_absolute_get_duration (last_pulse).rel_value > 100) { gtk_progress_bar_pulse (adcc->progress_dialog_bar); last_pulse = GNUNET_TIME_absolute_get (); } #if VERBOSE_PROGRESS if (is_directory) { GNUNET_asprintf (&s, _("Scanning directory `%s'.\n"), filename); insert_progress_dialog_text (adcc, s); GNUNET_free (s); } else adcc->total++; #else if (! is_directory) adcc->total++; #endif break; case GNUNET_FS_DIRSCANNER_FILE_IGNORED: GNUNET_assert (NULL != filename); GNUNET_asprintf (&s, _("Failed to scan `%s' (access error). Skipping.\n"), filename); #if ! VERBOSE_PROGRESS gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (adcc->progress_dialog_builder, "GNUNET_FS_GTK_progress_dialog_scrolled_window"))); #endif insert_progress_dialog_text (adcc, s); GNUNET_free (s); break; case GNUNET_FS_DIRSCANNER_ALL_COUNTED: fraction = (adcc->total == 0) ? 1.0 : (1.0 * adcc->done) / adcc->total; GNUNET_asprintf (&s, "%u/%u (%3f%%)", adcc->done, adcc->total, 100.0 * fraction); gtk_progress_bar_set_text (adcc->progress_dialog_bar, s); GNUNET_free (s); gtk_progress_bar_set_fraction (adcc->progress_dialog_bar, fraction); break; case GNUNET_FS_DIRSCANNER_EXTRACT_FINISHED: GNUNET_assert (NULL != filename); #if VERBOSE_PROGRESS GNUNET_asprintf (&s, _("Processed file `%s'.\n"), filename); insert_progress_dialog_text (adcc, s); GNUNET_free (s); #endif adcc->done++; GNUNET_assert (adcc->done <= adcc->total); fraction = (adcc->total == 0) ? 1.0 : (1.0 * adcc->done) / adcc->total; GNUNET_asprintf (&s, "%u/%u (%3f%%)", adcc->done, adcc->total, 100.0 * fraction); gtk_progress_bar_set_text (adcc->progress_dialog_bar, s); GNUNET_free (s); gtk_progress_bar_set_fraction (adcc->progress_dialog_bar, fraction); break; case GNUNET_FS_DIRSCANNER_INTERNAL_ERROR: insert_progress_dialog_text (adcc, _("Operation failed (press cancel)\n")); GNUNET_FS_directory_scan_abort (adcc->ds); adcc->ds = NULL; break; case GNUNET_FS_DIRSCANNER_FINISHED: { struct GNUNET_FS_ShareTreeItem *directory_scan_result; insert_progress_dialog_text (adcc, _("Scanner has finished.\n")); directory_scan_result = GNUNET_FS_directory_scan_get_result (adcc->ds); adcc->ds = NULL; GNUNET_FS_share_tree_trim (directory_scan_result); add_share_items_to_treestore (adcc, directory_scan_result, NULL); GNUNET_FS_share_tree_free (directory_scan_result); update_selectivity (adcc->ctx); destroy_progress_dialog (adcc); } break; default: GNUNET_break (0); break; } } /** * Setup the context and progress dialog for scanning a file or * directory structure (for meta data) and importing it into * the tree view. * * @param ctx publishing context for the main publishing window * @param filename name of the file or directory to scan * @param bo options for the operation * @param do_index should we index or insert files (by default) */ static void scan_file_or_directory (struct MainPublishingDialogContext *ctx, gchar *filename, struct GNUNET_FS_BlockOptions *bo, int do_index) { struct AddDirClientContext *adcc; adcc = GNUNET_malloc (sizeof (struct AddDirClientContext)); adcc->ctx = ctx; GNUNET_CONTAINER_DLL_insert_tail (ctx->adddir_head, ctx->adddir_tail, adcc); adcc->directory_scan_bo = *bo; adcc->directory_scan_do_index = do_index; /* setup the dialog and get the widgets we need most */ adcc->progress_dialog_builder = GNUNET_GTK_get_new_builder ("gnunet_fs_gtk_progress_dialog.glade", adcc); adcc->progress_dialog = GTK_WIDGET (gtk_builder_get_object (adcc->progress_dialog_builder, "GNUNET_FS_GTK_progress_dialog")); adcc->progress_dialog_bar = GTK_PROGRESS_BAR (gtk_builder_get_object (adcc->progress_dialog_builder, "GNUNET_FS_GTK_progress_dialog_progressbar")); adcc->progress_dialog_textview = GTK_TEXT_VIEW (gtk_builder_get_object (adcc->progress_dialog_builder, "GNUNET_FS_GTK_progress_dialog_textview")); adcc->textview_vertical_adjustment = GTK_ADJUSTMENT (gtk_builder_get_object (adcc->progress_dialog_builder, "GNUNET_FS_GTK_progress_dialog_textview_vertical_adjustment")); adcc->progress_dialog_textbuffer = GTK_TEXT_BUFFER (gtk_builder_get_object (adcc->progress_dialog_builder, "GNUNET_FS_GTK_progress_dialog_textbuffer")); /* show the window */ gtk_window_set_transient_for (GTK_WINDOW (adcc->progress_dialog), adcc->ctx->master_pubdialog); gtk_window_set_title (GTK_WINDOW (adcc->progress_dialog), filename); gtk_window_present (GTK_WINDOW (adcc->progress_dialog)); /* actually start the scan */ adcc->ds = GNUNET_FS_directory_scan_start (filename, GNUNET_NO, NULL, &directory_scan_cb, adcc); /* disables 'cancel' button of the master dialog */ update_selectivity (ctx); } /** * Function called when the "open" (directory) dialog was closed. * * @param dialog the open dialog * @param response_id result of the dialog ("-5" means to "run") * @param user_data master publishing dialog context of our window */ static void publish_directory_dialog_response_cb (GtkDialog * dialog, gint response_id, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; /* FIXME-UGLY: how about using a separate closure and not needing this mess? In fact, even without it I don't see why we need to disconnect the handler... */ if (g_signal_handler_is_connected (G_OBJECT (dialog), ctx->open_directory_handler_id)) g_signal_handler_disconnect (G_OBJECT (dialog), ctx->open_directory_handler_id); ctx->open_directory_handler_id = 0; if (response_id == -5 /* OK */) { char *filename; int do_index; struct GNUNET_FS_BlockOptions bo; filename = GNUNET_GTK_filechooser_get_filename_utf8 (GTK_FILE_CHOOSER (dialog)); if (! GNUNET_GTK_get_selected_anonymity_level (ctx->open_directory_builder, "GNUNET_GTK_publish_directory_dialog_anonymity_combobox", &bo.anonymity_level)) { GNUNET_break (0); bo.anonymity_level = 1; } bo.content_priority = gtk_spin_button_get_value (GTK_SPIN_BUTTON (gtk_builder_get_object (ctx->open_directory_builder, "GNUNET_GTK_publish_directory_dialog_priority_spin_button"))); bo.replication_level = gtk_spin_button_get_value (GTK_SPIN_BUTTON (gtk_builder_get_object (ctx->open_directory_builder, "GNUNET_GTK_publish_directory_dialog_replication_spin_button"))); { GtkSpinButton *sb; sb = GTK_SPIN_BUTTON (gtk_builder_get_object (ctx->open_directory_builder, "GNUNET_GTK_publish_directory_dialog_expiration_year_spin_button")); bo.expiration_time = GNUNET_FS_GTK_get_expiration_time (sb); } do_index = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (gtk_builder_get_object (ctx->open_directory_builder, "GNUNET_GTK_publish_directory_dialog_do_index_checkbutton"))); scan_file_or_directory (ctx, filename, &bo, do_index); g_free (filename); } gtk_widget_destroy (GTK_WIDGET (dialog)); g_object_unref (G_OBJECT (ctx->open_directory_builder)); ctx->open_directory_builder = NULL; } /** * Function called when the "open" (file) dialog was closed. * * @param dialog the open dialog * @param response_id result of the dialog ("-5" means to "run") * @param user_data master publishing dialog context of our window */ static void publish_file_dialog_response_cb (GtkDialog * dialog, gint response_id, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; /* FIXME-UGLY: how about using a separate closure and not needing this mess? In fact, even without it I don't see why we need to disconnect the handler... */ if (g_signal_handler_is_connected (G_OBJECT (dialog), ctx->open_file_handler_id)) g_signal_handler_disconnect (G_OBJECT (dialog), ctx->open_file_handler_id); ctx->open_file_handler_id = 0; if (response_id == -5 /* OK */) { char *filename; struct GNUNET_FS_BlockOptions bo; int do_index; filename = GNUNET_GTK_filechooser_get_filename_utf8 (GTK_FILE_CHOOSER (dialog)); if (!GNUNET_GTK_get_selected_anonymity_level (ctx->open_file_builder, "GNUNET_GTK_publish_file_dialog_anonymity_combobox", &bo.anonymity_level)) { GNUNET_break (0); bo.anonymity_level = 1; } bo.content_priority = gtk_spin_button_get_value (GTK_SPIN_BUTTON (gtk_builder_get_object (ctx->open_file_builder, "GNUNET_GTK_publish_file_dialog_priority_spin_button"))); { GtkSpinButton *sb; sb = GTK_SPIN_BUTTON (gtk_builder_get_object (ctx->open_file_builder, "GNUNET_GTK_publish_file_dialog_expiration_year_spin_button")); bo.expiration_time = GNUNET_FS_GTK_get_expiration_time (sb); } bo.replication_level = gtk_spin_button_get_value (GTK_SPIN_BUTTON (gtk_builder_get_object (ctx->open_file_builder, "GNUNET_GTK_publish_file_dialog_replication_spin_button"))); do_index = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (gtk_builder_get_object (ctx->open_file_builder, "GNUNET_GTK_publish_file_dialog_do_index_checkbutton"))); scan_file_or_directory (ctx, filename, &bo, do_index); g_free (filename); } gtk_widget_destroy (GTK_WIDGET (dialog)); g_object_unref (G_OBJECT (ctx->open_file_builder)); ctx->open_file_builder = NULL; } /** * User clicked on the 'add' button in the master publish dialog. * Create the dialog to allow the user to select a file to add. * * FIXME-UGLY: lots of code duplication between files & directories here... * * @param dummy the button that was pressed * @param user_data master publishing dialog context of our window */ void GNUNET_GTK_master_publish_dialog_add_button_clicked_cb (GtkWidget * dummy, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; GtkWidget *ad; GtkComboBox *combo; /* FIXME-UGLY: should we use a fresh, specific context for this dialog? FIXME-BUG: how does this right now prevent two dialogs from being opened? (or the master dialog from being closed in the meantime?) */ ctx->open_file_builder = GNUNET_GTK_get_new_builder ("gnunet_fs_gtk_publish_file_dialog.glade", ctx); GNUNET_FS_GTK_setup_expiration_year_adjustment (ctx->open_file_builder); ad = GTK_WIDGET (gtk_builder_get_object (ctx->open_file_builder, "GNUNET_GTK_publish_file_dialog")); /* FIXME-FEATURE: Use some kind of adjustable defaults instead of 1000, 0 and TRUE */ gtk_spin_button_set_value (GTK_SPIN_BUTTON (gtk_builder_get_object (ctx->open_file_builder, "GNUNET_GTK_publish_file_dialog_priority_spin_button")), 1000); gtk_spin_button_set_value (GTK_SPIN_BUTTON (gtk_builder_get_object (ctx->open_file_builder, "GNUNET_GTK_publish_file_dialog_replication_spin_button")), 0); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gtk_builder_get_object (ctx->open_file_builder, "GNUNET_GTK_publish_file_dialog_do_index_checkbutton")), TRUE); ctx->open_file_handler_id = g_signal_connect (G_OBJECT (ad), "response", G_CALLBACK (publish_file_dialog_response_cb), ctx); combo = GTK_COMBO_BOX (gtk_builder_get_object (ctx->open_file_builder, "GNUNET_GTK_publish_file_dialog_anonymity_combobox")); gtk_combo_box_set_model (combo, GNUNET_FS_GTK_get_anonymity_level_list_store ()); /* show dialog */ gtk_window_set_transient_for (GTK_WINDOW (ad), ctx->master_pubdialog); gtk_window_present (GTK_WINDOW (ad)); } /** * User clicked on the 'open' button in the master publish dialog. * Create the dialog to allow the user to select a directory. * * FIXME-UGLY: lots of code duplication between files & directories here... * * @param dummy the button that was pressed * @param user_data master publishing dialog context of our window */ void GNUNET_GTK_master_publish_dialog_open_button_clicked_cb (GtkWidget * dummy, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; GtkWidget *ad; GtkComboBox *combo; GtkTreeModel *anon_treemodel; /* FIXME-UGLY: should we use a fresh, specific context for this dialog? FIXME-BUG: how does this right now prevent two dialogs from being opened? (or the master dialog from being closed in the meantime?) */ ctx->open_directory_builder = GNUNET_GTK_get_new_builder ("gnunet_fs_gtk_publish_directory_dialog.glade", ctx); GNUNET_FS_GTK_setup_expiration_year_adjustment (ctx->open_directory_builder); /* FIXME-FEATURE: Use some kind of adjustable defaults instead of 1000, 0 and TRUE */ gtk_spin_button_set_value (GTK_SPIN_BUTTON (gtk_builder_get_object (ctx->open_directory_builder, "GNUNET_GTK_publish_directory_dialog_priority_spin_button")), 1000); gtk_spin_button_set_value (GTK_SPIN_BUTTON (gtk_builder_get_object (ctx->open_directory_builder, "GNUNET_GTK_publish_directory_dialog_replication_spin_button")), 0); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gtk_builder_get_object (ctx->open_directory_builder, "GNUNET_GTK_publish_directory_dialog_do_index_checkbutton")), TRUE); ad = GTK_WIDGET (gtk_builder_get_object (ctx->open_directory_builder, "GNUNET_GTK_publish_directory_dialog")); ctx->open_directory_handler_id = g_signal_connect (G_OBJECT (ad), "response", G_CALLBACK (publish_directory_dialog_response_cb), ctx); /* FIXME-BUG-MAYBE: possibly bad sharing of the anonymity tree model */ anon_treemodel = GTK_TREE_MODEL (gtk_builder_get_object (ctx->main_window_builder, "main_window_search_anonymity_liststore")); combo = GTK_COMBO_BOX (gtk_builder_get_object (ctx->open_directory_builder, "GNUNET_GTK_publish_directory_dialog_anonymity_combobox")); gtk_combo_box_set_model (combo, anon_treemodel); /* show dialog */ gtk_window_set_transient_for (GTK_WINDOW (ad), ctx->master_pubdialog); gtk_window_present (GTK_WINDOW (ad)); } /* ********************************* editing sub-dialog ****************************************** */ /** * Context we keep while an "edit" sub-dialog is open. * * FIXME-MAYBE: we should probably keep these also in a DLL with the * master dialog to prevent the master dialog from closing while the * edit dialog is running? Or are we otherwise already preventing * this? */ struct EditPublishContext { /** * File information that is being edited */ struct GNUNET_FS_FileInformation *fip; /** * Tree model that is being edited (where to store the results afterwards). */ GtkTreeModel *tm; /** * Position in the tree that is being edited. * * FIXME-MAYBE: using a TreeIter here is not great, as the row may be * moved (or even deleted) while the edit dialog is running. Should * use a RowReference instead. Also, how do we guard against two * edit dialogs being opened for the same file? (Shouldn't we store * the EditPublishContext with, say, the tree model?) */ GtkTreeIter iter; }; /** * Update tree view based on the information from the * GNUNET_FS_FileInformation publish-structure. * * @param cls closure, a 'struct EditPublishContext *' * @param fi the entry in the publish-structure * @param length length of the file or directory * @param meta metadata for the file or directory (can be modified) * @param uri pointer to the keywords that will be used for this entry (can be modified) * @param bo block options (can be modified) * @param do_index should we index (can be modified) * @param client_info pointer to client context set upon creation (can be modified) * @return GNUNET_OK to continue, GNUNET_NO to remove * this entry from the directory, GNUNET_SYSERR * to abort the iteration */ static int update_treeview_after_edit (void *cls, struct GNUNET_FS_FileInformation *fi, uint64_t length, struct GNUNET_CONTAINER_MetaData *meta, struct GNUNET_FS_Uri **uri, struct GNUNET_FS_BlockOptions *bo, int *do_index, void **client_info) { struct EditPublishContext *epc = cls; gtk_tree_store_set (GTK_TREE_STORE (epc->tm), &epc->iter, 1, *do_index, 3, (guint) bo->anonymity_level, 4, (guint) bo->content_priority, 6, (guint64) bo->expiration_time.abs_value, 7, (guint) bo->replication_level, -1); return GNUNET_SYSERR; } /** * Function called when the edit publish dialog has been closed. * * @param cls closure * @param ret GTK_RESPONSE_OK if the dialog was closed with "OK" * @param root unused (namespace root name) */ static void master_publish_edit_publish_dialog_cb (gpointer cls, gint ret, const char *root) { struct EditPublishContext *epc = cls; if (ret == GTK_RESPONSE_OK) GNUNET_FS_file_information_inspect (epc->fip, &update_treeview_after_edit, epc); GNUNET_free (epc); } /** * The user clicked on the "edit" button in the master dialog. Edit * the selected tree item. * * @param dummy the 'edit' button * @param user_data master publishing dialog context of our window */ void GNUNET_GTK_master_publish_dialog_edit_button_clicked_cb (GtkWidget * dummy, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; struct EditPublishContext *epc; epc = GNUNET_malloc (sizeof (struct EditPublishContext)); epc->tm = ctx->file_info_treemodel; if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &epc->iter)) { GNUNET_break (0); GNUNET_free (epc); return; } gtk_tree_model_get (ctx->file_info_treemodel, &epc->iter, 5, &epc->fip, -1); GNUNET_FS_GTK_edit_publish_dialog (ctx->master_pubdialog, epc->fip, GNUNET_YES, &master_publish_edit_publish_dialog_cb, epc); } /* ******************** master edit dialog shutdown *********************** */ /** * Get the file information struct corresponding to the * given iter in the publish dialog tree model. Recursively * builds the file information struct from the subtree. * * @param tm model to grab fi from * @param iter position to grab fi from * @return file information from the given position (never NULL) */ static struct GNUNET_FS_FileInformation * get_file_information (GtkTreeModel * tm, GtkTreeIter * iter) { struct GNUNET_FS_FileInformation *fi; struct GNUNET_FS_FileInformation *fic; GtkTreeIter child; gtk_tree_model_get (tm, iter, 5, &fi, -1); gtk_tree_store_set (GTK_TREE_STORE (tm), iter, 5, NULL, -1); GNUNET_assert (fi != NULL); if (gtk_tree_model_iter_children (tm, &child, iter)) { GNUNET_break (GNUNET_YES == GNUNET_FS_file_information_is_directory (fi)); do { fic = get_file_information (tm, &child); GNUNET_break (GNUNET_OK == GNUNET_FS_file_information_add (fi, fic)); } while (gtk_tree_model_iter_next (tm, &child)); } return fi; } /** * (Recursively) clean up the tree store with the pseudonyms. * * @param tm tree model we are cleaning up * @param iter current position to clean up */ static void free_pseudonym_tree_store (GtkTreeModel * tm, GtkTreeIter * iter) { GtkTreeIter child; struct GNUNET_CONTAINER_MetaData *meta; struct GNUNET_FS_Namespace *ns; gtk_tree_model_get (tm, iter, 1, &ns, 4, &meta, -1); if (NULL != meta) GNUNET_CONTAINER_meta_data_destroy (meta); if (NULL != ns) { /* FIXME-BUG-MAYBE: should we not delete ns here? (resource leak?) */ // GNUNET_FS_namespace_delete (nso, GNUNET_NO); } /* recursively clean up children */ if (gtk_tree_model_iter_children (tm, &child, iter)) { do { free_pseudonym_tree_store (tm, &child); } while (gtk_tree_model_iter_next (tm, &child)); } } /** * Recursively clean up the tree store with the file information in it. * * @param tm tree model we are cleaning up * @param iter current position to clean up */ static void free_file_information_tree_store (GtkTreeModel * tm, GtkTreeIter * iter) { GtkTreeIter child; struct GNUNET_FS_FileInformation *fip; gtk_tree_model_get (tm, iter, 5, &fip, -1); if (NULL != fip) GNUNET_FS_file_information_destroy (fip, NULL, NULL); /* recursively clean up children */ if (gtk_tree_model_iter_children (tm, &child, iter)) { do { free_file_information_tree_store (tm, &child); } while (gtk_tree_model_iter_next (tm, &child)); } } /** * Close the master publish dialog. If the response code was OK, starts * the publishing operation. Otherwise, this function just cleans up the * memory and the window itself. * * @param ctx master dialog context * @return GNUNET_NO if we cannot clean up right now (sub-windows are still open) */ static int close_master_publish_dialog (struct MainPublishingDialogContext *ctx) { GtkTreeIter iter; /* Refuse to close until all scanners are finished */ /* FIXME: what about open-directory operations? */ if (NULL != ctx->adddir_head) return GNUNET_NO; /* free state from 'ptm' */ if (gtk_tree_model_get_iter_first (ctx->pseudonym_treemodel, &iter)) do { free_pseudonym_tree_store (ctx->pseudonym_treemodel, &iter); } while (gtk_tree_model_iter_next (ctx->pseudonym_treemodel, &iter)); gtk_tree_store_clear (GTK_TREE_STORE (ctx->pseudonym_treemodel)); /* free state from 'tm' */ if (gtk_tree_model_get_iter_first (ctx->file_info_treemodel, &iter)) do { free_file_information_tree_store (ctx->file_info_treemodel, &iter); } while (gtk_tree_model_iter_next (ctx->file_info_treemodel, &iter)); gtk_tree_store_clear (GTK_TREE_STORE (ctx->file_info_treemodel)); gtk_widget_destroy (GTK_WIDGET (ctx->master_pubdialog)); g_object_unref (G_OBJECT (ctx->builder)); GNUNET_free (ctx); return GNUNET_YES; } /** * The user pushed the 'execute' button. Start the publishing * operation and clean up the memory and the window itself. * * @param button the button that was clicked * @param user_data master publishing dialog context of our window */ void GNUNET_GTK_master_publish_dialog_execute_button_clicked_cb (GtkButton * button, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; gpointer namespace; gchar *namespace_id; gchar *namespace_uid; struct GNUNET_FS_FileInformation *fi; GtkTreeIter iter; if (NULL != ctx->adddir_head) { GNUNET_break (0); return; } if (TRUE == gtk_tree_selection_get_selected (ctx->pseudonym_selection, NULL, &iter)) { gtk_tree_model_get (ctx->pseudonym_treemodel, &iter, 1, &namespace, 2, &namespace_id, 5, &namespace_uid, -1); } else { namespace = NULL; namespace_id = NULL; namespace_uid = NULL; } if (gtk_tree_model_get_iter_first (ctx->file_info_treemodel, &iter)) do { fi = get_file_information (ctx->file_info_treemodel, &iter); /* FIXME: should we convert namespace id and uid from UTF8? */ GNUNET_FS_publish_start (GNUNET_FS_GTK_get_fs_handle (), fi, namespace, namespace_id, namespace_uid, GNUNET_FS_PUBLISH_OPTION_NONE); } while (gtk_tree_model_iter_next (ctx->file_info_treemodel, &iter)); g_free (namespace_id); g_free (namespace_uid); GNUNET_break (GNUNET_YES == close_master_publish_dialog (ctx)); } /** * The user pushed the 'cancel' button. Close the master publish dialog. * * @param button the button that was clicked * @param user_data master publishing dialog context of our window */ void GNUNET_GTK_master_publish_dialog_cancel_button_clicked_cb (GtkButton * button, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; GNUNET_break (GNUNET_YES == close_master_publish_dialog (ctx)); } /** * The user attempted to close the publish window. Check if this is * allowed and if so, close it. * * @param widget the widget that generated the close event * @param even the close event * @param user_data master publishing dialog context of our window * @return TRUE to refuse to close, FALSE if we closed the window * FIXME: are these return codes correct? */ gboolean GNUNET_GTK_master_publish_dialog_delete_event_cb (GtkWidget * widget, GdkEvent * event, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; if (GNUNET_NO == close_master_publish_dialog (ctx)) return TRUE; return FALSE; } /* ******************** master edit dialog initialization *********************** */ /** * Closure for 'add_updateable_to_ts'. */ struct UpdateableContext { /** * Parent of current insertion. */ GtkTreeIter *parent; /** * Tree store we are modifying. */ GtkTreeStore *ts; /** * Name of the namespace. */ const char *namespace_name; /** * Handle to the namespace. */ struct GNUNET_FS_Namespace *ns; /** * Hash codes of identifiers already added to tree store. */ struct GNUNET_CONTAINER_MultiHashMap *seen; /** * Did the iterator get called? */ int update_called; }; /** * Add updateable entries to the tree view. * * @param cls closure * @param last_id ID to add * @param last_uri associated URI * @param last_meta associate meta data * @param next_id ID for future updates */ static void add_updateable_to_ts (void *cls, const char *last_id, const struct GNUNET_FS_Uri *last_uri, const struct GNUNET_CONTAINER_MetaData *last_meta, const char *next_id) { struct UpdateableContext *uc = cls; struct UpdateableContext sc; GtkTreeIter iter; GtkTreeIter titer; char *desc; GNUNET_HashCode hc; uc->update_called = GNUNET_YES; GNUNET_CRYPTO_hash (last_id, strlen (last_id), &hc); if (NULL != GNUNET_CONTAINER_multihashmap_get (uc->seen, &hc)) return; GNUNET_CONTAINER_multihashmap_put (uc->seen, &hc, "dummy", GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST); /* FIXME: what if this put fails? Not convinced it cannot... */ desc = GNUNET_CONTAINER_meta_data_get_first_by_types (last_meta, EXTRACTOR_METATYPE_DESCRIPTION, EXTRACTOR_METATYPE_TITLE, EXTRACTOR_METATYPE_BOOK_TITLE, EXTRACTOR_METATYPE_FILENAME, EXTRACTOR_METATYPE_SUMMARY, EXTRACTOR_METATYPE_ALBUM, EXTRACTOR_METATYPE_COMMENT, EXTRACTOR_METATYPE_SUBJECT, -1); if (desc == NULL) desc = GNUNET_strdup (_("no description supplied")); else { char *utf8_desc = NULL; utf8_desc = GNUNET_FS_GTK_dubious_meta_to_utf8 (EXTRACTOR_METAFORMAT_UTF8, desc, strlen (desc) + 1); GNUNET_free (desc); if (utf8_desc != NULL) desc = utf8_desc; else desc = NULL; } gtk_tree_store_insert_with_values (uc->ts, &iter, uc->parent, G_MAXINT, 0, uc->namespace_name, 1, uc->ns, 2, last_id, 3, GNUNET_FS_uri_dup (last_uri), 4, GNUNET_CONTAINER_meta_data_duplicate (last_meta), 5, "", 6, desc, 7, TRUE /* update editable (always) */ , 8, FALSE /* current not editable (only for top-level) */ , -1); GNUNET_free_non_null (desc); sc.parent = &iter; sc.ts = uc->ts; sc.namespace_name = uc->namespace_name; sc.ns = uc->ns; sc.seen = uc->seen; sc.update_called = GNUNET_NO; GNUNET_FS_namespace_list_updateable (uc->ns, next_id, &add_updateable_to_ts, &sc); if ((sc.update_called == GNUNET_NO) && (next_id != NULL) && (strlen (next_id) > 0)) { /* add leaf */ gtk_tree_store_insert_with_values (uc->ts, &titer, &iter, G_MAXINT, 0, uc->namespace_name, 1, uc->ns, 2, next_id, 3, NULL, 4, NULL, 5, "", 6, "", 7, TRUE /* update editable (always) */ , 8, FALSE /* current not editable (only for top-level) */ , -1); } } /** * Add all updateable entries of the current namespace to the * tree store. * * @param cls the 'GtkTreeStore' to update * @param name name of the namespace to add * @param id identity of the namespace to add */ static void add_namespace_to_ts (void *cls, const char *name, const GNUNET_HashCode * id) { GtkTreeStore *ts = cls; struct UpdateableContext uc; GtkTreeIter iter; uc.parent = &iter; uc.namespace_name = name; uc.ts = ts; uc.ns = GNUNET_FS_namespace_create (GNUNET_FS_GTK_get_fs_handle (), name); uc.update_called = GNUNET_NO; gtk_tree_store_insert_with_values (ts, &iter, NULL, G_MAXINT, 0, name, 1, uc.ns, 2, NULL /* last-id */ , 3, NULL /* last-uri (as string!) */ , 4, NULL /* meta */ , 5, NULL /* next-ID */ , 6, NULL /* last-description */ , 7, TRUE /* update editable */ , 8, TRUE /* current editable */ , -1); uc.seen = GNUNET_CONTAINER_multihashmap_create (128); GNUNET_FS_namespace_list_updateable (uc.ns, NULL, &add_updateable_to_ts, &uc); GNUNET_CONTAINER_multihashmap_destroy (uc.seen); } /** * Run the file-publishing operation (by opening the master publishing dialog). * * @param dummy widget that triggered the action * @param user_data builder of the main window */ void GNUNET_GTK_main_menu_file_publish_activate_cb (GtkWidget * dummy, gpointer user_data) { struct MainPublishingDialogContext *ctx; GtkTreeView *pseudonym_treeview; ctx = GNUNET_malloc (sizeof (struct MainPublishingDialogContext)); /* FIXME-UNCLEAN: why bother keeping this one? */ ctx->main_window_builder = GTK_BUILDER (user_data); ctx->builder = GNUNET_GTK_get_new_builder ("gnunet_fs_gtk_publish_dialog.glade", ctx); if (ctx->builder == NULL) { GNUNET_break (0); GNUNET_free (ctx); return; } /* initialize widget references */ ctx->up_button = GTK_WIDGET (gtk_builder_get_object (ctx->builder, "GNUNET_GTK_master_publish_dialog_up_button")); ctx->down_button = GTK_WIDGET (gtk_builder_get_object (ctx->builder, "GNUNET_GTK_master_publish_dialog_down_button")); ctx->left_button = GTK_WIDGET (gtk_builder_get_object (ctx->builder, "GNUNET_GTK_master_publish_dialog_left_button")); ctx->right_button = GTK_WIDGET (gtk_builder_get_object (ctx->builder, "GNUNET_GTK_master_publish_dialog_right_button")); ctx->delete_button = GTK_WIDGET (gtk_builder_get_object (ctx->builder, "GNUNET_GTK_master_publish_dialog_delete_button")); ctx->edit_button = GTK_WIDGET (gtk_builder_get_object (ctx->builder, "GNUNET_GTK_master_publish_dialog_edit_button")); ctx->execute_button = GTK_WIDGET (gtk_builder_get_object (ctx->builder, "GNUNET_GTK_master_publish_dialog_execute_button")); ctx->cancel_button = GTK_WIDGET (gtk_builder_get_object (ctx->builder , "GNUNET_GTK_master_publish_dialog_cancel_button")); ctx->file_info_treeview = GTK_TREE_VIEW (gtk_builder_get_object (ctx->builder, "GNUNET_GTK_master_publish_dialog_file_information_tree_view")); ctx->file_info_selection = gtk_tree_view_get_selection (ctx->file_info_treeview); ctx->file_info_treemodel = gtk_tree_view_get_model (ctx->file_info_treeview); ctx->master_pubdialog = GTK_WINDOW (gtk_builder_get_object (ctx->builder, "GNUNET_GTK_master_publish_dialog")); pseudonym_treeview = GTK_TREE_VIEW (gtk_builder_get_object (ctx->builder, "GNUNET_GTK_master_publish_dialog_pseudonym_tree_view")); ctx->pseudonym_selection = gtk_tree_view_get_selection (pseudonym_treeview); ctx->pseudonym_treemodel = gtk_tree_view_get_model (pseudonym_treeview); /* connect signals; FIXME-UNCLEAN: these could be connected with (modern) Glade */ g_signal_connect (G_OBJECT (ctx->file_info_selection), "changed", G_CALLBACK (selection_changed_cb), ctx); g_signal_connect (G_OBJECT (ctx->pseudonym_selection), "changed", G_CALLBACK (selection_changed_cb), ctx); /* populate namespace model */ GNUNET_FS_namespace_list (GNUNET_FS_GTK_get_fs_handle (), &add_namespace_to_ts, GTK_TREE_STORE (ctx->pseudonym_treemodel)); /* show dialog */ gtk_window_present (GTK_WINDOW (ctx->master_pubdialog)); } /* end of gnunet-fs-gtk_publish-dialog.c */