/* This file is part of GNUnet Copyright (C) 2005-2013 GNUnet e.V. 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 3, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @file src/fs/gnunet-fs-gtk_publish-dialog.c * @author Christian Grothoff */ #include "gnunet-fs-gtk.h" #include "gnunet-fs-gtk_common.h" #include "gnunet-fs-gtk_publish-edit-dialog.h" #include #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_EXTRA_LOGGING /** * Columns in the publish model. */ enum PUBLISH_ModelColumns { /** * A gchararray. */ PUBLISH_MC_FILESIZE = 0, /** * A gboolean. */ PUBLISH_MC_DO_INDEX = 1, /** * A gchararray. */ PUBLISH_MC_FILENAME = 2, /** * A guint. */ PUBLISH_MC_ANONYMITY_LEVEL = 3, /** * A guint. */ PUBLISH_MC_PRIORITY = 4, /** * A gpointer. */ PUBLISH_MC_FILE_INFORMATION_STRUCT = 5, /** * A guint64. */ PUBLISH_MC_EXPIRATION_TIME_ABSOLUTE = 6, /** * A guint. */ PUBLISH_MC_REPLICATION_LEVEL = 7 }; /** * Columns in the identifiers model. */ enum IDENTIFIERS_ModelColumns { /** * A gchararray. */ IDENTIFIERS_MC_ID = 0, /** * A gchararray. */ IDENTIFIERS_MC_UPDATE_ID = 1, /** * A gchararray. */ IDENTIFIERS_MC_DESCRIPTION = 2, /** * A gchararray. */ IDENTIFIERS_MC_PATH_TO_ORIGINAL = 3, /** * A gchararray. */ IDENTIFIERS_MC_ID_MARKED_UP = 4 }; /** * The columns in the "GNUNET_GTK_master_publish_dialog_ego_liststore" */ enum EGO_ModelColumns { /** * A gchararray. */ EGO_MC_NAME = 0, /** * A 'struct GNUNET_IDENTIFIER_Ego'. */ EGO_MC_EGO = 1 }; /** * 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; /** * Connection to the identity service. */ struct GNUNET_IDENTITY_Handle *identity; /** * Handle to the main window of the publishing dialog. */ GtkWindow *master_pubdialog; /** * 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; /** * Checkbox that enables/diables global namespace publication. */ GtkWidget *global_checkbox; /** * Checkbox that enables/diables private namespace publication. */ GtkWidget *own_checkbox; /** * Treeview that shows previously-used identifiers and possible update IDs. */ GtkTreeView *identifiers_treeview; /** * Model of identifiers_treeview. */ GtkTreeModel *identifiers_treemodel; /** * Selection of identifiers_treeview. */ GtkTreeSelection *identifiers_selection; /** * Entry that allows users to specify the identifier. */ GtkWidget *identifier_entry; /** * Checkbox that enables/diables updateable private ns publications. */ GtkWidget *updateable_checkbox; /** * Entry that allows users to specify the update identifier. */ GtkWidget *update_id_entry; /** * VBox into which ns-related widgets are packed. */ GtkWidget *own_vbox; /** * HBox into which updateability-related widgets are packed. */ GtkWidget *update_id_hbox; /** * Name of the default namespace. May be NULL. */ gchar *ns_name; /** * Default namespace. May be NULL. */ struct GNUNET_IDENTITY_Ego *ns; /** * Stores the value that was in identifier_entry previously. */ gchar *previous_id; /** * The top-level paned widget. */ GtkWidget *vpaned; /** * The frame that is packed at the bottom of vpaned. */ GtkWidget *bottom_frame; /** * Scrolled window into which identifiers_treeview is packed. */ GtkWidget *identifiers_scrolled; /** * Expander that controls identifiers_treeview visibility. */ GtkWidget *identifiers_expander; /** * Button to publish all files from the dialog */ GtkWidget *execute_button; /** * Button to abort the publishing operation */ GtkWidget *cancel_button; /** * Builder for the open directory dialog (non-NULL while the dialog is open) */ GtkBuilder *open_directory_builder; /** * Builder for the open file dialog (non-NULL while the dialog is open) */ 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; /** * Task scheduled to stop the scanner on errors. */ struct GNUNET_SCHEDULER_Task *kill_task; /** * 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; }; static void clear_keywords_from_tm (struct MainPublishingDialogContext *ctx); /* ************************ 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 execute/cancel buttons in the master dialog. * * @param ctx master dialog to update selectivity for */ static void update_selectivity_execute_cancel (struct MainPublishingDialogContext *ctx) { gboolean pub_in_global; gboolean pub_in_own; gboolean updateable; const gchar *ns_id; GtkTreeIter iter; pub_in_global = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ctx->global_checkbox)); pub_in_own = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ctx->own_checkbox)); updateable = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ctx->updateable_checkbox)); gtk_widget_set_sensitive (ctx->update_id_hbox, updateable); gtk_widget_set_sensitive (ctx->own_vbox, pub_in_own); ns_id = gtk_entry_get_text (GTK_ENTRY (ctx->identifier_entry)); /* Don't let the user close the dialog until all scanners are finished and their windows are closed */ if ((gtk_tree_model_get_iter_first (ctx->file_info_treemodel, &iter)) && (pub_in_global || pub_in_own) && ((! pub_in_own) || ((NULL != ctx->ns) && (NULL != ns_id) && ('\0' != ns_id[0]))) && (NULL == ctx->adddir_head)) 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" */ if (ctx->adddir_head == NULL) gtk_widget_set_sensitive (ctx->cancel_button, TRUE); else gtk_widget_set_sensitive (ctx->cancel_button, FALSE); } /** * Update selectivity of up/down/left/right buttons in the master dialog. * * @param ctx master dialog to update selectivity for */ static void update_selectivity_edit (struct MainPublishingDialogContext *ctx) { GtkTreeIter iter; GtkTreeIter parent; GtkTreeIter pred; int is_dir; struct GNUNET_FS_FileInformation *fip; 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, PUBLISH_MC_FILE_INFORMATION_STRUCT, &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 */ void GNUNET_GTK_master_publish_dialog_file_informatino_treeview_selection_changed_cb ( GtkTreeSelection *ts, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; update_selectivity_edit (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_FS_MetaData *meta; meta = GNUNET_FS_meta_data_create (); GNUNET_FS_meta_data_make_directory (meta); GNUNET_FS_meta_data_insert (meta, "", EXTRACTOR_METATYPE_GNUNET_ORIGINAL_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_FS_meta_data_destroy (meta); gtk_tree_store_set (GTK_TREE_STORE (ctx->file_info_treemodel), pos, PUBLISH_MC_FILESIZE, MARKER_DIR_FILE_SIZE, PUBLISH_MC_DO_INDEX, (gboolean) GNUNET_NO, PUBLISH_MC_FILENAME, name, PUBLISH_MC_ANONYMITY_LEVEL, (guint) bo->anonymity_level, PUBLISH_MC_PRIORITY, (guint) bo->content_priority, PUBLISH_MC_FILE_INFORMATION_STRUCT, fi, PUBLISH_MC_EXPIRATION_TIME_ABSOLUTE, (guint64) bo->expiration_time.abs_value_us, PUBLISH_MC_REPLICATION_LEVEL, (guint) bo->replication_level, -1); update_selectivity_edit (ctx); update_selectivity_execute_cancel (ctx); } /** * 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 the subtree remover * 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, PUBLISH_MC_FILESIZE, &fsf, PUBLISH_MC_DO_INDEX, &do_index, PUBLISH_MC_FILENAME, &short_fn, PUBLISH_MC_ANONYMITY_LEVEL, &anonymity_level, PUBLISH_MC_PRIORITY, &priority, PUBLISH_MC_FILE_INFORMATION_STRUCT, &fip, PUBLISH_MC_EXPIRATION_TIME_ABSOLUTE, &expiration_time_abs, PUBLISH_MC_REPLICATION_LEVEL, &replication_level, -1); gtk_tree_store_set (GTK_TREE_STORE (tm), newpos, PUBLISH_MC_FILESIZE, fsf, PUBLISH_MC_DO_INDEX, do_index, PUBLISH_MC_FILENAME, short_fn, PUBLISH_MC_ANONYMITY_LEVEL, anonymity_level, PUBLISH_MC_PRIORITY, priority, PUBLISH_MC_FILE_INFORMATION_STRUCT, fip, PUBLISH_MC_EXPIRATION_TIME_ABSOLUTE, expiration_time_abs, PUBLISH_MC_REPLICATION_LEVEL, 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 (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 (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_edit (ctx); } } /** * Called when global ns publication checkbox is toggled. * Adjusts execute/cancel button sensitivity. * * @param togglebutton button that was toggled * @param user_data context */ void GNUNET_GTK_master_publish_dialog_global_checkbox_toggled_cb ( GtkToggleButton *togglebutton, gpointer user_data) { update_selectivity_execute_cancel (user_data); } /** * Called when private ns publication checkbox is toggled. * Adjusts execute/cancel button sensitivity. * * @param togglebutton button that was toggled * @param user_data context */ void GNUNET_GTK_master_publish_dialog_own_checkbox_toggled_cb ( GtkToggleButton *togglebutton, gpointer user_data) { update_selectivity_execute_cancel (user_data); } /** * Called when updateability checkbox is toggled. * Adjusts execute/cancel button sensitivity. * * @param togglebutton button that was toggled * @param user_data context */ void GNUNET_GTK_master_publish_dialog_updateable_checkbox_toggled_cb ( GtkToggleButton *togglebutton, gpointer user_data) { update_selectivity_execute_cancel (user_data); } /** * Generates an update id from a new id. * * @param new_text new id for which to generate update id. * @return new update id (free with g_free()). */ static gchar * generate_update_id (const gchar *new_text) { gchar *dash; gint64 num; gchar *new_update_id; gchar *copy_part; copy_part = g_strdup (new_text); dash = strrchr (copy_part, '-'); if (! dash) num = 0; else { gchar *endptr; num = g_ascii_strtoll (&dash[1], &endptr, 10); if (((0 <= num) && (endptr == &dash[1])) || (G_MAXINT64 == num) || ('\0' != endptr[0])) num = 0; else dash[0] = '\0'; } if ('\0' != new_text[0]) new_update_id = g_strdup_printf ("%s-%" G_GINT64_FORMAT, copy_part, num + 1); else new_update_id = g_strdup (""); g_free (copy_part); return new_update_id; } /** * Checks whether existing update id was generated or not. * Generates an update id from the previous id, then checks if * it matches the one that is currently specified. * If it does, then current update id was autogenerated. * * @param existing_update_id current update id * @param previous_id previous value of the identifier, from which * current update id might have been generated * @return TRUE if existing_update_id was generated from previous_id, * FALSE otherwise */ static gboolean update_id_is_autofilled (const gchar *existing_update_id, const gchar *previous_id) { gboolean result; gchar *gen_update_id; result = TRUE; gen_update_id = generate_update_id (previous_id); if (0 != strcmp (gen_update_id, existing_update_id)) result = FALSE; g_free (gen_update_id); return result; } /** * Generates a new update id and fills the entry, if necessary. * Stores new identifier as previous_id in the context struct. * * @param ctx context * @param new_text new identifier */ static void maybe_change_update_id (struct MainPublishingDialogContext *ctx, const gchar *new_text) { const gchar *existing_update_id; gchar *new_update_id; new_update_id = NULL; existing_update_id = gtk_entry_get_text (GTK_ENTRY (ctx->update_id_entry)); if (((NULL == ctx->previous_id) && ('\0' == existing_update_id[0])) || ((NULL != ctx->previous_id) && update_id_is_autofilled (existing_update_id, ctx->previous_id))) new_update_id = generate_update_id (new_text); if (NULL != new_update_id) { gtk_entry_set_text (GTK_ENTRY (ctx->update_id_entry), new_update_id); g_free (new_update_id); } g_free (ctx->previous_id); ctx->previous_id = g_strdup (new_text); } /** * Called when identifier entry contents are changed by anything. * Generates a new update id and fills the entry, if necessary. * Updates execute/cancel buttons sensitivity. * * @param widget the entry that was changed * @param user_data our `struct MainPublishingDialogContext` */ void GNUNET_GTK_master_publish_dialog_identifier_entry_changed_cb ( GtkWidget *widget, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; const gchar *new_text; new_text = gtk_entry_get_text (GTK_ENTRY (widget)); if (NULL == new_text) return; maybe_change_update_id (ctx, new_text); update_selectivity_execute_cancel (ctx); } /** * The selection in the identifier tree view changed. * Copy text into identifier entry, or * adjust selection, if selected item is a clone. * * @param ts the changed selection * @param user_data master publishing dialog context of our window */ void GNUNET_GTK_master_publish_dialog_previous_identifiers_treeview_selection_changed_cb ( GtkTreeSelection *ts, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; GtkTreeIter iter; gchar *new_text; gchar *spath; GtkTreePath *path; if (! gtk_tree_selection_get_selected (ts, NULL, &iter)) return; gtk_tree_model_get (ctx->identifiers_treemodel, &iter, IDENTIFIERS_MC_ID, &new_text, IDENTIFIERS_MC_PATH_TO_ORIGINAL, &spath, -1); path = NULL; if (spath) path = gtk_tree_path_new_from_string (spath); if (path) { gtk_tree_selection_select_path (ts, path); gtk_tree_path_free (path); } else if (new_text) gtk_entry_set_text (GTK_ENTRY (ctx->identifier_entry), new_text); g_free (new_text); } /** * 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); GNUNET_FS_GTK_remove_treestore_subtree (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); GNUNET_FS_GTK_remove_treestore_subtree (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; } 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); GNUNET_FS_GTK_remove_treestore_subtree (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); GNUNET_FS_GTK_remove_treestore_subtree (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_FS_MetaData *meta, struct GNUNET_FS_Uri **uri, struct GNUNET_FS_BlockOptions *bo, int *do_index, void **client_info) { GtkTreeRowReference *row = *client_info; if (NULL == row) { 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; GtkTreeRowReference *rr; GtkTreePath *path; /* initially, both 'iter' and 'next' point to the selected row */ if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter)) { GNUNET_break (0); return; } path = gtk_tree_model_get_path (ctx->file_info_treemodel, &iter); if (gtk_tree_model_iter_next (ctx->file_info_treemodel, &iter)) gtk_tree_path_next (path); else { if (! gtk_tree_path_prev (path)) { if ((1 == gtk_tree_path_get_depth (path)) || (! gtk_tree_path_up (path))) { gtk_tree_path_free (path); path = NULL; } } } if (NULL == path) { rr = NULL; } else { rr = gtk_tree_row_reference_new (ctx->file_info_treemodel, path); gtk_tree_path_free (path); } /* 'iter' might have again been clobbered, get it one more time... */ GNUNET_assert ( gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter)); /* now delete the subtree */ gtk_tree_model_get (ctx->file_info_treemodel, &iter, PUBLISH_MC_FILE_INFORMATION_STRUCT, &fip, -1); GNUNET_FS_file_information_destroy (fip, &free_fi_row_reference, NULL); GNUNET_FS_GTK_remove_treestore_subtree (GTK_TREE_STORE ( ctx->file_info_treemodel), &iter); /* finally, select the item from 'rr' (if any) */ if (NULL != rr) { path = gtk_tree_row_reference_get_path (rr); gtk_tree_row_reference_free (rr); gtk_tree_selection_select_path (ctx->file_info_selection, path); gtk_tree_path_free (path); } /* and now, depending on the selection, update the sensitivity of buttons */ update_selectivity_execute_cancel (ctx); update_selectivity_edit (ctx); } /* ******************** progress dialog / import of directories ************* */ /** * Close the progress dialog and free its handle. * Updates all buttons sensitivity. * * @param adcc context for the progress dialog to close */ static void destroy_progress_dialog (struct AddDirClientContext *adcc) { GNUNET_assert (NULL == adcc->ds); if (NULL != adcc->kill_task) { GNUNET_SCHEDULER_cancel (adcc->kill_task); adcc->kill_task = NULL; } 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_execute_cancel (adcc->ctx); update_selectivity_edit (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) { /* 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 cls progress dialog context of our window * @return TRUE 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 TRUE; } /** * 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; uint64_t fsize; if (0 != stat (item->filename, &sbuf)) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "stat", item->filename); return; } if ((item->is_directory != GNUNET_YES) && (GNUNET_OK != GNUNET_DISK_file_size (item->filename, &fsize, GNUNET_YES, GNUNET_YES))) 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 == GNUNET_YES) { /* update meta data mime type (force to be GNUnet-directory) */ if (NULL != item->meta) GNUNET_FS_meta_data_delete (item->meta, EXTRACTOR_METATYPE_MIMETYPE, NULL, 0); else item->meta = GNUNET_FS_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 (fsize); } gtk_tree_store_set (ts, item_iter, PUBLISH_MC_FILESIZE, file_size_fancy, PUBLISH_MC_DO_INDEX, (gboolean) adcc->directory_scan_do_index, PUBLISH_MC_FILENAME, item->short_filename, PUBLISH_MC_ANONYMITY_LEVEL, (guint) adcc->directory_scan_bo.anonymity_level, PUBLISH_MC_PRIORITY, (guint) adcc->directory_scan_bo.content_priority, PUBLISH_MC_FILE_INFORMATION_STRUCT, fi, PUBLISH_MC_EXPIRATION_TIME_ABSOLUTE, (guint64) adcc->directory_scan_bo.expiration_time.abs_value_us, PUBLISH_MC_REPLICATION_LEVEL, (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 == GNUNET_YES) add_share_items_to_treestore (adcc, item->children_head, sibling_iter); } } /** * Function called when the scanner had some trouble and we * need to abort the scanning process (which we need to do * in a separate task). * * @param cls progress dialog context of our window */ static void stop_scanner_task (void *cls) { struct AddDirClientContext *adcc = cls; adcc->kill_task = NULL; if (NULL != adcc->ds) { GNUNET_FS_directory_scan_abort (adcc->ds); adcc->ds = NULL; } } /** * 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, #GNUNET_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_us > 100000LL) { gtk_progress_bar_pulse (adcc->progress_dialog_bar); last_pulse = GNUNET_TIME_absolute_get (); } #if VERBOSE_PROGRESS if (is_directory == GNUNET_YES) { GNUNET_asprintf (&s, _ ("Scanning directory `%s'.\n"), filename); insert_progress_dialog_text (adcc, s); GNUNET_free (s); } else adcc->total++; #else if (is_directory != GNUNET_YES) 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: if ((NULL != adcc->ds) && (NULL == adcc->kill_task)) { insert_progress_dialog_text (adcc, _ ("Operation failed (press cancel)\n")); adcc->kill_task = GNUNET_SCHEDULER_add_now (&stop_scanner_task, adcc); } 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); 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_new (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_execute_cancel (ctx); } /** * Function called when the "open" (directory) dialog was closed. * * @param dialog the open dialog * @param response_id result of the dialog (GTK_RESPONSE_OK means to "run") * @param user_data master publishing dialog context of our window */ void GNUNET_GTK_publish_directory_dialog_response_cb (GtkDialog *dialog, gint response_id, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; if (GTK_RESPONSE_OK == response_id) { 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_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 (GTK_RESPONSE_OK means to "run") * @param user_data master publishing dialog context of our window */ void GNUNET_GTK_publish_file_dialog_response_cb (GtkDialog *dialog, gint response_id, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; if (GTK_RESPONSE_OK == response_id) { 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_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; ctx->open_file_builder = GNUNET_GTK_get_new_builder ("gnunet_fs_gtk_publish_file_dialog.glade", ctx); GNUNET_GTK_setup_expiration_year_adjustment (ctx->open_file_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_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); { GtkComboBox *combo; 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 */ { GtkWidget *ad; ad = GTK_WIDGET (gtk_builder_get_object (ctx->open_file_builder, "GNUNET_GTK_publish_file_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; ctx->open_directory_builder = GNUNET_GTK_get_new_builder ("gnunet_fs_gtk_publish_directory_dialog.glade", ctx); GNUNET_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); { GtkComboBox *combo; 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, GNUNET_FS_GTK_get_anonymity_level_list_store ()); } /* show dialog */ { GtkWidget *ad; ad = GTK_WIDGET ( gtk_builder_get_object (ctx->open_directory_builder, "GNUNET_GTK_publish_directory_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. */ 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. */ GtkTreeIter iter; }; /** * Update tree view based on the information from the * `struct GNUNET_FS_FileInformation` for publishing. * * @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_FS_MetaData *meta, struct GNUNET_FS_Uri **uri, struct GNUNET_FS_BlockOptions *bo, int *do_index, void **client_info) { struct EditPublishContext *epc = cls; char *name; name = GNUNET_FS_meta_data_get_by_type ( meta, EXTRACTOR_METATYPE_GNUNET_ORIGINAL_FILENAME); gtk_tree_store_set (GTK_TREE_STORE (epc->tm), &epc->iter, PUBLISH_MC_DO_INDEX, *do_index, PUBLISH_MC_FILENAME, name, PUBLISH_MC_ANONYMITY_LEVEL, (guint) bo->anonymity_level, PUBLISH_MC_PRIORITY, (guint) bo->content_priority, PUBLISH_MC_EXPIRATION_TIME_ABSOLUTE, (guint64) bo->expiration_time.abs_value_us, PUBLISH_MC_REPLICATION_LEVEL, (guint) bo->replication_level, -1); GNUNET_free (name); 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_new (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, PUBLISH_MC_FILE_INFORMATION_STRUCT, &epc->fip, -1); GNUNET_FS_GTK_edit_publish_dialog (ctx->master_pubdialog, epc->fip, GNUNET_NO, &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, PUBLISH_MC_FILE_INFORMATION_STRUCT, &fi, -1); gtk_tree_store_set (GTK_TREE_STORE (tm), iter, PUBLISH_MC_FILE_INFORMATION_STRUCT, 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 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, PUBLISH_MC_FILE_INFORMATION_STRUCT, &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 */ if (NULL != ctx->adddir_head) return GNUNET_NO; /* free state from the identifiers treemodel */ gtk_tree_store_clear (GTK_TREE_STORE (ctx->identifiers_treemodel)); /* free state from the file info treemodel */ 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)); GNUNET_IDENTITY_disconnect (ctx->identity); gtk_widget_destroy (GTK_WIDGET (ctx->master_pubdialog)); g_object_unref (G_OBJECT (ctx->builder)); GNUNET_free (ctx->ns_name); g_free (ctx->previous_id); GNUNET_free (ctx); return GNUNET_YES; } /** * Insert namespace advertisement into metadata when * publishing in both private namespace and global namespace. * * @param cls closure, a 'struct MainPublishingDialogContext *' * @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 insert_advertisement (void *cls, struct GNUNET_FS_FileInformation *fi, uint64_t length, struct GNUNET_FS_MetaData *meta, struct GNUNET_FS_Uri **uri, struct GNUNET_FS_BlockOptions *bo, int *do_index, void **client_info) { struct MainPublishingDialogContext *ctx = cls; struct GNUNET_IDENTITY_PublicKey pub; struct GNUNET_FS_Uri *sks_uri; char *sks_uri_string; if (NULL == ctx->ns) return GNUNET_SYSERR; GNUNET_IDENTITY_ego_get_public_key (ctx->ns, &pub); if (GNUNET_IDENTITY_TYPE_ECDSA != pub.type) { GNUNET_break (0); /* FIXME: EDDSA keys not yet supported for file-sharing! */ return GNUNET_SYSERR; } sks_uri = GNUNET_FS_uri_sks_create (&pub.ecdsa_key, "/"); sks_uri_string = GNUNET_FS_uri_to_string (sks_uri); GNUNET_FS_uri_destroy (sks_uri); if (NULL == sks_uri_string) return GNUNET_SYSERR; GNUNET_FS_meta_data_insert (meta, "", EXTRACTOR_METATYPE_URI, EXTRACTOR_METAFORMAT_UTF8, "text/plain", sks_uri_string, strlen (sks_uri_string) + 1); GNUNET_free (sks_uri_string); return GNUNET_SYSERR; } /** * 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; gchar *namespace_id; struct GNUNET_IDENTITY_Ego *ns; const gchar *namespace_uid; struct GNUNET_FS_FileInformation *fi; GtkTreeIter iter; gboolean do_global; gboolean do_updateable; gboolean do_own; gboolean disable_ads_insertion; if (NULL != ctx->adddir_head) { GNUNET_break (0); return; } do_global = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ctx->global_checkbox)); do_updateable = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ctx->updateable_checkbox)); do_own = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ctx->own_checkbox)); disable_ads_insertion = GNUNET_CONFIGURATION_get_value_yesno ( GNUNET_FS_GTK_get_configuration (), "gnunet-fs-gtk", "DISABLE_AUTOMATIC_NAMESPACE_ADVERTISEMENT_INSERTION"); if (disable_ads_insertion == GNUNET_SYSERR) disable_ads_insertion = GNUNET_NO; ns = NULL; namespace_id = NULL; namespace_uid = NULL; if (ctx->ns && do_own) { const gchar *id_entry_text; if (NULL != (id_entry_text = gtk_entry_get_text (GTK_ENTRY (ctx->identifier_entry)))) namespace_id = g_strdup (id_entry_text); else if (! do_global) GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Global publication is disabled, but " "namespace_id is not available\n"); if ((NULL != namespace_id) && ('\0' == namespace_id[0])) { g_free (namespace_id); namespace_id = NULL; } if (NULL != namespace_id) { namespace_uid = gtk_entry_get_text (GTK_ENTRY (ctx->update_id_entry)); if ((NULL == namespace_uid) || ('\0' == namespace_uid[0]) || (! do_updateable)) namespace_uid = NULL; ns = ctx->ns; } else if (! do_global) GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Global publication is disabled, but namespace_id unavailable " "or zero-length\n"); } else if (! do_global) GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Global publication is disabled, ns is either NULL or disabled, " "yet 'Execute' was pressed\n"); if ((! do_global) && (NULL == ns)) { g_free (namespace_id); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ctx->own_checkbox), FALSE); return; } if (gtk_tree_model_get_iter_first (ctx->file_info_treemodel, &iter)) { if (! do_global) clear_keywords_from_tm (ctx); do { const struct GNUNET_IDENTITY_PrivateKey *pk; if (NULL != ns) { pk = GNUNET_IDENTITY_ego_get_private_key (ns); if (GNUNET_IDENTITY_TYPE_ECDSA != pk->type) { GNUNET_break (0); /* FIXME: EDDSA keys not yet supported by FS */ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ctx->own_checkbox), FALSE); return; } } else { pk = NULL; } fi = get_file_information (ctx->file_info_treemodel, &iter); if (do_global && do_own && ! disable_ads_insertion) GNUNET_FS_file_information_inspect (fi, insert_advertisement, ctx); GNUNET_FS_publish_start (GNUNET_FS_GTK_get_fs_handle (), fi, (NULL == pk) ? NULL : &pk->ecdsa_key, namespace_id, namespace_uid, GNUNET_FS_PUBLISH_OPTION_NONE); } while (gtk_tree_model_iter_next (ctx->file_info_treemodel, &iter)); } else { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to get first item in files tree view. Is it empty?\n"); } g_free (namespace_id); GNUNET_break (GNUNET_YES == close_master_publish_dialog (ctx)); } /** * Function called on entries in a `struct GNUNET_FS_FileInformation` for * publishing. * * @param cls closure, unused * @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 (will be * set to NULL) * @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) */ static int clear_keywords_in_file_information (void *cls, struct GNUNET_FS_FileInformation *fi, uint64_t length, struct GNUNET_FS_MetaData *meta, struct GNUNET_FS_Uri **uri, struct GNUNET_FS_BlockOptions *bo, int *do_index, void **client_info) { if (NULL == *uri) return GNUNET_OK; GNUNET_FS_uri_destroy (*uri); *uri = NULL; return GNUNET_OK; } /** * Remove all of the keywords from the file information item in the tree store * * @param tm tree model / store * @param iter current position in the recursion */ static void clear_keywords_from_file_information_in_tree_store (GtkTreeModel *tm, GtkTreeIter *iter) { GtkTreeIter child; struct GNUNET_FS_FileInformation *fip; gtk_tree_model_get (tm, iter, PUBLISH_MC_FILE_INFORMATION_STRUCT, &fip, -1); if (NULL != fip) { GNUNET_FS_file_information_inspect (fip, &clear_keywords_in_file_information, NULL); } /* recursively clean up children */ if (gtk_tree_model_iter_children (tm, &child, iter)) { do { clear_keywords_from_file_information_in_tree_store (tm, &child); } while (gtk_tree_model_iter_next (tm, &child)); } } /** * Remove all of the keywords from all file information structs in the tree * store * * @param ctx context */ static void clear_keywords_from_tm (struct MainPublishingDialogContext *ctx) { GtkTreeIter iter; /* clear keywords from 'tm' */ if (gtk_tree_model_get_iter_first (ctx->file_info_treemodel, &iter)) do { clear_keywords_from_file_information_in_tree_store (ctx ->file_info_treemodel, &iter); } while (gtk_tree_model_iter_next (ctx->file_info_treemodel, &iter)); } /** * The user pushed the 'clear' button. Remove all keywords. * * @param button the button that was clicked * @param user_data master publishing dialog context of our window */ void GNUNET_GTK_master_publish_dialog_clear_button_clicked_cb (GtkButton *button, gpointer user_data) { clear_keywords_from_tm (user_data); } /** * 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 event the close event * @param user_data master publishing dialog context of our window * @return TRUE to refuse to close (stops other handlers from being invoked) * FALSE to allow closing the window */ 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; } /** * Called when expander changes its state (expanded/hidden). * Hides the widget, adjusts the paned position to compensate, * changes resizability of the bottom frame (it will not get any * extra space while its only resizable widget is hidden). * * @param object expander * @param param_spec parameter that was changed * @param user_data context */ static void expander_callback (GObject *object, GParamSpec *param_spec, gpointer user_data) { GtkExpander *expander; struct MainPublishingDialogContext *ctx = user_data; GtkAllocation scrolled_allocation; gint paned_pos; GValue gv = G_VALUE_INIT; gboolean expanded; expander = GTK_EXPANDER (object); gtk_widget_get_allocation (GTK_WIDGET (ctx->identifiers_scrolled), &scrolled_allocation); expanded = gtk_expander_get_expanded (expander); gtk_widget_set_visible (GTK_WIDGET (ctx->identifiers_scrolled), expanded); g_value_init (&gv, G_TYPE_BOOLEAN); g_value_set_boolean (&gv, expanded); gtk_container_child_set_property (GTK_CONTAINER (ctx->vpaned), ctx->bottom_frame, "resize", &gv); paned_pos = gtk_paned_get_position (GTK_PANED (ctx->vpaned)); paned_pos = paned_pos + (scrolled_allocation.height * (expanded ? -1 : 1)); gtk_paned_set_position (GTK_PANED (ctx->vpaned), paned_pos); } /* ******************** 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; /** * Handle to the namespace. */ struct GNUNET_IDENTITY_Ego *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_FS_MetaData *last_meta, const char *next_id) { struct UpdateableContext *uc = cls; struct UpdateableContext sc; GtkTreeIter iter; GtkTreeIter titer; char *desc; int desc_is_a_dup; struct GNUNET_HashCode hc; gchar *displaytext; gchar *hashbuf; char *mdbuf; ssize_t hashbufsize; ssize_t mdsize; ssize_t next_id_len; char *uristring; ssize_t urilen; ssize_t last_id_len; gchar *spath; uc->update_called = GNUNET_YES; if (next_id) next_id_len = strlen (next_id); else next_id_len = 0; if (last_id) last_id_len = strlen (last_id); else last_id_len = 0; uristring = GNUNET_FS_uri_to_string (last_uri); if (uristring) urilen = strlen (uristring); else urilen = 0; mdbuf = NULL; mdsize = GNUNET_FS_meta_data_serialize ( last_meta, &mdbuf, 64 * 1024, GNUNET_FS_META_DATA_SERIALIZE_FULL); if (0 > mdsize) mdsize = 0; hashbufsize = last_id_len + next_id_len + urilen + mdsize; hashbuf = GNUNET_malloc (hashbufsize); if (last_id) strcpy (&hashbuf[0], last_id); if (next_id) strcpy (&hashbuf[last_id_len], next_id); if (uristring) strcpy (&hashbuf[last_id_len + next_id_len], uristring); if (mdbuf) memcpy (&hashbuf[last_id_len + next_id_len + urilen], mdbuf, mdsize); GNUNET_CRYPTO_hash (hashbuf, hashbufsize, &hc); GNUNET_free (uristring); GNUNET_free (mdbuf); GNUNET_free (hashbuf); spath = NULL; spath = GNUNET_CONTAINER_multihashmap_get (uc->seen, &hc); desc = GNUNET_FS_GTK_get_description_from_metadata (last_meta, &desc_is_a_dup); if (NULL != spath) { displaytext = g_strdup_printf ("%s", last_id); gtk_tree_store_insert_with_values (uc->ts, &iter, uc->parent, G_MAXINT, IDENTIFIERS_MC_ID, last_id, IDENTIFIERS_MC_UPDATE_ID, next_id, IDENTIFIERS_MC_DESCRIPTION, desc, IDENTIFIERS_MC_PATH_TO_ORIGINAL, spath, IDENTIFIERS_MC_ID_MARKED_UP, displaytext, -1); } else if (NULL == uc->parent) { displaytext = g_strdup_printf ("%s", last_id); gtk_tree_store_insert_with_values (uc->ts, &iter, uc->parent, G_MAXINT, IDENTIFIERS_MC_ID, last_id, IDENTIFIERS_MC_UPDATE_ID, next_id, IDENTIFIERS_MC_DESCRIPTION, desc, IDENTIFIERS_MC_PATH_TO_ORIGINAL, NULL, IDENTIFIERS_MC_ID_MARKED_UP, displaytext, -1); } else { displaytext = g_strdup_printf ("%s", last_id); gtk_tree_store_insert_with_values (uc->ts, &iter, uc->parent, G_MAXINT, IDENTIFIERS_MC_ID, last_id, IDENTIFIERS_MC_UPDATE_ID, next_id, IDENTIFIERS_MC_DESCRIPTION, desc, IDENTIFIERS_MC_PATH_TO_ORIGINAL, NULL, IDENTIFIERS_MC_ID_MARKED_UP, displaytext, -1); } g_free (displaytext); if (NULL != spath) return; { GtkTreePath *path; char *gspath; path = gtk_tree_model_get_path (GTK_TREE_MODEL (uc->ts), &iter); spath = gtk_tree_path_to_string (path); gtk_tree_path_free (path); /* for the sake of consistency, only put GNUNET_free'able things into the * map */ gspath = GNUNET_strdup (spath); if (GNUNET_SYSERR == GNUNET_CONTAINER_multihashmap_put ( uc->seen, &hc, gspath, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST)) GNUNET_free (gspath); } sc.parent = &iter; sc.ts = uc->ts; sc.ns = uc->ns; sc.seen = uc->seen; sc.update_called = GNUNET_NO; { const struct GNUNET_IDENTITY_PrivateKey *pk; pk = GNUNET_IDENTITY_ego_get_private_key (uc->ns); if (GNUNET_IDENTITY_TYPE_ECDSA != pk->type) { GNUNET_break (0); return; } GNUNET_FS_namespace_list_updateable (GNUNET_FS_GTK_get_fs_handle (), &pk->ecdsa_key, next_id, &add_updateable_to_ts, &sc); } if ((sc.update_called == GNUNET_NO) && (NULL != next_id) && (strlen (next_id) > 0)) { /* add leaf */ displaytext = g_strdup_printf ("%s", next_id); gtk_tree_store_insert_with_values (uc->ts, &titer, &iter, G_MAXINT, IDENTIFIERS_MC_ID, next_id, IDENTIFIERS_MC_UPDATE_ID, NULL, IDENTIFIERS_MC_DESCRIPTION, NULL, IDENTIFIERS_MC_PATH_TO_ORIGINAL, NULL, IDENTIFIERS_MC_ID_MARKED_UP, displaytext, -1); g_free (displaytext); } } /** * Called by a hashmap iterator. * Frees its argument with GNUNET_free(). * * @param cls closure * @param key key from the map * @param value value from the map * @return #GNUNET_YES, always. */ static int free_seen_paths (void *cls, const struct GNUNET_HashCode *key, void *value) { GNUNET_free (value); return GNUNET_YES; } /** * User changed the selected namespace. Check if it is valid, * and if so, update the rest of the dialog accordingly. * * @param combo the namespace selection combo box * @param user_data the `struct MainPublishingDialogContext` */ void GNUNET_GTK_master_publish_dialog_ego_combobox_changed_cb (GtkComboBox *combo, gpointer user_data) { struct MainPublishingDialogContext *ctx = user_data; GtkTreeIter iter; GtkTreeStore *ts; GtkTreeModel *tm; struct GNUNET_IDENTITY_Ego *ego; struct UpdateableContext uc; ts = GTK_TREE_STORE ( gtk_builder_get_object (ctx->builder, "GNUNET_GTK_update_identifiers_treestore")); gtk_tree_store_clear (ts); if (! gtk_combo_box_get_active_iter (combo, &iter)) { /* entry unselected, etc. */ ctx->ns = NULL; gtk_widget_set_sensitive (ctx->identifier_entry, FALSE); gtk_widget_set_sensitive (ctx->updateable_checkbox, FALSE); gtk_widget_set_sensitive (ctx->update_id_entry, FALSE); update_selectivity_execute_cancel (ctx); return; } tm = GTK_TREE_MODEL ( gtk_builder_get_object (ctx->builder, "GNUNET_GTK_master_publish_dialog_ego_liststore")); gtk_tree_model_get (tm, &iter, EGO_MC_EGO, &ego, -1); if (NULL == ego) { GNUNET_break (0); gtk_widget_set_sensitive (ctx->identifier_entry, FALSE); gtk_widget_set_sensitive (ctx->updateable_checkbox, FALSE); gtk_widget_set_sensitive (ctx->update_id_entry, FALSE); update_selectivity_execute_cancel (ctx); return; } ctx->ns = ego; uc.parent = NULL; uc.ts = ts; uc.ns = ego; uc.update_called = GNUNET_NO; uc.seen = GNUNET_CONTAINER_multihashmap_create (128, GNUNET_NO); { const struct GNUNET_IDENTITY_PrivateKey *pk; pk = GNUNET_IDENTITY_ego_get_private_key (ego); if (GNUNET_IDENTITY_TYPE_ECDSA != pk->type) { GNUNET_break (0); gtk_widget_set_sensitive (ctx->identifier_entry, FALSE); gtk_widget_set_sensitive (ctx->updateable_checkbox, FALSE); gtk_widget_set_sensitive (ctx->update_id_entry, FALSE); update_selectivity_execute_cancel (ctx); return; } GNUNET_FS_namespace_list_updateable (GNUNET_FS_GTK_get_fs_handle (), &pk->ecdsa_key, NULL, &add_updateable_to_ts, &uc); } GNUNET_CONTAINER_multihashmap_iterate (uc.seen, &free_seen_paths, NULL); GNUNET_CONTAINER_multihashmap_destroy (uc.seen); gtk_widget_set_sensitive (ctx->identifier_entry, TRUE); gtk_widget_set_sensitive (ctx->updateable_checkbox, TRUE); gtk_widget_set_sensitive (ctx->update_id_entry, TRUE); update_selectivity_execute_cancel (ctx); } /** * Add all updateable entries of the current namespace to the * tree store. * * @param cls our `struct MainPublishingDialogContext` * @param ego identity of the namespace to add * @param ego_ctx where to store context data * @param name name of the namespace to add */ static void add_namespace_to_ts (void *cls, struct GNUNET_IDENTITY_Ego *ego, void **ego_ctx, const char *name) { struct MainPublishingDialogContext *ctx = cls; GtkListStore *ls; GtkTreePath *path; GtkTreeRowReference *rr; GtkTreeIter iter; gboolean have_ns; if (NULL == ego) return; /* nothing to be done */ ls = GTK_LIST_STORE ( gtk_builder_get_object (ctx->builder, "GNUNET_GTK_master_publish_dialog_ego_liststore")); rr = *ego_ctx; if (NULL == rr) { /* insert operation */ GNUNET_assert (NULL != name); gtk_list_store_insert_with_values (ls, &iter, G_MAXINT, EGO_MC_NAME, name, EGO_MC_EGO, ego, -1); path = gtk_tree_model_get_path (GTK_TREE_MODEL (ls), &iter); rr = gtk_tree_row_reference_new (GTK_TREE_MODEL (ls), path); gtk_tree_path_free (path); *ego_ctx = rr; } else if (NULL == name) { /* delete operation */ path = gtk_tree_row_reference_get_path (rr); gtk_tree_row_reference_free (rr); GNUNET_assert (gtk_tree_model_get_iter (GTK_TREE_MODEL (ls), &iter, path)); gtk_tree_path_free (path); gtk_list_store_remove (ls, &iter); *ego_ctx = NULL; } else { /* rename operation */ path = gtk_tree_row_reference_get_path (rr); GNUNET_assert (gtk_tree_model_get_iter (GTK_TREE_MODEL (ls), &iter, path)); gtk_list_store_set (ls, &iter, G_MAXINT, EGO_MC_NAME, name, -1); gtk_tree_path_free (path); } have_ns = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ls), &iter); gtk_widget_set_sensitive (ctx->own_checkbox, have_ns); gtk_widget_set_sensitive (ctx->identifier_entry, have_ns); gtk_widget_set_sensitive (ctx->updateable_checkbox, have_ns); gtk_widget_set_sensitive (ctx->update_id_entry, have_ns); } /** * 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; const struct GNUNET_CONFIGURATION_Handle *cfg; GtkWidget *toplevel; int updateable_enabled; int own_enabled; int global_enabled; ctx = GNUNET_new (struct MainPublishingDialogContext); ctx->builder = GNUNET_GTK_get_new_builder ("gnunet_fs_gtk_publish_dialog.glade", ctx); if (NULL == ctx->builder) { 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")); ctx->global_checkbox = GTK_WIDGET ( gtk_builder_get_object (ctx->builder, "GNUNET_GTK_master_publish_dialog_global_checkbox")); ctx->own_checkbox = GTK_WIDGET ( gtk_builder_get_object (ctx->builder, "GNUNET_GTK_master_publish_dialog_own_checkbox")); ctx->updateable_checkbox = GTK_WIDGET (gtk_builder_get_object ( ctx->builder, "GNUNET_GTK_master_publish_dialog_updateable_checkbox")); ctx->update_id_entry = GTK_WIDGET ( gtk_builder_get_object (ctx->builder, "GNUNET_GTK_master_publish_dialog_update_id_entry")); ctx->own_vbox = GTK_WIDGET ( gtk_builder_get_object (ctx->builder, "GNUNET_GTK_master_publish_dialog_own_vbox")); ctx->update_id_hbox = GTK_WIDGET ( gtk_builder_get_object (ctx->builder, "GNUNET_GTK_master_publish_dialog_update_id_hbox")); ctx->identifiers_expander = GTK_WIDGET (gtk_builder_get_object ( ctx->builder, "GNUNET_GTK_master_publish_dialog_previous_identifiers_expander")); ctx->vpaned = GTK_WIDGET ( gtk_builder_get_object (ctx->builder, "GNUNET_GTK_master_publish_dialog_vpaned")); ctx->bottom_frame = GTK_WIDGET ( gtk_builder_get_object (ctx->builder, "GNUNET_GTK_master_publish_dialog_bottom_frame")); ctx->identifier_entry = GTK_WIDGET ( gtk_builder_get_object (ctx->builder, "GNUNET_GTK_master_publish_dialog_identifier_entry")); ctx->identifiers_scrolled = GTK_WIDGET (gtk_builder_get_object ( ctx->builder, "GNUNET_GTK_master_publish_dialog_previous_identifiers_scrolled")); ctx->identifiers_treeview = GTK_TREE_VIEW (gtk_builder_get_object ( ctx->builder, "GNUNET_GTK_master_publish_dialog_previous_identifiers_treeview")); ctx->identifiers_treemodel = gtk_tree_view_get_model (ctx->identifiers_treeview); ctx->identifiers_selection = gtk_tree_view_get_selection (ctx->identifiers_treeview); g_signal_connect (ctx->identifiers_expander, "notify::expanded", G_CALLBACK (expander_callback), ctx); cfg = GNUNET_FS_GTK_get_configuration (); ctx->identity = GNUNET_IDENTITY_connect (cfg, &add_namespace_to_ts, ctx); updateable_enabled = GNUNET_CONFIGURATION_get_value_yesno ( cfg, "gnunet-fs-gtk", "MAKE_UPDATEABLE_PUBLICATIONS_BY_DEFAULT"); if (GNUNET_SYSERR == updateable_enabled) updateable_enabled = GNUNET_YES; own_enabled = GNUNET_CONFIGURATION_get_value_yesno (cfg, "gnunet-fs-gtk", "MAKE_NAMESPACE_PUBLICATIONS_BY_DEFAULT"); if (GNUNET_SYSERR == own_enabled) own_enabled = GNUNET_NO; global_enabled = GNUNET_CONFIGURATION_get_value_yesno (cfg, "gnunet-fs-gtk", "MAKE_GLOBAL_PUBLICATIONS_BY_DEFAULT"); if (GNUNET_SYSERR == global_enabled) global_enabled = GNUNET_YES; /* This will emit appropriate signals, their handlers will hide * parts of the dialog as needed. */ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ctx->updateable_checkbox), GNUNET_YES == updateable_enabled); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ctx->own_checkbox), GNUNET_YES == own_enabled); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ctx->global_checkbox), GNUNET_YES == global_enabled); /* show dialog */ toplevel = gtk_widget_get_toplevel (dummy); if (GTK_IS_WINDOW (toplevel)) gtk_window_set_transient_for (GTK_WINDOW (ctx->master_pubdialog), GTK_WINDOW (toplevel)); gtk_window_present (GTK_WINDOW (ctx->master_pubdialog)); } /* end of gnunet-fs-gtk_publish-dialog.c */