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
- COPYRIGHT +1/−1
- Commands/Download.hs +84/−26
- Commands/ImportIpodder.hs +1/−1
- Commands/SetStatus.hs +1/−1
- Commands/Setup.hs +1/−1
- Commands/Update.hs +1/−1
- Config.hs +15/−2
- DB.hs +51/−11
- FeedParser.hs +8/−2
- INSTALL +18/−0
- Types.hs +1/−0
- doc/SConstruct +30/−0
- doc/hpodder-manpage.sgml +1238/−0
- doc/hpodder.sgml +41/−0
- doc/local.dsl +41/−0
- doc/man.hpodder.sgml +23/−0
- doc/printlocal.dsl +25/−0
- doc/sgml-common/COPYING +342/−0
- doc/sgml-common/COPYRIGHT +39/−0
- doc/sgml-common/ChangeLog +265/−0
- doc/sgml-common/Makefile.common +229/−0
- doc/sgml-common/SConstruct +208/−0
- doc/sgml-common/ps2epsi +77/−0
- hpodder.cabal +10/−5
COPYRIGHT view
@@ -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 © 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