epub-tools-4.0: src/app/EpubTools/EpubMeta/Edit.hs
{-# LANGUAGE ScopedTypeVariables #-}
module EpubTools.EpubMeta.Edit
( edit )
where
import Control.Applicative (asum)
import Data.Maybe (fromJust)
import System.Directory (copyFile, findExecutable, getFileSize,
removeFile)
import System.FilePath ((</>), (<.>))
import System.Environment (lookupEnv)
import System.Exit (ExitCode (ExitSuccess))
import System.Process (getCurrentPid, system)
import Text.Printf (printf)
import EpubTools.EpubMeta.Export (exportOpf)
import EpubTools.EpubMeta.Import (importOpf)
import EpubTools.EpubMeta.Opts (Backup (BackupSuffix, NoBackup),
EpubPath (..), ImportPath (..), Output (OutputFilename))
import EpubTools.EpubMeta.Util (EM, liftIO, throwError)
{- Look through the user's environment for their preferred editor.
Use vi wherever it may be on the PATH as a default if none found.
-}
findEditor :: EM FilePath
findEditor = do
mbEditor <- liftIO $ asum <$> sequence
[ lookupEnv "EDITOR"
, lookupEnv "VISUAL"
, findExecutable "vi"
]
maybe (throwError "epubmeta: Could not find a suitable editor in your EDITOR or VISUAL environment variables, and could not find the vi binary. Fix this situation or use epubmeta export/import.") return mbEditor
{- Modern Linux systems with systemd provide per-user temp space
-}
locateTempDir :: IO FilePath
locateTempDir = fromJust . asum <$> sequence
[ lookupEnv "TMPDIR" -- Often unset, but use if we can
, lookupEnv "XDG_RUNTIME_DIR" -- Should always exist on any systemd-based distro
, pure . Just $ "/tmp" -- Every system should have this
]
edit :: Backup -> EpubPath -> EM ()
edit NoBackup epubPath = do
-- Make a temp path for the OPF document
tempDir <- liftIO $ locateTempDir
pid <- liftIO $ getCurrentPid
let opfPath = tempDir </> ("epubmeta-" <> show pid <> "-content") <.> "opf"
-- Dump the OPF document to this path
exportOpf (OutputFilename opfPath) epubPath
-- Open this file in the user's editor and wait for it
editor <- findEditor
ec <- liftIO $ system $ printf "%s %s" editor opfPath
tempFileEmpty <- liftIO $ (== 0) <$> getFileSize opfPath
case (ec, tempFileEmpty) of
(ExitSuccess, False) -> do
-- Modify the book with the now-edited OPF data
importOpf (ImportPath opfPath) NoBackup epubPath
-- Delete the temp file
liftIO $ removeFile opfPath
_ -> do
-- Delete the temp file
liftIO $ removeFile opfPath
throwError "epubmeta: ERROR: Edit of metadata aborted, your file has not been changed."
edit (BackupSuffix suffix) epubPath@(EpubPath zipPath) = do
liftIO $ copyFile zipPath (zipPath ++ suffix)
edit NoBackup epubPath