[evolution-data-server] ECalBackendStore: Add a "timezone-cache" construct-only property.



commit ae4564ddce30b28ac07f82f7a4b141dc1c8c7813
Author: Matthew Barnes <mbarnes redhat com>
Date:   Thu Jan 3 10:26:51 2013 -0500

    ECalBackendStore: Add a "timezone-cache" construct-only property.
    
    Instead of keeping its own an internal hash table of icaltimezones, have
    ECalBackendStore take an ETimezoneCache in e_cal_backend_store_new().
    
    Usually the ETimezoneCache will be an ECalBackend, which implements the
    ETimezoneCache interface and owns the ECalBackendStore.  For that reason,
    the store only keeps a weak reference on its ETimezoneCache to avoid a
    reference cycle.
    
    New functions:
    
        e_cal_backend_store_ref_timezone_cache()
    
    This is an API break.  3rd party backends will have to be adjusted.

 calendar/backends/caldav/e-cal-backend-caldav.c    |    9 +-
 calendar/backends/http/e-cal-backend-http.c        |    3 +-
 calendar/libedata-cal/e-cal-backend-store.c        |  285 ++++++++++++++------
 calendar/libedata-cal/e-cal-backend-store.h        |    6 +-
 .../libedata-cal/libedata-cal-sections.txt         |    1 +
 5 files changed, 214 insertions(+), 90 deletions(-)
