packages feed

hpodder 1.0.3 → 1.1.0

raw patch · 24 files changed

+2750/−51 lines, 24 filesdep +directorydep +old-timedep +processdep ~MissingH

Dependencies added: directory, old-time, process

Dependency ranges changed: MissingH

Files

@@ -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:
Commands/Download.hs view
@@ -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.
Commands/ImportIpodder.hs view
@@ -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 
Commands/SetStatus.hs view
@@ -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
Commands/Setup.hs view
@@ -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"
Commands/Update.hs view
@@ -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
Config.hs view
@@ -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+  
DB.hs view
@@ -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
FeedParser.hs view
@@ -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"]
+ INSTALL view
@@ -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+
Types.hs view
@@ -60,6 +60,7 @@                         epid :: Integer,                         eptitle :: String,                         epurl :: String,+                        epguid :: Maybe String,                         eptype :: String,                         epstatus :: EpisodeStatus,                         eplength :: Integer,
+ doc/SConstruct view
@@ -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)++
+ doc/hpodder-manpage.sgml view
@@ -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>
+ doc/hpodder.sgml view
@@ -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:++-->+
+ doc/local.dsl view
@@ -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+-->
+ doc/man.hpodder.sgml view
@@ -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:++    -->
+ doc/printlocal.dsl view
@@ -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+-->
+ doc/sgml-common/COPYING view
@@ -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
+ doc/sgml-common/COPYRIGHT view
@@ -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
+ doc/sgml-common/ChangeLog view
@@ -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++
+ doc/sgml-common/Makefile.common view
@@ -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)+
+ doc/sgml-common/SConstruct view
@@ -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)
+ doc/sgml-common/ps2epsi view
@@ -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
hpodder.cabal view
@@ -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