photoname-5.0: src/lib/Photoname/CopyLink.hs
{-# LANGUAGE ScopedTypeVariables #-}
module Photoname.CopyLink
( createNewLink
)
where
import Control.Exception ( try )
import Control.Monad ( unless, when )
import Control.Newtype.Generics ( op )
import Data.Time.LocalTime ( LocalTime )
import GHC.IO.Exception ( IOException )
import System.Directory ( copyFile, createDirectoryIfMissing )
import System.FilePath ( (</>), takeDirectory )
import System.Posix ( createLink, fileExist, removeLink )
import Text.Printf ( printf )
import Photoname.Common ( CopySwitch (..), DestPath (..), MoveSwitch (..),
NoActionSwitch (..), NoDirsSwitch (..), ParentDir (..), Options (..),
Ph, Prefix (..), SrcPath (..), Suffix (..),
ask, asks, liftIO, throwError )
import Photoname.Date
( PhDate (ExifDate, FilenameDate, NoDateFound)
, formatDateHyphens, formatDateTime, formatYear
)
import Photoname.Log ( lname, noticeM, warningM )
createNewLink :: PhDate -> SrcPath -> Ph DestPath
createNewLink imageDate srcPath@(SrcPath srcFp) = do
opts <- ask
destPath@(DestPath destFp) <- case imageDate of
ExifDate lt -> buildDatePath lt
FilenameDate lt -> buildDatePath lt
NoDateFound -> throwError "Could not extract any date information"
-- Check for existance of the target file
exists <- liftIO $ fileExist destFp
when exists $ throwError $ "Destination " ++ destFp ++ " exists!"
-- Display what will be done
liftIO $ noticeM lname $ srcFp ++ " -> " ++ destFp
unless (op NoActionSwitch . optNoAction $ opts) $ do
-- Make the target dir
liftIO $ createDirectoryIfMissing True $ takeDirectory destFp
-- Make the new file
if (op CopySwitch . optCopy $ opts)
then liftIO $ copyFile srcFp destFp
else tryHardLink srcPath destPath
-- If user has specified, remove the original link
when (op MoveSwitch . optMove $ opts) $
liftIO $ removeLink srcFp
pure destPath
tryHardLink :: SrcPath -> DestPath -> Ph ()
tryHardLink (SrcPath srcFp) (DestPath destFp) = do
ei <- liftIO $ try $ createLink srcFp destFp
either failureHandler pure ei
where
failureHandler :: IOException -> Ph ()
failureHandler _ = do
liftIO $ warningM lname "Hard link failed, attempting to copy instead"
liftIO $ copyFile srcFp destFp
{- Construct the destination file path based on the information we have (parent
dir, subdirs wanted or not, prefix and suffix, and the date info that was
gathered).
-}
buildDatePath :: LocalTime -> Ph DestPath
buildDatePath date = do
prefixStr <- asks (op Prefix . optPrefix)
suffixStr <- asks (op Suffix . optSuffix)
let fileName = printf "%s%s%s.jpg" prefixStr (formatDateTime date) suffixStr
parentFp <- asks (op ParentDir . optParentDir)
noDirs <- asks (op NoDirsSwitch . optNoDirs)
pure . DestPath $ if (noDirs)
then parentFp </> fileName
else parentFp </> (formatYear date) </>
(formatDateHyphens date) </> fileName