[brasero] Fix #590385 – Fails to recognize blank disk media
- From: Philippe Rouquier <philippr src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [brasero] Fix #590385 – Fails to recognize blank disk media
- Date: Sun, 2 Aug 2009 11:43:31 +0000 (UTC)
commit e6996cb2d32f0e185459e3617438f2a4264c04a1
Author: Philippe Rouquier <bonfire-app wanadoo fr>
Date: Sun Aug 2 13:13:52 2009 +0200
Fix #590385 â?? Fails to recognize blank disk media
Workaround for a strange behavior of GIO's which does not create GDrive when a blank disc is inserted
As brasero relied on GDrive to create its own BraseroDrive, this caused failure to see blank media
libbrasero-media/brasero-drive.c | 132 +++++++++++++----
libbrasero-media/brasero-medium-monitor.c | 228 ++++++++++++++++++++++-------
2 files changed, 277 insertions(+), 83 deletions(-)
---
diff --git a/libbrasero-media/brasero-drive.c b/libbrasero-media/brasero-drive.c
index 78521cc..da2165f 100644
--- a/libbrasero-media/brasero-drive.c
+++ b/libbrasero-media/brasero-drive.c
@@ -70,6 +70,8 @@ struct _BraseroDrivePrivate
gchar *udi;
+ gchar *name;
+
gchar *path;
gchar *block_path;
@@ -90,6 +92,7 @@ static gulong drive_signals [LAST_SIGNAL] = {0, };
enum {
PROP_NONE = 0,
+ PROP_DEVICE,
PROP_GDRIVE,
PROP_UDI
};
@@ -119,6 +122,9 @@ brasero_drive_get_gdrive (BraseroDrive *drive)
priv = BRASERO_DRIVE_PRIVATE (drive);
+ if (!priv->gdrive)
+ return NULL;
+
return g_object_ref (priv->gdrive);
}
@@ -289,7 +295,7 @@ brasero_drive_is_fake (BraseroDrive *drive)
g_return_val_if_fail (BRASERO_IS_DRIVE (drive), FALSE);
priv = BRASERO_DRIVE_PRIVATE (drive);
- return (priv->gdrive == NULL);
+ return (priv->block_path == NULL);
}
/**
@@ -311,10 +317,10 @@ brasero_drive_is_door_open (BraseroDrive *drive)
g_return_val_if_fail (BRASERO_IS_DRIVE (drive), FALSE);
priv = BRASERO_DRIVE_PRIVATE (drive);
- if (!priv->gdrive)
+ if (!!priv->block_path)
return FALSE;
- handle = brasero_device_handle_open (priv->path, FALSE, NULL);
+ handle = brasero_device_handle_open (priv->block_path, FALSE, NULL);
if (!handle)
return FALSE;
@@ -379,7 +385,7 @@ brasero_drive_lock (BraseroDrive *drive,
g_return_val_if_fail (BRASERO_IS_DRIVE (drive), FALSE);
priv = BRASERO_DRIVE_PRIVATE (drive);
- if (!priv->gdrive)
+ if (!priv->block_path)
return FALSE;
device = brasero_drive_get_device (drive);
@@ -418,7 +424,7 @@ brasero_drive_unlock (BraseroDrive *drive)
g_return_val_if_fail (BRASERO_IS_DRIVE (drive), FALSE);
priv = BRASERO_DRIVE_PRIVATE (drive);
- if (!priv->gdrive)
+ if (!!priv->path)
return FALSE;
device = brasero_drive_get_device (drive);
@@ -455,14 +461,17 @@ brasero_drive_get_display_name (BraseroDrive *drive)
g_return_val_if_fail (BRASERO_IS_DRIVE (drive), NULL);
priv = BRASERO_DRIVE_PRIVATE (drive);
- if (!priv->gdrive) {
+ if (!priv->block_path) {
/* Translators: This is a fake drive, a file, and means that
* when we're writing, we're writing to a file and create an
* image on the hard drive. */
return g_strdup (_("Image File"));
}
- return g_drive_get_name (priv->gdrive);
+ if (priv->gdrive)
+ return g_drive_get_name (priv->gdrive);
+
+ return g_strdup (priv->name);
}
/**
@@ -490,7 +499,7 @@ brasero_drive_get_device (BraseroDrive *drive)
* @drive: a #BraseroDrive
*
* Gets a string holding the block device path for the drive. This can be used on
- * some other OS, like Solaris, for burning operations instead of the device
+ * some other OSes, like Solaris, for burning operations instead of the device
* path.
*
* Return value: a string holding the block device path
@@ -514,8 +523,7 @@ brasero_drive_get_block_device (BraseroDrive *drive)
* Gets a string holding the HAL udi corresponding to this device. It can be used
* to uniquely identify the drive.
*
- * Return value: a string holding the HAL udi. Not to be freed
- * Deprecated since 2.27.3
+ * Return value: a string holding the HAL udi or NULL. Not to be freed
**/
const gchar *
brasero_drive_get_udi (BraseroDrive *drive)
@@ -528,7 +536,7 @@ brasero_drive_get_udi (BraseroDrive *drive)
g_return_val_if_fail (BRASERO_IS_DRIVE (drive), NULL);
priv = BRASERO_DRIVE_PRIVATE (drive);
- if (!priv->gdrive)
+ if (!priv->block_path || !priv->gdrive)
return NULL;
if (priv->udi)
@@ -824,7 +832,7 @@ brasero_drive_medium_gdrive_changed_cb (BraseroDrive *gdrive,
}
static void
-brasero_drive_init_gdrive (BraseroDrive *drive)
+brasero_drive_update_gdrive (BraseroDrive *drive)
{
BraseroDrivePrivate *priv;
@@ -842,17 +850,14 @@ brasero_drive_init_gdrive (BraseroDrive *drive)
static gboolean
brasero_drive_probed (gpointer data)
{
- BraseroDrive *drive = BRASERO_DRIVE (data);
BraseroDrivePrivate *priv;
priv = BRASERO_DRIVE_PRIVATE (data);
g_thread_join (priv->probe);
priv->probe = NULL;
-
- brasero_drive_init_gdrive (drive);
-
priv->probe_id = 0;
+
return FALSE;
}
@@ -1019,8 +1024,40 @@ brasero_drive_probe_thread (gpointer data)
}
if (handle) {
+ BraseroScsiInquiry hdr;
+ BraseroScsiResult res;
+
BRASERO_MEDIA_LOG ("Open () succeeded");
+ /* get additional information like the name */
+ res = brasero_spc1_inquiry (handle, &hdr, NULL);
+ if (res == BRASERO_SCSI_OK) {
+ gchar *name_utf8;
+ gchar *vendor;
+ gchar *model;
+ gchar *name;
+
+ vendor = strndup ((gchar *) hdr.vendor, sizeof (hdr.vendor));
+ model = strndup ((gchar *) hdr.name, sizeof (hdr.name));
+ name = g_strdup_printf ("%s %s", g_strstrip (vendor), g_strstrip (model));
+ g_free (vendor);
+ g_free (model);
+
+ /* make sure that's proper UTF-8 */
+ name_utf8 = g_convert_with_fallback (name,
+ -1,
+ "ASCII",
+ "UTF-8",
+ "_",
+ NULL,
+ NULL,
+ NULL);
+ g_free (name);
+
+ priv->name = name_utf8;
+ }
+
+ /* Get supported medium types */
if (!brasero_drive_get_caps_profiles (drive, handle, &code))
brasero_drive_get_caps_2A (drive, handle, &code);
@@ -1036,17 +1073,16 @@ brasero_drive_probe_thread (gpointer data)
}
static void
-brasero_drive_init_real (BraseroDrive *drive,
- GDrive *gdrive)
+brasero_drive_init_real_device (BraseroDrive *drive,
+ const gchar *device)
{
BraseroDrivePrivate *priv;
priv = BRASERO_DRIVE_PRIVATE (drive);
- priv->gdrive = g_object_ref (gdrive);
- priv->block_path = g_drive_get_identifier (priv->gdrive, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
+ priv->block_path = g_strdup (device);
- BRASERO_MEDIA_LOG ("Initializing drive %s", priv->block_path);
+ BRASERO_MEDIA_LOG ("Initializing drive %s from device", priv->block_path);
/* NOTE: why a thread? Because in case of a damaged medium, brasero can
* block on some functions until timeout and if we do this in the main
@@ -1077,25 +1113,44 @@ brasero_drive_set_property (GObject *object,
case PROP_UDI:
break;
case PROP_GDRIVE:
+ if (!priv->block_path)
+ break;
+
gdrive = g_value_get_object (value);
- if (!gdrive) {
- priv->medium = g_object_new (BRASERO_TYPE_VOLUME,
- "drive", object,
- NULL);
- priv->probed = TRUE;
- }
- else if (priv->gdrive) {
+ if (priv->gdrive) {
g_signal_handlers_disconnect_by_func (priv->gdrive,
brasero_drive_medium_gdrive_changed_cb,
object);
g_object_unref (priv->gdrive);
+ }
+
+ BRASERO_MEDIA_LOG ("Setting GDrive %p", gdrive);
+ if (gdrive) {
priv->gdrive = g_object_ref (gdrive);
- brasero_drive_init_gdrive (BRASERO_DRIVE (object));
+ brasero_drive_update_gdrive (BRASERO_DRIVE (object));
}
- else
- brasero_drive_init_real (BRASERO_DRIVE (object), gdrive);
+ else if (!priv->medium) {
+ priv->probed = FALSE;
+ priv->medium = g_object_new (BRASERO_TYPE_VOLUME,
+ "drive", object,
+ NULL);
+ g_signal_connect (priv->medium,
+ "probed",
+ G_CALLBACK (brasero_drive_medium_probed),
+ object);
+ }
+ break;
+ case PROP_DEVICE:
+ if (!g_value_get_string (value)) {
+ priv->medium = g_object_new (BRASERO_TYPE_VOLUME,
+ "drive", object,
+ NULL);
+ priv->probed = TRUE;
+ }
+ else
+ brasero_drive_init_real_device (BRASERO_DRIVE (object), g_value_get_string (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -1122,6 +1177,9 @@ brasero_drive_get_property (GObject *object,
case PROP_GDRIVE:
g_value_set_object (value, priv->gdrive);
break;
+ case PROP_DEVICE:
+ g_value_set_string (value, priv->block_path);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -1157,6 +1215,11 @@ brasero_drive_finalize (GObject *object)
priv->medium = NULL;
}
+ if (priv->name) {
+ g_free (priv->name);
+ priv->name = NULL;
+ }
+
if (priv->block_path) {
g_free (priv->block_path);
priv->block_path = NULL;
@@ -1245,5 +1308,12 @@ brasero_drive_class_init (BraseroDriveClass *klass)
"A GDrive object for the drive",
G_TYPE_DRIVE,
G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_DEVICE,
+ g_param_spec_string ("device",
+ "Device",
+ "Device path for the drive",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
}
diff --git a/libbrasero-media/brasero-medium-monitor.c b/libbrasero-media/brasero-medium-monitor.c
index 7c03819..c565d25 100644
--- a/libbrasero-media/brasero-medium-monitor.c
+++ b/libbrasero-media/brasero-medium-monitor.c
@@ -315,22 +315,18 @@ brasero_medium_monitor_medium_removed_cb (BraseroDrive *drive,
static gboolean
brasero_medium_monitor_is_drive (BraseroMediumMonitor *monitor,
- GDrive *gdrive)
+ const gchar *device)
{
BraseroMediumMonitorPrivate *priv;
BraseroDeviceHandle *handle;
BraseroScsiErrCode code;
gboolean result;
- gchar *device;
priv = BRASERO_MEDIUM_MONITOR_PRIVATE (monitor);
- device = g_drive_get_identifier (gdrive, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
BRASERO_MEDIA_LOG ("Testing drive %s", device);
handle = brasero_device_handle_open (device, FALSE, &code);
- g_free (device);
-
if (!handle)
return FALSE;
@@ -343,33 +339,25 @@ brasero_medium_monitor_is_drive (BraseroMediumMonitor *monitor,
}
static void
-brasero_medium_monitor_connected_cb (GVolumeMonitor *monitor,
- GDrive *gdrive,
- BraseroMediumMonitor *self)
+brasero_medium_monitor_device_added (BraseroMediumMonitor *self,
+ const gchar *device,
+ GDrive *gdrive)
{
BraseroMediumMonitorPrivate *priv;
BraseroDrive *drive = NULL;
- gchar *device;
priv = BRASERO_MEDIUM_MONITOR_PRIVATE (self);
- BRASERO_MEDIA_LOG ("Device addition signal");
-
- if (!brasero_medium_monitor_is_drive (self, gdrive))
- return;
-
/* See if the drive is waiting removal.
* This is necessary as GIO behaves strangely sometimes
* since it sends the "disconnected" signal when a medium
* is removed soon followed by a "connected" signal */
- device = g_drive_get_identifier (gdrive, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
drive = brasero_medium_monitor_get_drive (self, device);
- g_free (device);
-
- if (drive && g_slist_find (priv->waiting_removal, drive)) {
+ if (drive) {
+ /* Just in case that drive was waiting removal */
priv->waiting_removal = g_slist_remove (priv->waiting_removal, drive);
- BRASERO_MEDIA_LOG ("Added signal was emitted but the drive is already in the list. Updating GDrive associated object.");
+ BRASERO_MEDIA_LOG ("Added signal was emitted but the drive is in the removal list. Updating GDrive associated object.");
g_object_set (drive,
"gdrive", gdrive,
NULL);
@@ -378,15 +366,14 @@ brasero_medium_monitor_connected_cb (GVolumeMonitor *monitor,
return;
}
- if (drive) {
- BRASERO_MEDIA_LOG ("A drive was connected which has the same device path as an already registered one");
- g_object_unref (drive);
+ /* Make sure it's an optical drive */
+ if (!brasero_medium_monitor_is_drive (self, device))
return;
- }
BRASERO_MEDIA_LOG ("New drive added");
drive = g_object_new (BRASERO_TYPE_DRIVE,
+ "device", device,
"gdrive", gdrive,
NULL);
priv->drives = g_slist_prepend (priv->drives, drive);
@@ -415,6 +402,45 @@ brasero_medium_monitor_connected_cb (GVolumeMonitor *monitor,
brasero_drive_get_medium (drive));
}
+static void
+brasero_medium_monitor_connected_cb (GVolumeMonitor *monitor,
+ GDrive *gdrive,
+ BraseroMediumMonitor *self)
+{
+ gchar *device;
+
+ BRASERO_MEDIA_LOG ("GDrive addition signal");
+
+ device = g_drive_get_identifier (gdrive, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
+ brasero_medium_monitor_device_added (self, device, gdrive);
+ g_free (device);
+}
+
+static void
+brasero_medium_monitor_volume_added_cb (GVolumeMonitor *monitor,
+ GVolume *gvolume,
+ BraseroMediumMonitor *self)
+{
+ gchar *device;
+ GDrive *gdrive;
+
+ BRASERO_MEDIA_LOG ("GVolume addition signal");
+
+ /* No need to signal that addition if the GVolume
+ * object has an associated GDrive as this is just
+ * meant to trap blank discs which have no GDrive
+ * associated but a GVolume. */
+ gdrive = g_volume_get_drive (gvolume);
+ if (gdrive) {
+ g_object_unref (gdrive);
+ return;
+ }
+
+ device = g_volume_get_identifier (gvolume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
+ brasero_medium_monitor_device_added (self, device, NULL);
+ g_free (device);
+}
+
static gboolean
brasero_medium_monitor_disconnected_real (gpointer data)
{
@@ -462,49 +488,87 @@ brasero_medium_monitor_disconnected_real (gpointer data)
}
static void
-brasero_medium_monitor_disconnected_cb (GVolumeMonitor *monitor,
- GDrive *gdrive,
- BraseroMediumMonitor *self)
+brasero_medium_monitor_device_removed (BraseroMediumMonitor *self,
+ const gchar *device,
+ GDrive *gdrive)
{
BraseroMediumMonitorPrivate *priv;
- GSList *iter;
+ GDrive *associated_gdrive;
+ BraseroDrive *drive;
priv = BRASERO_MEDIUM_MONITOR_PRIVATE (self);
- BRASERO_MEDIA_LOG ("Device removal signal");
-
/* Make sure it's one already detected */
/* GIO behaves strangely: every time a medium
* is removed from a drive it emits the disconnected
* signal (which IMO it shouldn't) soon followed by
* a connected signal.
* So delay the removal by one or two seconds. */
- for (iter = priv->drives; iter; iter = iter->next) {
- GDrive *gdrive_iter;
- BraseroDrive *drive;
- drive = iter->data;
+ drive = brasero_medium_monitor_get_drive (self, device);
+ if (!drive)
+ return;
- gdrive_iter = brasero_drive_get_gdrive (drive);
- if (!gdrive_iter)
- continue;
+ if (G_UNLIKELY (g_slist_find (priv->waiting_removal, drive) != NULL)) {
+ g_object_unref (drive);
+ return;
+ }
- if (gdrive == gdrive_iter) {
- BRASERO_MEDIA_LOG ("Found device to remove");
+ associated_gdrive = brasero_drive_get_gdrive (drive);
+ if (associated_gdrive == gdrive) {
+ BRASERO_MEDIA_LOG ("Found device to remove");
+ priv->waiting_removal = g_slist_append (priv->waiting_removal, drive);
- g_object_unref (gdrive_iter);
+ if (!priv->waiting_removal_id)
+ priv->waiting_removal_id = g_timeout_add_seconds (2,
+ brasero_medium_monitor_disconnected_real,
+ self);
+ }
+ else if (associated_gdrive) {
+ /* do nothing and wait for a "drive-disconnected" signal */
+ g_object_unref (associated_gdrive);
+ }
- priv->waiting_removal = g_slist_append (priv->waiting_removal, drive);
+ g_object_unref (drive);
+}
- if (!priv->waiting_removal_id)
- priv->waiting_removal_id = g_timeout_add_seconds (2,
- brasero_medium_monitor_disconnected_real,
- self);
- return;
- }
+static void
+brasero_medium_monitor_volume_removed_cb (GVolumeMonitor *monitor,
+ GVolume *gvolume,
+ BraseroMediumMonitor *self)
+{
+ gchar *device;
+ GDrive *gdrive;
+
+ BRASERO_MEDIA_LOG ("Volume removal signal");
- g_object_unref (gdrive_iter);
+ /* No need to signal that removal if the GVolume
+ * object has an associated GDrive as this is just
+ * meant to trap blank discs which have no GDrive
+ * associated but a GVolume. */
+ gdrive = g_volume_get_drive (gvolume);
+ if (gdrive) {
+ g_object_unref (gdrive);
+ return;
}
+
+ device = g_volume_get_identifier (gvolume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
+ brasero_medium_monitor_device_removed (self, device, NULL);
+ g_free (device);
+}
+
+static void
+brasero_medium_monitor_disconnected_cb (GVolumeMonitor *monitor,
+ GDrive *gdrive,
+ BraseroMediumMonitor *self)
+{
+ gchar *device;
+
+ BRASERO_MEDIA_LOG ("Drive removal signal");
+
+ device = g_drive_get_identifier (gdrive, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
+ brasero_medium_monitor_device_removed (self, device, gdrive);
+ g_free (device);
}
static void
@@ -512,6 +576,7 @@ brasero_medium_monitor_init (BraseroMediumMonitor *object)
{
GList *iter;
GList *drives;
+ GList *volumes;
BraseroDrive *drive;
BraseroMediumMonitorPrivate *priv;
@@ -519,9 +584,6 @@ brasero_medium_monitor_init (BraseroMediumMonitor *object)
BRASERO_MEDIA_LOG ("Probing drives and media");
- /* This must done early on. GVolumeMonitor when it relies on HAL (like
- * us) must be able to update its list of volumes before us so it must
- * connect to HAL before us. */
priv->gmonitor = g_volume_monitor_get ();
drives = g_volume_monitor_get_connected_drives (priv->gmonitor);
@@ -529,12 +591,17 @@ brasero_medium_monitor_init (BraseroMediumMonitor *object)
for (iter = drives; iter; iter = iter->next) {
GDrive *gdrive;
+ gchar *device;
gdrive = iter->data;
- if (brasero_medium_monitor_is_drive (object, gdrive)) {
+
+ device = g_drive_get_identifier (gdrive, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
+ if (brasero_medium_monitor_is_drive (object, device)) {
drive = g_object_new (BRASERO_TYPE_DRIVE,
+ "device", device,
"gdrive", gdrive,
NULL);
+
priv->drives = g_slist_prepend (priv->drives, drive);
g_signal_connect (drive,
@@ -546,10 +613,59 @@ brasero_medium_monitor_init (BraseroMediumMonitor *object)
G_CALLBACK (brasero_medium_monitor_medium_removed_cb),
object);
}
+ g_free (device);
}
g_list_foreach (drives, (GFunc) g_object_unref, NULL);
g_list_free (drives);
+ volumes = g_volume_monitor_get_volumes (priv->gmonitor);
+ BRASERO_MEDIA_LOG ("Found %d volumes", g_list_length (volumes));
+
+ for (iter = volumes; iter; iter = iter->next) {
+ GVolume *gvolume;
+ gchar *device;
+
+ gvolume = iter->data;
+ device = g_volume_get_identifier (gvolume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
+
+ /* make sure it isn't already in our list */
+ drive = brasero_medium_monitor_get_drive (object, device);
+ if (drive) {
+ g_free (device);
+ g_object_unref (drive);
+ continue;
+ }
+
+ if (brasero_medium_monitor_is_drive (object, device)) {
+ drive = g_object_new (BRASERO_TYPE_DRIVE,
+ "device", device,
+ "gdrive", NULL,
+ NULL);
+ priv->drives = g_slist_prepend (priv->drives, drive);
+
+ g_signal_connect (drive,
+ "medium-added",
+ G_CALLBACK (brasero_medium_monitor_medium_added_cb),
+ object);
+ g_signal_connect (drive,
+ "medium-removed",
+ G_CALLBACK (brasero_medium_monitor_medium_removed_cb),
+ object);
+ }
+
+ g_free (device);
+ }
+ g_list_foreach (volumes, (GFunc) g_object_unref, NULL);
+ g_list_free (volumes);
+
+ g_signal_connect (priv->gmonitor,
+ "volume-added",
+ G_CALLBACK (brasero_medium_monitor_volume_added_cb),
+ object);
+ g_signal_connect (priv->gmonitor,
+ "volume-removed",
+ G_CALLBACK (brasero_medium_monitor_volume_removed_cb),
+ object);
g_signal_connect (priv->gmonitor,
"drive-connected",
G_CALLBACK (brasero_medium_monitor_connected_cb),
@@ -560,7 +676,9 @@ brasero_medium_monitor_init (BraseroMediumMonitor *object)
object);
/* add fake/file drive */
- drive = g_object_new (BRASERO_TYPE_DRIVE, "gdrive", NULL, NULL);
+ drive = g_object_new (BRASERO_TYPE_DRIVE,
+ "device", NULL,
+ NULL);
priv->drives = g_slist_prepend (priv->drives, drive);
return;
@@ -591,6 +709,12 @@ brasero_medium_monitor_finalize (GObject *object)
if (priv->gmonitor) {
g_signal_handlers_disconnect_by_func (priv->gmonitor,
+ brasero_medium_monitor_volume_added_cb,
+ object);
+ g_signal_handlers_disconnect_by_func (priv->gmonitor,
+ brasero_medium_monitor_volume_removed_cb,
+ object);
+ g_signal_handlers_disconnect_by_func (priv->gmonitor,
brasero_medium_monitor_connected_cb,
object);
g_signal_handlers_disconnect_by_func (priv->gmonitor,
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]