[PATCH] nm-device AvailableConnections properties



From: "Nathanael D. Noblet" <nathanael gnat ca>

Implements a new property that provides a list of currently
available connections a device could connect to. For example
if a connection for a particular wireless connection exists and
that wireless connection is connectable it would show in the
AvailableConnections property of the device.
---
 introspection/nm-device.xml |   5 ++
 src/nm-device-private.h     |   4 +
 src/nm-device-wifi.c        |  62 ++++++++++----
 src/nm-device.c             | 196 ++++++++++++++++++++++++++++++++++++++++++++
 src/nm-device.h             |   8 ++
 src/wimax/nm-device-wimax.c |  24 ++++++
 6 files changed, 283 insertions(+), 16 deletions(-)

diff --git a/introspection/nm-device.xml b/introspection/nm-device.xml
index 20bbb91..5b2c98b 100644
--- a/introspection/nm-device.xml
+++ b/introspection/nm-device.xml
@@ -120,6 +120,11 @@
         The general type of the network device; ie Ethernet, WiFi, etc.
       </tp:docstring>
     </property>
+    <property name="AvailableConnections" type="ao" access="read">
+      <tp:docstring>
+        An array of object paths of every configured connection that is currently 'available' through this device.
+      </tp:docstring>
+    </property>
 
     <method name="Disconnect">
       <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="impl_device_disconnect"/>
diff --git a/src/nm-device-private.h b/src/nm-device-private.h
index 824beb5..5c6b8c1 100644
--- a/src/nm-device-private.h
+++ b/src/nm-device-private.h
@@ -69,4 +69,8 @@ gboolean nm_device_match_ip_config (NMDevice *device, NMConnection *connection);
 
 NMConnectionProvider *nm_device_get_connection_provider (NMDevice *device);
 
+void nm_device_clear_available_connections (NMDevice *device, gboolean do_signal);
+gboolean nm_device_private_add_available_connection (NMDevice *device, NMConnection *connection);
+void nm_device_private_recheck_available_connections (NMDevice *device);
+
 #endif	/* NM_DEVICE_PRIVATE_H */
diff --git a/src/nm-device-wifi.c b/src/nm-device-wifi.c
index e2ff77e..fbc31b1 100644
--- a/src/nm-device-wifi.c
+++ b/src/nm-device-wifi.c
@@ -884,9 +884,13 @@ _set_hw_addr (NMDeviceWifi *self, const guint8 *addr, const char *detail)
 }
 
 static void
-access_point_removed (NMDeviceWifi *device, NMAccessPoint *ap)
+remove_access_point (NMDeviceWifi *device, NMAccessPoint *ap)
 {
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(device);
 	g_signal_emit (device, signals[ACCESS_POINT_REMOVED], 0, ap);
+	priv->ap_list = g_slist_remove (priv->ap_list, ap);
+	g_object_unref(ap);
+	nm_device_private_recheck_available_connections (NM_DEVICE (device));
 }
 
 static void
@@ -897,10 +901,7 @@ remove_all_aps (NMDeviceWifi *self)
 	/* Remove outdated APs */
 	while (g_slist_length (priv->ap_list)) {
 		NMAccessPoint *ap = NM_AP (priv->ap_list->data);
-
-		access_point_removed (self, ap);
-		priv->ap_list = g_slist_remove (priv->ap_list, ap);
-		g_object_unref (ap);
+		remove_access_point (self, ap);
 	}
 	g_slist_free (priv->ap_list);
 	priv->ap_list = NULL;
