[tracker/api-cleanup: 7/12] libtracker-miner: Add TrackerMinerOnline



commit 72652c9b8c0ea01e23f3488cb8b4b905be79897b
Author: Carlos Garnacho <carlosg gnome org>
Date:   Wed Jan 29 00:03:47 2014 +0100

    libtracker-miner: Add TrackerMinerOnline
    
    This TrackerMiner is a simpler replacement for TrackerMinerWeb that
    doesn't get into credentials handling. It handles network state,
    emitting ::connected or ::disconnected on the way, and ensuring the
    miner is paused/resumed as necessary when suitable networks come and
    go, implementations of this miner can control this behavior through
    the return value in the ::connected signal.

 .../libtracker-miner/libtracker-miner-docs.sgml    |    1 +
 .../libtracker-miner/libtracker-miner-sections.txt |   16 +
 src/libtracker-miner/Makefile.am                   |    5 +-
 src/libtracker-miner/tracker-miner-enums.h         |   28 ++
 src/libtracker-miner/tracker-miner-online.c        |  379 ++++++++++++++++++++
 src/libtracker-miner/tracker-miner-online.h        |   70 ++++
 src/libtracker-miner/tracker-miner.h               |    1 +
 7 files changed, 499 insertions(+), 1 deletions(-)
---
diff --git a/docs/reference/libtracker-miner/libtracker-miner-docs.sgml 
b/docs/reference/libtracker-miner/libtracker-miner-docs.sgml
index 1a5438a..8f7eb23 100644
--- a/docs/reference/libtracker-miner/libtracker-miner-docs.sgml
+++ b/docs/reference/libtracker-miner/libtracker-miner-docs.sgml
@@ -32,6 +32,7 @@
       <title>Base abstract miner classes</title>
       <xi:include href="xml/tracker-miner-object.xml"/>
       <xi:include href="xml/tracker-miner-web.xml"/>
+      <xi:include href="xml/tracker-miner-online.xml"/>
       <xi:include href="xml/tracker-decorator.xml"/>
     </chapter>
 
diff --git a/docs/reference/libtracker-miner/libtracker-miner-sections.txt 
b/docs/reference/libtracker-miner/libtracker-miner-sections.txt
index 41c7f50..f7b3b1c 100644
--- a/docs/reference/libtracker-miner/libtracker-miner-sections.txt
+++ b/docs/reference/libtracker-miner/libtracker-miner-sections.txt
@@ -194,6 +194,22 @@ tracker_password_provider_get_type
 </SECTION>
 
 <SECTION>
+<FILE>tracker-miner-online</FILE>
+<TITLE>TrackerMinerOnline</FILE>
+TrackerMinerOnline
+TrackerMinerOnlineClass
+tracker_miner_online_get_type
+tracker_miner_online_get_network_type
+<SUBSECTION Standard>
+TRACKER_MINER_ONLINE
+TRACKER_MINER_ONLINE_CLASS
+TRACKER_MINER_ONLINE_GET_CLASS
+TRACKER_IS_MINER_ONLINE
+TRACKER_IS_MINER_ONLINE_CLASS
+TRACKER_TYPE_MINER_ONLINE
+</SECTION>
+
+<SECTION>
 <FILE>tracker-decorator</FILE>
 <TITLE>TrackerDecorator</TITLE>
 TrackerDecorator
diff --git a/src/libtracker-miner/Makefile.am b/src/libtracker-miner/Makefile.am
index b831b22..36be1a2 100644
--- a/src/libtracker-miner/Makefile.am
+++ b/src/libtracker-miner/Makefile.am
@@ -60,6 +60,8 @@ miner_sources =                                      \
        tracker-miner-enum-types.h                     \
        tracker-miner-object.c                         \
        tracker-miner-object.h                         \
+       tracker-miner-online.c                         \
+       tracker-miner-online.h                         \
        tracker-miner-fs.c                             \
        tracker-miner-fs.h                             \
        tracker-miner-web.c                            \
@@ -87,6 +89,7 @@ libtracker_minerinclude_HEADERS =                      \
        tracker-miner-enums.h                          \
        tracker-miner-enum-types.h                     \
        tracker-miner-object.h                         \
+       tracker-miner-online.h                         \
        tracker-miner-fs.h                             \
        tracker-miner-web.h                            \
        tracker-network-provider.h                     \
@@ -98,7 +101,7 @@ if !ENABLE_GCOV
 # Using enable_gcov instead of have_unit_test because when doing a release
 #  we disable gcov but NOT the unit tests
 libtracker_miner_ TRACKER_API_VERSION@_la_LDFLAGS +=    \
