gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[GNUnet-SVN] r19638 - gnunet-gtk/src/fs


From: gnunet
Subject: [GNUnet-SVN] r19638 - gnunet-gtk/src/fs
Date: Thu, 2 Feb 2012 17:32:51 +0100

Author: grothoff
Date: 2012-02-02 17:32:51 +0100 (Thu, 02 Feb 2012)
New Revision: 19638

Modified:
   gnunet-gtk/src/fs/gnunet-fs-gtk_publish-dialog.c
Log:
-more bugfixes and code cleanup

Modified: gnunet-gtk/src/fs/gnunet-fs-gtk_publish-dialog.c
===================================================================
--- gnunet-gtk/src/fs/gnunet-fs-gtk_publish-dialog.c    2012-02-02 16:13:56 UTC 
(rev 19637)
+++ gnunet-gtk/src/fs/gnunet-fs-gtk_publish-dialog.c    2012-02-02 16:32:51 UTC 
(rev 19638)
@@ -974,7 +974,14 @@
 
 
 /**
+ * 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, 
@@ -1002,33 +1009,33 @@
   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);
-    if (NULL == item->ksk_uri)
-      item->ksk_uri = GNUNET_FS_uri_ksk_create (GNUNET_FS_DIRECTORY_MIME, 
NULL);
-    else
-      GNUNET_FS_uri_ksk_add_keyword (item->ksk_uri, GNUNET_FS_DIRECTORY_MIME,
-                                    GNUNET_NO);
-    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);
+
+    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);
+    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);
   }
-  if (item->is_directory)
-    file_size_fancy = GNUNET_strdup (MARKER_DIR_FILE_SIZE);
-  else
-    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,
@@ -1043,8 +1050,11 @@
 
 
 /**
- * Traverse the share tree and add it to the tree store
+ * 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,
@@ -1058,7 +1068,7 @@
   struct GNUNET_FS_ShareTreeItem *item;
 
   sibling_iter = NULL;  
-  for (item = toplevel; item != NULL; item = item->next)
+  for (item = toplevel; NULL != item; item = item->next)
   {
     add_item (adcc, ts, item, parent_iter, sibling_iter, &last_added);
     sibling_iter = &last_added;
@@ -1071,22 +1081,28 @@
 
 
 /**
+ * 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;
-  struct AddDirClientContext *adcc = cls;
   char *s;
   gdouble fraction;
 
   switch (reason)
   {
   case GNUNET_FS_DIRSCANNER_FILE_START:
-    GNUNET_assert (filename != NULL);
+    GNUNET_assert (NULL != filename);
     if (GNUNET_TIME_absolute_get_duration (last_pulse).rel_value > 100)
     {
       gtk_progress_bar_pulse (adcc->progress_dialog_bar);
@@ -1107,7 +1123,7 @@
 #endif
     break;
   case GNUNET_FS_DIRSCANNER_FILE_IGNORED:
-    GNUNET_assert (filename != NULL);
+    GNUNET_assert (NULL != filename);
     GNUNET_asprintf (&s,
                     _("Failed to scan `%s' (access error). Skipping.\n"),
                     filename);
@@ -1131,6 +1147,7 @@
                                   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);
@@ -1178,7 +1195,14 @@
 
 
 /**
+ * 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, 
@@ -1187,78 +1211,80 @@
                        int do_index)
 {
   struct AddDirClientContext *adcc;
-  GtkTextIter iter;
 
   adcc = GNUNET_malloc (sizeof (struct AddDirClientContext));
   adcc->ctx = ctx;
   GNUNET_CONTAINER_DLL_insert_tail (ctx->adddir_head, ctx->adddir_tail, adcc);
-  adcc->ds = GNUNET_FS_directory_scan_start (filename,
-      GNUNET_NO, NULL, &directory_scan_cb, adcc);
   adcc->directory_scan_bo = *bo;
   adcc->directory_scan_do_index = do_index;
 
-  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"));
-  gtk_text_buffer_get_end_iter (adcc->progress_dialog_textbuffer,
-      &iter);
-#if VERBOSE_PROGRESS
-  gtk_widget_show (GTK_WIDGET (gtk_builder_get_object 
(adcc->progress_dialog_builder,
-                                                      
"GNUNET_FS_GTK_progress_dialog_scrolled_window")));
-#endif
+  /* 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,
-                                     struct MainPublishingDialogContext *ctx)
+                                     gpointer user_data)
 {
-  char *filename;
-  int do_index;
-  GtkSpinButton *sb;
-  struct GNUNET_FS_BlockOptions bo;
+  struct MainPublishingDialogContext *ctx = user_data;
   GtkWidget *ad;
 
+  /* 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;
 
   ad = GTK_WIDGET (gtk_builder_get_object
-                   (ctx->open_directory_builder, 
"GNUNET_GTK_publish_directory_dialog"));
-  if (response_id == -5)
+                   (ctx->open_directory_builder,
+                   "GNUNET_GTK_publish_directory_dialog"));
+  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 
(ad));
-    sb = GTK_SPIN_BUTTON (gtk_builder_get_object
-                          (ctx->open_directory_builder,
-                           
"GNUNET_GTK_publish_directory_dialog_expiration_year_spin_button"));
-    if (!GNUNET_GTK_get_selected_anonymity_level
-        (ctx->open_directory_builder, 
"GNUNET_GTK_publish_directory_dialog_anonymity_combobox",
+    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
@@ -1269,59 +1295,79 @@
                                 (gtk_builder_get_object
                                  (ctx->open_directory_builder,
                                   
"GNUNET_GTK_publish_directory_dialog_replication_spin_button")));
-    bo.expiration_time = GNUNET_FS_GTK_get_expiration_time (sb);
+    {
+      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);
   }
+  /* FIXME: do we need to do widget-destroy + builder destroy? */
   gtk_widget_destroy (ad);
   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,
