File synchronization copies files from a source path to a destination path while avoiding unnecessary copies. A practical synchronizer can restrict eligible file types, recursion depth, and the number of transferred files, while recording successes, skips, and failures. section 15.1 section 15.2
This is one-way copying: source files are traversed and copied into the corresponding destination paths. A destination file is skipped when it exists and has the same modification time as its source; otherwise it is copied. Destination-only files are not removed by the base behavior. section 15.2 section 15.3.4
A synchronization run combines these decisions:
- Read source and destination paths plus optional limits from configuration. section 15.1 section 15.2
- Traverse directories only while the current recursion depth is within the configured bound. section 15.2
- Skip files whose type does not match an optional required type. section 15.2
- Stop copying after an optional maximum transfer count is reached. section 15.1 section 15.2
- Log a success, skip reason, or failure for each attempted operation. section 15.1 section 15.2
The implementation keeps three kinds of data separate: immutable configuration, mutable statistics for recursion depth and transferred files, and an accumulated log. An RWST-based application reads configuration, updates state, and writes output while lifting filesystem operations into IO. Exceptions are caught at the runner boundary, reported to standard error, and cause a failure exit. section 15.1 section 15.2
Listing 15.11 An action to traverse a directory structure and synchronize all found files
traverseAndCopy :: FilePath -> App ()
traverseAndCopy pathSuffix = do
fromPath <- asks configFromPath #1
toPath <- asks configToPath #1
mMaxRecDepth <- asks configMaxRecursionDepth #1
curRecDepth <- gets statsRecursionDepth #1
let path = fromPath </> pathSuffix
when (maybe True (curRecDepth <=) mMaxRecDepth) $ do #2
mFilesAndDirs <-
liftIO $ Dir.safeListFilesAndDirectories path #3
case mFilesAndDirs of
Left ex -> logFailure $ FailureEntry path ex #4
Right (files, dirs) -> do
forM_ files $ \fileName ->
syncFile #5
(fromPath </> pathSuffix </> fileName)
(toPath </> pathSuffix </> fileName)
modify (modifyRecursionDepth (+ 1)) #6
forM_ dirs $ traverseAndCopy . (pathSuffix </>) #7
modify (modifyRecursionDepth (\x -> x - 1)) #8
The traversal action shows how source and destination paths are joined with each relative path, depth is checked, directory listing failures are logged, and each discovered file is sent to synchronization. section 15.2
Synchronizing a directory tree
A tree containing fileA.txt plus bar and baz subdirectories is synchronized with `stack run -- --from-path "foo" --to-path "/tmp/bar" --verbose`. The verbose log marks copied files, and the destination preserves the source directory structure. section 15.3.4
