[hacktree] Write some code for importing



commit 7c1c61beb173f887ab83f94a6412e16f7e7710ec
Author: Colin Walters <walters verbum org>
Date:   Tue Oct 11 20:58:50 2011 -0400

    Write some code for importing

 Makefile-hacktree.am                               |   26 +-
 src/hacktree-repo.c                                |  123 -------
 src/libhacktree/hacktree-repo.c                    |  383 ++++++++++++++++++++
 src/{ => libhacktree}/hacktree-repo.h              |    5 +-
 src/{ => libhacktree}/hacktree-types.h             |    0
 src/{ => libhacktree}/hacktree.h                   |    0
 src/libhtutil/ht-gio-utils.c                       |   58 +++
 src/{hacktree-types.h => libhtutil/ht-gio-utils.h} |    6 +-
 src/libhtutil/ht-unix-utils.c                      |   88 +++++
 .../ht-unix-utils.h}                               |   18 +-
 src/{hacktree.h => libhtutil/htutil.h}             |    6 +-
 11 files changed, 572 insertions(+), 141 deletions(-)
---
diff --git a/Makefile-hacktree.am b/Makefile-hacktree.am
index d3f05f8..dcbc95b 100644
--- a/Makefile-hacktree.am
+++ b/Makefile-hacktree.am
@@ -1,12 +1,22 @@
+noinst_LTLIBRARIES += libhtutil.la
+
+libhtutil_la_SOURCES = \
+	src/libhtutil/ht-unix-utils.c \
+	src/libhtutil/ht-unix-utils.h \
+	src/libhtutil/htutil.h \
+	$(NULL)
+libhtutil_la_CFLAGS = -I$(srcdir)/src/libhtutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
+libhtutil_la_LIBADD = $(GIO_UNIX_LIBS)
+
 noinst_LTLIBRARIES += libhacktree.la
 
-libhacktree_la_SOURCES = src/hacktree.h \
-	src/hacktree-repo.c \
-	src/hacktree-repo.h \
-	src/hacktree-types.h \
+libhacktree_la_SOURCES = src/libhacktree/hacktree.h \
+	src/libhacktree/hacktree-repo.c \
+	src/libhacktree/hacktree-repo.h \
+	src/libhacktree/hacktree-types.h \
 	$(NULL)
-libhacktree_la_CFLAGS = -I$(srcdir)/src -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
-libhacktree_la_LIBADD = $(GIO_UNIX_LIBS)
+libhacktree_la_CFLAGS = -I$(srcdir)/src/libhacktree -I$(srcdir)/src/libhtutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
+libhacktree_la_LIBADD = libhtutil.la $(GIO_UNIX_LIBS)
 
 bin_PROGRAMS += hacktree
 
@@ -14,5 +24,5 @@ hacktree_SOURCES = src/main.c \
 	src/ht-builtins.h \
 	src/ht-builtin-init.c \
 	$(NULL)
