packages feed

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