-                                struct MainPublishingDialogContext *ctx)
+                                gpointer user_data)
 {
-  char *filename;
-  struct GNUNET_FS_BlockOptions bo;
-  int do_index;
-  GtkSpinButton *sb;
+  struct MainPublishingDialogContext *ctx = user_data;
   GtkWidget *ad;
 
+  /* 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;
 
   ad = GTK_WIDGET (gtk_builder_get_object
                    (ctx->open_file_builder, "GNUNET_GTK_publish_file_dialog"));
-
-  if (response_id == -5)
+  if (response_id == -5 /* OK */)
   {
-    /* OK */
-    sb = GTK_SPIN_BUTTON (gtk_builder_get_object
-                          (ctx->open_file_builder,
-                           
"GNUNET_GTK_publish_file_dialog_expiration_year_spin_button"));
+    char *filename;
+    struct GNUNET_FS_BlockOptions bo;
+    int do_index;
 
+    filename = GNUNET_GTK_filechooser_get_filename_utf8 (GTK_FILE_CHOOSER 
(ad));
     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")));
-    bo.expiration_time = GNUNET_FS_GTK_get_expiration_time (sb);
+    {
+      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
@@ -1333,17 +1379,13 @@
                                        (ctx->open_file_builder,
                                         
"GNUNET_GTK_publish_file_dialog_do_index_checkbutton")));
 
-    filename = GNUNET_GTK_filechooser_get_filename_utf8 (GTK_FILE_CHOOSER 
(ad));
-
     scan_file_or_directory (ctx, filename, &bo, do_index);
-
     g_free (filename);
   }
-  else
-  {
-    /* Cancel/Escape/close/etc */
-  }
+  /* FIXME: do we need to do widget-destroy + builder destroy? */
   gtk_widget_destroy (ad);
+  g_object_unref (G_OBJECT (ctx->open_file_builder));
+  ctx->open_file_builder = NULL;
 }
 
 
