module Propellor.Property.Rsync where

import Propellor.Base
import qualified Propellor.Property.Apt as Apt

type Src = FilePath
type Dest = FilePath

class RsyncParam p where
	toRsync :: p -> String

-- | A pattern that matches all files under a directory, but does not
-- match the directory itself.
filesUnder :: FilePath -> Pattern
filesUnder d = Pattern (d ++ "/*")

-- | Ensures that the Dest directory exists and has identical contents as
-- the Src directory.
syncDir :: Src -> Dest -> Property DebianLike
syncDir = syncDirFiltered []

data Filter 
	= Include Pattern
	| Exclude Pattern
	| Protect Pattern

instance RsyncParam Filter where
	toRsync (Include (Pattern p)) = "--include=" ++ p
	toRsync (Exclude (Pattern p)) = "--exclude=" ++ p
	toRsync (Protect (Pattern p)) = "--filter=P " ++ p

-- | A pattern to match against files that rsync is going to transfer.
--
-- See "INCLUDE/EXCLUDE PATTERN RULES" in the rsync(1) man page.
--
-- For example, Pattern "/foo/*" matches all files under the "foo"
-- directory, relative to the 'Src' that rsync is acting on.
newtype Pattern = Pattern String

-- | Like syncDir, but avoids copying anything that the filter list
-- excludes. Anything that's filtered out will be deleted from Dest.
--
-- Rsync checks each name to be transferred against its list of Filter
-- rules, and the first matching one is acted on. If no matching rule
-- is found, the file is processed.
syncDirFiltered :: [Filter] -> Src -> Dest -> Property DebianLike
syncDirFiltered filters src dest = rsync $
	[ "-av"
	-- Add trailing '/' to get rsync to sync the Dest directory,
	-- rather than a subdir inside it, which it will do without a
	-- trailing '/'.
	, addTrailingPathSeparator src
	, addTrailingPathSeparator dest
	, "--delete"
	, "--delete-excluded"
	, "--quiet"
	] ++ map toRsync filters

rsync :: [String] -> Property DebianLike
rsync ps = cmdProperty "rsync" ps
	`assume` MadeChange
	`requires` Apt.installed ["rsync"]