@@ -952,9 +953,7 @@ real_deactivate (NMDevice *dev)
 	 * and thus the AP culling never happens. (bgo #569241)
 	 */
 	if (orig_ap && nm_ap_get_fake (orig_ap)) {
-		access_point_removed (self, orig_ap);
-		priv->ap_list = g_slist_remove (priv->ap_list, orig_ap);
-		g_object_unref (orig_ap);
+	    remove_access_point (self, orig_ap);
 	}
 
 	/* Reset MAC address back to initial address */
@@ -1902,6 +1901,7 @@ merge_scanned_ap (NMDeviceWifi *self,
 		priv->ap_list = g_slist_prepend (priv->ap_list, merge_ap);
 		nm_ap_export_to_dbus (merge_ap);
 		g_signal_emit (self, signals[ACCESS_POINT_ADDED], 0, merge_ap);
+		nm_device_private_recheck_available_connections (NM_DEVICE (self));
 	}
 }
 
@@ -1964,9 +1964,7 @@ cull_scan_list (NMDeviceWifi *self)
 		            ssid ? nm_utils_escape_ssid (ssid->data, ssid->len) : "(none)",
 		            ssid ? "'" : "");
 
-		access_point_removed (self, outdated_ap);
-		priv->ap_list = g_slist_remove (priv->ap_list, outdated_ap);
-		g_object_unref (outdated_ap);
+		remove_access_point (self, outdated_ap);
 		removed++;
 	}
 	g_slist_free (outdated_list);
@@ -1977,6 +1975,9 @@ cull_scan_list (NMDeviceWifi *self)
 
 	ap_list_dump (self);
 
+	if(removed > 0)
+	    nm_device_private_recheck_available_connections (NM_DEVICE (self));
+
 	return FALSE;
 }
 
@@ -2839,6 +2840,7 @@ real_act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *reason)
 		priv->ap_list = g_slist_prepend (priv->ap_list, ap);
 		nm_ap_export_to_dbus (ap);
 		g_signal_emit (self, signals[ACCESS_POINT_ADDED], 0, ap);
+		nm_device_private_recheck_available_connections (NM_DEVICE (self));
 	}
 
 	nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req), nm_ap_get_dbus_path (ap));
@@ -3162,7 +3164,6 @@ static void
 activation_failure_handler (NMDevice *dev)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (dev);
-	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	NMAccessPoint *ap;
 	NMConnection *connection;
 
@@ -3180,9 +3181,7 @@ activation_failure_handler (NMDevice *dev)
 			 * list because we don't have any scan or capability info
 			 * for it, and they are pretty much useless.
 			 */
-			access_point_removed (self, ap);
-			priv->ap_list = g_slist_remove (priv->ap_list, ap);
-			g_object_unref (ap);
+			remove_access_point (self, ap);
 		}
 	}
 }
@@ -3390,6 +3389,35 @@ real_set_enabled (NMDevice *device, gboolean enabled)
 	}
 }
 
