photoname-5.5: src/lib/Photoname/CopyLink.hs
{-# LANGUAGE OverloadedRecordDot #-}
module Photoname.CopyLink
( createNewLink
)
where
import Colog.Simple (logNotice, logWarning)
import Control.Exception (try)
import Control.Monad (unless, when)
import Data.Time.LocalTime (LocalTime)
import Formatting ((%+), sformat, string)
import GHC.IO.Exception (IOException)
import System.Directory (copyFile, createDirectoryIfMissing)
import System.FilePath ((</>), (<.>), takeDirectory, takeExtension)
import System.Posix (createLink, fileExist, removeLink)
import Photoname.Common (CopySwitch (v), DestPath (..),
Extension (Extension, UseExistingExtension), MoveSwitch (v),
NoActionSwitch (v), NoDirsSwitch (NoDirsSwitch), ParentDir (ParentDir),
Options (copy, formatter, extension, move, noAction, noDirs, parentDir, prefix, suffix),
Prefix (Prefix), SrcPath (SrcPath), Suffix (Suffix))
import Photoname.Date (PhDate (ExifDate, FilenameDate, NoDateFound),
formatDateHyphens, formatYear)
import Photoname.Monad (Env (envOptions), Ph, asks, liftIO)
createNewLink :: PhDate -> SrcPath -> Ph (Maybe DestPath)
createNewLink NoDateFound (SrcPath srcFp) = do
logWarning $ sformat ("Could not extract any date information from" %+ string) srcFp
pure Nothing
createNewLink (ExifDate lt) srcPath = createNewLink' srcPath lt
createNewLink (FilenameDate lt) srcPath = createNewLink' srcPath lt
createNewLink' :: SrcPath -> LocalTime -> Ph (Maybe DestPath)
createNewLink' srcPath@(SrcPath srcFp) lt = do
opts <- asks envOptions
let ext = case opts.extension of
(Extension ext') -> ext'
UseExistingExtension -> takeExtension srcFp
destPath@(DestPath destFp) <- buildDatePath lt ext
-- Check for existence of the target file
targetExists <- liftIO $ fileExist destFp
if targetExists
then do
logWarning $ sformat ("Destination" %+ string %+ "exists!") destFp
pure Nothing
else do
-- Display what will be done
logNotice $ sformat (string %+ "->" %+ string) srcFp destFp
unless opts.noAction.v $ do
-- Make the target dir
liftIO $ createDirectoryIfMissing True $ takeDirectory destFp
-- Make the new file
if opts.copy.v
then liftIO $ copyFile srcFp destFp
else tryHardLink srcPath destPath
-- If user has specified, remove the original link
when opts.move.v $
liftIO $ removeLink srcFp
pure $ Just 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
logWarning "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 -> FilePath -> Ph DestPath
buildDatePath date ext = do
(Prefix prefixStr) <- asks $ prefix . envOptions
(Suffix suffixStr) <- asks $ suffix . envOptions
dateFormatter <- asks $ formatter . envOptions
let fileName = prefixStr <> dateFormatter date <> suffixStr
(ParentDir parentDir') <- asks $ parentDir . envOptions
(NoDirsSwitch noDirs') <- asks $ noDirs . envOptions
pure . DestPath $ if noDirs'
then parentDir' </> fileName <.> ext
else parentDir' </> formatYear date </>
formatDateHyphens date </> fileName <.> ext