-hacktree_CFLAGS = -I$(srcdir)/src -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
-hacktree_LDADD = libhacktree.la $(GIO_UNIX_LIBS)
+hacktree_CFLAGS = -I$(srcdir)/src -I$(srcdir)/src/libhacktree -I$(srcdir)/src/libhtutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
+hacktree_LDADD = libhtutil.la libhacktree.la $(GIO_UNIX_LIBS)
diff --git a/src/libhacktree/hacktree-repo.c b/src/libhacktree/hacktree-repo.c
new file mode 100644
index 0000000..c51ec5d
--- /dev/null
+++ b/src/libhacktree/hacktree-repo.c
@@ -0,0 +1,383 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters verbum org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters verbum org>
+ */
+
+#include "config.h"
+
+#include "hacktree-repo.h"
+#include "htutil.h"
+
+enum {
+  PROP_0,
+
+  PROP_PATH
+};
+
+G_DEFINE_TYPE (HacktreeRepo, hacktree_repo, G_TYPE_OBJECT)
+
+#define GET_PRIVATE(o) \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), HACKTREE_TYPE_REPO, HacktreeRepoPrivate))
+
+typedef struct _HacktreeRepoPrivate HacktreeRepoPrivate;
+
+struct _HacktreeRepoPrivate {
+  char *path;
+  char *objects_path;
+
+  gboolean inited;
+};
+
+static void
+hacktree_repo_finalize (GObject *object)
+{
+  G_OBJECT_CLASS (hacktree_repo_parent_class)->finalize (object);
+}
+
+static void
+hacktree_repo_set_property(GObject         *object,
+			   guint            prop_id,
+			   const GValue    *value,
+			   GParamSpec      *pspec)
+{
+  HacktreeRepo *self = HACKTREE_REPO (object);
+  HacktreeRepoPrivate *priv = GET_PRIVATE (self);
+
+  switch (prop_id)
+    {
+    case PROP_PATH:
+      priv->path = g_value_dup_string (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+hacktree_repo_get_property(GObject         *object,
+			   guint            prop_id,
+			   GValue          *value,
+			   GParamSpec      *pspec)
+{
+  HacktreeRepo *self = HACKTREE_REPO (object);
+  HacktreeRepoPrivate *priv = GET_PRIVATE (self);
+
+  switch (prop_id)
+    {
+    case PROP_PATH:
+      g_value_set_string (value, priv->path);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+hacktree_repo_class_init (HacktreeRepoClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (HacktreeRepoPrivate));
+
+  object_class->finalize = hacktree_repo_finalize;
+
+  g_object_class_install_property (object_class,
+                                   PROP_PATH,
+                                   g_param_spec_string ("path",
+                                                        "",
+                                                        "",
+                                                        NULL,
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+hacktree_repo_init (HacktreeRepo *self)
+{
+  HacktreeRepoPrivate *priv = GET_PRIVATE (self);
+  
+  g_assert (priv->path);
+
+  priv->objects_path = g_build_filename (priv->path, "objects", NULL);
+}
+
+HacktreeRepo*
+hacktree_repo_new (const char *path)
+{
+  return g_object_new (HACKTREE_TYPE_REPO, "path", path, NULL);
+}
+
+gboolean
+hacktree_repo_check (HacktreeRepo *self, GError **error)
+{
+  HacktreeRepoPrivate *priv = GET_PRIVATE (self);
+  
+  priv->inited = TRUE;
+  return TRUE;
+}
+
+char *
+stat_to_string (struct stat *stbuf)
+{
+  return g_strdup_printf ("%d:%d:%d:%" G_GUINT64_FORMAT ":%" G_GUINT64_FORMAT ":%" G_GUINT64_FORMAT,
+                          stbuf->st_mode,
+                          stbuf->st_uid,
+                          stbuf->st_gid,
+                          stbuf->st_atime,
+                          stbuf->st_mtime,
+                          stbuf->st_ctime);
+}
+
+static char *
+canonicalize_xattrs (char *xattr_string, size_t len)
+{
+  char *p;
+  GSList *xattrs = NULL;
+  GSList *iter;
+  GString *result;
+
+  result = g_string_new (0);
+
+  p = xattr_string;
+  while (p < xattr_string+len)
+    {
+      xattrs = g_slist_prepend (xattrs, p);
+      p += strlen (p) + 1;
+    }
+
+  xattrs = g_slist_sort (xattrs, (GCompareFunc) strcmp);
+  for (iter = xattrs; iter; iter = iter->next)
+    g_string_append (result, iter->data);
+
+  g_slist_free (xattrs);
+  return g_string_free (result, FALSE);
+}
+
+static gboolean
+stat_and_compute_checksum (int dirfd, const char *path,
+                           GChecksum **out_checksum,
+                           struct stat *out_stbuf,
+                           GError **error)
+{
+  GChecksum *content_sha256 = NULL;
+  GChecksum *content_and_meta_sha256 = NULL;
+  char *stat_string = NULL;
+  ssize_t bytes_read;
+  char *xattrs = NULL;
+  char *xattrs_canonicalized = NULL;
+  int fd = -1;
+  char *basename = NULL;
+  gboolean ret = FALSE;
+  char *symlink_target = NULL;
+  char *device_id = NULL;
+
+  if (fstat (fd, out_stbuf) < 0)
+    {
+      ht_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+
+  basename = g_path_get_basename (path);
+
+  stat_string = stat_to_string (out_stbuf);
+
+  /* FIXME - Add llistxattrat */
+  if (!S_ISLNK(out_stbuf->st_mode))
+    bytes_read = flistxattr (fd, NULL, 0);
+  else
+    bytes_read = llistxattr (path, NULL, 0);
+
+  if (bytes_read < 0)
+    {
+      if (errno != ENOTSUP)
+        {
+          ht_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+    }
+  if (errno != ENOTSUP)
+    {
+      gboolean tmp;
+      xattrs = g_malloc (bytes_read);
+      /* FIXME - Add llistxattrat */
+      if (!S_ISLNK(out_stbuf->st_mode))
+        tmp = flistxattr (fd, xattrs, bytes_read);
+      else
+        tmp = llistxattr (path, xattrs, bytes_read);
+          
+      if (!tmp)
+        {
+          ht_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+
+      xattrs_canonicalized = canonicalize_xattrs (xattrs, bytes_read);
+    }
+
+  content_sha256 = g_checksum_new (G_CHECKSUM_SHA256);
+ 
+  if (S_ISREG(out_stbuf->st_mode))
+    {
+      guint8 buf[8192];
+
+      fd = ht_util_open_file_read_at (dirfd, basename, error);
+      if (fd < 0)
+        {
+          ht_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+      while ((bytes_read = read (fd, buf, sizeof (buf))) > 0)
+        g_checksum_update (content_sha256, buf, bytes_read);
+      if (bytes_read < 0)
+        {
+          ht_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+    }
+  else if (S_ISLNK(out_stbuf->st_mode))
+    {
+      symlink_target = g_malloc (PATH_MAX);
+
+      if (readlinkat (dirfd, basename, symlink_target, PATH_MAX) < 0)
+        {
+          ht_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+      g_checksum_update (content_sha256, symlink_target, strlen (symlink_target));
+    }
+  else if (S_ISDEV(out_stbuf->st_mode))
+    {
+      device_id = g_strdup_printf ("%d", out_stbuf->st_rdev);
+      g_checksum_update (content_sha256, device_id, strlen (device_id));
+    }
+  else
+    {
+      g_set_error (error, G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+                   "Unsupported file '%s' (must be regular, symbolic link, or device)",
+                   path);
+      goto out;
+    }
+
+  content_and_meta_sha256 = g_checksum_copy (content_sha256);
+
+  g_checksum_update (content_and_meta_sha256, stat_string, strlen (stat_string));
+  g_checksum_update (content_and_meta_sha256, xattrs_canonicalized, strlen (stat_string));
+
+  *out_checksum = content_and_meta_sha256;
+ out:
+  if (fd >= 0)
+    close (fd);
+  g_free (symlink_target);
+  g_free (basename);
+  g_free (stat_string);
+  g_free (xattrs);
+  g_free (xattrs_canonicalized);
+  if (content_sha256)
+    g_checksum_free (content_sha256);
+
+  return ret;
+}
+
+static char *
+get_object_dir_for_checksum (HacktreeRepo *self,
+                             GChecksum    *checksum,
+                             GError      **error)
+{
+  HacktreeRepoPrivate *priv = GET_PRIVATE (self);
+  char *checksum_prefix;
+  char *path;
+  gboolean ret = FALSE;
+  GError *temp_error = NULL;
+
+  checksum_prefix = g_strdup (g_checksum_get_string (checksum));
+  checksum_prefix[3] = '\0';
+  path = g_build_filename (priv->objects_path, checksum_prefix, NULL);
+
+  if (!ht_util_ensure_directory (path, FALSE, error))
+    goto out;
+  
+  ret = TRUE;
+ out:
+  g_free (checksum_prefix);
+  if (ret)
+    return path;
+  g_free (path);
+  return NULL;
+}
+
+static gboolean
+import_one_file (HacktreeRepo *self, const char *path, GError **error)
+{
+  HacktreeRepoPrivate *priv = GET_PRIVATE (self);
+  char *basename = NULL;
+  char *dirname = NULL;
+  GChecksum *id = NULL;
+  DIR *dir = NULL;
+  gboolean ret = FALSE;
+  int fd;
+  struct stat stbuf;
+  char *dest_path = NULL;
+  char *checksum_prefix;
+
+  basename = g_path_get_dirname (path);
+  dirname = g_path_get_dirname (path);
+
+  dir = opendir (dirname);
+  if (dir == NULL)
+    {
+      ht_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+
+  if (!stat_and_compute_checksum (dirfd (dir), path, &id, &stbuf, error))
+    goto out;
+  dest_path = get_object_dir_for_checksum (self, id, error);
+  if (!dest_path)
+    goto out;
+  
+  if (linkat (dirfd, basename, -1, dest_path, 0) < 0)
+    {
+      ht_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+
+  ret = TRUE;
+ out:
+  if (id != NULL)
+    g_checksum_free (id);
+  if (dir != NULL)
+    closedir (dir);
+  g_free (basename);
+  g_free (dirname);
+  return ret;
+}
+
+gboolean
+hacktree_repo_import (HacktreeRepo *self,
+                      const char   *path,
+                      GError      **error)
+{
+  HacktreeRepoPrivate *priv = GET_PRIVATE (self);
+
+  g_return_val_if_fail (priv->inited, FALSE);
+
+  return import_one_file (self, path, error);
+}
diff --git a/src/hacktree-repo.h b/src/libhacktree/hacktree-repo.h
similarity index 90%
rename from src/hacktree-repo.h
rename to src/libhacktree/hacktree-repo.h
index 420dab4..2d7e59a 100644
--- a/src/hacktree-repo.h
+++ b/src/libhacktree/hacktree-repo.h
@@ -51,8 +51,11 @@ GType hacktree_repo_get_type (void);
 
 HacktreeRepo* hacktree_repo_new (const char *path);
 
+gboolean      hacktree_repo_check (HacktreeRepo  *repo, GError **error);
+
 gboolean      hacktree_repo_import (HacktreeRepo *repo,
-                                    const char   *path);
+                                    const char   *path,
+                                    GError      **error);
 
 G_END_DECLS
 
diff --git a/src/hacktree-types.h b/src/libhacktree/hacktree-types.h
similarity index 100%
copy from src/hacktree-types.h
copy to src/libhacktree/hacktree-types.h
diff --git a/src/hacktree.h b/src/libhacktree/hacktree.h
similarity index 100%
copy from src/hacktree.h
copy to src/libhacktree/hacktree.h
diff --git a/src/libhtutil/ht-gio-utils.c b/src/libhtutil/ht-gio-utils.c
new file mode 100644
index 0000000..29f193c
--- /dev/null
+++ b/src/libhtutil/ht-gio-utils.c
@@ -0,0 +1,58 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters verbum org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters verbum org>
+ */
+
+#include "config.h"
+
+#include <glib-unix.h>
+#include <gio/gio.h>
+
+#include <string.h>
+
+#include "ht-gio-utils.h"
+
+gboolean
+ht_util_ensure_directory (const char *path, gboolean with_parents, GError **error)
+{
+  GFile *dir;
+  GError *temp_error = NULL;
+  gboolean ret = FALSE;
+
+  dir = g_file_new_for_path (path);
+  if (with_parents)
+    ret = g_file_make_directory_with_parents (dir, NULL, &temp_error);
+  else
+    ret = g_file_make_directory (dir, NULL, &temp_error);
+  if (!ret)
+    {
+      if (!g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+        {
+          g_propagate_error (error, temp_error);
+          goto out;
+        }
+      else
+        g_clear_error (&temp_error);
+    }
+
+  ret = TRUE;
+ out:
+  g_clear_object (&dir);
+  return ret;
+}
diff --git a/src/hacktree-types.h b/src/libhtutil/ht-gio-utils.h
similarity index 85%
copy from src/hacktree-types.h
copy to src/libhtutil/ht-gio-utils.h
index 0824ad4..5b0c65c 100644
--- a/src/hacktree-types.h
+++ b/src/libhtutil/ht-gio-utils.h
@@ -19,14 +19,14 @@
  * Author: Colin Walters <walters verbum org>
  */
 
-#ifndef __HACKTREE_TYPES_H__
-#define __HACKTREE_TYPES_H__
+#ifndef __HACKTREE_UNIX_UTILS_H__
+#define __HACKTREE_UNIX_UTILS_H__
 
 #include <gio/gio.h>
 
 G_BEGIN_DECLS
 
-#define HACKTREE_REPO_DIR ".ht"
+gboolean ht_util_ensure_directory (const char *path, gboolean with_parents, GError **error);
 
 G_END_DECLS
 
diff --git a/src/libhtutil/ht-unix-utils.c b/src/libhtutil/ht-unix-utils.c
new file mode 100644
index 0000000..f0aebb2
--- /dev/null
+++ b/src/libhtutil/ht-unix-utils.c
@@ -0,0 +1,88 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters verbum org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters verbum org>
+ */
+
+#include "config.h"
+
+#include "ht-unix-utils.h"
+
+#include <glib-unix.h>
+#include <gio/gio.h>
+
+#include <string.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+
+void
+ht_util_set_error_from_errno (GError **error,
+                              gint     saved_errno)
+{
+  g_set_error_literal (error,
+                       G_UNIX_ERROR,
+                       0,
+                       g_strerror (saved_errno));
+  errno = saved_errno;
+}
+
+int
+ht_util_open_file_read (const char *path, GError **error)
+{
+  char *dirname = NULL;
+  char *basename = NULL;
+  DIR *dir = NULL;
+  int fd = -1;
+
+  dirname = g_path_get_dirname (path);
+  basename = g_path_get_basename (path);
+  dir = opendir (dirname);
+  if (dir == NULL)
+    {
+      ht_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+
+  fd = ht_util_open_file_read_at (dirfd (dir), basename, error);
+
+ out:
+  g_free (basename);
+  g_free (dirname);
+  if (dir != NULL)
+    closedir (dir);
+  return fd;
+}
+
+int
+ht_util_open_file_read_at (int dirfd, const char *name, GError **error)
+{
+  int fd;
+  int flags = O_RDONLY;
+  
+#ifdef O_CLOEXEC
+  flags |= O_CLOEXEC;
+#endif
+#ifdef O_NOATIME
+  flags |= O_NOATIME;
+#endif
+  fd = openat (dirfd, name, flags);
+  if (fd < 0)
+    ht_util_set_error_from_errno (error, errno);
+  return fd;
+}
diff --git a/src/hacktree-types.h b/src/libhtutil/ht-unix-utils.h
similarity index 67%
rename from src/hacktree-types.h
rename to src/libhtutil/ht-unix-utils.h
index 0824ad4..ae3731b 100644
--- a/src/hacktree-types.h
+++ b/src/libhtutil/ht-unix-utils.h
@@ -19,14 +19,26 @@
  * Author: Colin Walters <walters verbum org>
  */
 
-#ifndef __HACKTREE_TYPES_H__
-#define __HACKTREE_TYPES_H__
+#ifndef __HACKTREE_UNIX_UTILS_H__
+#define __HACKTREE_UNIX_UTILS_H__
 
 #include <gio/gio.h>
+#include <glib-unix.h>
+
+/* I just put all this shit here. Sue me. */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <string.h>
 
 G_BEGIN_DECLS
 
-#define HACKTREE_REPO_DIR ".ht"
+int ht_util_open_file_read (const char *path, GError **error);
+
+int ht_util_open_file_read_at (int dirfd, const char *name, GError **error);
+
+void ht_util_set_error_from_errno (GError **error, gint saved_errno);
 
 G_END_DECLS
 
diff --git a/src/hacktree.h b/src/libhtutil/htutil.h
similarity index 91%
rename from src/hacktree.h
rename to src/libhtutil/htutil.h
index 11f0a89..4434b38 100644
--- a/src/hacktree.h
+++ b/src/libhtutil/htutil.h
@@ -19,9 +19,9 @@
  * Author: Colin Walters <walters verbum org>
  */
 
-#ifndef __HACKTREE_H__
+#ifndef __HACKTREE_UTIL_H__
 
-#include <hacktree-repo.h>
-#include <hacktree-types.h>
+#include <ht-unix-utils.h>
+#include <ht-gio-utils.h>
 
 #endif



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