diff --git a/COPYRIGHT b/COPYRIGHT
--- a/COPYRIGHT
+++ b/COPYRIGHT
@@ -1,4 +1,4 @@
-Copyright (C) 2006 John Goerzen <jgoerzen@complete.org>
+Copyright (C) 2006-2008 John Goerzen <jgoerzen@complete.org>
 
 All code, documentation, and build scripts are under the following
 license unless otherwise noted:
diff --git a/Commands/Download.hs b/Commands/Download.hs
--- a/Commands/Download.hs
+++ b/Commands/Download.hs
@@ -1,5 +1,5 @@
 {- hpodder component
-Copyright (C) 2006-2007 John Goerzen <jgoerzen@complete.org>
+Copyright (C) 2006-2008 John Goerzen <jgoerzen@complete.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
@@ -30,13 +30,16 @@
 import Control.Monad hiding(forM_)
 import Utils
 import Data.Hash.MD5
+import Data.Maybe(fromJust)
 import System.FilePath
 import System.IO
 import System.Directory
 import System.Cmd.Utils
 import System.Posix.Process
+import System.Process
+import System.Environment(getEnvironment)
 import Data.ConfigFile
-import Data.String
+import Data.String.Utils
 import Data.Either.Utils
 import Data.List
 import System.Exit
@@ -194,38 +197,93 @@
        let newfn = (strip $ forceEither $ cfg "downloaddir") ++ "/" ++
                    (strip $ forceEither $ cfg "namingpatt")
        createDirectoryIfMissing True (fst . splitFileName $ newfn)
-       finalfn <- if ((eptype ep) `elem` ["audio/mpeg", "audio/mp3", 
-                                          "x-audio/mp3"]) && 
-                     not (isSuffixOf ".mp3" newfn)
-                  then movefile tmpfp (newfn ++ ".mp3")
-                  else movefile tmpfp newfn
-       when (isSuffixOf ".mp3" finalfn) $ 
-            do d "   Setting ID3 tags..."
-               --posixRawSystem "id3v2" ["-C", finalfn] >>= id3result
-               --posixRawSystem "id3v2" ["-s", finalfn] >>= id3result
-               posixRawSystem "id3v2" ["-A", castname . podcast $ ep,
-                                       "-t", eptitle ep,
-                                       "--WOAF", epurl ep,
-                                       "--WOAS", feedurl . podcast $ ep,
-                                        -- "--WXXX", feedurl . podcast $ ep,
-                                       finalfn] >>= id3result
-       cp <- getCP ep idstr fnpart
-       let cfg = get cp (show . castid . podcast $ ep)
+       let renameTypes = getRenameTypes 
+       
+       realType <- (mkEnviron ep tmpfp) >>= (getRealType ep)
+       let newep = ep {eptype = realType}
+       finalfn <- case lookup (eptype newep) renameTypes of
+                    Nothing -> movefile tmpfp newfn
+                    Just suffix -> 
+                        if not (isSuffixOf suffix newfn)
+                           then movefile tmpfp (newfn ++ suffix)
+                           else movefile tmpfp newfn
+
+       environ <- mkEnviron newep finalfn
+       let postProcTypes = fromJust $ getList (gcp gi) idstr "postproctypes"
+       let postProcCommand = forceEither $ get (gcp gi) idstr "postproccommand" >>=
+                          (return . strip)
+       
+       when (postProcCommand /= "" &&
+             (postProcTypes == ["ALL"] ||
+             (eptype newep) `elem` postProcTypes)) $
+            do let postProcCommand = forceEither $ get (gcp gi) idstr "postproccommand"
+               d $ "   Running postprocess command " ++ postProcCommand
+               runSimpleCmd environ postProcCommand
+
+       cp <- getCP newep idstr fnpart
+       let cfg = get cp (show . castid . podcast $ newep)
        forM_ (either (const Nothing) Just $ cfg "posthook")
              (runHook finalfn)
        curtime <- now
        updateEpisode (gdbh gi) $ 
-           updateAttempt curtime $ (ep {epstatus = Downloaded})
+           updateAttempt curtime $ (newep {epstatus = Downloaded})
        commit (gdbh gi)
        
     where idstr = show . castid . podcast $ ep
+          runSimpleCmd environ cmd =
+              do ph <- runProcess "/bin/sh" ["-c", cmd] Nothing (Just environ)
+                       Nothing Nothing Nothing
+                 ec <- waitForProcess ph
+                 d $ "  command exited with: " ++ show ec
+
           fnpart = snd . splitFileName $ epurl ep
-          id3result res = 
-               case res of
-                 Exited (ExitSuccess) -> d $ "   id3v2 was successful."
-                 Exited (ExitFailure y) -> w $ "\n   id3v2 returned: " ++ show y
-                 Terminated y -> w $ "\n   id3v2 terminated by signal " ++ show y
-                 _ -> fail "Stopped unexpected"
+          -- Given an episode and an environment, call the external
+          -- command that determines the MIME type of that episode.
+          -- If the command returns the empty string or exits with
+          -- an error, just return (eptype ep) back to the caller.
+          getRealType ep environ =
+              do let typecmd = forceEither $ get (gcp gi) idstr "gettypecommand"
+                 d $ "  Running gettypecommand " ++ typecmd
+                 d $ "  Enrivonment for this command is " ++ show environ
+                 (stdinh, stdouth, stderrh, ph) <-
+                     runInteractiveProcess "/bin/sh" ["-c", typecmd]
+                         Nothing (Just environ)
+                 hClose stdinh
+                 forkIO $ do c <- hGetContents stderrh
+                             hPutStr stderr c
+
+                 c <- hGetLine stdouth
+                 hClose stdouth
+                 ec <- waitForProcess ph
+                 d $ "  gettypecommand exited with: " ++ show ec
+                 d $ "  gettypecommand sent to stdout: " ++ show c
+                 d $ "  original type was: " ++ show (eptype ep)
+                 case ec of
+                   ExitSuccess -> case (strip c) of
+                                    "" -> return (eptype ep)
+                                    x -> return x
+                   _ -> return (eptype ep)
+
+          getRenameTypes =
+              case getList (gcp gi) idstr "renametypes" of
+                Just x -> map procpair (map (span (/= ':')) x)
+                Nothing -> []
+          procpair (t, []) = (t, [])
+          procpair (t, ':':x) = (t, x)
+          procpair (t, x) = error $ "Invalid pair in renametypes: " ++ 
+                            show (t, x)
+          
+          mkEnviron ep fn =
+              do oldenviron <- getEnvironment
+                 return $ newenviron ++ oldenviron
+              where newenviron =
+                        [("CASTID", show . castid . podcast $ ep),
+                         ("CASTTITLE", castname . podcast $ ep),
+                         ("EPFILENAME", fn),
+                         ("EPURL", epurl ep),
+                         ("FEEDURL", feedurl . podcast $ ep),
+                         ("SAFECASTTITLE", sanitize_fn . castname . podcast $ ep),
+                         ("SAFEEPTITLE", sanitize_fn . eptitle $ ep)]
 
 -- | Runs a hook script.
 runHook :: String -- ^ The name of the file to pass as an argument to the script.
diff --git a/Commands/ImportIpodder.hs b/Commands/ImportIpodder.hs
--- a/Commands/ImportIpodder.hs
+++ b/Commands/ImportIpodder.hs
@@ -33,7 +33,7 @@
 import qualified Commands.Update
 import System.FilePath
 import Data.List
-import Data.String
+import Data.String.Utils
 import System.Directory
 import Control.Exception
 
diff --git a/Commands/SetStatus.hs b/Commands/SetStatus.hs
--- a/Commands/SetStatus.hs
+++ b/Commands/SetStatus.hs
@@ -28,7 +28,7 @@
 import Database.HDBC
 import Control.Monad hiding (join)
 import Utils
-import Data.String
+import Data.String.Utils
 import System.IO
 import System.Console.GetOpt
 import System.Console.GetOpt.Utils
diff --git a/Commands/Setup.hs b/Commands/Setup.hs
--- a/Commands/Setup.hs
+++ b/Commands/Setup.hs
@@ -35,7 +35,7 @@
 import Data.ConfigFile
 import System.IO
 import Data.Either.Utils
-import Data.String
+import Data.String.Utils
 
 i = infoM "setup"
 w = warningM "setup"
diff --git a/Commands/Update.hs b/Commands/Update.hs
--- a/Commands/Update.hs
+++ b/Commands/Update.hs
@@ -31,7 +31,7 @@
 import Database.HDBC
 import Control.Monad
 import Utils
-import Data.String
+import Data.String.Utils
 import System.Exit
 import System.Posix.Process
 import System.Directory
diff --git a/Config.hs b/Config.hs
--- a/Config.hs
+++ b/Config.hs
@@ -1,5 +1,5 @@
 {- hpodder component
-Copyright (C) 2006-2007 John Goerzen <jgoerzen@complete.org>
+Copyright (C) 2006-2008 John Goerzen <jgoerzen@complete.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
@@ -18,7 +18,7 @@
 
 {- |
    Module     : Config
-   Copyright  : Copyright (C) 2006-2007 John Goerzen
+   Copyright  : Copyright (C) 2006-2008 John Goerzen
    License    : GNU GPL, version 2 or above
 
    Maintainer : John Goerzen <jgoerzen@complete.org>
@@ -35,6 +35,7 @@
 import Control.Monad
 import Data.Either.Utils
 import System.Path
+import Data.String.Utils(strip, split)
 
 getFeedTmp =
     do appdir <- getAppDir
@@ -70,6 +71,10 @@
                  cp <- set cp "DEFAULT" "podcastfailattempts" "15"
                  cp <- set cp "DEFAULT" "epfaildays" "21"
                  cp <- set cp "DEFAULT" "epfailattempts" "15"
+                 cp <- set cp "DEFAULT" "renametypes" "audio/mpeg:.mp3,audio/mp3:.mp3,x-audio/mp3:.mp3"
+                 cp <- set cp "DEFAULT" "postproctypes" "audio/mpeg,audio/mp3,x-audio/mp3"
+                 cp <- set cp "DEFAULT" "gettypecommand" "file -b -i \"${EPFILENAME}\""
+                 cp <- set cp "DEFAULT" "postproccommand" "id3v2 -A \"${CASTTITLE}\" -t \"${EPTITLE}\" --WOAF \"${EPURL}\" --WOAS \"${FEDDURL}\" \"${EPFILENAME}\""
                  return cp
 
 startingcp = emptyCP {accessfunc = interpolatingAccess 10}
@@ -102,3 +107,11 @@
 getProgressInterval =
     do cp <- loadCP
        return $ read . forceEither $ get cp "general" "progressinterval"
+
+getList :: ConfigParser -> String -> String -> Maybe [String]
+getList cp sect key = 
+       case get cp sect key of
+         Right x -> Just (splitit x)
+         Left _ -> Nothing
+    where splitit x = filter (/= "") . map strip . split "," $ x
+  
diff --git a/DB.hs b/DB.hs
--- a/DB.hs
+++ b/DB.hs
@@ -1,5 +1,5 @@
 {- hpodder component
-Copyright (C) 2006-2007 John Goerzen <jgoerzen@complete.org>
+Copyright (C) 2006-2008 John Goerzen <jgoerzen@complete.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
@@ -18,7 +18,7 @@
 
 {- |
    Module     : DB
-   Copyright  : Copyright (C) 2006-2007 John Goerzen
+   Copyright  : Copyright (C) 2006-2008 John Goerzen
    License    : GNU GPL, version 2 or above
 
    Maintainer : John Goerzen <jgoerzen@complete.org>
@@ -69,8 +69,39 @@
                commit dbh
                return 0
 
-upgradeSchema dbh 4 _ = return ()
+upgradeSchema dbh 5 _ = return ()
 
+upgradeSchema dbh 4 tables =
+    do dbdebug "Upgrading schema 4 -> 5"
+       dbdebug "Recreating episodes table to add epguid column and UNIQUE constaint"
+       -- Silly sqlite can't add a UNIQUE constaint to an existing table, so we
+       -- have to recreate it.
+       run dbh "CREATE TABLE episodes5 (\
+               \castid INTEGER NOT NULL,\
+               \episodeid INTEGER NOT NULL,\
+               \title TEXT NOT NULL,\
+               \epurl TEXT NOT NULL,\
+               \enctype TEXT NOT NULL,\
+               \status TEXT NOT NULL,\
+               \eplength INTEGER NOT NULL DEFAULT 0,\
+               \epfirstattempt INTEGER,\
+               \eplastattempt INTEGER,\
+               \epfailedattempts INTEGER NOT NULL DEFAULT 0,\
+               \epguid TEXT,\
+               \UNIQUE(castid, epurl),\
+               \UNIQUE(castid, episodeid),\
+               \UNIQUE(castid, epguid))" []
+       dbdebug "Copying data from old episodes table"
+       run dbh "INSERT INTO episodes5 SELECT *, NULL FROM episodes" []
+       dbdebug "Dropping old episodes table"
+       run dbh "DROP TABLE episodes" []
+       dbdebug "Renaming new episodes table"
+       run dbh "ALTER TABLE episodes5 RENAME TO episodes" []
+
+       setSchemaVer dbh 5
+       commit dbh
+       upgradeSchema dbh 5 tables
+
 upgradeSchema dbh 3 tables =
     do dbdebug "Upgrading schema 3 -> 4"
        dbdebug "Adding lastattempt column"
@@ -87,6 +118,7 @@
 
        setSchemaVer dbh 4
        commit dbh
+       upgradeSchema dbh 4 tables
 
 upgradeSchema dbh 2 tables =
     do dbdebug "Upgrading schema 2 -> 3"
@@ -202,12 +234,12 @@
 getEpisodes dbh pc =
     do r <- quickQuery dbh "SELECT episodeid, title, epurl, enctype,\
                             \status, eplength, epfirstattempt, eplastattempt, \
-                            \epfailedattempts FROM episodes \
+                            \epfailedattempts, epguid FROM episodes \
                             \WHERE castid = ? ORDER BY \
                             \episodeid" [toSql (castid pc)]
        return $ map toItem r
     where toItem [sepid, stitle, sepurl, senctype, sstatus, slength,
-                  slu, sla, sfa] =
+                  slu, sla, sfa, sepguid] =
               Episode {podcast = pc, epid = fromSql sepid,
                        eptitle = fromSql stitle,
                        epurl = fromSql sepurl, eptype = fromSql senctype,
@@ -215,7 +247,8 @@
                        eplength = fromSql slength,
                        epfirstattempt = fromSql slu,
                        eplastattempt = fromSql sla,
-                       epfailedattempts = fromSql sfa}
+                       epfailedattempts = fromSql sfa,
+                       epguid = fromSql sepguid}
           toItem x = error $ "Unexpected result in getEpisodes: " ++ show x
 
 podcast_convrow [svid, svname, svurl, isenabled, lupdate, lattempt,
@@ -225,11 +258,17 @@
              lastupdate = fromSql lupdate, lastattempt = fromSql lattempt,
              failedattempts = fromSql fattempts}
 
-{- | Add a new episode.  If the episode already exists, ignore the add request
-and preserve the existing record. -}
+{- | Add a new episode. If the episode already exists, update the URL, GUID
+and title fields while preserving other fields as they are. Returns the number
+of inserted rows. -}
 addEpisode :: Connection -> Episode -> IO Integer
 addEpisode dbh ep = 
-    do nextepid <- getepid
+    do run dbh "UPDATE episodes SET epurl = ?, epguid = ?, title = ? WHERE castid = ? AND (epurl = ? OR epguid = ?)"
+           [toSql (epurl ep), toSql (epguid ep), toSql (eptitle ep),
+            toSql (castid (podcast ep)), toSql (epurl ep), toSql (epguid ep)]
+       -- if the UPDATE was successful, that means that something with the same
+       -- URL or GUID already exists, so the INSERT below will be ignored.
+       nextepid <- getepid
        insertEpisode "INSERT OR IGNORE" dbh ep nextepid
     where getepid = 
               do r <- quickQuery dbh "SELECT MAX(episodeid) FROM episodes WHERE castid = ?" [toSql (castid (podcast ep))]
@@ -245,12 +284,13 @@
 insertEpisode insertsql dbh episode newepid =
     run dbh (insertsql ++ " INTO episodes (castid, episodeid, title,\
            \epurl, enctype, status, eplength, epfirstattempt, eplastattempt,\
-           \epfailedattempts) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
+           \epfailedattempts, epguid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
            [toSql (castid (podcast episode)), toSql newepid, 
             toSql (eptitle episode), toSql (epurl episode), 
             toSql (eptype episode), toSql (show (epstatus episode)),
             toSql (eplength episode), toSql (epfirstattempt episode),
-            toSql (eplastattempt episode), toSql (epfailedattempts episode)]
+            toSql (eplastattempt episode), toSql (epfailedattempts episode),
+            toSql (epguid episode)]
 
 getSelectedPodcasts dbh [] = getSelectedPodcasts dbh ["all"]
 getSelectedPodcasts dbh ["all"] = getPodcasts dbh
diff --git a/FeedParser.hs b/FeedParser.hs
--- a/FeedParser.hs
+++ b/FeedParser.hs
@@ -40,6 +40,7 @@
 import Data.List
 
 data Item = Item {itemtitle :: String,
+                  itemguid :: Maybe String,
                   enclosureurl :: String,
                   enclosuretype :: String,
                   enclosurelength :: String
@@ -54,6 +55,7 @@
     Episode {podcast = pc, epid = 0,
              eptitle = sanitize_basic (itemtitle item), 
              epurl = sanitize_basic (enclosureurl item),
+             epguid = fmap sanitize_basic (itemguid item),
              eptype = sanitize_basic (enclosuretype item), epstatus = Pending,
              eplength = case reads . sanitize_basic . enclosurelength $ item of
                           [] -> 0
@@ -85,13 +87,17 @@
 
 getEnclosures doc =
     concat . map procitem $ item doc
-    where procitem i = map (procenclosure title) enclosure
+    where procitem i = map (procenclosure title guid) enclosure
               where title = case strofm "title" [i] of
                               Left x -> "Untitled"
                               Right x -> x
+                    guid = case strofm "guid" [i] of
+                              Left _ -> Nothing
+                              Right x -> Just x
                     enclosure = tag "enclosure" `o` children $ i
-          procenclosure title e =
+          procenclosure title guid e =
               Item {itemtitle = title,
+                    itemguid = guid,
                     enclosureurl = head0 $ forceMaybe $ stratt "url" e,
                     enclosuretype = head0 $ case stratt "type" e of
                                               Nothing -> ["application/octet-stream"]
diff --git a/INSTALL b/INSTALL
new file mode 100644
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,18 @@
+
+Sorry, I need to work on this some more...
+
+PREREQUISITES:
+
+ * GHC 6.6          http://www.haskell.org/ghc
+ * Curl             http://curl.haxx.se
+ * id3v2            http://download.sourceforge.net/id3v2/id3v2-0.1.11.tar.gz
+ * hslogger         http://software.complete.org/hslogger
+ * MissingH 0.18.0  http://software.complete.org/missingh
+   hpodder also uses these libraries that MissingH also depends upon:
+   FilePath, mtl
+ * ConfigFile       http://software.complete.org/configfile
+ * Sqlite3          http://www.sqlite.org/
+ * HDBC-sqlite3     http://quux.org/devel/hdbc
+ * HaXml>=1.13.2    http://www.cs.york.ac.uk/fp/HaXml/
+ * HDBC             http://quux.org/devel/hdbc
+
diff --git a/Types.hs b/Types.hs
--- a/Types.hs
+++ b/Types.hs
@@ -60,6 +60,7 @@
                         epid :: Integer,
                         eptitle :: String,
                         epurl :: String,
+                        epguid :: Maybe String,
                         eptype :: String,
                         epstatus :: EpisodeStatus,
                         eplength :: Integer,
diff --git a/doc/SConstruct b/doc/SConstruct
new file mode 100644
--- /dev/null
+++ b/doc/SConstruct
@@ -0,0 +1,30 @@
+# Copyright (C) 2004-2006 John Goerzen <jgoerzen@complete.org>
+# <jgoerzen@complete.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
+
+env = Environment()
+env.Export("env")
+env.Append(MASTERBASE = 'hpodder', TOPNODE = 'hpodder', DOINDEX = 1,
+        PDFARGS = '-s local=printlocal')
+
+execfile('sgml-common/SConstruct')
+
+manpages = []
+for manpage in ['hpodder']:
+    manpages.append(env.MAN('%s.1' % manpage, 'man.%s.sgml' % manpage))
+env.Alias('manpages', manpages)
+
+
diff --git a/doc/hpodder-manpage.sgml b/doc/hpodder-manpage.sgml
new file mode 100644
--- /dev/null
+++ b/doc/hpodder-manpage.sgml
@@ -0,0 +1,1238 @@
+  <refentry id="hpodder.man">
+  <indexterm><primary>hpodder</><secondary>Reference For</></>
+  <refentryinfo>
+    <address><email>jgoerzen@complete.org</email></address>
+    <author><firstname>John</firstname><surname>Goerzen</surname></author>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>hpodder</refentrytitle>
+    <manvolnum>1</manvolnum>
+    <refmiscinfo>John Goerzen</refmiscinfo>
+  </refmeta>
+
+  <refnamediv>
+    <refname>hpodder</refname>
+    <refpurpose>Scan and download podcasts</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>hpodder</command>
+      <arg choice="opt">-d</arg>
+      <arg choice="opt"><replaceable>command</replaceable></arg>
+      <arg choice="opt"><replaceable>command_args</replaceable></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1 id="hpodder.description">
+    <title>Description</title>
+    <indexterm><primary>Podcasts</primary></indexterm>
+    <indexterm><primary>Podcatcher</primary></indexterm>
+    <para>
+      Podcasting
+      is a method of publishing radio-like programs on the
+      Internet.  Through podcasting, almost anyone can produce their
+      own audio program, and publish episodes of it as often or as
+      rarely as they like.
+    </para>
+
+    <para>
+      To listen to podcasts, you need a program to download the
+      podcast's episodes from the Internet.  Such a program is
+      called a podcatcher (or sometimes a podcast aggregator).
+      &hpodder; is this program.
+    </para>
+
+    <para>
+      If you'd like to get going RIGHT NOW, skip on down to the
+      Quick Start section.  Otherwise, let's take a look at the
+      features of &hpodder;.
+    </para>
+
+    <refsect2 id="hpodder.features">
+      <title>Feature List</title>
+      <itemizedlist>
+        <listitem><para>Convenient, easy to learn, and fast command-line
+            interface (it's simple to do simple things, and
+            advanced things are possible)</para>
+        </listitem>
+
+        <listitem><para>Automatic discovery of feed metadata such as title</para>
+        </listitem>
+
+        <listitem><para>Full history database for accurate
+            prevention of duplicate downloads and tracking of new episodes</para>
+        </listitem>
+
+        <listitem><para>Conversion tools to convert your existing
+            feed list and history from other applications to
+            &hpodder;.  Supported applications and formats include:
+            castpodder and ipodder.
+          </para>
+        </listitem>
+
+        <listitem><para>Most operations can work fully automatically
+            across your entire podcast database, or they can work
+            manually as well.</para>
+        </listitem>
+
+        <listitem><para>Automatic updating of ID3 (v1 and v2) tags
+            based on metadata in the podcast itself.  This important
+            feature is available through iTunes but is often missed by
+            other podcatchers.</para>
+        </listitem>
+
+        <listitem><para>&hpodder; operations can be easily scripted
+            or scheduled using regular operating system tools.</para>
+        </listitem>
+
+        <listitem><para>Fully customizable naming scheme for
+            downloaded episodes, including a name collision detection
+            and workaround algorithm.</para>
+        </listitem>
+
+        <listitem><para>Automatic support for appending .mp3
+            extensions to MP3 files that lack it.</para>
+        </listitem>
+
+        <listitem><para>Numerous database and history inquiry tools</para>
+        </listitem>
+
+        <listitem><para>Small, minimalist footprint</para>
+        </listitem>
+
+        <listitem><para>Power users and developers can interact
+            directly with the embedded Sqlite3 database used by
+            &hpodder;.  The database has a simple schema that is
+            developer-friendly.</para>
+        </listitem>
+
+        <listitem><para>Support for resuming interrupted downloads
+            of podcasts</para>
+        </listitem>
+
+        <listitem><para>&hpodder; is SAFE and is designed with data
+            integrity in mind from the beginning.  It should be
+            exceedingly difficult to lose a podcast episode, even in the
+            event of a power failure.
+          </para>
+        </listitem>
+
+      </itemizedlist>
+    </refsect2>
+
+    <refsect2 id="hpodder.mo">
+      <title>Method of Operation</title>
+      <para>
+        The basic pattern of operation with &hpodder; is to set up
+        each podcast you want to receive.  Each day (or hour, or
+        whatever), &hpodder; will go out and update its database by
+        pulling in the latest episode lists from the podcast feed.
+        Then, &hpodder; will proceed to download any episodes that
+        you haven't already downloaded.  After each episode is
+        downloaded, &hpodder; will note that fact so it isn't ever
+        downloaded again.
+      </para>
+      <para>
+        Let's look at this in a bit more detail.
+      </para>
+      <para>
+        &hpodder; maintains two tables in a database.  One table
+        lists all the podcasts you know about, as well as where the
+        podcast's feed is to be downloaded from.  The feed is a file
+        that the podcast's author publishes.  It lists all the
+        current episodes of the podcast, and some information about
+        them.  Data is added to this table with the <command>hpodder
+          add</command> command.
+      </para>
+      <para>
+        The second table lists each episode for a given podcast,
+        along with the location from which the episode can be
+        downloaded and some other information about the episode
+        (such as its title).  Information in this table is added by
+        <command>hpodder update</command> and updated by
+        <command>hpodder download</command> or 
+        <command>hpodder catchup</command>.
+      </para>
+      <para>
+        When you first fire up &hpodder;, it will read its
+        configuration file from
+        <filename>~/.hpodder/hpodder.conf</filename>.
+        What happens next depends on the command.
+      </para>
+      <para>
+        For <command>hpodder update</command>, the program will read
+        information about all your podcasts.  It will download each
+        feed.  Once it has the feed, it will look at each episode
+        and compare them to the database.  If a given episode is
+        already in the database, it is ignored.  Any new episodes
+        are recorded in the database, and set to Pending so they
+        will be downloaded on the next download run.
+      </para>
+      <para>
+        For <command>hpodder download</command>, the program will
+        read information about all your episodes.  For each episode
+        marked Pending, the program will download the episode.  It
+        will then update the episode's ID3 tags based on the podcast
+        feed.  Finally, it will move the episode in-place
+        atomically.  Only after all that has been done will
+        &hpodder; mark the episode as Downloaded in the database.
+        In this way, no episode is visible to outside tools until it
+        is completely downloaded in its final form, so you can
+        safely play any visible program in your download directory
+        even as downloads are happening.
+      </para>
+    </refsect2>
+
+  </refsect1>
+
+  <refsect1 id="hpodder.quickstart">
+    <title>Quick Start</title>
+    <para>
+      This section will describe how a first-time &hpodder; user can
+      get up and running quickly.  It assumes you already have
+      &hpodder; compiled or installed on your system.  If not,
+      please follow the instructions in the
+      <filename>INSTALL</filename>
+      file in the
+      source distribution.
+    </para>
+    <para>
+      To get started, simply run <command>hpodder</command> at your
+      shell prompt.  &hpodder; will lead you through the first-time
+      configuration -- which is only two questions and completely
+      self-explanatory!
+    </para>
+    <para>
+      After this, whenever you want to download the latest episodes
+      for your podcast, just run <command>hpodder</command> again.
+    </para>
+    <para>
+      At some point, you'll want to add more podcasts to &hpodder;.
+      To do that, just run a command such as:
+    </para>
+    <para><command>hpodder add
+        <replaceable>http://www.example.com/feed.xml</replaceable></command></para>
+    <para>
+      Just replace the example.com URL here with the real URL of the
+      feed you want to add.  Then run <command>hpodder
+        update</command>.  If the podcast you've just added has a
+      whole bunch of episodes, you may not want to download them
+      all.  In that case, run <command>hpodder catchup
+        <replaceable>id</replaceable></command>, where
+      <replaceable>id</replaceable> is the podcast number that
+      &hpodder; gave your new podcast when you added it.
+    </para>
+    <para>
+      Again, from here on, you can just run
+      <command>hpodder</command> to download all your new episodes.
+    </para>
+  </refsect1>
+
+  <refsect1 id="hpodder.options">
+    <indexterm><primary>Options (command-line)</primary></indexterm>
+    <title>Options</title>
+    <para>
+      &hpodder; always is invoked with the name of a specific
+      operation, such as <option>update</option> or
+      <option>add</option>.  In &hpodder;, these operations are
+      called <emphasis>commands</emphasis>.  Each command has its
+      own options, which are given after the command on the
+      &hpodder; command line.  A full summary of each command's
+      options is given later in this manual.
+    </para>
+    
+    <para>
+      You may obtain a list of all commands with <command>hpodder
+        lscommands</command>.  Help is available for any individual
+      command with <command>hpodder <replaceable>command</replaceable>
+        --help</command>.  Global help is available with
+      <command>hpodder --help</command>.
+    </para>
+    <refsect2 id="hpodder.options.global">
+      <title>Global Option</title>
+      <para>
+        This option may be specified <emphasis>before</emphasis> any
+        command.
+      </para>
+      <variablelist>
+        <varlistentry><term>-d</term><term>--debug</term>
+          <listitem><para>
+              Enables debugging output.  This verbose output helps you
+              learn what &hpodder; is doing every step of the way and
+              diagnose any problems you may encounter.
+            </para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+    </refsect2>
+
+  </refsect1>
+
+  <refsect1 id="hpodder.commands">
+    <indexterm><primary>Commands</primary></indexterm>
+    <title>Commands in hpodder</title>
+    <para>
+      &hpodder; has many different commands.  If you do not specify a
+      command, the <command>fetch</command> command is automatically
+      selected for you.  This section will discuss each command in
+      detail.  Note that all commands are case-sensitive and should be
+      <emphasis>given in lowercase</emphasis>.
+    </para>
+    <para>
+      All commands support the command <option>--help</option>.  Running
+      <command>hpodder <replaceable>command</replaceable>
+      --help</command> will display information about the command and
+      its options.  Since all commands support this, it won't be
+      explicitly listed for each command below.
+    </para>
+
+    <refsect2 id="hpodder.commands.add">
+      <indexterm><primary>Commands</primary><secondary>add</></>
+      <indexterm><primary>Podcasts</><secondary>Adding to hpodder</></>
+      <title>add</title>
+      <cmdsynopsis>
+        <command>hpodder add</command>
+        <arg choice="plain"><replaceable>URL</replaceable></arg>
+      </cmdsynopsis>
+      <para>
+        This command is used to add a new podcast to &hpodder;.  You
+        can must provide the URL (link) to the podcast you want to add
+        to this command.  For example:
+      </para>
+      <para><command>hpodder add
+      http://soundofhistory.complete.org/files_soh/podcast.xml</command></para>
+      <para>
+        A podcast can be later removed with <command>hpodder
+        rm</command>.  You can adjust its URL later with
+        <command>hpodder mv</command>.
+      </para>
+    </refsect2>
+    
+    <refsect2 id="hpodder.commands.catchup">
+      <indexterm><primary>Commands</><secondary>catchup</></>
+      <indexterm><primary>Episodes</><secondary>Skipping</></>
+      <title>catchup</title>
+      <cmdsynopsis>
+        <command>hpodder catchup</command>
+        <arg choice="opt">-n <replaceable>number</replaceable></arg>
+        &arg.castid;
+      </cmdsynopsis>
+      <para>
+        Running <command>catchup</command> will cause &hpodder; to
+        mark all but the most recent episodes as Skipped.  This will
+        prevent &hpodder; from automatically downloading such
+        episodes.
+      </para>
+      <variablelist>
+        <varlistentry><term>-n <replaceable>NUM</replaceable></term>
+          <term>--number-eps=<replaceable>NUM</replaceable></term>
+          <listitem><para>
+              By default, only the single most recent episode is exempted
+              from being "caught up".  If you want to exclude more
+              episodes from being "caught up" -- and thus allow more
+              to be downloaded -- use this option to allow more
+              episodes to remain downloadable.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        &var.castid;
+      </variablelist>
+    </refsect2>
+
+    <refsect2 id="hpodder.commands.disable">
+      <indexterm><primary>Commands</><secondary>disable</></>
+      <title>disable</title>
+      <cmdsynopsis>
+        <command>hpodder disable</command>
+        <arg choice="plain" rep="repeat">castid</>
+      </cmdsynopsis>
+      <para>
+        This command will flag podcasts as disabled.  Podcasts flagged
+        disabled will be skipped during an <command>update</command>,
+        <command>download</command>, or <command>fetch</command>.
+        They will still participate with all other commands.
+        <command>hpodder lscasts</command> will notify you of which
+        podcasts are disabled.
+      </para>
+      <para>
+        This can be useful if you want to stop following a podcast for
+        awhile, but think you may want to come back to it in the
+        future.  The podcast URL and your download history will remain
+        in the &hpodder; database, unlike with <command>hpodder
+        rm</command>.
+      </para>
+      <para>
+        Disabled podcasts can be re-enabled with <command>hpodder
+        enable</command>.
+      </para>
+      <para>
+        One or more podcast IDs are required; see the section below on
+        specifying podcast IDs for more details.
+      </para>
+    </refsect2>
+
+    <refsect2 id="hpodder.commands.download">
+      <indexterm><primary>Commands</><secondary>download</></>
+      <indexterm><primary>Episodes</><secondary>Downloading</></>
+      <title>download</title>
+      <cmdsynopsis>
+        <command>hpodder download</command>
+        &arg.castid;
+      </cmdsynopsis>
+      <para>
+        The <command>download</command> command is used to actually
+        perform the download of podcasts to your system.  By default,
+        <command>download</command> will download all available
+        episodes.  You can, however, specify only certain podcasts to
+        process; if you do, all available episodes for only those
+        podcasts will be downloaded.
+      </para>
+      <variablelist>&var.castid;</variablelist>
+    </refsect2>
+
+    <refsect2 id="hpodder.commands.enable">
+      <indexterm><primary>Commands</><secondary>enable</></>
+      <title>enable</title>
+      <cmdsynopsis>
+        <command>hpodder enable</command>
+        <arg choice="plain" rep="repeat">castid</>
+      </cmdsynopsis>
+      <para>
+        This command will flag podcasts as enabled.  This is the
+        default state.  See <command>hpodder disable</command> for
+        information on manually disabling podcasts and what it means
+        to be disabled.
+      </para>
+      <para>
+        One or more podcast IDs are required; see the section below on
+        specifying podcast IDs for more details.
+      </para>
+    </refsect2>
+
+    <refsect2 id="hpodder.commands.fetch">
+      <indexterm><primary>Commands</><secondary>fetch</></>
+      <indexterm><primary>Podcasts</><secondary>Updating</></>
+      <indexterm><primary>Episodes</><secondary>Downloading</></>
+      <title>fetch</title>
+      <cmdsynopsis><command>hpodder fetch</command>&arg.castid;</cmdsynopsis>
+      <para>
+        The <command>fetch</command> is the main worker command for
+        &hpodder;.  It is simply equivolent to <command>hpodder
+        update</command> followed by <command>hpodder
+        download</command>.  That is, it will scan all podcasts for
+        new episodes, then download any pending episodes.
+      </para>
+      <para>
+        This command is the default command if no command is given on
+        the &hpodder; command line.
+      </para>
+      <para>
+        As a special feature, the first time that
+        <command>fetch</command> is invoked, it will execute the new
+        user setup procedure.
+      </para>
+      <variablelist>&var.castid;</variablelist>
+    </refsect2>
+
+    <refsect2 id="hpodder.commands.import-ipodder">
+      <indexterm><primary>Commands</><secondary>import-ipodder</></>
+      <indexterm><primary>ipodder</><secondary>Converting From</></>
+      <title>import-ipodder</title>
+      <cmdsynopsis><command>hpodder import-ipodder</command>
+        <arg
+      choice="opt">--from=<replaceable>PATH</replaceable></arg></cmdsynopsis>
+      <para>
+        With this command, &hpodder; can import both your podcast list
+        and your download history from ipodder or CastPodder.
+        &hpodder; will import all podcasts referenced there, with the
+        exception that any podcasts that are already in &hpodder;'s
+        database will be entirely untouched.
+      </para>
+      <variablelist>
+        <varlistentry><term>--from=<replaceable>PATH</replaceable></term>
+          <listitem><para>By default, &hpodder; will look for the ipodder
+            database in the <filename>.ipodder</filename> directory in
+            the user's home directory.  This may not always be correct:
+            for instance, on non-Unix platforms or when using
+            CastPodder, this directory will be different.  With this
+            option, you can tell &hpodder; where to find the
+            ipodder/CastPodder database.
+            </para></listitem>
+        </varlistentry>
+      </variablelist>
+    </refsect2>
+
+    <refsect2 id="hpodder.commands.lscasts">
+      <indexterm><primary>Commands</><secondary>lscasts</></>
+      <indexterm><primary>Podcasts</><secondary>Listing</></>
+      <title>lscasts</title>
+      <cmdsynopsis><command>hpodder lscasts</command>
+        <arg choice="opt">-l</arg></cmdsynopsis>
+      <para>
+        This command will display all podcasts that are configured
+        within &hpodder;.  For each podcast, you will see the podcast
+        ID, the number of pending downloads, the total number of
+        episodes ever seen by &hpodder;, and the title of the podcast.
+      </para>
+      <variablelist>
+        <varlistentry><term>-l</term>
+          <listitem><para>
+              If you add the <option>-l</option> option, then
+          <command>lscasts</command> will also display the feed URL
+          for each podcast.
+            </para></listitem>
+        </varlistentry>
+      </variablelist>
+    </refsect2>
+
+    <refsect2 id="hpodder.commands.lscommands">
+      <indexterm><primary>Commands</><secondary>lscommands</></>
+      <title>lscommands</title>
+      <cmdsynopsis><command>hpodder lscommands</command></cmdsynopsis>
+      <para>
+        This command will display a list of all available &hpodder;
+        commands along with a brief description of each.
+      </para>
+    </refsect2>
+
+    <refsect2 id="hpodder.commands.lsepisodes">
+      <indexterm><primary>Commands</><secondary>lsepisodes</></>
+      <indexterm><primary>Commands</><secondary>lseps</></>
+      <indexterm><primary>Episodes</><secondary>Listing</></>
+      <title>lsepisodes / lseps</title>
+      <cmdsynopsis><command>hpodder lsepisodes</command>
+        <arg choice="opt">-l</arg>&arg.castid;
+      </cmdsynopsis>
+      <cmdsynopsis><command>hpodder lseps</command>
+        <arg choice="opt">-l</arg>&arg.castid;
+      </cmdsynopsis>
+      <para>
+        The <command>lsepisodes</command> command will display a list
+        of every episode known to &hpodder;.  The output will include
+        the ID of the podcast to which the episode belongs, the
+        episode ID, the status of the episode, and the title of the
+        episode.
+      </para>
+      <para>
+        <command>lseps</command> is simply an alias for
+        <command>lsepisodes</command> and performs in the same manner.
+      </para>
+      <variablelist>
+        <varlistentry><term>-l</term>
+          <listitem><para>
+              If you add the <option>-l</option> option, then
+          <command>lsepisodes</command> includes the download URL for
+          each episode in its output.</para></listitem></varlistentry>
+        &var.castid;
+      </variablelist>
+    </refsect2>
+    
+    <refsect2 id="hpodder.commands.rm">
+      <indexterm><primary>Commands</><secondary>rm</></>
+      <indexterm><primary>Podcasts</><secondary>Removing</></>
+      <title>rm</title>
+      <cmdsynopsis><command>hpodder rm</command>
+        <arg choice="plain" rep="repeat">castid</>
+      </cmdsynopsis>
+      <para>
+        This command will remove all knowledge about a given podcast
+        from hpodder, including all entries about that podcast in the
+        episode database.
+      </para>
+      <para>
+        One or more podcast IDs are required; see the section below on
+        specifying podcast IDs for more details.  Unlike most other
+        &hpodder; commands that accept an empty podcast ID list to
+        mean all podcasts, <command>rm</command> does not because of
+        the destructive potential of such a request.
+      </para>
+    </refsect2>
+
+    <refsect2 id="hpodder.commands.setstatus">
+      <indexterm><primary>Commands</><secondary>setstatus</></>
+      <indexterm><primary>Episodes</><secondary>Status Flags</><tertiary>Updating</></>
+      <title>setstatus</title>
+      <cmdsynopsis><command>hpodder setstatus</command>
+        <arg choice="plain">--castid=<replaceable>ID</replaceable></arg>
+        <arg choice="plain">--status=<replaceable>STATUS</replaceable></arg>
+        <arg choice="plain" rep="repeat">epid</arg></cmdsynopsis>
+      <para>
+        The <command>setstatus</command> command is used to manually
+        adjust the status flags on individual episodes.  You can use
+        it to flag individual episodes for downloading (or not).
+      </para>
+      <para>
+        You must specify at least one episode ID.  <emphasis>Note that
+        the plain IDs given to this command are episode IDs</emphasis>, and not
+        podcast IDs like other commands.
+      </para>
+      <para>
+        Statuses are case-sensitive and must be given with a leading
+        uppercase letter and trailing lowercase letters.  Available
+        status are given later in this manual.
+      </para>
+        
+    </refsect2>
+
+    <refsect2 id="hpodder.commands.settitle">
+      <indexterm><primary>Commands</><secondary>settitle</></>
+      <indexterm><primary>Podcasts</><secondary>Title</></>
+      <title>settitle</title>
+      <cmdsynopsis><command>hpodder settitle</command>
+        <arg choice="plain">--castid=<replaceable>ID</replaceable></arg>
+        <arg choice="plain">--title=<replaceable>TITLE</replaceable></arg>
+      </cmdsynopsis>
+      <para>
+        The <command>settitle</command> is used to manually set the
+        title of a given podcast.  Normally, &hpodder; will
+        automatically get the title from the podcast's XML feed.
+        Sometimes the XML feed for the podcast may not provide a
+        useful title.  In those situations, you can use
+        <command>settitle</command> to manually override the title.
+      </para>
+      <para>
+        Please note that if you want to set the title to a name that
+        contains spaces, you will need to quote it for the shell.
+      </para>
+    </refsect2>
+
+
+    <refsect2 id="hpodder.commands.update">
+      <indexterm><primary>Commands</><secondary>update</></>
+      <indexterm><primary>Podcasts</><secondary>Updating</></>
+      <title>update</title>
+      <cmdsynopsis><command>hpodder update</command>&arg.castid;</>
+      <para>
+        The update command will cause &hpodder; to look at each
+        podcast feed.  It will download the latest copy of the feed
+        and compare the episodes mentioned in the feed to its internal
+        database of episodes.  For any episode mentioned in the feed
+        that is not already in the internal database of episodes,
+        &hpodder; will add it to its database and set its status to
+        Pending.
+      </para>
+      <variablelist>&var.castid;</variablelist>
+    </refsect2>
+        
+  </refsect1>
+
+  
+  <refsect1 id="hpodder.castids">
+    <title>Specifying podcast IDs</title>
+    <indexterm><primary>Podcasts</><secondary>IDs</></>
+    <para>
+      Each podcast in &hpodder; gets a numeric ID.  This ID is
+      automatically assigned by &hpodder; and is not changable.  The
+      ID is given out when a podcast is added with the
+      <command>add</command> command, or with the
+      <command>lscasts</command> or <command>lsepisodes</command>
+      commands.
+    </para>
+    <para>
+      The ID is designed as a constant way to refer to a particular
+      podcast.  A podcast's title may change, or even its feed URL,
+      but the ID of a podcast will never change.  It also is short and
+      easy to type on the command line.
+    </para>
+    <para>
+      Several commands can take a list of podcast IDs.  If no IDs are
+      given, the commands will default to operating on all podcasts.
+      One or more IDs can be given, separated by spaces.  If IDs are
+      given, then the commands will operate only on the podcasts with
+      the given IDs.
+    </para>
+    <para>
+      The special keyword <option>all</option> may be given, which
+      tells the system to operate on all podcasts.  This yields the
+      same result as giving no IDs at all.
+    </para>
+  </refsect1>
+
+  <refsect1 id="hpodder.statuses">
+    <indexterm><primary>Episodes</><secondary>Status Flags</></>
+    <title>Status Flags in hpodder</title>
+    <para>
+      Several places in this manual, you've seen &hpodder; statuses
+      mentioned.  Each episode in &hpodder; has an associated status.
+      The statuses are:
+    </para>
+    <variablelist>
+      <varlistentry><term>Pending</term>
+        <listitem><para>The given episode is ready to
+        download</para></listitem>
+      </varlistentry>
+      <varlistentry><term>Downloaded</term>
+        <listitem><para>The given episode has already been
+        downloaded by &hpodder;</para></listitem>
+      </varlistentry>
+      <varlistentry><term>Error</term>
+        <listitem><para>An error occured while downloading this
+        episode.  It will not be downloaded again unless the flag is
+        set back to Pending.</para></listitem>
+      </varlistentry>
+      <varlistentry><term>Skipped</term>
+        <listitem><para>The user has requested that this episode not
+        be downloaded.  Commands such as <command>catchup</command> or
+            <command>import-ipodder</command> could cause this.
+          </para></listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1 id="hpodder.error">
+    <indexterm><primary>Errors</></>
+    <title>Automatic Error Handling</title>
+    <para>
+      For whatever reason, podcast feeds or individual episodes sometimes
+      fail to download.  The reasons for this range from the podcast being
+      taken down by its author to the network being disconnected from the
+      local computer.
+    </para>
+    <para>
+      People that track many podcasts over a long time will probably find it
+      annoying to have &hpodder; attempt to download invalid feeds or
+      episodes over and over again.  For that reason, &hpodder; 1.0.0
+      introduced automatic error handling.
+    </para>
+    <para>
+      Once a podcast feed or episode has failed at least 15 times, 
+      it's been at least 21 days since the first download attempt (episodes)
+      or last update (feeds), &hpodder; will automatically mark the item to
+      be skipped in future runs.  For podcast feeds, &hpodder; disabled the
+      podcast; this status will appear in <command>hpodder lscasts</>.
+      For episodes, &hpodder; sets the status to Error; this will appear
+      in <command>hpodder lseps</>.  Both can be changed later, with
+      <command>hpodder enable</> or <command>hpodder setstatus</>,
+      respectively.
+    </para>
+    <para>
+      The default minimums of 15 attempts and 21 days may be adjusted in
+      the &hpodder; configuration file, either globally or on a per-podcast
+      basis.
+    </para>
+    <para>
+      If you wish to disable checking entirely, you can put lines such as
+      <literal>epfaildays = 123456789</literal> and
+      <literal>podcastfaildays = 123456789</literal> in your <literal>DEFAULT</>
+      section in <filename>~/.hpodder/hpodder.conf</>.  Of course, if you
+      have podcasts that still fail after 338,237 years, you could be in
+      trouble.
+    </para>
+  </refsect1>
+
+  <refsect1 id="hpodder.config">
+    <indexterm><primary>Configuration File</><secondary>hpodder</></>
+    <title>hpodder Configuration File</title>
+    <para>
+      &hpodder; has a configuration file in which you can set various
+      options.  This file normally lives under
+      <filename>~/.hpodder/hpodder.conf</filename>.
+    </para>
+    <para>
+      The configuration file has multiple sections.  Each section has
+      a name and is introduced with the name in brackets.  Each
+      section has one or more options.
+    </para>
+    <para>
+      The section named DEFAULT is special in that it provides
+      defaults that will be used whenever an option can't be found
+      under a different section.
+    </para>
+    <para>
+      Let's start by looking at an example file, and then proceed to
+      examine all the options that are available.
+    </para>
+    <programlisting><![CDATA[
+[DEFAULT]
+
+; Most podcasts are downloaded to here
+downloaddir = /home/jgoerzen/podcasts
+
+namingpatt = %(safecasttitle)s/%(safefilename)s
+
+; Don't disable a podcast due to errors unless it's been at least 20
+; days since the last (or first) attempt
+podcastfaildays = 20
+
+[general]
+
+; The following line tells hpodder that
+; you have already gone through the intro.
+showintro = no
+
+maxthreads = 2
+progressinterval = 1
+
+[31]
+; Store this particular podcast somewhere else
+downloaddir = /nfs/remote/podcasts
+
+; And we don't care as much about disabling it
+podcastfaildays = 5
+]]></programlisting>
+    <para>
+      In this example, you saw some "general" options, such as
+      <option>showintro</option>.  There are two other sections
+      represented: <option>31</option> and <option>DEFAULT</option>.
+    </para>
+    <para>
+      Whenever &hpodder; looks for information about a particular
+      podcast, it first checks to see if it can find that option in a
+      section for that podcast.  If not, it checks the
+      <option>DEFAULT</option> section.  If it still doesn't find an
+      answer, it consults its built-in defaults.
+    </para>
+    <para>
+      In this example, all podcasts share the same naming scheme.  All
+      podcasts except podcast 31 are downloaded to the same place.
+      That podcast goes elsewhere because its
+      <option>downloaddir</option> overrides the default.
+    </para>
+    <refsect2 id="hpodder.config.general">
+      <title>General Options</title>
+      <para>These are specified in the <option>general</option>
+        section.
+      </para>
+      <variablelist>
+        <varlistentry><term>maxthreads</term>
+          <listitem><para>The maximum number of simultaneous download
+              threads that will be active at any given time.
+              &hpodder; can download multiple files at once, and this
+              options says how many it can download simultaneously.
+              It defaults to 2.
+            </para></listitem>
+        </varlistentry>
+        <varlistentry><term>progressinterval</term>
+          <listitem><para>How frequently to update the status bar on
+          the screen, in seconds.  It defaults to 1, which will update
+          the status every second.  Raise it if you are running
+          &hpodder; over a very low-bandwidth link and are concerned
+          about flooding it with status updates.</para></listitem>
+        </varlistentry>
+        <varlistentry><term>showintro</term>
+          <listitem><para>The first time you run
+              <command>fetch</command>, &hpodder; automatically writes a
+              configuration file for you that sets this option to
+              <option>no</option>.  This prevents you from having to
+              do the new user intro more than once.
+            </para></listitem>
+        </varlistentry>
+      </variablelist>
+    </refsect2>              
+
+    <refsect2 id="hpodder.config.podcast">
+      <title>Per-Podcast Options</title>
+      <para>
+        These options may be specified in <option>DEFAULT</option> or
+        in a per-podcast section.  If placed in
+        <option>DEFAULT</option>, they will apply to all podcasts
+        unless overridden.
+      </para>
+      <refsect3 id="hpodder.config.podcast.basic">
+        <title>Basic Per-Podcast Options</title>
+      <variablelist>
+        <varlistentry><term>downloaddir</term>
+          <listitem><para>The main directory into which all podcasts
+              should be stored.  It will be created by &hpodder; when
+              necessary if it does not already exist.  The default is
+            <filename>~/podcasts</filename></para></listitem>
+        </varlistentry>
+        <varlistentry><term>epfailattempts</term>
+          <listitem><para>
+            The minimum number of attempts to download this episode before
+            the episode will be considered to be marked Error.  Default is
+            15.
+          </para></listitem>
+        </varlistentry>
+        <varlistentry><term>epfaildays</term>
+          <listitem><para>
+            The minimum number of days that must have elapsed between the
+            first attempt to download the episode and the present time before
+            the episode will be considered to be marked Error.  Default is
+            21.
+            </para>
+            </listitem>
+            </varlistentry>
+        <varlistentry><term>namingpatt</term>
+          <listitem><para>How to name downloaded files.  This pattern
+          is relative to the <option>downloaddir</option>.  The
+          default is
+          <filename>%(safecasttitle)s/%(safefilename)s</filename>
+            </para>
+            <para>
+              This option will be provided with several replaceable
+              tokens.  Tokens have the form
+              <option>%(<replaceable>tokname</replaceable>)s</option>.
+              That is, the percent sign, the token name in
+              perenthesis, and then an "s" character.  The tokens made
+              available for this option are:
+            </para>
+            <variablelist>
+              <varlistentry><term>castid</term>
+                <listitem><para>The numeric ID for this
+                podcast</para>
+                </listitem></varlistentry>
+              
+              <varlistentry><term>epid</term>
+                <listitem><para>The numeric ID for this
+                episode
+                  </para>
+                </listitem>
+              </varlistentry>
+
+                <varlistentry><term>safecasttitle</term>
+                  <listitem><para>The title of the podcast, as specified
+                      in the feed.  Special characters, such as spaces or
+                      exclamation marks, are converted to
+                      underscores.</para></listitem>
+                </varlistentry>
+
+                <varlistentry><term>safeeptitle</term>
+                  <listitem><para>The title of this episode, as
+                      specified in the podcast's feed, with special
+                      characters converted to underscores.
+                    </para></listitem></varlistentry>
+
+                <varlistentry><term>safefilename</term>
+                  <listitem><para>The component from the URL for this
+                      episode after the last slash in the URL, with special
+                      characters converted to underscores.
+                    </para>
+                  </listitem>
+                </varlistentry>
+              </variablelist>
+            </listitem>
+          </varlistentry>
+          <varlistentry><term>podcastfailattempts</term>
+            <listitem><para>
+                The minimum number of attempts to download this podcast before
+                the episode will be considered to be marked disabled.  Default is
+                15.
+              </para></listitem>
+          </varlistentry>
+          <varlistentry><term>podcastfaildays</term>
+            <listitem><para>
+                The minimum number of days that must have elapsed between the
+                last successful download of the podcast's feed
+                and the present time before
+                the podcast will be considered to be marked disabled.  Default is
+                21.
+              </para>
+            </listitem>
+          </varlistentry>
+        </variablelist>
+      </refsect3>
+      <refsect3 id="hpodder.config.podcast.processing">
+        <title>Per-Podcast Command Options</title>
+        <para>
+          These are external commands that will be run in certain
+          situations.  For each of the commands, several environment
+          variables are set.  These variables are not pre-sanitized
+          and may contain whitespace or special characters.  
+          <emphasis>Extreme
+          caution must be exercized to properly quote these variables
+          when using them in shell commands or scripts.</emphasis>
+          The following
+          environment variables are set:
+        </para>
+        <variablelist>
+          <varlistentry><term>CASTID</term>
+            <listitem><para>The numeric ID for this podcast</para>
+            </listitem>
+          </varlistentry>
+          
+          <varlistentry><term>CASTTITLE</term>
+            <listitem><para>The title of the podcast,
+            verbatim</para></listitem>
+          </varlistentry>
+
+          <varlistentry><term>EPFILENAME</term>
+            <listitem><para>The on-disk filename where this episode
+            has been stored</para></listitem></varlistentry>
+
+          <varlistentry><term>EPID</term>
+            <listitem><para>The numeric epidose ID for this
+            episode</para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry><term>EPTITLE</term>
+            <listitem><para>The title of this episode, as specified in
+            the podcast's feed.</para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry><term>EPURL</term>
+            <listitem><para>The URL of this episode.</para></listitem>
+          </varlistentry>
+
+          <varlistentry><term>FEEDURL</term>
+            <listitem><para>The URL of the podcast's
+            feed.</para></listitem>
+          </varlistentry>
+
+
+          <varlistentry><term>SAFECASTTITLE</term>
+            <listitem><para>The title of the podcast, as specified in
+            the feed.  Special characters, such as spaces or
+            exclamation marks, are converted to
+            underscores.</para></listitem>
+          </varlistentry>
+
+          <varlistentry><term>SAFEEPTITLE</term>
+            <listitem><para>The title of this episode, as specified in
+            the podcast's feed, with special characters converted to
+            underscores.
+              </para></listitem></varlistentry>
+        </variablelist>
+        <para>
+          Here are the supported commands:
+        </para>
+        <variablelist>
+          <varlistentry><term>gettypecommand</term>
+            <listitem><para>This command is intended to analyze the
+                content of the file and return the true MIME type of the
+                file, based on the on-disk content.  If this command exits
+                with an error, the MIME type given in the podcast feed will
+                be used.  If you want to always use the MIME type in the
+                podcast feed, you can set this to
+                <literal>/bin/false</literal> or the empty string.
+              </para>
+              <para>The default value is: <literal>file -b -i "${EPFILENAME}"</literal>
+              </para>
+              <para>
+                It is expected that this program will write its result
+                to standard output.  The first token of the output is
+                taken to be the MIME type.  The remainder will be
+                discarded.  For instance, for the output
+                <literal>text/x-pascal; charset=us-ascii</literal>,
+                the type will be taken to be
+                <literal>text/x-pascal</literal>.  If the program
+                exits with a nonzero exit code, its output will not be used.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry><term>postproccommand</term>
+            <listitem><para>This command provides a user-configurable
+                post-processing hook for downloaded podcasts.  It is only
+                invoked on files whose type matches the
+                <option>postproctypes</option> list.  This command is the
+                very last step in the downloading process.
+              </para>
+              <para>
+                The default value adds ID3 tags to MP3 files.  It is:
+                <literal>id3v2 -A "${CASTTITLE}" -t "${EPTITLE}" --WOAF
+                  "${EPURL}" --WOAS "${FEEDURL}" "${EPFILENAME}"</literal>
+              </para>
+            </listitem>
+          </varlistentry>
+        </variablelist>
+      </refsect3>
+
+      <refsect3 id="hpodder.config.podcast.types">
+        <title>Per-Podcast Type Processing Lists</title>
+        <para>
+          These options govern what types of files are processed in
+          different ways.  The types used here are MIME types.  They
+          will be the actual type determined by
+          <option>gettypecommand</option>, or if that command is
+          unable to determine a useful type, the MIME type given by
+          the podcast's RSS feed.  Items in these lists are to be
+          separated by commas.
+        </para>
+        <variablelist>
+          <varlistentry><term>postproctypes</term>
+            <listitem>
+              <para>
+                This is the comma-separated list of MIME types on
+                which <option>postproccommand</option> will operate.
+                The special single token <literal>ALL</literal> means
+                to operate on all types.  To disable post-processing
+                entirely, you can set this to the empty string.
+                The default is: <literal>audio/mpeg, audio/mp3,
+                x-audio/mp3</literal>
+              </para></listitem></varlistentry>
+
+          <varlistentry><term>renametypes</term>
+            <listitem><para>
+                This option governs the automatic renaming of
+                downloaded files.  Some servers do not present files
+                with proper extensions to match their file type.  This
+                can confuse various software and devices.  &hpodder;
+                can automatically fix up extensions on such files.
+                Each entry in the list in a MIME type, a colon, and
+                the desired filename suffix.  Note that no whitespace
+                is allowed around the colon.
+              </para>
+              <para>
+                The default is: <literal>audio/mpeg:.mp3,
+                audio/mp3:.mp3, x-audio/mp3:.mp3</literal>
+              </para>
+            </listitem>
+          </varlistentry>
+        </variablelist>
+      </refsect3>
+
+    </refsect2>
+  </refsect1>
+              
+  <refsect1 id="hpodder.curl.config">
+    <indexterm><primary>Configuration File</><secondary>Curl</></>
+    <title>Curl Configuration File</title>
+    <para>
+      Internally, &hpodder; uses the Curl application to perform
+      downloads across the Internet.  Curl is a remarkably flexible
+      application, and &hpodder; takes advantage of that to provide
+      you with quite a few options.
+    </para>
+    <para>
+      You can customize Curl as much as you like by creating a Curl
+      configuration file in <filename>~/.hpodder/curlrc</filename>.
+      Please see <application>curl</application>(1) for more details
+      on the content of that file.
+    </para>
+    <para>Some things you can do with this file include restricting
+    the maximum download rate, suppressing or adjusting the progress
+    meter, configuring proxies, etc.
+    </para>
+  </refsect1>
+
+  <refsect1 id="hpodder.tips">
+    <title>Tips & Hints</title>
+    <para>
+      Here are a few tips and hints to make &hpodder; more pleasant
+      for you.
+    </para>
+    <refsect2 id="hpodder.tips.proxy">
+      <title>Going Through a Proxy</title>
+      <indexterm><primary>Proxy</><secondary>Using with hpodder</></>
+      <para>
+        If your connections must go through a proxy, you have two
+        options: set an environment varilable or configure the proxy
+        in your <filename>~/.hpodder/curlrc</filename>.  If you use an
+        environment variable, your settings will also impact other
+        applications -- and that's probably what you want.  See the
+        Environment section later for tips on doing that.
+      </para>
+    </refsect2>
+
+    <refsect2 id="hpodder.tips.ratelimit">
+      <title>Limiting Your Download Speed</title>
+      <indexterm><primary>Download Rate</><secondary>Limiting</></>
+      <para>
+        Sometimes, you may not want &hpodder; to use all of your
+        available bandwidth.  Perhaps you don't want it to slow down
+        other activities too much.  To do this, just create a
+        <filename>~/.hpodder/curlrc</filename> file.  Put in it
+        something like this:
+      </para>
+      <programlisting>limit-rate=20k</programlisting>
+      <para>
+        This will limit the download rate to 20 KB/sec.
+      </para>
+      <para>
+        This rate limitation is imperfect and may not do well during
+        <command>update</command>, but it should do exactly what you
+        want during <command>download</command>.
+      </para>
+    </refsect2>
+  </refsect1>
+
+  <refsect1 id="hpodder.environment">
+    <title>Environment</title>
+    <indexterm><primary>Environment Variables</></>
+    <para>
+      &hpodder; does not read any environment variables directly.
+      However, it does pass on the environment to the
+      programs it calls, such as Curl.  This can be useful for
+      specifying proxies.  Please see
+      <application>curl</application>(1) for more details.
+      As specified in the Per-Podcast Command Options section, &hpodder;
+      will also set certain variables for post-processing of
+      downloaded files.
+    </para>
+  </refsect1>
+
+  <refsect1 id="hpodder.conforming">
+    <title>Conforming To</title>
+    <itemizedlist>
+      <listitem><para>The <ulink
+      url="http://www.w3.org/XML/">Extensible Markup Language
+            (XML)</ulink> standard (W3C)</para></listitem>
+      <listitem><para><ulink
+      url="http://blogs.law.harvard.edu/tech/rss">RSS 2.0</ulink>
+      (Harvard Law)</para></listitem>
+      <listitem><para>HTTP 1.1, FTP, plus SSL/TLS and any other
+      protocols supported by Curl</para></listitem>
+      <listitem><para>ID3 v1 and v2</para></listitem>
+      </itemizedlist>
+  </refsect1>
+
+  <refsect1 id="hpodder.copyright">
+    <title>Copyright</title>
+    <indexterm><primary>Copyright</></>
+    <para>
+      &hpodder;, all code, documentation, files, and build scripts are
+      Copyright &copy; 2006 John Goerzen.  All code, documentation,
+      sripts, and files are under the following license unless
+      otherwise noted:
+    </para>
+    <para>
+      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.
+    </para>
+    
+    <para>
+      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.
+    </para>
+    
+    <para>
+      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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+    </para>
+    <para>
+      The GNU General Public License is available in the file COPYING
+      in the source distribution.  Debian GNU/Linux users may find this in
+      /usr/share/common-licenses/GPL-2.
+    </para>
+    <para>
+      If the GPL is unacceptable for your uses, please e-mail me; alternative
+      terms can be negotiated for your project.
+    </para>
+  </refsect1>
+  
+  <refsect1 id="hpodder.author">
+    <title>Author</title>
+    <para>
+      &hpodder;, its modules, documentation, executables, and all
+      included files, except where noted, was written by
+      John Goerzen <email>jgoerzen@complete.org</email> and
+      copyright is held as stated in the COPYRIGHT section.
+    </para>
+  </refsect1>
+
+  <refsect1 id="dfs.seealso">
+    <title>See Also</title>
+    <para>
+      <application>curl</application>(1),
+      <application>id3v2</application>(1)
+    </para>
+    <para>
+      The &hpodder; homepage at <ulink
+      url="http://quux.org/devel/hpodder"></>,
+      a general description of podcasting at
+      <ulink url="http://en.wikipedia.org/wiki/Podcast"></>
+    </para>
+  </refsect1>
+  
+  </refentry>
diff --git a/doc/hpodder.sgml b/doc/hpodder.sgml
new file mode 100644
--- /dev/null
+++ b/doc/hpodder.sgml
@@ -0,0 +1,41 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook V4.1//EN" [
+  <!ENTITY index SYSTEM "index/index.sgml">
+  <!ENTITY man.hpodder SYSTEM "hpodder-manpage.sgml">
+  <!ENTITY hpodder "<application>hpodder</application>">
+<!ENTITY arg.castid "<arg rep='repeat' choice='opt'><replaceable>castid</replaceable></arg>">
+<!ENTITY var.castid "<varlistentry><term><replaceable>castid ...</replaceable></term><listitem><para>By default, this command will operate on all podcasts.  You can limit the podcasts on which it operates with this option.  See specifying podcast IDs later in this manual for more information.</para></listitem></varlistentry>">
+
+]>
+
+<book id="hpodder">
+  <bookinfo>
+    <title>Using the hpodder podcast aggregator</title>
+    <author>
+      <firstname>John</firstname> <surname>Goerzen</surname>
+    </author>
+    <address><email>jgoerzen@complete.org</email></address>
+<!--
+    <legalnotice>
+      <para>
+        FIXME: add copyright here
+      </para>
+    </legalnotice>
+-->
+  </bookinfo>
+
+  <reference>
+    <title>hpodder Manual</title>
+     &man.hpodder;
+  </reference>
+
+  &index;
+</book>
+
+<!--
+Local Variables:
+mode: sgml
+sgml-set-face: T
+End:
+
+-->
+
diff --git a/doc/local.dsl b/doc/local.dsl
new file mode 100644
--- /dev/null
+++ b/doc/local.dsl
@@ -0,0 +1,41 @@
+<!DOCTYPE style-sheet PUBLIC "-//James Clark//DTD DSSSL Style Sheet//EN" [
+<!ENTITY dbstyle PUBLIC "-//GtkViaAlcove//DOCUMENT Gtk-doc HTML Stylesheet//EN" CDATA DSSSL>
+]>
+
+<style-sheet>
+<style-specification use="gtk">
+<style-specification-body>
+
+(define (toc-depth nd)
+  (if (string=? (gi nd) (normalize "book"))
+      1
+      1))
+(define %generate-article-toc% #t)
+
+;; Don't split up the doc as much.
+(define (chunk-element-list)
+  (list (normalize "preface")
+        (normalize "chapter")
+        (normalize "appendix")
+        (normalize "article")
+        (normalize "glossary")
+        (normalize "bibliography")
+        (normalize "index")
+        (normalize "colophon")
+        (normalize "setindex")
+        (normalize "reference")
+        (normalize "refentry")
+        (normalize "part")
+        (normalize "book") ;; just in case nothing else matches...
+        (normalize "set")  ;; sets are definitely chunks...
+        ))
+
+
+</style-specification-body>
+</style-specification>
+<external-specification id="gtk" document="dbstyle">
+</style-sheet>
+
+<!--
+# arch-tag: web stylesheet for documentation
+-->
diff --git a/doc/man.hpodder.sgml b/doc/man.hpodder.sgml
new file mode 100644
--- /dev/null
+++ b/doc/man.hpodder.sgml
@@ -0,0 +1,23 @@
+<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook V4.1//EN" [
+<!ENTITY hpodder "<application>hpodder</application>">
+<!ENTITY man.hpodder SYSTEM "hpodder-manpage.sgml">
+<!ENTITY arg.castid "<arg rep='repeat' choice='opt'><replaceable>castid</replaceable></arg>">
+<!ENTITY var.castid "<varlistentry><term><replaceable>castid ...</replaceable></term><listitem><para>By default, this command will operate on all podcasts.  You can limit the podcasts on which it operates with this option.  See specifying podcast IDs later in this manual for more information.</para></listitem></varlistentry>">
+
+    ]>
+
+    <reference>
+  <title>hpodder Manual</title>
+  
+  &man.hpodder;
+
+    </reference>
+
+
+    <!--
+    Local Variables:
+    mode: sgml
+    sgml-set-face: T
+    End:
+
+    -->
diff --git a/doc/printlocal.dsl b/doc/printlocal.dsl
new file mode 100644
--- /dev/null
+++ b/doc/printlocal.dsl
@@ -0,0 +1,25 @@
+<!DOCTYPE style-sheet PUBLIC "-//James Clark//DTD DSSSL Style Sheet//EN" [
+<!ENTITY dbstyle PUBLIC "-//Norman Walsh//DOCUMENT DocBook Print Stylesheet//EN" CDATA DSSSL>
+]>
+
+<style-sheet>
+<style-specification use="docbook">
+<style-specification-body>
+
+(define tex-backend #t)
+(define bop-footnotes #t)
+(define %two-side% #t)
+(define %footnote-ulinks% #t)
+
+;    (declare-characteristic heading-level
+;          "UNREGISTERED::James Clark//Characteristic::heading-level" 4)
+
+
+</style-specification-body>
+</style-specification>
+<external-specification id="docbook" document="dbstyle">
+</style-sheet>
+
+<!--
+# arch-tag: print stylesheet for manual
+-->
diff --git a/doc/sgml-common/COPYING b/doc/sgml-common/COPYING
new file mode 100644
--- /dev/null
+++ b/doc/sgml-common/COPYING
@@ -0,0 +1,342 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year  name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
+
+# arch-tag: License for sgml-common
diff --git a/doc/sgml-common/COPYRIGHT b/doc/sgml-common/COPYRIGHT
new file mode 100644
--- /dev/null
+++ b/doc/sgml-common/COPYRIGHT
@@ -0,0 +1,39 @@
+Copyright for all code except ps2epsi
+-------------------------------------
+# Copyright (C) 2002, 2003 John Goerzen
+# <jgoerzen@complete.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
+
+Copyright for ps2epsi
+---------------------
+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, 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, 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+
+My local changes were to the page size.
+
+# arch-tag: Copyright statements and information for sgml-common
diff --git a/doc/sgml-common/ChangeLog b/doc/sgml-common/ChangeLog
new file mode 100644
--- /dev/null
+++ b/doc/sgml-common/ChangeLog
@@ -0,0 +1,265 @@
+# do not edit -- automatically generated by arch changelog
+# arch-tag: automatic-ChangeLog--jgoerzen@complete.org--projects/sgml-common--head--1.0
+#
+
+2004-05-21 14:28:00 GMT	John Goerzen <jgoerzen@complete.org>	patch-20
+
+    Summary:
+      Fixed text target
+    Revision:
+      sgml-common--head--1.0--patch-20
+
+
+    modified files:
+     ChangeLog SConstruct
+
+
+2004-02-27 15:22:59 GMT	John Goerzen <jgoerzen@complete.org>	patch-19
+
+    Summary:
+      Added manpage generation support
+    Revision:
+      sgml-common--head--1.0--patch-19
+
+
+    modified files:
+     ChangeLog SConstruct
+
+
+2004-02-03 19:50:11 GMT	John Goerzen <jgoerzen@complete.org>	patch-18
+
+    Summary:
+      More clearing up of REs
+    Revision:
+      sgml-common--head--1.0--patch-18
+
+
+    modified files:
+     ChangeLog SConstruct
+
+
+2004-02-03 19:40:22 GMT	John Goerzen <jgoerzen@complete.org>	patch-17
+
+    Summary:
+      Tightened up re for finding image tags
+    Revision:
+      sgml-common--head--1.0--patch-17
+
+
+    modified files:
+     ChangeLog SConstruct
+
+
+2004-02-03 19:28:03 GMT	John Goerzen <jgoerzen@complete.org>	patch-16
+
+    Summary:
+      More changes to support scanner
+    Revision:
+      sgml-common--head--1.0--patch-16
+
+    Removed outdated code and made more bugfixes relevant to documents without
+    images or with only pre-generated images.
+
+    new files:
+     ChangeLog
+
+    modified files:
+     SConstruct
+
+
+2004-02-03 18:41:51 GMT	John Goerzen <jgoerzen@complete.org>	patch-15
+
+    Summary:
+      HTML gen now basically functional with scanning
+    Revision:
+      sgml-common--head--1.0--patch-15
+
+
+    modified files:
+     SConstruct
+
+
+2004-02-03 17:41:51 GMT	John Goerzen <jgoerzen@complete.org>	patch-14
+
+    Summary:
+      Auto-scanning is now close for PDFs.
+    Revision:
+      sgml-common--head--1.0--patch-14
+
+
+    modified files:
+     SConstruct
+
+
+2004-02-03 16:59:02 GMT	John Goerzen <jgoerzen@complete.org>	patch-13
+
+    Summary:
+      Scanners starting to work
+    Revision:
+      sgml-common--head--1.0--patch-13
+
+
+    modified files:
+     SConstruct
+
+
+2004-02-03 16:22:51 GMT	John Goerzen <jgoerzen@complete.org>	patch-12
+
+    Summary:
+      Cleaned up HTML situation
+    Revision:
+      sgml-common--head--1.0--patch-12
+
+
+    modified files:
+     SConstruct
+
+
+2004-02-02 22:33:50 GMT	John Goerzen <jgoerzen@complete.org>	patch-11
+
+    Summary:
+      SConstruct file now working
+    Revision:
+      sgml-common--head--1.0--patch-11
+
+
+    modified files:
+     SConstruct
+
+
+2004-02-02 22:07:27 GMT	John Goerzen <jgoerzen@complete.org>	patch-10
+
+    Summary:
+      Fixed nasty PNG gen bug
+    Revision:
+      sgml-common--head--1.0--patch-10
+
+
+    modified files:
+     SConstruct
+
+
+2004-02-02 21:57:32 GMT	John Goerzen <jgoerzen@complete.org>	patch-9
+
+    Summary:
+      Checkpointing some more...
+    Revision:
+      sgml-common--head--1.0--patch-9
+
+
+    modified files:
+     SConstruct
+
+
+2004-02-02 21:37:29 GMT	John Goerzen <jgoerzen@complete.org>	patch-8
+
+    Summary:
+      Checkpointing some more...
+    Revision:
+      sgml-common--head--1.0--patch-8
+
+
+    modified files:
+     SConstruct
+
+
+2004-02-02 20:19:02 GMT	John Goerzen <jgoerzen@complete.org>	patch-7
+
+    Summary:
+      Checkpointing
+    Revision:
+      sgml-common--head--1.0--patch-7
+
+
+    modified files:
+     SConstruct
+
+
+2004-02-02 19:18:40 GMT	John Goerzen <jgoerzen@complete.org>	patch-6
+
+    Summary:
+      Added Plucker
+    Revision:
+      sgml-common--head--1.0--patch-6
+
+
+    modified files:
+     SConstruct
+
+
+2004-02-02 19:09:16 GMT	John Goerzen <jgoerzen@complete.org>	patch-5
+
+    Summary:
+      Added cleanup rules
+    Revision:
+      sgml-common--head--1.0--patch-5
+
+
+    modified files:
+     SConstruct
+
+
+2004-02-02 18:58:11 GMT	John Goerzen <jgoerzen@complete.org>	patch-4
+
+    Summary:
+      sources now checks only chapters/ to prevent dep cycle with index
+    Revision:
+      sgml-common--head--1.0--patch-4
+
+
+    modified files:
+     Makefile.common SConstruct {arch}/=tagging-method
+
+
+2004-02-02 17:51:27 GMT	John Goerzen <jgoerzen@complete.org>	patch-3
+
+    Summary:
+      Experimental SCons conversion
+    Revision:
+      sgml-common--head--1.0--patch-3
+
+
+    new files:
+     SConstruct
+
+
+2003-10-21 20:24:04 GMT	John Goerzen <jgoerzen@complete.org>	patch-2
+
+    Summary:
+      Added plain text generation target
+    Revision:
+      sgml-common--head--1.0--patch-2
+
+    Added plain text generation target
+    
+
+    modified files:
+     ./Makefile.common
+
+
+2003-09-10 14:27:58 GMT	John Goerzen <jgoerzen@complete.org>	patch-1
+
+    Summary:
+      Minor updates to PNG generation and gtk-doc icon locations
+    Revision:
+      sgml-common--head--1.0--patch-1
+
+
+    modified files:
+     Makefile.common
+
+
+2003-09-10 14:24:24 GMT	John Goerzen <jgoerzen@complete.org>	base-0
+
+    Summary:
+      initial import
+    Revision:
+      sgml-common--head--1.0--base-0
+
+    
+    (automatically generated log message)
+
+    new files:
+     COPYING COPYRIGHT Makefile.common ps2epsi
+
+
diff --git a/doc/sgml-common/Makefile.common b/doc/sgml-common/Makefile.common
new file mode 100644
--- /dev/null
+++ b/doc/sgml-common/Makefile.common
@@ -0,0 +1,229 @@
+# -*- Mode: makefile; -*-
+# arch-tag: Primary sgml-common top-level Makefile
+# Common Makefile for SGML documents
+#
+# Copyright (C) 2002, 2003 John Goerzen
+# <jgoerzen@complete.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
+
+# The following variables should be set:
+# MASTERBASE -- basename of master file -- example: my-guide
+# BASICDEPS -- various dependencies of the master file.  For instance,
+#   this might include files included in the SGML.  It could also be empty.
+# TOPNODE -- Basename of top id for HTML link.
+
+MASTER := $(MASTERBASE).sgml
+FIGUREDIRS := $(wildcard figures/*)
+DOINDEX ?= yes
+
+######################################################################
+# Index generation
+######################################################################
+
+ifeq ($(DOINDEX), yes)
+
+INDEXSGMLFILE := index/index.sgml
+INDEXDATAFILE := index/HTML.index
+
+$(INDEXSGMLFILE): $(INDEXDATAFILE)
+	@echo " *** Generating SGML index from index list"
+	collateindex.pl -i ch.index -g -o index/index.sgml index/HTML.index
+
+$(INDEXDATAFILE): $(MASTER) $(BASICDEPS)
+#	jade -t sgml -d docbook.dsl -V html-index $(MASTER)
+#	jade -t sgml -V html-index $(MASTER)
+	@echo " *** Generating index list from document"
+	-rm -r index
+	mkdir index
+	collateindex.pl -i ch.index -N -o index/index.sgml
+	#mkdir html-temp
+	#docbook2html --output html-temp -V html-index $(MASTER)
+	docbook-2-html -O -V -O html-index $(HTMLARGS) $(MASTER)
+	mv $(MASTERBASE)-html/HTML.index index/
+	rm -r $(MASTERBASE)-html
+endif # DOINDEX
+
+######################################################################
+# Text generation
+######################################################################
+$(MASTERBASE).txt: $(MASTER) $(BASICDEPS) $(INDEXSGMLFILE)
+	@echo " *** Generating text output"
+	docbook2txt $(MASTER)
+
+######################################################################
+# PostScript generation
+######################################################################
+
+$(MASTERBASE).ps: $(MASTER) $(BASICDEPS) $(INDEXSGMLFILE) $(EPSFILES)
+	@echo " *** Generating PostScript output"
+# This works too: docbook2ps -V paper-size=Letter $(MASTER)
+	docbook-2-ps -q -O -V -O paper-size=Letter $(PSARGS) $(MASTER)
+
+######################################################################
+# Figure generation
+######################################################################
+
+%_1.epi: %.ps
+	$(get-epi)
+%_2.epi: %.ps
+	$(get-epi)
+%_3.epi: %.ps
+	$(get-epi)
+%_4.epi: %.ps
+	$(get-epi)
+%_5.epi: %.ps
+	$(get-epi)
+%_6.epi: %.ps
+	$(get-epi)
+%_7.epi: %.ps
+	$(get-epi)
+%_8.epi: %.ps
+	$(get-epi)
+%_9.epi: %.ps
+	$(get-epi)
+%_10.epi: %.ps
+	$(get-epi)
+%_11.epi: %.ps
+	$(get-epi)
+%_12.epi: %.ps
+	$(get-epi)
+
+%_1_l.epi: %.ps
+	$(get-epil)
+%_2_l.epi: %.ps
+	$(get-epil)
+%_3_l.epi: %.ps
+	$(get-epil)
+%_4_l.epi: %.ps
+	$(get-epil)
+%_5_l.epi: %.ps
+	$(get-epil)
+%_6_l.epi: %.ps
+	$(get-epil)
+%_7_l.epi: %.ps
+	$(get-epil)
+%_8_l.epi: %.ps
+	$(get-epil)
+%_9_l.epi: %.ps
+	$(get-epil)
+%_10_l.epi: %.ps
+	$(get-epil)
+%_11_l.epi: %.ps
+	$(get-epil)
+%_12_l.epi: %.ps
+	$(get-epil)
+
+%.png: %_l.epi
+	@echo " *** Generating PNG image for $<"
+	gs -q -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -r90 -dBATCH -dNOPAUSE \
+		-dSAFER -sOutputFile=$@ -sDEVICE=png16m $< -c showpage
+
+%.ps: %.pdf
+	pdftops $<
+
+######################################################################
+# HTML generation
+######################################################################
+
+define copy-figures-worker
+mkdir html/figures
+for DIRECTORY in $(FIGUREDIRS); do mkdir html/$$DIRECTORY; cp -v $$DIRECTORY/*.png html/$$DIRECTORY/; done
+endef
+
+define copy-figures
+$(if $(FIGUREDIRS),$(copy-figures-worker))
+endef
+
+html/index.html: $(MASTER) $(BASICDEPS) $(INDEXSGMLFILE) $(PNGFILES)
+	@echo " *** Generating HTML output"
+	-rm -r html
+	mkdir html
+	#docbook2html --output html $(MASTER)
+	docbook-2-html $(HTMLARGS) $(MASTER)
+	mv $(MASTERBASE)-html/* html/
+	rmdir $(MASTERBASE)-html
+	$(copy-figures)
+#	tidy -m html/*.html
+	ln -s $(TOPNODE).html html/index.html
+	-cp -v /usr/share/gtk-doc/data/*.png html/
+
+######################################################################
+# Cleaning
+######################################################################
+
+clean:
+	-rm -f `find . -name "*~"` `find . -name "*.png"` `find . -name "*.epi"`
+	-rm -r html-temp html index $(MASTERBASE).txt
+	-rm *.aux *.log *.dvi *.tex *.jtex *.ps *.html *.log *.out jadetex.cfg
+	-rm *.ps html/*.html figures/topology/*.epi figures/topology/*.png
+	-rm *.log *.pdb
+	-rm `find . -name ".ps"` `find . -name "*.epi"` *.pdf
+	-rm `find . -name "*.png"`
+
+######################################################################
+# Utility functions
+######################################################################
+
+GETPAGE=$(shell echo $(1) | sed -e "s/^.*_\([0-9]*\).epi/\\1/g")
+define get-epi
+@echo " *** Generating EPI image for $<"
+psselect -q $(call GETPAGE,$@) $< temp.ps
+psresize -w 6.375in -h 8.25in temp.ps temp2.ps
+../sgml-common/ps2epsi temp2.ps $@
+rm temp.ps temp2.ps
+endef
+
+GETPAGEL=$(shell echo $(1) | sed -e "s/^.*_\([0-9]*\)_l.epi/\\1/g")
+define get-epil
+@echo " *** Generating large EPI image for $<"
+psselect -q $(call GETPAGEL,$@) $< temp.ps
+psresize -w 8.5in -h 11in temp.ps temp2.ps
+../sgml-common/ps2epsi temp2.ps $@
+rm temp.ps temp2.ps
+endef
+
+
+pdf: $(MASTERBASE).pdf
+
+$(MASTERBASE).pdf: $(MASTERBASE).ps
+	ps2pdf14 $(MASTERBASE).ps
+
+plucker: $(MASTERBASE).pdb
+$(MASTERBASE).pdb: html
+	plucker-build --bpp=4 --compression=zlib --doc-name="$(MASTERBASE)" \
+	-H file:`pwd`/html/index.html -M 5 \
+	--maxheight=320 --maxwidth=310 \
+	--staybelow=file:`pwd`/html --title="$(MASTERBASE)" -p . \
+	-f $(MASTERBASE)
+
+###########################################################################
+# These are obsolete but should still work.
+###########################################################################
+
+
+$(MASTERBASE).dvi: $(MASTERBASE).tex
+	@echo " *** Generating DVI file."
+	jadetex unix-guide.tex
+	jadetex unix-guide.tex
+	jadetex unix-guide.tex
+
+$(MASTERBASE).tex: $(MASTER) $(BASICDEPS) $(INDEXSGMLFILE)
+	@echo " *** Generating TeX files."
+	docbook2tex -V paper-size=Letter $(MASTER)
+#	jade -t tex -V tex-backend -d \
+#		/usr/share/sgml/docbook/stylesheet/dsssl/modular/print/docbook.dsl \
+#		$(MASTER)
+
diff --git a/doc/sgml-common/SConstruct b/doc/sgml-common/SConstruct
new file mode 100644
--- /dev/null
+++ b/doc/sgml-common/SConstruct
@@ -0,0 +1,208 @@
+# vim: set filetype=python :
+# arch-tag: general-purpose SCons build file for sgml-common
+
+from glob import glob
+import os, re
+
+############################################################
+# Setup
+############################################################
+
+SConsignFile('.sconsign-master')
+#Import('env')
+d = env.Dictionary()
+if not 'JADE' in d:
+    d['JADE'] = 'jade'
+if not 'INDEXNODE' in d:
+    d['INDEXNODE'] = 'ch.index'
+if not 'GTKIMGPATH' in d:
+    d['GTKIMGPATH'] = '/usr/share/gtk-doc/data'
+if not 'PS2EPSI' in d:
+    d['PS2EPSI'] = '../sgml-common/ps2epsi'
+
+def removeindex(l):
+    while 'index/index.sgml' in l:
+        l.remove('index/index.sgml')
+
+master = d['MASTERBASE'] 
+mastersgml = master + '.sgml'
+sources = [mastersgml] + glob('*/*.sgml') + glob('*/*/*.sgml')
+removeindex(sources)
+db2htmlcmd = 'docbook-2-html -D $JADE ${HTMLARGS}  ${SOURCE}'
+db2htmlindexcmd = 'docbook-2-html -D $JADE -O -V -O html-index ${HTMLARGS} ${SOURCE}'
+
+##################################################
+# SCANNERS
+##################################################
+def recursescan(scanner, node, env):
+    result = scanner(node, env) 
+    retval = []
+    for item in result:
+        retval.append(item)
+        retval.extend(recursescan(scanner, item, env))
+    return retval
+
+SGML_includes_re = re.compile(r'<!ENTITY[^>]+SYSTEM[^>]+"(.+)"', re.M)
+def SGML_includes_scan(node, env, path):
+    ret = SGML_includes_re.findall(node.get_contents())
+    removeindex(ret)
+    return ret
+
+SGML_includes_scanner = Scanner(name = 'SGML_includes',
+    function = SGML_includes_scan, recursive = 1, skeys = ['.sgml', '.ent'])
+
+SGML_image_pdf_re = re.compile(r'<(graphic|imagedata).+?fileref="([^"]+\.pdf)"', re.S)
+SGML_image_png_re = re.compile(r'<(graphic|imagedata).+?fileref="([^"]+\.png)"', re.S)
+def SGML_image_scanner(node, env, path, arg):
+    root, ext = os.path.splitext(str(node))
+    contents = node.get_contents()
+    return SGML_includes_scan(node, env, path) + \
+            [os.getcwd() + '/' + x[1] for x in arg.findall(contents)]
+
+SGML_pdf_scanner = Scanner(name = 'SGML_pdf',
+         function = SGML_image_scanner, argument = SGML_image_pdf_re,
+         recursive = 1)
+SGML_png_scanner = Scanner(name = 'SGML_png',
+        function = SGML_image_scanner, argument = SGML_image_png_re,
+        recursive = 1)
+
+##################################################
+# BUILDERS
+##################################################
+
+#### PLAIN TEXT
+Btxt = Builder(action="docbook2txt $SOURCE", src_suffix='.sgml', suffix='.txt')
+
+#### PDF / POSTSCRIPT
+Bpdf = Builder(action="docbook-2-pdf -D ${JADE} -q -O -V -O paper-size=Letter ${PDFARGS} ${SOURCE}",
+        src_suffix='.sgml', suffix='.pdf')
+Bpdf2ps = Builder(action="pdftops ${SOURCE}", src_suffix='.pdf', suffix='.ps')
+
+#### MAN PAGES
+# FIXME: test this
+Bman = Builder(action="docbook2man $SOURCE", src_suffix='.sgml', suffix='.1')
+
+#### HTML
+Bhtml = Builder(action = [ \
+        'if test -d ${TARGET.dir} ; then rm -r ${TARGET.dir} ; fi',
+        'mkdir ${TARGET.dir}',
+        db2htmlcmd,
+        'mv ${MASTERBASE}-html/* ${TARGET.dir}/',
+        'rmdir ${MASTERBASE}-html',
+        'ln -s ${TOPNODE}.html ${TARGET.dir}/index.html',
+        'cp ${GTKIMGPATH}/*.png ${TARGET.dir}/'])
+
+#### PNG
+Bepip2png = Builder(action = 'gs -q -dTextAlphaBits=4 -dGraphicsAlphaBits=4 ' +\
+        '-r90 -dBATCH -dNOPAUSE -dSAFER -sOutputFile=$TARGET ' + \
+        '-sDEVICE=png16m $SOURCE -c showpage', suffix='.png', src_suffix='.pngepi')
+
+#### EPI from PS
+def getpagenumfromname(target, source, env, for_signature):
+    return re.search('^.*_(\d+)\.(png){0,1}epi$', str(target[0])).group(1)
+d['GETPAGE'] = getpagenumfromname
+
+Aps2epi = Action(['psselect -q ${GETPAGE} $SOURCE temp.ps',
+        'psresize -w ${WIDTH} -h ${HEIGHT} temp.ps temp2.ps',
+        '$PS2EPSI temp2.ps $TARGET',
+        'rm temp.ps temp2.ps'])
+Bps2epi = Builder(action=Aps2epi, src_suffix='.ps', suffix='.epi')
+Bps2epip = Builder(action=Aps2epi, src_suffix='.ps', suffix='.pngepi')
+Bepi2pdf = Builder(action="epstopdf -o=${TARGET} ${SOURCE}", suffix='.pdf',
+        src_suffix='.epi')
+
+#### PLUCKER
+Bplucker = Builder(action = 'plucker-build --bpp=4 --compression=zlib ' + \
+        '--doc-name="${MASTERBASE}" -H file:${SOURCE.abspath} -M 5 ' + \
+        '--maxheight=320 --maxwidth=310 --staybelow=file:`pwd`/${SOURCE.dir} ' + \
+        '--title="${MASTERBASE}" -p . -f ${MASTERBASE}')
+
+##################################################
+# General setup
+##################################################
+
+env.Append(BUILDERS = {'Text': Btxt, 'PDF2PS': Bpdf2ps, 'PDF': Bpdf, 'HTML': Bhtml,
+        'Plucker': Bplucker, 'PS2EPI': Bps2epi, 'PS2EPIP': Bps2epip,
+        'EPI2PDF': Bepi2pdf, 'EPIP2PNG': Bepip2png, 'MAN': Bman})
+
+#### INDEX GENERATION
+if 'DOINDEX' in d:
+    Bindex = Builder(action = ['if test -d ${TARGET.dir}  ; then rm -r ${TARGET.dir} ; fi',
+        "mkdir ${TARGET.dir}",
+        "collateindex.pl -i $INDEXNODE -N -o $TARGET",
+        db2htmlindexcmd,
+        "mv ${MASTERBASE}-html/HTML.index ${TARGET.dir}/",
+        "rm -r ${MASTERBASE}-html",
+        "collateindex.pl -i $INDEXNODE -g -o $TARGET ${TARGET.dir}/HTML.index"])
+    env['BUILDERS']['Index'] = Bindex
+    index = env.Index('index/index.sgml', mastersgml)
+    env.Depends(index, sources)
+    env.Clean(index, 'index')
+    deps = sources + [index]
+else:
+    deps = sources
+
+##################################################
+# BUILD RULES
+###################################################
+# Text
+text = env.Text(mastersgml)
+env.Depends(text, deps)
+env.Alias('text', text)
+
+# PDF
+pdfsgml = File(mastersgml)
+pdf = env.PDF(pdfsgml)
+figsindoc = [x for x in recursescan(SGML_pdf_scanner, pdfsgml, env) if str(x).endswith('.pdf')]
+epipdf = []
+for file in figsindoc:
+    pdfname = re.sub('_\d+\.pdf$', '.pdf', str(file))
+    if pdfname == str(file):
+        # This is not a filename that fits our pattern; add unmodified.
+        epipdf.append(file)
+        continue
+    psfile = env.PDF2PS(source = pdfname)
+    epifile = env.PS2EPI(str(file).replace(".pdf", ".epi"), psfile,
+            WIDTH='6.375in', HEIGHT='8.25in')
+    epipdf.append(env.EPI2PDF(source = epifile))
+
+env.Depends(pdf, deps)
+env.Depends(pdf, epipdf)
+env.Alias('pdf', pdf)
+env.Clean(pdf, ['jadetex.cfg', '${MASTERBASE}.aux', '${MASTERBASE}.dvi',
+        '${MASTERBASE}.jtex', '${MASTERBASE}.log', '${MASTERBASE}.out',
+        'jade-out.fot'])
+
+# PS
+ps = env.PDF2PS(source = pdf)
+env.Alias('ps', ps)
+
+# HTML
+htmlsgml = File(mastersgml)
+buildhtml = env.HTML('html/index.html', htmlsgml)
+figsindoc = [x for x in recursescan(SGML_png_scanner, htmlsgml, env) if str(x).endswith('.png')]
+epipng = []
+for file in figsindoc:
+    pdfname = re.sub('_\d+\.png$', '.pdf', str(file))
+    if pdfname == str(file):
+        # This is not a filename that fits our pattern; add unmodified. 
+        epipng.append(file)
+        continue
+    psfile = env.PDF2PS(source = pdfname)
+    epifile = env.PS2EPIP(str(file).replace(".png", ".pngepi"), psfile,
+            WIDTH='8.5in', HEIGHT='11in')
+    epipng.append(env.EPIP2PNG(source = epifile))
+
+env.Depends(buildhtml, epipng)
+env.Depends(buildhtml, deps)
+pnginstalls = env.InstallAs(['html/' + str(x) for x in epipng], epipng)
+env.Depends(pnginstalls, buildhtml)
+html = env.Alias('html', buildhtml)
+html = env.Alias('html', pnginstalls)
+env.Clean(buildhtml, 'html')
+
+# Plucker
+plucker = env.Plucker(master + '.pdb', 'html/index.html')
+env.Alias('plucker', plucker)
+
+env.Default(html)
diff --git a/doc/sgml-common/ps2epsi b/doc/sgml-common/ps2epsi
new file mode 100644
--- /dev/null
+++ b/doc/sgml-common/ps2epsi
@@ -0,0 +1,77 @@
+#!/bin/sh
+# $RCSfile: ps2epsi,v $ $Revision: 1.4.2.2 $
+# arch-tag: ps2epsi customized for sgml-common
+
+tmpfile=/tmp/ps2epsi$$
+
+export outfile
+
+if [ $# -lt 1 -o $# -gt 2 ]; then
+	echo "Usage: `basename $0` file.ps [file.epsi]" 1>&2
+	exit 1
+fi
+
+infile=$1;
+
+if [ $# -eq 1 ]
+then
+	case "${infile}" in
+	  *.ps)		base=`basename ${infile} .ps` ;;
+	  *.cps)	base=`basename ${infile} .cps` ;;
+	  *.eps)	base=`basename ${infile} .eps` ;;
+	  *.epsf)	base=`basename ${infile} .epsf` ;;
+	  *)		base=`basename ${infile}` ;;
+	esac
+	outfile=${base}.epsi
+else
+	outfile=$2
+fi
+
+ls -l ${infile} |
+awk 'F==1	{
+		cd="%%CreationDate: " $6 " " $7 " " $8;
+		t="%%Title: " $9;
+		f="%%For:" U " " $3;
+		c="%%Creator: Ghostscript ps2epsi from " $9;
+		next;
+		}
+	/^%!/	{next;}
+	/^%%Title:/	{t=$0; next;}
+	/^%%Creator:/	{c=$0; next;}
+	/^%%CreationDate:/	{cd=$0; next;}
+	/^%%For:/	{f=$0; next;}
+	!/^%/	{
+		print "/ps2edict 30 dict def";
+		print "ps2edict begin";
+		print "/epsititle (" t "\\n) def";
+		print "/epsicreator (" c "\\n) def";
+		print "/epsicrdt (" cd "\\n) def";
+		print "/epsifor (" f "\\n) def";
+		print "end";
+		exit(0);
+		}
+	' U="$USERNAME$LOGNAME"  F=1 - F=2 ${infile} >$tmpfile
+
+gs -q -dNOPAUSE -dSAFER -dDELAYSAFER -r72 -sDEVICE=bit -sOutputFile=/dev/null $tmpfile ps2epsi.ps $tmpfile <${infile} 1>&2
+rm -f $tmpfile
+
+(
+cat << BEGINEPS
+save countdictstack mark newpath /showpage {} def /setpagedevice {pop} def
+%%EndProlog
+%%Page 1 1
+BEGINEPS
+
+cat ${infile} |
+sed -e '/^%%BeginPreview:/,/^%%EndPreview[^!-~]*$/d' -e '/^%!PS-Adobe/d'\
+    -e '/^%%[A-Za-z][A-Za-z]*[^!-~]*$/d' -e '/^%%[A-Za-z][A-Za-z]*: /d'
+
+cat << ENDEPS
+%%Trailer
+cleartomark countdictstack exch sub { end } repeat restore
+%%EOF
+ENDEPS
+
+) >> ${outfile}
+
+exit 0
diff --git a/hpodder.cabal b/hpodder.cabal
--- a/hpodder.cabal
+++ b/hpodder.cabal
@@ -1,12 +1,17 @@
 Name: hpodder
-Version: 1.0.3
+Version: 1.1.0
 License: GPL
 Maintainer: John Goerzen <jgoerzen@complete.org>
 Author: John Goerzen
 Stability: Stable
-Copyright: Copyright (c) 2006-2007 John Goerzen
+Copyright: Copyright (c) 2006-2008 John Goerzen
 license-file: COPYRIGHT
-extra-source-files: COPYING
+extra-source-files: COPYING, INSTALL, doc/SConstruct,
+  doc/hpodder-manpage.sgml, doc/hpodder.sgml, doc/local.dsl,
+  doc/man.hpodder.sgml, doc/printlocal.dsl, doc/sgml-common/COPYING,
+  doc/sgml-common/COPYRIGHT, doc/sgml-common/ChangeLog,
+  doc/sgml-common/Makefile.common, doc/sgml-common/SConstruct,
+  doc/sgml-common/ps2epsi
 homepage: http://software.complete.org/hpodder
 Category: Network
 Synopsis: Podcast Aggregator (downloader)
@@ -62,9 +67,9 @@
  hpodder is SAFE and is designed with data integrity in mind from the
  beginning.  It should be exceedingly difficult to lose a podcast
  episode, even in the event of a power failure.
-Build-Depends: haskell98, network, unix, parsec, MissingH>=0.18.0,
+Build-Depends: haskell98, network, unix, parsec, MissingH>=1.0.0,
  HDBC>=1.1.0, HDBC-sqlite3>=1.1.0, mtl, base, HaXml>=1.13.2, hslogger,
- ConfigFile, filepath
+ ConfigFile, filepath, old-time, directory, process
 
 Executable: hpodder
 Main-Is: hpodder.hs
