liveBook · Manning

File synchronization

As explained in Learn Haskell by Example

File synchronization is the process of copying or reconciling files between a source location and a destination location so that their contents correspond.

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:

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

How Learn Haskell by Example covers it

How deep it goes
Explains file comparison, filtering, copying, traversal, state, logs, exception handling, and CLI execution; defers destination-only deletion to an exercise.
Examples
Demonstrates a foo directory tree copied to /tmp/bar with Stack and verbose success output.
What you can do after
Build it from scratch as a configurable Haskell synchronizer that traverses directories, skips unchanged files, and reports outcomes.
How it connects
Connects synchronization to monad transformers, RWST, IO, exception handling, filesystem operations, and command-line parsing.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Learn Haskell by Example. The section links point to the parts of the book it draws on.