packages feed

photoname-5.2: src/lib/Photoname/CopyLink.hs

{-# LANGUAGE OverloadedRecordDot, ScopedTypeVariables #-}

module Photoname.CopyLink
   ( createNewLink
   )
   where

import Control.Exception (try)
import Control.Monad (unless, when)
import Data.Time.LocalTime (LocalTime)
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),
  Ph, Prefix (Prefix), SrcPath (SrcPath), Suffix (Suffix), ask, asks, liftIO,
  throwError)
import Photoname.Date (PhDate (ExifDate, FilenameDate, NoDateFound),
  formatDateHyphens, formatYear)
import Photoname.Log (lname, noticeM, warningM)


createNewLink :: PhDate -> SrcPath -> Ph DestPath
createNewLink imageDate srcPath@(SrcPath srcFp) = do
  opts <- ask

  let ext = case opts.extension of
        (Extension ext') -> ext'
        UseExistingExtension -> takeExtension srcFp

  destPath@(DestPath destFp) <- case imageDate of
    ExifDate lt -> buildDatePath lt ext
    FilenameDate lt -> buildDatePath lt ext
    NoDateFound -> throwError "Could not extract any date information"

  -- Check for existence 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 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 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 -> FilePath -> Ph DestPath
buildDatePath date ext = do
   (Prefix prefixStr) <- asks prefix
   (Suffix suffixStr) <- asks suffix
   dateFormatter <- asks formatter
   let fileName = prefixStr <> dateFormatter date <> suffixStr

   (ParentDir parentDir') <- asks parentDir
   (NoDirsSwitch noDirs') <- asks noDirs
   pure . DestPath $ if noDirs'
      then parentDir' </> fileName <.> ext
      else parentDir' </> formatYear date </>
         formatDateHyphens date </> fileName <.> ext