@@ -1454,12 +1496,35 @@
 
 
 
+/**
+ * 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;
 };
 
@@ -1512,45 +1577,51 @@
                                       gint ret,
                                        const char *root)
 {
-  struct EditPublishContext *cbargs = cls;
+  struct EditPublishContext *epc = cls;
 
   if (ret == GTK_RESPONSE_OK)
-    GNUNET_FS_file_information_inspect (cbargs->fip, 
&update_treeview_after_edit, cbargs);
-  GNUNET_free (cbargs);
+    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,
-                                                         struct 
MainPublishingDialogContext *ctx)
+                                                         gpointer user_data)
 {
-  struct EditPublishContext *cbargs;
+  struct MainPublishingDialogContext *ctx = user_data;
+  struct EditPublishContext *epc;
   GtkListStore *anon_liststore;
 
   anon_liststore = GTK_LIST_STORE (gtk_builder_get_object 
(ctx->main_window_builder, 
                                                           
"main_window_search_anonymity_liststore"));
-  cbargs = GNUNET_malloc (sizeof (struct EditPublishContext));
-  cbargs->tm = ctx->file_info_treemodel;
-  if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, 
&cbargs->iter))
+  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 (cbargs);
+    GNUNET_free (epc);
     return;
   }
-  gtk_tree_model_get (ctx->file_info_treemodel, &cbargs->iter,
-                     5, &cbargs->fip, 
+  gtk_tree_model_get (ctx->file_info_treemodel,
+                     &epc->iter,
+                     5, &epc->fip, 
                      -1);
-  /* FIXME: can we just give our anon_liststore out like this? What about
+  /* FIXME-UGLY: can we just give our anon_liststore out like this? What about
      (unintended) sharing of state? */
   GNUNET_FS_GTK_edit_publish_dialog (ctx->master_pubdialog, 
-                                     cbargs->fip, 
+                                     epc->fip, 
                                     GNUNET_YES, 
                                     anon_liststore,
                                      &master_publish_edit_publish_dialog_cb,
-                                     cbargs);
+                                     epc);
 }
 
 
@@ -1592,33 +1663,45 @@
 }
 
 
-
+/**
+ * (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;
-  GtkTreeIter child;
 
   gtk_tree_model_get (tm, iter, 1, &ns, 4, &meta, -1);
-  if (meta != NULL)
+  if (NULL != meta)
     GNUNET_CONTAINER_meta_data_destroy (meta);
-  if (ns != NULL)
+  if (NULL != ns)
   {
-    // FIXME: delete ns?
+    /* FIXME-BUG-MAYBE: should we not delete ns here? (resource leak?) */
     // GNUNET_FS_namespace_delete (nso, GNUNET_NO);
   }
-  if (TRUE == gtk_tree_model_iter_children (tm, &child, iter))
+  /* recursively clean up children */
+  if (gtk_tree_model_iter_children (tm, &child, iter))
   {
     do
     {
       free_pseudonym_tree_store (tm, &child);
     }
-    while (TRUE == gtk_tree_model_iter_next (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)
 {
@@ -1626,15 +1709,16 @@
   struct GNUNET_FS_FileInformation *fip;
 
   gtk_tree_model_get (tm, iter, 5, &fip, -1);
-  if (fip != NULL)
+  if (NULL != fip)
     GNUNET_FS_file_information_destroy (fip, NULL, NULL);
-  if (TRUE == gtk_tree_model_iter_children (tm, &child, iter))
+  /* recursively clean up children */
+  if (gtk_tree_model_iter_children (tm, &child, iter))
   {
     do
     {
       free_file_information_tree_store (tm, &child);
     }
-    while (TRUE == gtk_tree_model_iter_next (tm, &child));
+    while (gtk_tree_model_iter_next (tm, &child));
   }
 }
 




reply via email to

[Prev in Thread] Current Thread [Next in Thread]