-       -export-symbols-regex 
'^tracker_(miner|password_provider|network_provider|indexing_tree|file_system|file_notifier|directory_flags|filter_type|filter_policy|decorator)_.*'
+       -export-symbols-regex 
'^tracker_(miner|password_provider|network_provider|indexing_tree|file_system|file_notifier|directory_flags|filter_type|filter_policy|network_type|decorator)_.*'
 endif
 
 libtracker_miner_ TRACKER_API_VERSION@_la_LIBADD =     \
diff --git a/src/libtracker-miner/tracker-miner-enums.h b/src/libtracker-miner/tracker-miner-enums.h
index e2b978f..33f04d8 100644
--- a/src/libtracker-miner/tracker-miner-enums.h
+++ b/src/libtracker-miner/tracker-miner-enums.h
@@ -79,6 +79,34 @@ typedef enum {
        TRACKER_FILTER_POLICY_ACCEPT
 } TrackerFilterPolicy;
 
+/**
+ * TrackerNetworkType:
+ * @TRACKER_NETWORK_TYPE_NONE: Network is disconnected
+ * @TRACKER_NETWORK_TYPE_UNKNOWN: Network status is unknown
+ * @TRACKER_NETWORK_TYPE_GPRS: Network is connected over a GPRS
+ * connection
+ * @TRACKER_NETWORK_TYPE_EDGE: Network is connected over an EDGE
+ * connection
+ * @TRACKER_NETWORK_TYPE_3G: Network is connected over a 3G or
+ * faster (HSDPA, UMTS, ...) connection
+ * @TRACKER_NETWORK_TYPE_LAN: Network is connected over a local
+ * network connection. This can be ethernet, wifi, etc.
+ *
+ * Enumerates the different types of connections that the device might
+ * use when connected to internet. Note that not all providers might
+ * provide this information.
+ *
+ * Since: 0.18
+ **/
+typedef enum {
+       TRACKER_NETWORK_TYPE_NONE,
+       TRACKER_NETWORK_TYPE_UNKNOWN,
+       TRACKER_NETWORK_TYPE_GPRS,
+       TRACKER_NETWORK_TYPE_EDGE,
+       TRACKER_NETWORK_TYPE_3G,
+       TRACKER_NETWORK_TYPE_LAN
+} TrackerNetworkType;
+
 G_END_DECLS
 
 #endif /* __TRACKER_MINER_ENUMS_H__ */