---
diff --git a/calendar/backends/caldav/e-cal-backend-caldav.c b/calendar/backends/caldav/e-cal-backend-caldav.c
index ab18654..6193109 100644
--- a/calendar/backends/caldav/e-cal-backend-caldav.c
+++ b/calendar/backends/caldav/e-cal-backend-caldav.c
@@ -2690,13 +2690,8 @@ initialize_backend (ECalBackendCalDAV *cbdav,
 	if (cbdav->priv->store == NULL) {
 		/* remove the old cache while migrating to ECalBackendStore */
 		e_cal_backend_cache_remove (cache_dir, "cache.xml");
-		cbdav->priv->store = e_cal_backend_store_new (cache_dir);
-
-		if (cbdav->priv->store == NULL) {
-			g_propagate_error (perror, EDC_ERROR_EX (OtherError, _("Cannot create local store")));
-			return FALSE;
-		}
-
+		cbdav->priv->store = e_cal_backend_store_new (
+			cache_dir, E_TIMEZONE_CACHE (cbdav));
 		e_cal_backend_store_load (cbdav->priv->store);
 	}
 
diff --git a/calendar/backends/http/e-cal-backend-http.c b/calendar/backends/http/e-cal-backend-http.c
index 96b327a..f5accf2 100644
--- a/calendar/backends/http/e-cal-backend-http.c
+++ b/calendar/backends/http/e-cal-backend-http.c
@@ -879,7 +879,8 @@ e_cal_backend_http_open (ECalBackendSync *backend,
 	if (priv->store == NULL) {
 		/* remove the old cache while migrating to ECalBackendStore */
 		e_cal_backend_cache_remove (cache_dir, "cache.xml");
-		priv->store = e_cal_backend_store_new (cache_dir);
+		priv->store = e_cal_backend_store_new (
+			cache_dir, E_TIMEZONE_CACHE (backend));
 		e_cal_backend_store_load (priv->store);
 
 		if (!priv->store) {
diff --git a/calendar/libedata-cal/e-cal-backend-store.c b/calendar/libedata-cal/e-cal-backend-store.c
index 32b9e66..4f19c37 100644
--- a/calendar/libedata-cal/e-cal-backend-store.c
+++ b/calendar/libedata-cal/e-cal-backend-store.c
@@ -46,7 +46,9 @@ struct _ECalBackendStorePrivate {
 	EIntervalTree *intervaltree;
 	gboolean loaded;
 
-	GHashTable *timezones;
+	GWeakRef timezone_cache;
+	gulong timezone_added_handler_id;
+
 	GHashTable *comp_uid_hash;
 	EFileCache *keys_cache;
 
@@ -59,21 +61,17 @@ struct _ECalBackendStorePrivate {
 	gboolean freeze_changes;
 
 	guint save_timeout_id;
+	GMutex save_timeout_lock;
 };
 
 enum {
 	PROP_0,
-	PROP_PATH
+	PROP_PATH,
+	PROP_TIMEZONE_CACHE,
 };
 
 G_DEFINE_TYPE (ECalBackendStore, e_cal_backend_store, G_TYPE_OBJECT)
 
-static void
-free_timezone (icaltimezone *zone)
-{
-	icaltimezone_free (zone, 1);
-}
-
 static FullCompObject *
 create_new_full_object (void)
 {
@@ -103,59 +101,55 @@ destroy_full_object (FullCompObject *obj)
 	g_free (obj);
 }
 
-static icaltimezone *
-copy_timezone (icaltimezone *zone)
-{
-	icaltimezone *copy;
-	icalcomponent *icalcomp;
-
-	copy = icaltimezone_new ();
-	icalcomp = icaltimezone_get_component (zone);
-	icalcomp = icalcomponent_new_clone (icalcomp);
-	icaltimezone_set_component (copy, icalcomp);
-
-	return copy;
-}
-
 static void
 cal_backend_store_add_timezone (ECalBackendStore *store,
                                 icalcomponent *vtzcomp)
 {
+	ETimezoneCache *timezone_cache;
 	icalproperty *prop;
 	icaltimezone *zone;
 	const gchar *tzid;
 
+	timezone_cache = e_cal_backend_store_ref_timezone_cache (store);
+
 	prop = icalcomponent_get_first_property (vtzcomp, ICAL_TZID_PROPERTY);
-	if (!prop)
-		return;
+	if (prop == NULL)
+		goto exit;
 
 	tzid = icalproperty_get_tzid (prop);
-	if (g_hash_table_lookup (store->priv->timezones, tzid))
-		return;
+	if (e_timezone_cache_get_timezone (timezone_cache, tzid) != NULL)
+		goto exit;
 
 	zone = icaltimezone_new ();
-	if (!icaltimezone_set_component (zone, icalcomponent_new_clone (vtzcomp))) {
+
+	vtzcomp = icalcomponent_new_clone (vtzcomp);
+	if (!icaltimezone_set_component (zone, vtzcomp)) {
 		icaltimezone_free (zone, TRUE);
-		return;
+		icalcomponent_free (vtzcomp);
+		goto exit;
 	}
 
-	g_rw_lock_writer_lock (&store->priv->lock);
-	g_hash_table_insert (store->priv->timezones, g_strdup (tzid), zone);
-	g_rw_lock_writer_unlock (&store->priv->lock);
+	e_timezone_cache_add_timezone (timezone_cache, zone);
+
+	icaltimezone_free (zone, TRUE);
+
+exit:
+	g_object_unref (timezone_cache);
 }
 
 static icaltimezone *
 resolve_tzid (const gchar *tzid,
               gpointer user_data)
 {
+	ECalBackendStore *store;
+	ETimezoneCache *timezone_cache;
 	icaltimezone *zone;
 
-	zone = (!strcmp (tzid, "UTC"))
-		? icaltimezone_get_utc_timezone ()
-		: icaltimezone_get_builtin_timezone_from_tzid (tzid);
+	store = E_CAL_BACKEND_STORE (user_data);
 
-	if (!zone)
-		zone = (icaltimezone *) e_cal_backend_store_get_timezone (E_CAL_BACKEND_STORE (user_data), tzid);
+	timezone_cache = e_cal_backend_store_ref_timezone_cache (store);
+	zone = e_timezone_cache_get_timezone (timezone_cache, tzid);
+	g_object_unref (timezone_cache);
 
 	return zone;
 }
@@ -285,33 +279,40 @@ cal_backend_store_scan_vcalendar (ECalBackendStore *store,
 	}
 }
 
-static gboolean
-cal_backend_store_save_cache_timeout_cb (gpointer user_data)
+static void
+cal_backend_store_save_cache_now (ECalBackendStore *store)
 {
-	ECalBackendStore *store = user_data;
+	ETimezoneCache *timezone_cache;
 	GHashTableIter iter;
+	GList *list, *link;
 	icalcomponent *vcalcomp;
 	gchar *data = NULL, *tmpfile;
 	gsize len, nwrote;
 	gpointer value;
 	FILE *f;
 
-	g_rw_lock_reader_lock (&store->priv->lock);
+	/* Since we only hold a weak reference on the ETimezoneCache,
+	 * make sure we still have it before proceeding.  If we lost
+	 * the ETimezoneCache, abort the save with a console warning
+	 * to avoid wiping out saved time zone data. */
+	timezone_cache = e_cal_backend_store_ref_timezone_cache (store);
+	g_return_if_fail (timezone_cache != NULL);
 
-	store->priv->save_timeout_id = 0;
+	g_rw_lock_reader_lock (&store->priv->lock);
 
 	vcalcomp = e_cal_util_new_top_level ();
 
 	/* Add all timezone components. */
-	g_hash_table_iter_init (&iter, store->priv->timezones);
-	while (g_hash_table_iter_next (&iter, NULL, &value)) {
-		icaltimezone *tz = value;
+	list = e_timezone_cache_list_timezones (timezone_cache);
+	for (link = list; link != NULL; link = g_list_next (link)) {
+		icaltimezone *tz = link->data;
 		icalcomponent *tzcomp;
 
 		tzcomp = icaltimezone_get_component (tz);
 		tzcomp = icalcomponent_new_clone (tzcomp);
 		icalcomponent_add_component (vcalcomp, tzcomp);
 	}
+	g_list_free (list);
 
 	/* Add all non-timezone components. */
 	g_hash_table_iter_init (&iter, store->priv->comp_uid_hash);
@@ -357,8 +358,23 @@ cal_backend_store_save_cache_timeout_cb (gpointer user_data)
 
 error:
 	g_rw_lock_reader_unlock (&store->priv->lock);
+	g_object_unref (timezone_cache);
 	g_free (tmpfile);
 	g_free (data);
+}
+
+static gboolean
+cal_backend_store_save_cache_timeout_cb (gpointer user_data)
+{
+	ECalBackendStore *store;
+
+	store = E_CAL_BACKEND_STORE (user_data);
+
+	g_mutex_lock (&store->priv->save_timeout_lock);
+	store->priv->save_timeout_id = 0;
+	g_mutex_unlock (&store->priv->save_timeout_lock);
+
+	cal_backend_store_save_cache_now (store);
 
 	return FALSE;
 }
@@ -366,13 +382,27 @@ error:
 static void
 cal_backend_store_save_cache (ECalBackendStore *store)
 {
-	if (store->priv->save_timeout_id) {
+	g_mutex_lock (&store->priv->save_timeout_lock);
+
+	if (store->priv->save_timeout_id > 0)
 		g_source_remove (store->priv->save_timeout_id);
-	}
 
 	store->priv->save_timeout_id = g_timeout_add_seconds (
 		IDLE_SAVE_TIMEOUT_SECONDS,
 		cal_backend_store_save_cache_timeout_cb, store);
+
+	g_mutex_unlock (&store->priv->save_timeout_lock);
+}
+
+static void
+cal_backend_store_timezone_added_cb (ETimezoneCache *timezone_cache,
+                                     icaltimezone *zone,
+                                     ECalBackendStore *store)
+{
+	store->priv->dirty = TRUE;
+
+	if (!store->priv->freeze_changes)
+		cal_backend_store_save_cache (store);
 }
 
 static void
@@ -386,6 +416,15 @@ cal_backend_store_set_path (ECalBackendStore *store,
 }
 
 static void
+cal_backend_store_set_timezone_cache (ECalBackendStore *store,
+                                      ETimezoneCache *timezone_cache)
+{
+	g_return_if_fail (E_IS_TIMEZONE_CACHE (timezone_cache));
+
+	g_weak_ref_set (&store->priv->timezone_cache, timezone_cache);
+}
+
+static void
 cal_backend_store_set_property (GObject *object,
                                 guint property_id,
                                 const GValue *value,
@@ -397,6 +436,12 @@ cal_backend_store_set_property (GObject *object,
 				E_CAL_BACKEND_STORE (object),
 				g_value_get_string (value));
 			return;
+
+		case PROP_TIMEZONE_CACHE:
+			cal_backend_store_set_timezone_cache (
+				E_CAL_BACKEND_STORE (object),
+				g_value_get_object (value));
+			return;
 	}
 
 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -411,7 +456,15 @@ cal_backend_store_get_property (GObject *object,
 	switch (property_id) {
 		case PROP_PATH:
 			g_value_set_string (
-				value, e_cal_backend_store_get_path (
+				value,
+				e_cal_backend_store_get_path (
+				E_CAL_BACKEND_STORE (object)));
+			return;
+
+		case PROP_TIMEZONE_CACHE:
+			g_value_take_object (
+				value,
+				e_cal_backend_store_ref_timezone_cache (
 				E_CAL_BACKEND_STORE (object)));
 			return;
 	}
@@ -423,9 +476,34 @@ static void
 cal_backend_store_dispose (GObject *object)
 {
 	ECalBackendStorePrivate *priv;
+	ETimezoneCache *timezone_cache;
+	gboolean save_needed = FALSE;
 
 	priv = E_CAL_BACKEND_STORE_GET_PRIVATE (object);
 
+	/* If a save is scheduled, cancel it and save now. */
+	g_mutex_lock (&priv->save_timeout_lock);
+	if (priv->save_timeout_id > 0) {
+		g_source_remove (priv->save_timeout_id);
+		priv->save_timeout_id = 0;
+		save_needed = TRUE;
+	}
+	g_mutex_unlock (&priv->save_timeout_lock);
+	if (save_needed)
+		cal_backend_store_save_cache_now (
+			E_CAL_BACKEND_STORE (object));
+
+	timezone_cache = g_weak_ref_get (&priv->timezone_cache);
+	if (timezone_cache != NULL) {
+		g_signal_handler_disconnect (
+			timezone_cache,
+			priv->timezone_added_handler_id);
+		g_object_unref (timezone_cache);
+
+		g_weak_ref_set (&priv->timezone_cache, NULL);
+		priv->timezone_added_handler_id = 0;
+	}
+
 	g_hash_table_remove_all (priv->comp_uid_hash);
 
 	if (priv->keys_cache != NULL) {
@@ -457,6 +535,8 @@ cal_backend_store_finalize (GObject *object)
 	g_free (priv->cache_file_name);
 	g_free (priv->key_file_name);
 
+	g_mutex_clear (&priv->save_timeout_lock);
+
 	/* Chain up to parent's finalize() method. */
 	G_OBJECT_CLASS (e_cal_backend_store_parent_class)->finalize (object);
 }
@@ -465,7 +545,9 @@ static void
 cal_backend_store_constructed (GObject *object)
 {
 	ECalBackendStore *store;
+	ETimezoneCache *timezone_cache;
 	const gchar *path;
+	gulong handler_id;
 
 	store = E_CAL_BACKEND_STORE (object);
 	path = e_cal_backend_store_get_path (store);
@@ -474,6 +556,16 @@ cal_backend_store_constructed (GObject *object)
 	store->priv->key_file_name =
 		g_build_filename (path, KEY_FILE_NAME, NULL);
 
+	timezone_cache = e_cal_backend_store_ref_timezone_cache (store);
+
+	handler_id = g_signal_connect (
+		timezone_cache, "timezone-added",
+		G_CALLBACK (cal_backend_store_timezone_added_cb), store);
+
+	store->priv->timezone_added_handler_id = handler_id;
+
+	g_object_unref (timezone_cache);
+
 	/* Chain up to parent's constructed() method. */
 	G_OBJECT_CLASS (e_cal_backend_store_parent_class)->
 		constructed (object);
@@ -517,7 +609,6 @@ cal_backend_store_clean (ECalBackendStore *store)
 
 	e_file_cache_clean (store->priv->keys_cache);
 	g_hash_table_remove_all (store->priv->comp_uid_hash);
-	g_hash_table_remove_all (store->priv->timezones);
 
 	g_rw_lock_writer_unlock (&store->priv->lock);
 
@@ -624,11 +715,12 @@ static const icaltimezone *
 cal_backend_store_get_timezone (ECalBackendStore *store,
                                 const gchar *tzid)
 {
+	ETimezoneCache *timezone_cache;
 	const icaltimezone *zone = NULL;
 
-	g_rw_lock_reader_lock (&store->priv->lock);
-	zone = g_hash_table_lookup (store->priv->timezones, tzid);
-	g_rw_lock_reader_unlock (&store->priv->lock);
+	timezone_cache = e_cal_backend_store_ref_timezone_cache (store);
+	zone = e_timezone_cache_get_timezone (timezone_cache, tzid);
+	g_object_unref (timezone_cache);
 
 	return zone;
 }
@@ -637,18 +729,11 @@ static gboolean
 cal_backend_store_put_timezone (ECalBackendStore *store,
                                 icaltimezone *zone)
 {
-	icaltimezone *copy;
+	ETimezoneCache *timezone_cache;
 
-	g_return_val_if_fail (store != NULL, FALSE);
-	g_return_val_if_fail (zone != NULL, FALSE);
-
-	g_rw_lock_writer_lock (&store->priv->lock);
-	copy = copy_timezone ((icaltimezone *) zone);
-	g_hash_table_insert (
-		store->priv->timezones,
-		g_strdup (icaltimezone_get_tzid ((icaltimezone *) zone)),
-		copy);
-	g_rw_lock_writer_unlock (&store->priv->lock);
+	timezone_cache = e_cal_backend_store_ref_timezone_cache (store);
+	e_timezone_cache_add_timezone (timezone_cache, zone);
+	g_object_unref (timezone_cache);
 
 	store->priv->dirty = TRUE;
 
@@ -661,18 +746,23 @@ cal_backend_store_put_timezone (ECalBackendStore *store,
 static const icaltimezone *
 cal_backend_store_get_default_timezone (ECalBackendStore *store)
 {
-	const gchar *tzid;
+	ETimezoneCache *timezone_cache;
 	const icaltimezone *zone = NULL;
+	const gchar *tzid;
+
+	timezone_cache = e_cal_backend_store_ref_timezone_cache (store);
 
 	g_rw_lock_reader_lock (&store->priv->lock);
 
 	tzid = e_file_cache_get_object (
 		store->priv->keys_cache, "default-zone");
-	if (tzid)
-		zone = g_hash_table_lookup (store->priv->timezones, tzid);
+	if (tzid != NULL)
+		zone = e_timezone_cache_get_timezone (timezone_cache, tzid);
 
 	g_rw_lock_reader_unlock (&store->priv->lock);
 
+	g_object_unref (timezone_cache);
+
 	return zone;
 }
 
@@ -680,15 +770,17 @@ static gboolean
 cal_backend_store_set_default_timezone (ECalBackendStore *store,
                                         icaltimezone *zone)
 {
-	const gchar *tzid;
-	icaltimezone *copy;
+	ETimezoneCache *timezone_cache;
 	const gchar *key = "default-zone";
+	const gchar *tzid;
+
+	timezone_cache = e_cal_backend_store_ref_timezone_cache (store);
+	e_timezone_cache_add_timezone (timezone_cache, zone);
+	g_object_unref (timezone_cache);
 
 	g_rw_lock_writer_lock (&store->priv->lock);
 
 	tzid = icaltimezone_get_tzid (zone);
-	copy = copy_timezone (zone);
-	g_hash_table_insert (store->priv->timezones, g_strdup (tzid), copy);
 
 	if (e_file_cache_get_object (store->priv->keys_cache, key))
 		e_file_cache_replace_object (
@@ -895,21 +987,28 @@ e_cal_backend_store_class_init (ECalBackendStoreClass *class)
 			NULL,
 			NULL,
 			G_PARAM_READWRITE |
-			G_PARAM_CONSTRUCT_ONLY));
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TIMEZONE_CACHE,
+		g_param_spec_object (
+			"timezone-cache",
+			"Timezone Cache",
+			"An object implementing the "
+			"ETimezoneCache interface",
+			E_TYPE_TIMEZONE_CACHE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
 }
 
 static void
 e_cal_backend_store_init (ECalBackendStore *store)
 {
-	GHashTable *timezones;
 	GHashTable *comp_uid_hash;
 
-	timezones = g_hash_table_new_full (
-		(GHashFunc) g_str_hash,
-		(GEqualFunc) g_str_equal,
-		(GDestroyNotify) g_free,
-		(GDestroyNotify) free_timezone);
-
 	comp_uid_hash = g_hash_table_new_full (
 		(GHashFunc) g_str_hash,
 		(GEqualFunc) g_str_equal,
@@ -919,29 +1018,32 @@ e_cal_backend_store_init (ECalBackendStore *store)
 	store->priv = E_CAL_BACKEND_STORE_GET_PRIVATE (store);
 
 	store->priv->intervaltree = e_intervaltree_new ();
-	store->priv->timezones = timezones;
 	store->priv->comp_uid_hash = comp_uid_hash;
 	g_rw_lock_init (&store->priv->lock);
+	g_mutex_init (&store->priv->save_timeout_lock);
 }
 
 /**
  * e_cal_backend_store_new:
  * @path: the directory for the store file
+ * @cache: an #ETimezoneCache
  *
- * Creates a new #ECalBackendStore.
+ * Creates a new #ECalBackendStore from @path and @cache.
  *
  * Returns: a new #ECalBackendStore
  *
  * Since: 3.8
  **/
 ECalBackendStore *
-e_cal_backend_store_new (const gchar *path)
+e_cal_backend_store_new (const gchar *path,
+                         ETimezoneCache *cache)
 {
 	g_return_val_if_fail (path != NULL, NULL);
+	g_return_val_if_fail (E_IS_TIMEZONE_CACHE (cache), NULL);
 
 	return g_object_new (
 		E_TYPE_CAL_BACKEND_STORE,
-		"path", path, NULL);
+		"path", path, "timezone-cache", cache, NULL);
 }
 
 /**
@@ -958,6 +1060,27 @@ e_cal_backend_store_get_path (ECalBackendStore *store)
 }
 
 /**
+ * e_cal_backend_store_ref_timezone_cache:
+ * @store: an #ECalBackendStore
+ *
+ * Returns the #ETimezoneCache passed to e_cal_backend_store_new().
+ *
+ * The returned #ETimezoneCache is referenced for thread-safety and must
+ * be unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: an #ETimezoneCache
+ *
+ * Since: 3.8
+ **/
+ETimezoneCache *
+e_cal_backend_store_ref_timezone_cache (ECalBackendStore *store)
+{
+	g_return_val_if_fail (E_IS_CAL_BACKEND_STORE (store), NULL);
+
+	return g_weak_ref_get (&store->priv->timezone_cache);
+}
+
+/**
  * e_cal_backend_store_load:
  *
  * Since: 2.28
diff --git a/calendar/libedata-cal/e-cal-backend-store.h b/calendar/libedata-cal/e-cal-backend-store.h
index b028a02..5b083d2 100644
--- a/calendar/libedata-cal/e-cal-backend-store.h
+++ b/calendar/libedata-cal/e-cal-backend-store.h
@@ -105,8 +105,12 @@ struct _ECalBackendStoreClass {
 
 GType		e_cal_backend_store_get_type	(void);
 ECalBackendStore *
-		e_cal_backend_store_new		(const gchar *path);
+		e_cal_backend_store_new		(const gchar *path,
+						 ETimezoneCache *cache);
 const gchar *	e_cal_backend_store_get_path	(ECalBackendStore *store);
+ETimezoneCache *
+		e_cal_backend_store_ref_timezone_cache
+						(ECalBackendStore *store);
 gboolean	e_cal_backend_store_load	(ECalBackendStore *store);
 gboolean	e_cal_backend_store_is_loaded	(ECalBackendStore *store);
 gboolean	e_cal_backend_store_clean	(ECalBackendStore *store);
diff --git a/docs/reference/calendar/libedata-cal/libedata-cal-sections.txt b/docs/reference/calendar/libedata-cal/libedata-cal-sections.txt
index a8e7c72..2049182 100644
--- a/docs/reference/calendar/libedata-cal/libedata-cal-sections.txt
+++ b/docs/reference/calendar/libedata-cal/libedata-cal-sections.txt
@@ -129,6 +129,7 @@ ECalBackendFactoryPrivate
 ECalBackendStore
 e_cal_backend_store_new
 e_cal_backend_store_get_path
+e_cal_backend_store_ref_timezone_cache
 e_cal_backend_store_load
 e_cal_backend_store_is_loaded
 e_cal_backend_store_clean



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