+static gboolean
+add_available_connection (NMDevice *device, NMConnection *connection)
+{
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (device);
+	NMSettingWireless *s_wireless;
+	const char *mode;
+	GSList *ap_iter = NULL;
+
+	s_wireless = nm_connection_get_setting_wireless (connection);
+	mode = nm_setting_wireless_get_mode (s_wireless);
+
+	if (mode) {
+		if (strcmp (mode, "adhoc") == 0) {
+			nm_device_private_add_available_connection (device, connection);
+			return TRUE;
+		}
+	}
+
+	/* check if its visible */
+	for(ap_iter = priv->ap_list; ap_iter; ap_iter = g_slist_next (ap_iter)) {
+		if (nm_ap_check_compatible (NM_AP (ap_iter->data), connection)) {
+			nm_device_private_add_available_connection (device, connection);
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
 /********************************************************************/
 
 NMDevice *
@@ -3557,6 +3585,8 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	parent_class->spec_match_list = spec_match_list;
 	parent_class->hwaddr_matches = hwaddr_matches;
 
+	parent_class->add_available_connection = add_available_connection;
+
 	klass->scanning_allowed = scanning_allowed;
 
 	/* Properties */
@@ -3633,7 +3663,7 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 		g_signal_new ("access-point-removed",
 		              G_OBJECT_CLASS_TYPE (object_class),
 		              G_SIGNAL_RUN_FIRST,
-		              G_STRUCT_OFFSET (NMDeviceWifiClass, access_point_removed),
+		              0,
 		              NULL, NULL,
 		              g_cclosure_marshal_VOID__OBJECT,
 		              G_TYPE_NONE, 1,
diff --git a/src/nm-device.c b/src/nm-device.c
index 9432204..9309229 100644
--- a/src/nm-device.c
+++ b/src/nm-device.c
@@ -65,6 +65,7 @@
 #include "nm-connection-provider.h"
 #include "nm-posix-signals.h"
 #include "nm-manager-auth.h"
+#include "nm-dbus-glib-types.h"
 
 static void impl_device_disconnect (NMDevice *device, DBusGMethodInvocation *context);
 
@@ -123,6 +124,7 @@ enum {
 	PROP_TYPE_DESC,
 	PROP_RFKILL_TYPE,
 	PROP_IFINDEX,
+	PROP_AVAILABLE_CONNECTIONS,
 	LAST_PROP
 };
 
@@ -169,6 +171,7 @@ typedef struct {
 	gboolean      managed; /* whether managed by NM or not */
 	RfKillType    rfkill_type;
 	gboolean      firmware_missing;
+	GHashTable *  available_connections;
 
 	guint32         ip4_address;
 
@@ -241,6 +244,12 @@ typedef struct {
 	NMDevice *	master;
 
 	NMConnectionProvider *con_provider;
+
+	/* connection provider signals for available connections property */
+	guint cp_added_id;
+	guint cp_loaded_id;
+	guint cp_removed_id;
+	guint cp_updated_id;
 } NMDevicePrivate;
 
 static void nm_device_take_down (NMDevice *dev, gboolean wait, NMDeviceStateReason reason);
@@ -262,6 +271,11 @@ static void dhcp4_cleanup (NMDevice *self, gboolean stop, gboolean release);
 
 static const char *reason_to_string (NMDeviceStateReason reason);
 
+static void cp_connection_added (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data);
+static void cp_connections_loaded (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data);
+static void cp_connection_removed (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data);
+static void cp_connection_updated (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data);
+
 static void
 nm_device_init (NMDevice *self)
 {
@@ -274,6 +288,7 @@ nm_device_init (NMDevice *self)
 	priv->dhcp_timeout = 0;
 	priv->rfkill_type = RFKILL_TYPE_UNKNOWN;
 	priv->autoconnect = DEFAULT_AUTOCONNECT;
+	priv->available_connections = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
 }
 
 static void
@@ -712,6 +727,25 @@ nm_device_set_connection_provider (NMDevice *device,
 	g_return_if_fail (priv->con_provider == NULL);
 
 	priv->con_provider = provider;
+	priv->cp_added_id = g_signal_connect (priv->con_provider,
+	                                      NM_CP_SIGNAL_CONNECTION_ADDED,
+	                                      G_CALLBACK (cp_connection_added),
+	                                      device);
+
+	priv->cp_loaded_id = g_signal_connect (priv->con_provider,
+	                                       NM_CP_SIGNAL_CONNECTIONS_LOADED,
+	                                       G_CALLBACK (cp_connections_loaded),
+	                                       device);
+
+	priv->cp_removed_id = g_signal_connect (priv->con_provider,
+	                                        NM_CP_SIGNAL_CONNECTION_REMOVED,
+	                                        G_CALLBACK (cp_connection_removed),
+	                                        device);
+
+	priv->cp_updated_id = g_signal_connect (priv->con_provider,
+	                                        NM_CP_SIGNAL_CONNECTION_UPDATED,
+	                                        G_CALLBACK (cp_connection_updated),
+	                                        device);
 }
 
 NMConnectionProvider *
@@ -3803,6 +3837,28 @@ dispose (GObject *object)
 	}
 	g_free (priv->ip6_privacy_tempaddr_path);
 
+	if (priv->cp_added_id) {
+	    g_signal_handler_disconnect (priv->con_provider, priv->cp_added_id);
+	    priv->cp_added_id = 0;
+	}
+
+	if (priv->cp_loaded_id) {
+	    g_signal_handler_disconnect (priv->con_provider, priv->cp_loaded_id);
+	    priv->cp_loaded_id = 0;
+	}
+
+	if (priv->cp_removed_id) {
+	    g_signal_handler_disconnect (priv->con_provider, priv->cp_removed_id);
+	    priv->cp_removed_id = 0;
+	}
+
+	if (priv->cp_updated_id) {
+	    g_signal_handler_disconnect (priv->con_provider, priv->cp_updated_id);
+	    priv->cp_updated_id = 0;
+	}
+
+	g_hash_table_unref (priv->available_connections);
+
 	activation_source_clear (self, TRUE, AF_INET);
 	activation_source_clear (self, TRUE, AF_INET6);
 
@@ -3928,6 +3984,9 @@ get_property (GObject *object, guint prop_id,
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMDeviceState state;
 	const char *ac_path = NULL;
+	GPtrArray *array;
+	GHashTableIter iter;
+	NMConnection *connection;
 
 	state = nm_device_get_state (self);
 
@@ -4020,6 +4079,13 @@ get_property (GObject *object, guint prop_id,
 	case PROP_RFKILL_TYPE:
 		g_value_set_uint (value, priv->rfkill_type);
 		break;
+	case PROP_AVAILABLE_CONNECTIONS:
+		array = g_ptr_array_sized_new (g_hash_table_size (priv->available_connections));
+		g_hash_table_iter_init (&iter, priv->available_connections);
+		while (g_hash_table_iter_next (&iter, (gpointer) &connection, NULL))
+			g_ptr_array_add (array, g_strdup (nm_connection_get_path (connection)));
+		g_value_take_boxed (value, array);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -4050,6 +4116,7 @@ nm_device_class_init (NMDeviceClass *klass)
 	klass->act_stage3_ip6_config_start = real_act_stage3_ip6_config_start;
 	klass->act_stage4_ip4_config_timeout = real_act_stage4_ip4_config_timeout;
 	klass->act_stage4_ip6_config_timeout = real_act_stage4_ip6_config_timeout;
+	klass->add_available_connection = nm_device_private_add_available_connection;
 
 	/* Properties */
 	g_object_class_install_property
@@ -4229,6 +4296,14 @@ nm_device_class_init (NMDeviceClass *klass)
 		                   0, G_MAXINT, 0,
 		                   G_PARAM_READABLE | NM_PROPERTY_PARAM_NO_EXPORT));
 
+	g_object_class_install_property
+		(object_class, PROP_AVAILABLE_CONNECTIONS,
+		 g_param_spec_boxed (NM_DEVICE_AVAILABLE_CONNECTIONS,
+		                     "AvailableConnections",
+		                     "AvailableConnections",
+		                     DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH,
+		                     G_PARAM_READABLE));
+
 	/* Signals */
 	signals[STATE_CHANGED] =
 		g_signal_new ("state-changed",
@@ -4517,6 +4592,8 @@ nm_device_state_changed (NMDevice *device,
 		 */
 		if (reason != NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED)
 			nm_device_deactivate (device, reason);
+
+		nm_device_clear_available_connections (device, TRUE);
 		break;
 	case NM_DEVICE_STATE_DISCONNECTED:
 		if (old_state != NM_DEVICE_STATE_UNAVAILABLE)
@@ -4855,3 +4932,122 @@ nm_device_get_autoconnect (NMDevice *device)
 	return NM_DEVICE_GET_PRIVATE (device)->autoconnect;
 }
 
+void
+nm_device_clear_available_connections (NMDevice *device, gboolean do_signal)
+{
+	NMDevicePrivate *priv;
+	g_return_if_fail (device != NULL);
+	g_return_if_fail (NM_IS_DEVICE (device));
+
+	priv = NM_DEVICE_GET_PRIVATE (device);
+
+	g_hash_table_remove_all (priv->available_connections);
+	if (do_signal == TRUE)
+		nm_device_signal_available_connections_changed (device);
+}
+
+static gboolean
+add_available_connection (NMDevice *self, NMConnection *connection)
+{
+	gboolean added = TRUE;
+
+	if (nm_device_get_state (self) < NM_DEVICE_STATE_DISCONNECTED)
+		return FALSE;
+
+	if (nm_device_check_connection_compatible (self, connection, NULL)) {
+		/* if there are additional checks (wifi APs, wimax NSPs) override this in the specific device's scope */
+		if (NM_DEVICE_GET_CLASS (self)->add_available_connection)
+			added = NM_DEVICE_GET_CLASS (self)->add_available_connection (self, connection);
+
+		if (added)
+			nm_device_signal_available_connections_changed (self);
+	}
+
+	return added;
+}
+
+void
+nm_device_del_available_connection (NMDevice *device, NMConnection *connection, gboolean do_signal)
+{
+	NMDevicePrivate *priv;
+	g_return_if_fail (device != NULL);
+	g_return_if_fail (NM_IS_DEVICE (device));
+
+	priv = NM_DEVICE_GET_PRIVATE (device);
+
+	g_hash_table_remove (priv->available_connections, connection);
+
+	if (do_signal == TRUE)
+		nm_device_signal_available_connections_changed (device);
+}
+
+gboolean
+nm_device_private_add_available_connection (NMDevice *device, NMConnection *connection)
+{
+	NMDevicePrivate *priv;
+	g_return_val_if_fail (device != NULL, FALSE);
+	g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
+
+	priv = NM_DEVICE_GET_PRIVATE (device);
+
+	g_hash_table_insert (priv->available_connections, g_object_ref (connection), GUINT_TO_POINTER (1));
+
+	return TRUE;
+}
+
+void
+nm_device_private_recheck_available_connections (NMDevice *device)
+{
+	NMDevicePrivate *priv;
+	const GSList *connections, *iter;
+
+	g_return_if_fail (device != NULL);
+	g_return_if_fail (NM_IS_DEVICE (device));
+
+	priv = NM_DEVICE_GET_PRIVATE(device);
+
+	nm_device_clear_available_connections (device, FALSE);
+
+	connections = nm_connection_provider_get_connections (priv->con_provider);
+	for (iter = connections; iter; iter = g_slist_next (iter))
+		add_available_connection (device, NM_CONNECTION (iter->data));
+
+	nm_device_signal_available_connections_changed (device);
+}
+
+static void
+cp_connection_added (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data)
+{
+	add_available_connection (NM_DEVICE (user_data), connection);
+}
+
+static void
+cp_connections_loaded (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data)
+{
+	const GSList *connections, *iter;
+
+	connections = nm_connection_provider_get_connections (cp);
+	for (iter = connections; iter; iter = g_slist_next (iter))
+		add_available_connection (NM_DEVICE (user_data), NM_CONNECTION (iter->data));
+}
+
+static void
+cp_connection_removed (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data)
+{
+	nm_device_del_available_connection (NM_DEVICE (user_data), connection, TRUE);
+}
+
+static void
+cp_connection_updated (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data)
+{
+	nm_device_del_available_connection (NM_DEVICE (user_data), connection, FALSE);
+
+	if (!add_available_connection (NM_DEVICE (user_data), connection))
+		nm_device_signal_available_connections_changed (NM_DEVICE (user_data));
+}
+
+void
+nm_device_signal_available_connections_changed (NMDevice *device)
+{
+	g_object_notify (G_OBJECT (device), NM_DEVICE_AVAILABLE_CONNECTIONS);
+}
diff --git a/src/nm-device.h b/src/nm-device.h
index 91d9166..589c041 100644
--- a/src/nm-device.h
+++ b/src/nm-device.h
@@ -59,6 +59,7 @@
 #define NM_DEVICE_TYPE_DESC        "type-desc"    /* Internal only */
 #define NM_DEVICE_RFKILL_TYPE      "rfkill-type"  /* Internal only */
 #define NM_DEVICE_IFINDEX          "ifindex"      /* Internal only */
+#define NM_DEVICE_AVAILABLE_CONNECTIONS "available-connections"
 
 /* Internal signals */
 #define NM_DEVICE_AUTH_REQUEST "auth-request"
@@ -172,6 +173,8 @@ typedef struct {
 
 	gboolean        (* release_slave) (NMDevice *self,
 	                                   NMDevice *slave);
+
+	gboolean        (* add_available_connection) (NMDevice *self, NMConnection *connection);
 } NMDeviceClass;
 
 
@@ -276,6 +279,11 @@ gboolean nm_device_activate (NMDevice *device, NMActRequest *req, GError **error
 
 void nm_device_set_connection_provider (NMDevice *device, NMConnectionProvider *provider);
 
+void nm_device_update_available_connections (NMDevice *device, const GSList *connections);
+void nm_device_add_available_connection (NMDevice *device, NMConnection *connection, gboolean do_signal);
+void nm_device_del_available_connection (NMDevice *device, NMConnection *connection, gboolean do_signal);
+void nm_device_signal_available_connections_changed (NMDevice *device);
+
 G_END_DECLS
 
 #endif	/* NM_DEVICE_H */
diff --git a/src/wimax/nm-device-wimax.c b/src/wimax/nm-device-wimax.c
index 1f1a622..d099414 100644
--- a/src/wimax/nm-device-wimax.c
+++ b/src/wimax/nm-device-wimax.c
@@ -254,6 +254,8 @@ remove_all_nsps (NMDeviceWimax *self)
 		g_object_unref (nsp);
 	}
 
+	nm_device_private_recheck_available_connections (NM_DEVICE (self));
+
 	g_slist_free (priv->nsp_list);
 	priv->nsp_list = NULL;
 }
@@ -1070,6 +1072,9 @@ remove_outdated_nsps (NMDeviceWimax *self,
 		g_object_unref (nsp);
 	}
 
+	if (g_slist_length(to_remove) > 0)
+	    nm_device_private_recheck_available_connections (NM_DEVICE (self));
+
 	g_slist_free (to_remove);
 }
 
@@ -1117,6 +1122,7 @@ wmx_scan_result_cb (struct wmxsdk *wmxsdk,
 			priv->nsp_list = g_slist_append (priv->nsp_list, nsp);
 			nm_wimax_nsp_export_to_dbus (nsp);
 			g_signal_emit (self, signals[NSP_ADDED], 0, nsp);
+			nm_device_private_recheck_available_connections (NM_DEVICE (self));
 		}
 	}
 }
@@ -1350,6 +1356,23 @@ wmx_new_sdk_cb (struct wmxsdk *sdk, void *user_data)
 		priv->sdk_action_defer_id = g_idle_add (sdk_action_defer_cb, self);
 }
 
+static gboolean
+add_available_connection (NMDevice *device, NMConnection *connection)
+{
+	NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (device);
+	const GSList *ns_iter = NULL;
+
+	// check if its 'visible' and more closely matches the connection
+	for (ns_iter = priv->nsp_list; ns_iter; ns_iter = ns_iter->next) {
+		if (nm_wimax_nsp_check_compatible (NM_WIMAX_NSP (ns_iter->data), connection)) {
+			nm_device_private_add_available_connection (device, connection);
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
 /*************************************************************************/
 
 NMDevice *
@@ -1513,6 +1536,7 @@ nm_device_wimax_class_init (NMDeviceWimaxClass *klass)
 	device_class->deactivate = real_deactivate;
     device_class->set_enabled = real_set_enabled;
     device_class->hwaddr_matches = hwaddr_matches;
+	device_class->add_available_connection = add_available_connection;
 
 	/* Properties */
 	g_object_class_install_property
-- 
1.7.11.2



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