diff --git a/src/libtracker-miner/tracker-miner-online.c b/src/libtracker-miner/tracker-miner-online.c
new file mode 100644
index 0000000..f84c923
--- /dev/null
+++ b/src/libtracker-miner/tracker-miner-online.c
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2009-2014, Adrien Bustany <abustany gnome org>
+ * Copyright (C) 2014, Carlos Garnacho <carlosg gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include "tracker-miner-online.h"
+#include "tracker-miner-enum-types.h"
+
+#include <libnm-glib/nm-client.h>
+#include <glib/gi18n.h>
+
+#ifndef NM_CHECK_VERSION
+#define NM_CHECK_VERSION(x,y,z) (0)
+#endif
+
+#include <libnm-glib/nm-device-ethernet.h>
+#include <libnm-glib/nm-device-wifi.h>
+#if (NM_CHECK_VERSION (0,8,992))
+#include <libnm-glib/nm-device-modem.h>
+#include <libnm-glib/nm-device-wimax.h>
+#else
+#include <libnm-glib/nm-gsm-device.h>
+#include <libnm-glib/nm-cdma-device.h>
+#endif
+
+/**
+ * SECTION:tracker-miner-online
+ * @short_description: Abstract base class for miners connecting to
+ *   online resources
+ * @include: libtracker-miner/tracker-miner.h
+ *
+ * #TrackerMinerOnline is an abstract base class for miners retrieving data
+ * from online resources. It's a very thin layer above #TrackerMiner that
+ * additionally handles network connection status.
+ *
+ * #TrackerMinerOnline implementations can implement the
+ * <literal>connected</literal> vmethod in order to tell the miner whether
+ * a connection is valid to retrieve data or not. The miner data extraction
+ * still must be dictated through the #TrackerMiner vmethods.
+ **/
+
+typedef struct _TrackerMinerOnlinePrivate TrackerMinerOnlinePrivate;
+
+struct _TrackerMinerOnlinePrivate {
+#ifdef HAVE_NETWORK_MANAGER
+       NMClient *client;
+#endif
+       TrackerNetworkType network_type;
+       gint pause_id;
+};
+
+enum {
+       PROP_NETWORK_TYPE = 1
+};
+
+enum {
+       CONNECTED,
+       DISCONNECTED,
+       N_SIGNALS
+};
+
+static void       miner_online_initable_iface_init (GInitableIface         *iface);
+
+static GInitableIface* miner_online_initable_parent_iface;
+static guint signals[N_SIGNALS] = { 0 };
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (TrackerMinerOnline, tracker_miner_online, TRACKER_TYPE_MINER,
+                                  G_ADD_PRIVATE (TrackerMinerOnline)
+                                  G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+                                                         miner_online_initable_iface_init));
+
+static void
+miner_online_finalize (GObject *object)
+{
+       TrackerMinerOnlinePrivate *priv;
+       TrackerMinerOnline *miner;
+
+       miner = TRACKER_MINER_ONLINE (object);
+       priv = tracker_miner_online_get_instance_private (miner);
+
+       if (priv->client)
+               g_object_unref (priv->client);
+
+       G_OBJECT_CLASS (tracker_miner_online_parent_class)->finalize (object);
+}
+
+static void
+miner_online_set_property (GObject      *object,
+                           guint         param_id,
+                           const GValue *value,
+                           GParamSpec   *pspec)
+{
+       switch (param_id) {
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               break;
+       }
+}
+
+static void
+miner_online_get_property (GObject    *object,
+                           guint       param_id,
+                           GValue     *value,
+                           GParamSpec *pspec)
+{
+       TrackerMinerOnlinePrivate *priv;
+       TrackerMinerOnline *miner;
+
+       miner = TRACKER_MINER_ONLINE (object);
+       priv = tracker_miner_online_get_instance_private (miner);
+
+       switch (param_id) {
+       case PROP_NETWORK_TYPE:
+               g_value_set_enum (value, priv->network_type);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               break;
+       }
+}
+
+static void
+tracker_miner_online_class_init (TrackerMinerOnlineClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize     = miner_online_finalize;
+       object_class->set_property = miner_online_set_property;
+       object_class->get_property = miner_online_get_property;
+
+       g_object_class_install_property (object_class,
+                                        PROP_NETWORK_TYPE,
+                                        g_param_spec_enum ("network-type",
+                                                           "Network type",
+                                                           "Network type for the current connection",
+                                                           TRACKER_TYPE_NETWORK_TYPE,
+                                                           TRACKER_NETWORK_TYPE_NONE,
+                                                           G_PARAM_READABLE));
+       signals[CONNECTED] =
+               g_signal_new ("connected",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerMinerOnlineClass, connected),
+                             NULL, NULL, NULL,
+                             G_TYPE_BOOLEAN, 1, TRACKER_TYPE_NETWORK_TYPE);
+       signals[DISCONNECTED] =
+               g_signal_new ("disconnected",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerMinerOnlineClass, connected),
+                             NULL, NULL, NULL,
+                             G_TYPE_NONE, 0);
+}
+
+static void
+tracker_miner_online_init (TrackerMinerOnline *miner)
+{
+       TrackerMinerOnlinePrivate *priv;
+
+       priv = tracker_miner_online_get_instance_private (miner);
+       priv->network_type = TRACKER_NETWORK_TYPE_NONE;
+}
+
+#ifdef HAVE_NETWORK_MANAGER
+/*
+ * Returns the first NMActiveConnection with the "default" property set, or
+ * NULL if none is found.
+ */
+static NMActiveConnection*
+find_default_active_connection (NMClient *client)
+{
+       NMActiveConnection *active_connection = NULL;
+       const GPtrArray *active_connections;
+       gint i;
+
+       active_connections = nm_client_get_active_connections (client);
+
+       for (i = 0; i < active_connections->len; i++) {
+               active_connection = g_ptr_array_index (active_connections, i);
+
+               if (nm_active_connection_get_default (active_connection)) {
+                       break;
+               }
+       }
+
+       return active_connection;
+}
+
+static TrackerNetworkType
+_nm_client_get_network_type (NMClient *nm_client)
+{
+       NMActiveConnection *default_active_connection;
+       const GPtrArray *devices;
+       NMDevice *device;
+
+       if (!nm_client_get_manager_running (nm_client)) {
+               return TRACKER_NETWORK_TYPE_UNKNOWN;
+       }
+
+       switch (nm_client_get_state (nm_client)) {
+       case NM_STATE_UNKNOWN:
+               return TRACKER_NETWORK_TYPE_UNKNOWN;
+       case NM_STATE_CONNECTED:
+               break;
+       default:
+               return TRACKER_NETWORK_TYPE_NONE;
+       }
+
+       default_active_connection = find_default_active_connection (nm_client);
+
+       if (!default_active_connection) {
+               return TRACKER_NETWORK_TYPE_NONE;
+       }
+
+       switch (nm_active_connection_get_state (default_active_connection)) {
+       case NM_ACTIVE_CONNECTION_STATE_UNKNOWN:
+               return TRACKER_NETWORK_TYPE_UNKNOWN;
+       case NM_ACTIVE_CONNECTION_STATE_ACTIVATED:
+               break;
+       default:
+               return TRACKER_NETWORK_TYPE_NONE;
+       }
+
+       devices = nm_active_connection_get_devices (default_active_connection);
+
+       if (!devices->len) {
+               return TRACKER_NETWORK_TYPE_NONE;
+       }
+
+       /* Pick the first device, I don't know when there are more than one */
+       device = g_ptr_array_index (devices, 0);
+
+       switch (nm_device_get_state (device)) {
+       case NM_DEVICE_STATE_UNKNOWN:
+               return TRACKER_NETWORK_TYPE_UNKNOWN;
+               break;
+       case NM_DEVICE_STATE_ACTIVATED:
+               break;
+       default:
+               return TRACKER_NETWORK_TYPE_NONE;
+       }
+
+       if (NM_IS_DEVICE_ETHERNET (device) || NM_IS_DEVICE_WIFI (device)) {
+               return TRACKER_NETWORK_TYPE_LAN;
+       }
+
+#if (NM_CHECK_VERSION (0,8,992))
+       if (NM_IS_DEVICE_MODEM (device) || NM_IS_DEVICE_WIMAX (device)) {
+               return TRACKER_NETWORK_TYPE_3G;
+       }
+#else
+       if (NM_IS_GSM_DEVICE (device) || NM_IS_CDMA_DEVICE (device)) {
+               return TRACKER_NETWORK_TYPE_3G;
+       }
+#endif
+
+       /* We know the device is activated, but we don't know the type of device */
+       return TRACKER_NETWORK_TYPE_UNKNOWN;
+}
+
+static void
+_tracker_miner_online_set_network_type (TrackerMinerOnline *miner,
+                                        TrackerNetworkType  type)
+{
+       TrackerMinerOnlinePrivate *priv;
+       gboolean cont = FALSE;
+       GError *error = NULL;
+
+       priv = tracker_miner_online_get_instance_private (miner);
+
+       if (type == priv->network_type) {
+               return;
+       }
+
+       priv->network_type = type;
+
+       if (type != TRACKER_NETWORK_TYPE_NONE) {
+               g_signal_emit (miner, signals[CONNECTED], 0, type, &cont);
+       } else {
+               g_signal_emit (miner, signals[DISCONNECTED], 0);
+       }
+
+       if (cont && priv->pause_id)
+               tracker_miner_resume (TRACKER_MINER (miner),
+                                     priv->pause_id, &error);
+       else if (!cont && !priv->pause_id) {
+               const gchar *msg;
+
+               msg = (type == TRACKER_NETWORK_TYPE_NONE) ?
+                       _("No network connection") :
+                       _("Indexing not recommended on this network connection");
+
+               priv->pause_id =
+                       tracker_miner_pause (TRACKER_MINER (miner),
+                                            msg, &error);
+       }
+
+       if (error) {
+               g_warning ("There was an error after getting network type %d: %s",
+                          type, error->message);
+               g_error_free (error);
+       }
+}
+
+static void
+_nm_client_state_notify_cb (GObject            *object,
+                            GParamSpec         *pspec,
+                            TrackerMinerOnline *miner)
+{
+       TrackerMinerOnlinePrivate *priv;
+       TrackerNetworkType type;
+
+       priv = tracker_miner_online_get_instance_private (miner);
+       type = _nm_client_get_network_type (priv->client);
+       _tracker_miner_online_set_network_type (miner, type);
+}
+#endif /* HAVE_NETWORK_MANAGER */
+
+static gboolean
+miner_online_initable_init (GInitable     *initable,
+                            GCancellable  *cancellable,
+                            GError       **error)
+{
+       TrackerMinerOnlinePrivate *priv;
+       TrackerNetworkType network_type;
+       TrackerMinerOnline *miner;
+
+       miner = TRACKER_MINER_ONLINE (initable);
+       priv = tracker_miner_online_get_instance_private (miner);
+
+       if (!miner_online_initable_parent_iface->init (initable,
+                                                      cancellable, error)) {
+               return FALSE;
+       }
+
+#ifdef HAVE_NETWORK_MANAGER
+       priv->client = nm_client_new ();
+       g_signal_connect (priv->client, "notify::state",
+                         G_CALLBACK (_nm_client_state_notify_cb), miner);
+       network_type = _nm_client_get_network_type (priv->client);
+       _tracker_miner_online_set_network_type (miner, network_type);
+#endif
+
+       return TRUE;
+}
+
+static void
+miner_online_initable_iface_init (GInitableIface *iface)
+{
+       miner_online_initable_parent_iface = g_type_interface_peek_parent (iface);
+       iface->init = miner_online_initable_init;
+}
+
+TrackerNetworkType
+tracker_miner_online_get_network_type (TrackerMinerOnline *miner)
+{
+       TrackerMinerOnlinePrivate *priv;
+
+       priv = tracker_miner_online_get_instance_private (miner);
+
+       return priv->network_type;
+}
diff --git a/src/libtracker-miner/tracker-miner-online.h b/src/libtracker-miner/tracker-miner-online.h
new file mode 100644
index 0000000..91c5b32
--- /dev/null
+++ b/src/libtracker-miner/tracker-miner-online.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2009, Adrien Bustany <abustany gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#ifndef __LIBTRACKER_MINER_ONLINE_H__
+#define __LIBTRACKER_MINER_ONLINE_H__
+
+#if !defined (__LIBTRACKER_MINER_H_INSIDE__) && !defined (TRACKER_COMPILATION)
+#error "Only <libtracker-miner/tracker-miner.h> can be included directly."
+#endif
+
+#include <libtracker-miner/tracker-miner-object.h>
+#include <libtracker-miner/tracker-miner-enums.h>
+
+#define TRACKER_TYPE_MINER_ONLINE         (tracker_miner_online_get_type())
+#define TRACKER_MINER_ONLINE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_MINER_ONLINE, 
TrackerMinerOnline))
+#define TRACKER_MINER_ONLINE_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c),    TRACKER_TYPE_MINER_ONLINE, 
TrackerMinerOnlineClass))
+#define TRACKER_IS_MINER_ONLINE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_MINER_ONLINE))
+#define TRACKER_IS_MINER_ONLINE_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c),    TRACKER_TYPE_MINER_ONLINE))
+#define TRACKER_MINER_ONLINE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o),  TRACKER_TYPE_MINER_ONLINE, 
TrackerMinerOnlineClass))
+
+G_BEGIN_DECLS
+
+typedef struct _TrackerMinerOnline TrackerMinerOnline;
+typedef struct _TrackerMinerOnlineClass TrackerMinerOnlineClass;
+
+struct _TrackerMinerOnline {
+       TrackerMiner parent_instance;
+};
+
+/**
+ * TrackerMinerOnlineClass:
+ * @parent_class: parent object class
+ * @connected: called when there is a network connection, or a new
+ *   default route, returning #TRUE starts/resumes indexing.
+ * @disconnected: called when there is no network connection.
+ *
+ * Since: 0.18.
+ **/
+struct _TrackerMinerOnlineClass {
+       TrackerMinerClass parent_class;
+
+       /* vmethods */
+       gboolean (* connected)    (TrackerMinerOnline *miner,
+                                  TrackerNetworkType  network);
+       void     (* disconnected) (TrackerMinerOnline *miner);
+};
+
+GType               tracker_miner_online_get_type         (void) G_GNUC_CONST;
+
+TrackerNetworkType  tracker_miner_online_get_network_type (TrackerMinerOnline *miner);
+
+G_END_DECLS
+
+#endif /* __LIBTRACKER_MINER_ONLINE_H__ */
diff --git a/src/libtracker-miner/tracker-miner.h b/src/libtracker-miner/tracker-miner.h
index 208a3db..cd6acc1 100644
--- a/src/libtracker-miner/tracker-miner.h
+++ b/src/libtracker-miner/tracker-miner.h
@@ -27,6 +27,7 @@
 #include <libtracker-miner/tracker-network-provider.h>
 #include <libtracker-miner/tracker-password-provider.h>
 #include <libtracker-miner/tracker-miner-object.h>
+#include <libtracker-miner/tracker-miner-online.h>
 #include <libtracker-miner/tracker-miner-fs.h>
 #include <libtracker-miner/tracker-miner-web.h>
 #include <libtracker-miner/tracker-miner-enums.h>


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