diff --git a/arbtt.cabal b/arbtt.cabal
--- a/arbtt.cabal
+++ b/arbtt.cabal
@@ -1,6 +1,6 @@
 name:               arbtt
-version:            0.10
-license:            GPL
+version:            0.10.0.1
+license:            GPL-2
 license-file:       LICENSE
 category:           Desktop
 cabal-version:      >= 1.10
@@ -42,7 +42,7 @@
     build-depends:
                 base >= 4.7 && < 4.11,
                 filepath, directory, transformers, utf8-string, strict, containers,
-                aeson >= 0.6  && < 1.3,
+                aeson >= 0.10  && < 1.3,
                 binary >= 0.5,
                 bytestring, deepseq
 
@@ -61,6 +61,7 @@
         UpgradeLog1
         LeftFold
         LockFile
+        DumpFormat
 
     ghc-options: -rtsopts
 
@@ -104,9 +105,9 @@
         binary >= 0.5,
         deepseq, bytestring, utf8-string, strict,
         transformers, directory, filepath,
-        aeson >= 0.6  && < 1.3,
+        aeson >= 0.10  && < 1.3,
         array == 0.4.* || == 0.5.*,
-        terminal-progress-bar,
+        terminal-progress-bar >= 0.2 && < 0.3,
         bytestring-progress,
         mtl
 
@@ -147,7 +148,7 @@
         base >= 4.7 && < 4.11,
         parsec == 3.*,
         containers == 0.5.*,
-        aeson >= 0.6  && < 1.3,
+        aeson >= 0.10  && < 1.3,
         array == 0.4.* || == 0.5.*,
         binary >= 0.5,
         deepseq, bytestring, utf8-string, strict,
@@ -185,9 +186,8 @@
         parsec == 3.*,
         containers == 0.5.*,
         binary >= 0.5,
-        aeson >= 0.6  && < 1.3,
-        conduit == 1.2.*,
-        conduit-extra == 1.2.*,
+        aeson >= 0.10  && < 1.3,
+        conduit >= 1.2 && < 1.4,
         exceptions == 0.8.*,
         attoparsec == 0.13.*,
         deepseq, bytestring, utf8-string, strict,
@@ -208,14 +208,29 @@
         CommonStartup
         TimeLog
         LockFile
+        DumpFormat
     ghc-options: -rtsopts
     if os(windows)
         cpp-options:    -DWIN32
+        other-modules:
+            System.Win32.Mutex
     else
         other-modules:
             System.Locale.SetLocale
     default-language: Haskell98
 
+    -- We inline Data.Conduit.Attoparsec, copied
+    -- from conduit-extra to avoid dependencies on network etc.
+    -- The build-depends below are exclusively used by these modules
+    other-modules:
+        Data.Conduit.Attoparsec
+        Data.Conduit.Binary
+        Data.Streaming.FileRead
+    build-depends:
+        text,
+        resourcet >= 1.2,
+        unliftio-core
+
 executable arbtt-recover
     main-is:            recover-main.hs
     hs-source-dirs:     src
@@ -266,7 +281,7 @@
     TimeLog
   Build-depends:
       base >= 4.7 && < 4.11
-      , tasty >= 0.7 && < 0.13
+      , tasty >= 0.7 && < 1.1
       , tasty-golden >= 2.2.0.2  && < 2.4
       , tasty-hunit >= 0.2  && < 0.11
       , process-extras >= 0.2 && < 0.8
diff --git a/src/Data/Conduit/Attoparsec.hs b/src/Data/Conduit/Attoparsec.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Conduit/Attoparsec.hs
@@ -0,0 +1,246 @@
+{-# LANGUAGE BangPatterns       #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE FlexibleContexts   #-}
+{-# LANGUAGE RankNTypes         #-}
+
+-- |
+-- Copyright: 2011 Michael Snoyman, 2010 John Millikin
+-- License: MIT
+--
+-- Consume attoparsec parsers via conduit.
+--
+-- This code was taken from attoparsec-enumerator and adapted for conduits.
+module Data.Conduit.Attoparsec
+    ( -- * Sink
+      sinkParser
+    , sinkParserEither
+      -- * Conduit
+    , conduitParser
+    , conduitParserEither
+
+      -- * Types
+    , ParseError (..)
+    , Position (..)
+    , PositionRange (..)
+      -- * Classes
+    , AttoparsecInput
+    ) where
+
+import           Control.Exception          (Exception)
+import           Control.Monad              (unless)
+import qualified Data.ByteString            as B
+import qualified Data.Text                  as T
+import qualified Data.Text.Internal         as TI
+import           Data.Typeable              (Typeable)
+import           Prelude                    hiding (lines)
+
+import qualified Data.Attoparsec.ByteString
+import qualified Data.Attoparsec.Text
+import qualified Data.Attoparsec.Types      as A
+import           Data.Conduit
+import Control.Monad.Trans.Resource (MonadThrow, throwM)
+
+-- | The context and message from a 'A.Fail' value.
+data ParseError = ParseError
+    { errorContexts :: [String]
+    , errorMessage  :: String
+    , errorPosition :: Position
+    } | DivergentParser
+    deriving (Show, Typeable)
+
+instance Exception ParseError
+
+data Position = Position
+    { posLine :: {-# UNPACK #-} !Int
+    , posCol  :: {-# UNPACK #-} !Int
+    , posOffset :: {-# UNPACK #-} !Int
+    -- ^ @since 1.2.0
+    }
+    deriving (Eq, Ord)
+
+instance Show Position where
+    show (Position l c off) = show l ++ ':' : show c ++ " (" ++ show off ++ ")"
+
+data PositionRange = PositionRange
+    { posRangeStart :: {-# UNPACK #-} !Position
+    , posRangeEnd   :: {-# UNPACK #-} !Position
+    }
+    deriving (Eq, Ord)
+
+instance Show PositionRange where
+    show (PositionRange s e) = show s ++ '-' : show e
+
+-- | A class of types which may be consumed by an Attoparsec parser.
+class AttoparsecInput a where
+    parseA :: A.Parser a b -> a -> A.IResult a b
+    feedA :: A.IResult a b -> a -> A.IResult a b
+    empty :: a
+    isNull :: a -> Bool
+    notEmpty :: [a] -> [a]
+    getLinesCols :: a -> Position
+
+    -- | Return the beginning of the first input with the length of
+    -- the second input removed. Assumes the second string is shorter
+    -- than the first.
+    stripFromEnd :: a -> a -> a
+
+instance AttoparsecInput B.ByteString where
+    parseA = Data.Attoparsec.ByteString.parse
+    feedA = Data.Attoparsec.ByteString.feed
+    empty = B.empty
+    isNull = B.null
+    notEmpty = filter (not . B.null)
+    getLinesCols = B.foldl' f (Position 0 0 0)
+      where
+        f (Position l c o) ch
+          | ch == 10 = Position (l + 1) 0 (o + 1)
+          | otherwise = Position l (c + 1) (o + 1)
+    stripFromEnd b1 b2 = B.take (B.length b1 - B.length b2) b1
+
+instance AttoparsecInput T.Text where
+    parseA = Data.Attoparsec.Text.parse
+    feedA = Data.Attoparsec.Text.feed
+    empty = T.empty
+    isNull = T.null
+    notEmpty = filter (not . T.null)
+    getLinesCols = T.foldl' f (Position 0 0 0)
+      where
+        f (Position l c o) ch
+          | ch == '\n' = Position (l + 1) 0 (o + 1)
+          | otherwise = Position l (c + 1) (o + 1)
+    stripFromEnd (TI.Text arr1 off1 len1) (TI.Text _ _ len2) =
+        TI.text arr1 off1 (len1 - len2)
+
+-- | Convert an Attoparsec 'A.Parser' into a 'Sink'. The parser will
+-- be streamed bytes until it returns 'A.Done' or 'A.Fail'.
+--
+-- If parsing fails, a 'ParseError' will be thrown with 'throwM'.
+--
+-- Since 0.5.0
+sinkParser :: (AttoparsecInput a, MonadThrow m) => A.Parser a b -> Consumer a m b
+sinkParser = fmap snd . sinkParserPosErr (Position 1 1 0)
+
+-- | Same as 'sinkParser', but we return an 'Either' type instead
+-- of raising an exception.
+--
+-- Since 1.1.5
+sinkParserEither :: (AttoparsecInput a, Monad m) => A.Parser a b -> Consumer a m (Either ParseError b)
+sinkParserEither = (fmap.fmap) snd . sinkParserPos (Position 1 1 0)
+
+
+-- | Consume a stream of parsed tokens, returning both the token and
+-- the position it appears at. This function will raise a 'ParseError'
+-- on bad input.
+--
+-- Since 0.5.0
+conduitParser :: (AttoparsecInput a, MonadThrow m) => A.Parser a b -> Conduit a m (PositionRange, b)
+conduitParser parser =
+    conduit $ Position 1 1 0
+       where
+         conduit !pos = await >>= maybe (return ()) go
+             where
+               go x = do
+                   leftover x
+                   (!pos', !res) <- sinkParserPosErr pos parser
+                   yield (PositionRange pos pos', res)
+                   conduit pos'
+{-# SPECIALIZE conduitParser
+                   :: MonadThrow m
+                   => A.Parser T.Text b
+                   -> Conduit T.Text m (PositionRange, b) #-}
+{-# SPECIALIZE conduitParser
+                   :: MonadThrow m
+                   => A.Parser B.ByteString b
+                   -> Conduit B.ByteString m (PositionRange, b) #-}
+
+
+
+-- | Same as 'conduitParser', but we return an 'Either' type instead
+-- of raising an exception.
+conduitParserEither
+    :: (Monad m, AttoparsecInput a)
+    => A.Parser a b
+    -> Conduit a m (Either ParseError (PositionRange, b))
+conduitParserEither parser =
+    conduit $ Position 1 1 0
+  where
+    conduit !pos = await >>= maybe (return ()) go
+      where
+        go x = do
+          leftover x
+          eres <- sinkParserPos pos parser
+          case eres of
+            Left e -> yield $ Left e
+            Right (!pos', !res) -> do
+              yield $! Right (PositionRange pos pos', res)
+              conduit pos'
+{-# SPECIALIZE conduitParserEither
+                   :: Monad m
+                   => A.Parser T.Text b
+                   -> Conduit T.Text m (Either ParseError (PositionRange, b)) #-}
+{-# SPECIALIZE conduitParserEither
+                   :: Monad m
+                   => A.Parser B.ByteString b
+                   -> Conduit B.ByteString m (Either ParseError (PositionRange, b)) #-}
+
+
+
+
+sinkParserPosErr
+    :: (AttoparsecInput a, MonadThrow m)
+    => Position
+    -> A.Parser a b
+    -> Consumer a m (Position, b)
+sinkParserPosErr pos0 p = sinkParserPos pos0 p >>= f
+    where
+      f (Left e) = throwM e
+      f (Right a) = return a
+{-# INLINE sinkParserPosErr #-}
+
+
+sinkParserPos
+    :: (AttoparsecInput a, Monad m)
+    => Position
+    -> A.Parser a b
+    -> Consumer a m (Either ParseError (Position, b))
+sinkParserPos pos0 p = sink empty pos0 (parseA p)
+  where
+    sink prev pos parser = await >>= maybe close push
+      where
+        push c
+            | isNull c  = sink prev pos parser
+            | otherwise = go False c $ parser c
+
+        close = go True prev (feedA (parser empty) empty)
+
+        go end c (A.Done lo x) = do
+            let pos'
+                    | end       = pos
+                    | otherwise = addLinesCols prev pos
+                y = stripFromEnd c lo
+                pos'' = addLinesCols y pos'
+            unless (isNull lo) $ leftover lo
+            pos'' `seq` return $! Right (pos'', x)
+        go end c (A.Fail rest contexts msg) =
+            let x = stripFromEnd c rest
+                pos'
+                    | end       = pos
+                    | otherwise = addLinesCols prev pos
+                pos'' = addLinesCols x pos'
+             in pos'' `seq` return $! Left (ParseError contexts msg pos'')
+        go end c (A.Partial parser')
+            | end       = return $! Left DivergentParser
+            | otherwise =
+                pos' `seq` sink c pos' parser'
+              where
+                pos' = addLinesCols prev pos
+
+    addLinesCols :: AttoparsecInput a => a -> Position -> Position
+    addLinesCols x (Position lines cols off) =
+        lines' `seq` cols' `seq` off' `seq` Position lines' cols' off'
+      where
+        Position dlines dcols doff = getLinesCols x
+        lines' = lines + dlines
+        cols' = (if dlines > 0 then 1 else cols) + dcols
+        off' = off + doff
+{-# INLINE sinkParserPos #-}
diff --git a/src/Data/Conduit/Binary.hs b/src/Data/Conduit/Binary.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Conduit/Binary.hs
@@ -0,0 +1,688 @@
+{-# LANGUAGE CPP, RankNTypes #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+-- | Functions for interacting with bytes.
+--
+-- For many purposes, it's recommended to use the conduit-combinators library,
+-- which provides a more complete set of functions.
+module Data.Conduit.Binary
+    ( -- * Files and @Handle@s
+
+      -- | Note that most of these functions live in the @MonadResource@ monad
+      -- to ensure resource finalization even in the presence of exceptions. In
+      -- order to run such code, you will need to use @runResourceT@.
+
+      -- ** Sources
+      sourceFile
+    , sourceHandle
+    , sourceHandleUnsafe
+    , sourceIOHandle
+    , sourceFileRange
+    , sourceHandleRange
+    , sourceHandleRangeWithBuffer
+    , withSourceFile
+      -- ** Sinks
+    , sinkFile
+    , sinkFileCautious
+    , sinkTempFile
+    , sinkSystemTempFile
+    , sinkHandle
+    , sinkIOHandle
+    , sinkHandleBuilder
+    , sinkHandleFlush
+    , withSinkFile
+    , withSinkFileBuilder
+    , withSinkFileCautious
+      -- ** Conduits
+    , conduitFile
+    , conduitHandle
+      -- * Utilities
+      -- ** Sources
+    , sourceLbs
+      -- ** Sinks
+    , head
+    , dropWhile
+    , take
+    , drop
+    , sinkCacheLength
+    , sinkLbs
+    , mapM_
+      -- *** Storable
+    , sinkStorable
+    , sinkStorableEx
+      -- ** Conduits
+    , isolate
+    , takeWhile
+    , Data.Conduit.Binary.lines
+    ) where
+
+import qualified Data.ByteString.Builder as BB
+import qualified Data.Streaming.FileRead as FR
+import Prelude hiding (head, take, drop, takeWhile, dropWhile, mapM_)
+import qualified Data.ByteString as S
+import Data.ByteString.Unsafe (unsafeUseAsCString)
+import qualified Data.ByteString.Lazy as L
+import Data.Conduit
+import Data.Conduit.List (sourceList, consume)
+import qualified Data.Conduit.List as CL
+import Control.Exception (assert, finally, bracket)
+import Control.Monad (unless, when)
+import Control.Monad.IO.Class (liftIO, MonadIO)
+import Control.Monad.IO.Unlift
+import Control.Monad.Trans.Resource (allocate, release)
+import Control.Monad.Trans.Class (lift)
+import qualified System.IO as IO
+import Data.Word (Word8, Word64)
+#if (__GLASGOW_HASKELL__ < 710)
+import Control.Applicative ((<$>))
+#endif
+import System.Directory (getTemporaryDirectory, removeFile)
+import Data.ByteString.Lazy.Internal (defaultChunkSize)
+import Data.ByteString.Internal (ByteString (PS), inlinePerformIO)
+import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
+import Foreign.ForeignPtr (touchForeignPtr)
+import Foreign.Ptr (plusPtr, castPtr)
+import Foreign.Storable (Storable, peek, sizeOf)
+import GHC.ForeignPtr           (mallocPlainForeignPtrBytes)
+import Control.Monad.Trans.Resource (MonadResource)
+import Control.Monad.Catch (MonadThrow (..))
+import Control.Exception (Exception)
+import Data.Typeable (Typeable)
+import Foreign.Ptr (Ptr)
+#ifndef ALLOW_UNALIGNED_ACCESS
+import Foreign.Marshal (alloca, copyBytes)
+#endif
+import System.Directory (renameFile)
+import System.FilePath (takeDirectory, takeFileName, (<.>))
+import System.IO (hClose, openBinaryTempFile)
+import Control.Exception (throwIO, catch)
+import System.IO.Error (isDoesNotExistError)
+import qualified Data.ByteString.Builder as BB
+
+-- | Stream the contents of a file as binary data.
+--
+-- Since 0.3.0
+sourceFile :: MonadResource m
+           => FilePath
+           -> Producer m S.ByteString
+sourceFile fp =
+    bracketP
+        (FR.openFile fp)
+         FR.closeFile
+         loop
+  where
+    loop h = do
+        bs <- liftIO $ FR.readChunk h
+        unless (S.null bs) $ do
+            yield bs
+            loop h
+
+-- | Stream the contents of a 'IO.Handle' as binary data. Note that this
+-- function will /not/ automatically close the @Handle@ when processing
+-- completes, since it did not acquire the @Handle@ in the first place.
+--
+-- Since 0.3.0
+sourceHandle :: MonadIO m
+             => IO.Handle
+             -> Producer m S.ByteString
+sourceHandle h =
+    loop
+  where
+    loop = do
+        bs <- liftIO (S.hGetSome h defaultChunkSize)
+        if S.null bs
+            then return ()
+            else yield bs >> loop
+
+-- | Same as @sourceHandle@, but instead of allocating a new buffer for each
+-- incoming chunk of data, reuses the same buffer. Therefore, the @ByteString@s
+-- yielded by this function are not referentially transparent between two
+-- different @yield@s.
+--
+-- This function will be slightly more efficient than @sourceHandle@ by
+-- avoiding allocations and reducing garbage collections, but should only be
+-- used if you can guarantee that you do not reuse a @ByteString@ (or any slice
+-- thereof) between two calls to @await@.
+--
+-- Since 1.0.12
+sourceHandleUnsafe :: MonadIO m => IO.Handle -> Source m ByteString
+sourceHandleUnsafe handle = do
+    fptr <- liftIO $ mallocPlainForeignPtrBytes defaultChunkSize
+    let ptr = unsafeForeignPtrToPtr fptr
+        loop = do
+            count <- liftIO $ IO.hGetBuf handle ptr defaultChunkSize
+            when (count > 0) $ do
+                yield (PS fptr 0 count)
+                loop
+
+    loop
+
+    liftIO $ touchForeignPtr fptr
+
+-- | An alternative to 'sourceHandle'.
+-- Instead of taking a pre-opened 'IO.Handle', it takes an action that opens
+-- a 'IO.Handle' (in read mode), so that it can open it only when needed
+-- and close it as soon as possible.
+--
+-- Since 0.3.0
+sourceIOHandle :: MonadResource m
+               => IO IO.Handle
+               -> Producer m S.ByteString
+sourceIOHandle alloc = bracketP alloc IO.hClose sourceHandle
+
+-- | Stream all incoming data to the given 'IO.Handle'. Note that this function
+-- does /not/ flush and will /not/ close the @Handle@ when processing completes.
+--
+-- Since 0.3.0
+sinkHandle :: MonadIO m
+           => IO.Handle
+           -> Consumer S.ByteString m ()
+sinkHandle h = awaitForever (liftIO . S.hPut h)
+
+-- | Stream incoming builders, executing them directly on the buffer of the
+-- given 'IO.Handle'. Note that this function does /not/ automatically close the
+-- @Handle@ when processing completes.
+-- Pass 'Data.ByteString.Builder.Extra.flush' to flush the buffer.
+--
+-- @since 1.2.2
+sinkHandleBuilder :: MonadIO m => IO.Handle -> ConduitM BB.Builder o m ()
+sinkHandleBuilder h = awaitForever (liftIO . BB.hPutBuilder h)
+
+-- | Stream incoming @Flush@es, executing them on @IO.Handle@
+-- Note that this function does /not/ automatically close the @Handle@ when
+-- processing completes
+--
+-- @since 1.2.2
+sinkHandleFlush :: MonadIO m
+                => IO.Handle
+                -> ConduitM (Flush S.ByteString) o m ()
+sinkHandleFlush h =
+  awaitForever $ \mbs -> liftIO $
+  case mbs of
+    Chunk bs -> S.hPut h bs
+    Flush -> IO.hFlush h
+
+-- | An alternative to 'sinkHandle'.
+-- Instead of taking a pre-opened 'IO.Handle', it takes an action that opens
+-- a 'IO.Handle' (in write mode), so that it can open it only when needed
+-- and close it as soon as possible.
+--
+-- Since 0.3.0
+sinkIOHandle :: MonadResource m
+             => IO IO.Handle
+             -> Consumer S.ByteString m ()
+sinkIOHandle alloc = bracketP alloc IO.hClose sinkHandle
+
+-- | Stream the contents of a file as binary data, starting from a certain
+-- offset and only consuming up to a certain number of bytes.
+--
+-- Since 0.3.0
+sourceFileRange :: MonadResource m
+                => FilePath
+                -> Maybe Integer -- ^ Offset
+                -> Maybe Integer -- ^ Maximum count
+                -> Producer m S.ByteString
+sourceFileRange fp offset count = bracketP
+    (IO.openBinaryFile fp IO.ReadMode)
+    IO.hClose
+    (\h -> sourceHandleRange h offset count)
+
+-- | Stream the contents of a handle as binary data, starting from a certain
+-- offset and only consuming up to a certain number of bytes.
+--
+-- Since 1.0.8
+sourceHandleRange :: MonadIO m
+                  => IO.Handle
+                  -> Maybe Integer -- ^ Offset
+                  -> Maybe Integer -- ^ Maximum count
+                  -> Producer m S.ByteString
+sourceHandleRange handle offset count =
+  sourceHandleRangeWithBuffer handle offset count defaultChunkSize
+
+-- | Stream the contents of a handle as binary data, starting from a certain
+-- offset and only consuming up to a certain number of bytes. This function
+-- consumes chunks as specified by the buffer size.
+--
+-- Since 1.1.8
+sourceHandleRangeWithBuffer :: MonadIO m
+                  => IO.Handle
+                  -> Maybe Integer -- ^ Offset
+                  -> Maybe Integer -- ^ Maximum count
+                  -> Int -- ^ Buffer size
+                  -> Producer m S.ByteString
+sourceHandleRangeWithBuffer handle offset count buffer = do
+    case offset of
+        Nothing -> return ()
+        Just off -> liftIO $ IO.hSeek handle IO.AbsoluteSeek off
+    case count of
+        Nothing -> pullUnlimited
+        Just c -> pullLimited (fromInteger c)
+  where
+    pullUnlimited = do
+        bs <- liftIO $ S.hGetSome handle buffer
+        if S.null bs
+            then return ()
+            else do
+                yield bs
+                pullUnlimited
+
+    pullLimited c = do
+        bs <- liftIO $ S.hGetSome handle (min c buffer)
+        let c' = c - S.length bs
+        assert (c' >= 0) $
+            if S.null bs
+                then return ()
+                else do
+                    yield bs
+                    pullLimited c'
+
+-- | Stream all incoming data to the given file.
+--
+-- Since 0.3.0
+sinkFile :: MonadResource m
+         => FilePath
+         -> Consumer S.ByteString m ()
+sinkFile fp = sinkIOHandle (IO.openBinaryFile fp IO.WriteMode)
+
+-- | Cautious version of 'sinkFile'. The idea here is to stream the
+-- values to a temporary file in the same directory of the destination
+-- file, and only on successfully writing the entire file, moves it
+-- atomically to the destination path.
+--
+-- In the event of an exception occurring, the temporary file will be
+-- deleted and no move will be made. If the application shuts down
+-- without running exception handling (such as machine failure or a
+-- SIGKILL), the temporary file will remain and the destination file
+-- will be untouched.
+--
+-- @since 1.1.14
+sinkFileCautious
+  :: MonadResource m
+  => FilePath
+  -> ConduitM S.ByteString o m ()
+sinkFileCautious fp =
+    bracketP (cautiousAcquire fp) cautiousCleanup inner
+  where
+    inner (tmpFP, h) = do
+        sinkHandle h
+        liftIO $ do
+            hClose h
+            renameFile tmpFP fp
+
+-- | Stream data into a temporary file in the given directory with the
+-- given filename pattern, and return the temporary filename. The
+-- temporary file will be automatically deleted when exiting the
+-- active 'ResourceT' block, if it still exists.
+--
+-- @since 1.1.15
+sinkTempFile :: MonadResource m
+             => FilePath -- ^ temp directory
+             -> String -- ^ filename pattern
+             -> ConduitM ByteString o m FilePath
+sinkTempFile tmpdir pattern = do
+    (_releaseKey, (fp, h)) <- allocate
+        (IO.openBinaryTempFile tmpdir pattern)
+        (\(fp, h) -> IO.hClose h `finally` (removeFile fp `Control.Exception.catch` \e ->
+            if isDoesNotExistError e
+                then return ()
+                else throwIO e))
+    sinkHandle h
+    liftIO $ IO.hClose h
+    return fp
+
+-- | Same as 'sinkTempFile', but will use the default temp file
+-- directory for the system as the first argument.
+--
+-- @since 1.1.15
+sinkSystemTempFile
+    :: MonadResource m
+    => String -- ^ filename pattern
+    -> ConduitM ByteString o m FilePath
+sinkSystemTempFile pattern = do
+    dir <- liftIO getTemporaryDirectory
+    sinkTempFile dir pattern
+
+-- | Stream the contents of the input to a file, and also send it along the
+-- pipeline. Similar in concept to the Unix command @tee@.
+--
+-- Since 0.3.0
+conduitFile :: MonadResource m
+            => FilePath
+            -> Conduit S.ByteString m S.ByteString
+conduitFile fp = bracketP
+    (IO.openBinaryFile fp IO.WriteMode)
+    IO.hClose
+    conduitHandle
+
+-- | Stream the contents of the input to a @Handle@, and also send it along the
+-- pipeline. Similar in concept to the Unix command @tee@. Like @sourceHandle@,
+-- does not close the handle on completion. Related to: @conduitFile@.
+--
+-- Since 1.0.9
+conduitHandle :: MonadIO m => IO.Handle -> Conduit S.ByteString m S.ByteString
+conduitHandle h = awaitForever $ \bs -> liftIO (S.hPut h bs) >> yield bs
+
+-- | Ensure that only up to the given number of bytes are consumed by the inner
+-- sink. Note that this does /not/ ensure that all of those bytes are in fact
+-- consumed.
+--
+-- Since 0.3.0
+isolate :: Monad m
+        => Int
+        -> Conduit S.ByteString m S.ByteString
+isolate =
+    loop
+  where
+    loop 0 = return ()
+    loop count = do
+        mbs <- await
+        case mbs of
+            Nothing -> return ()
+            Just bs -> do
+                let (a, b) = S.splitAt count bs
+                case count - S.length a of
+                    0 -> do
+                        unless (S.null b) $ leftover b
+                        yield a
+                    count' -> assert (S.null b) $ yield a >> loop count'
+
+-- | Return the next byte from the stream, if available.
+--
+-- Since 0.3.0
+head :: Monad m => Consumer S.ByteString m (Maybe Word8)
+head = do
+    mbs <- await
+    case mbs of
+        Nothing -> return Nothing
+        Just bs ->
+            case S.uncons bs of
+                Nothing -> head
+                Just (w, bs') -> leftover bs' >> return (Just w)
+
+-- | Return all bytes while the predicate returns @True@.
+--
+-- Since 0.3.0
+takeWhile :: Monad m => (Word8 -> Bool) -> Conduit S.ByteString m S.ByteString
+takeWhile p =
+    loop
+  where
+    loop = await >>= maybe (return ()) go
+
+    go bs
+        | S.null x = next
+        | otherwise = yield x >> next
+      where
+        next = if S.null y then loop else leftover y
+        (x, y) = S.span p bs
+
+-- | Ignore all bytes while the predicate returns @True@.
+--
+-- Since 0.3.0
+dropWhile :: Monad m => (Word8 -> Bool) -> Consumer S.ByteString m ()
+dropWhile p =
+    loop
+  where
+    loop = do
+        mbs <- await
+        case S.dropWhile p <$> mbs of
+            Nothing -> return ()
+            Just bs
+                | S.null bs -> loop
+                | otherwise -> leftover bs
+
+-- | Take the given number of bytes, if available.
+--
+-- Since 0.3.0
+take :: Monad m => Int -> Consumer S.ByteString m L.ByteString
+take  0 = return L.empty
+take n0 = go n0 id
+  where
+    go n front =
+        await >>= maybe (return $ L.fromChunks $ front []) go'
+      where
+        go' bs =
+            case S.length bs `compare` n of
+                LT -> go (n - S.length bs) (front . (bs:))
+                EQ -> return $ L.fromChunks $ front [bs]
+                GT ->
+                    let (x, y) = S.splitAt n bs
+                     in assert (not $ S.null y) $ leftover y >> return (L.fromChunks $ front [x])
+
+-- | Drop up to the given number of bytes.
+--
+-- Since 0.5.0
+drop :: Monad m => Int -> Consumer S.ByteString m ()
+drop  0 = return ()
+drop n0 = go n0
+  where
+    go n =
+        await >>= maybe (return ()) go'
+      where
+        go' bs =
+            case S.length bs `compare` n of
+                LT -> go (n - S.length bs)
+                EQ -> return ()
+                GT ->
+                    let y = S.drop n bs
+                     in assert (not $ S.null y) $ leftover y >> return ()
+
+-- | Split the input bytes into lines. In other words, split on the LF byte
+-- (10), and strip it from the output.
+--
+-- Since 0.3.0
+lines :: Monad m => Conduit S.ByteString m S.ByteString
+lines =
+    loop []
+  where
+    loop acc = await >>= maybe (finish acc) (go acc)
+
+    finish acc =
+        let final = S.concat $ reverse acc
+         in unless (S.null final) (yield final)
+
+    go acc more =
+        case S.uncons second of
+            Just (_, second') -> yield (S.concat $ reverse $ first:acc) >> go [] second'
+            Nothing -> loop $ more:acc
+      where
+        (first, second) = S.break (== 10) more
+
+-- | Stream the chunks from a lazy bytestring.
+--
+-- Since 0.5.0
+sourceLbs :: Monad m => L.ByteString -> Producer m S.ByteString
+sourceLbs = sourceList . L.toChunks
+
+-- | Stream the input data into a temp file and count the number of bytes
+-- present. When complete, return a new @Source@ reading from the temp file
+-- together with the length of the input in bytes.
+--
+-- All resources will be cleaned up automatically.
+--
+-- Since 1.0.5
+sinkCacheLength :: (MonadResource m1, MonadResource m2)
+                => Sink S.ByteString m1 (Word64, Source m2 S.ByteString)
+sinkCacheLength = do
+    tmpdir <- liftIO getTemporaryDirectory
+    (releaseKey, (fp, h)) <- allocate
+        (IO.openBinaryTempFile tmpdir "conduit.cache")
+        (\(fp, h) -> IO.hClose h `finally` removeFile fp)
+    len <- sinkHandleLen h
+    liftIO $ IO.hClose h
+    return (len, sourceFile fp >> release releaseKey)
+  where
+    sinkHandleLen :: MonadResource m => IO.Handle -> Sink S.ByteString m Word64
+    sinkHandleLen h =
+        loop 0
+      where
+        loop x =
+            await >>= maybe (return x) go
+          where
+            go bs = do
+                liftIO $ S.hPut h bs
+                loop $ x + fromIntegral (S.length bs)
+
+-- | Consume a stream of input into a lazy bytestring. Note that no lazy I\/O
+-- is performed, but rather all content is read into memory strictly.
+--
+-- Since 1.0.5
+sinkLbs :: Monad m => Sink S.ByteString m L.ByteString
+sinkLbs = fmap L.fromChunks consume
+
+mapM_BS :: Monad m => (Word8 -> m ()) -> S.ByteString -> m ()
+mapM_BS f (PS fptr offset len) = do
+    let start = unsafeForeignPtrToPtr fptr `plusPtr` offset
+        end = start `plusPtr` len
+        loop ptr
+            | ptr >= end = inlinePerformIO (touchForeignPtr fptr) `seq` return ()
+            | otherwise = do
+                f (inlinePerformIO (peek ptr))
+                loop (ptr `plusPtr` 1)
+    loop start
+{-# INLINE mapM_BS #-}
+
+-- | Perform a computation on each @Word8@ in a stream.
+--
+-- Since 1.0.10
+mapM_ :: Monad m => (Word8 -> m ()) -> Consumer S.ByteString m ()
+mapM_ f = awaitForever (lift . mapM_BS f)
+{-# INLINE mapM_ #-}
+
+-- | Consume some instance of @Storable@ from the incoming byte stream. In the
+-- event of insufficient bytes in the stream, returns a @Nothing@ and returns
+-- all unused input as leftovers.
+--
+-- @since 1.1.13
+sinkStorable :: (Monad m, Storable a) => Consumer S.ByteString m (Maybe a)
+sinkStorable = sinkStorableHelper Just (return Nothing)
+
+-- | Same as 'sinkStorable', but throws a 'SinkStorableInsufficientBytes'
+-- exception (via 'throwM') in the event of insufficient bytes. This can be
+-- more efficient to use than 'sinkStorable' as it avoids the need to
+-- construct/deconstruct a @Maybe@ wrapper in the success case.
+--
+-- @since 1.1.13
+sinkStorableEx :: (MonadThrow m, Storable a) => Consumer S.ByteString m a
+sinkStorableEx = sinkStorableHelper id (throwM SinkStorableInsufficientBytes)
+
+sinkStorableHelper :: forall m a b. (Monad m, Storable a)
+                   => (a -> b)
+                   -> (Consumer S.ByteString m b)
+                   -> Consumer S.ByteString m b
+sinkStorableHelper wrap failure = do
+    start
+  where
+    size = sizeOf (undefined :: a)
+
+    -- try the optimal case: next chunk has all the data we need
+    start = do
+        mbs <- await
+        case mbs of
+            Nothing -> failure
+            Just bs
+                | S.null bs -> start
+                | otherwise ->
+                    case compare (S.length bs) size of
+                        LT -> do
+                            -- looks like we're stuck concating
+                            leftover bs
+                            lbs <- take size
+                            let bs = S.concat $ L.toChunks lbs
+                            case compare (S.length bs) size of
+                                LT -> do
+                                    leftover bs
+                                    failure
+                                EQ -> process bs
+                                GT -> assert False (process bs)
+                        EQ -> process bs
+                        GT -> do
+                            let (x, y) = S.splitAt size bs
+                            leftover y
+                            process x
+
+    -- Given a bytestring of exactly the correct size, grab the value
+    process bs = return $! wrap $! inlinePerformIO $!
+        unsafeUseAsCString bs (safePeek undefined . castPtr)
+
+    safePeek :: a -> Ptr a -> IO a
+#ifdef ALLOW_UNALIGNED_ACCESS
+    safePeek _ = peek
+#else
+    safePeek val ptr = alloca (\t -> copyBytes t ptr (sizeOf val) >> peek t)
+#endif
+{-# INLINE sinkStorableHelper #-}
+
+data SinkStorableException = SinkStorableInsufficientBytes
+    deriving (Show, Typeable)
+instance Exception SinkStorableException
+
+-- | Like 'IO.withBinaryFile', but provides a source to read bytes from.
+--
+-- @since 1.2.1
+withSourceFile
+  :: (MonadUnliftIO m, MonadIO n)
+  => FilePath
+  -> (ConduitM i ByteString n () -> m a)
+  -> m a
+withSourceFile fp inner =
+  withRunInIO $ \run ->
+  IO.withBinaryFile fp IO.ReadMode $
+  run . inner . sourceHandle
+
+-- | Like 'IO.withBinaryFile', but provides a sink to write bytes to.
+--
+-- @since 1.2.1
+withSinkFile
+  :: (MonadUnliftIO m, MonadIO n)
+  => FilePath
+  -> (ConduitM ByteString o n () -> m a)
+  -> m a
+withSinkFile fp inner =
+  withRunInIO $ \run ->
+  IO.withBinaryFile fp IO.ReadMode $
+  run . inner . sinkHandle
+
+-- | Same as 'withSinkFile', but lets you use a 'BB.Builder'.
+--
+-- @since 1.2.1
+withSinkFileBuilder
+  :: (MonadUnliftIO m, MonadIO n)
+  => FilePath
+  -> (ConduitM BB.Builder o n () -> m a)
+  -> m a
+withSinkFileBuilder fp inner =
+  withRunInIO $ \run ->
+  IO.withBinaryFile fp IO.ReadMode $ \h ->
+  run $ inner $ CL.mapM_ (liftIO . BB.hPutBuilder h)
+
+-- | Like 'sinkFileCautious', but uses the @with@ pattern instead of
+-- @MonadResource@.
+--
+-- @since 1.2.2
+withSinkFileCautious
+  :: (MonadUnliftIO m, MonadIO n)
+  => FilePath
+  -> (ConduitM S.ByteString o n () -> m a)
+  -> m a
+withSinkFileCautious fp inner =
+  withRunInIO $ \run -> bracket
+    (cautiousAcquire fp)
+    cautiousCleanup
+    (\(tmpFP, h) -> do
+        a <- run $ inner $ sinkHandle h
+        hClose h
+        renameFile tmpFP fp
+        return a)
+
+-- | Helper function for Cautious functions above, do not export!
+cautiousAcquire :: FilePath -> IO (FilePath, IO.Handle)
+cautiousAcquire fp = openBinaryTempFile (takeDirectory fp) (takeFileName fp <.> "tmp")
+
+-- | Helper function for Cautious functions above, do not export!
+cautiousCleanup :: (FilePath, IO.Handle) -> IO ()
+cautiousCleanup (tmpFP, h) = do
+  hClose h
+  removeFile tmpFP `Control.Exception.catch` \e ->
+    if isDoesNotExistError e
+      then return ()
+      else throwIO e
diff --git a/src/Data/Streaming/FileRead.hs b/src/Data/Streaming/FileRead.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Streaming/FileRead.hs
@@ -0,0 +1,37 @@
+{-# LANGUAGE CPP #-}
+-- | The standard @openFile@ call on Windows causing problematic file locking
+-- in some cases. This module provides a cross-platform file reading API
+-- without the file locking problems on Windows.
+--
+-- This module /always/ opens files in binary mode.
+--
+-- @readChunk@ will return an empty @ByteString@ on EOF.
+module Data.Streaming.FileRead
+    ( ReadHandle
+    , openFile
+    , closeFile
+    , readChunk
+    ) where
+
+#if WINDOWS
+
+import System.Win32File
+
+#else
+
+import qualified System.IO as IO
+import qualified Data.ByteString as S
+import Data.ByteString.Lazy.Internal (defaultChunkSize)
+
+newtype ReadHandle = ReadHandle IO.Handle
+
+openFile :: FilePath -> IO ReadHandle
+openFile fp = ReadHandle `fmap` IO.openBinaryFile fp IO.ReadMode
+
+closeFile :: ReadHandle -> IO ()
+closeFile (ReadHandle h) = IO.hClose h
+
+readChunk :: ReadHandle -> IO S.ByteString
+readChunk (ReadHandle h) = S.hGetSome h defaultChunkSize
+
+#endif
diff --git a/src/LockFile.hs b/src/LockFile.hs
--- a/src/LockFile.hs
+++ b/src/LockFile.hs
@@ -7,6 +7,7 @@
 
 #ifdef WIN32
 import System.Win32.Mutex
+import Control.Monad (unless)
 #else
 import System.Posix.IO
 #endif
diff --git a/src/stats-main.hs b/src/stats-main.hs
--- a/src/stats-main.hs
+++ b/src/stats-main.hs
@@ -189,7 +189,8 @@
         (_height, width) <- getTermSize
         hPutChar stderr '\r'
         hPutStr stderr $
-            mkProgressBar (msg "Processing data") percentage (fromIntegral width) (fromIntegral b) (fromIntegral size)
+            mkProgressBar (msg "Processing data") percentage (fromIntegral width) $
+                Progress (fromIntegral b) (fromIntegral size)
         when  (fromIntegral b >= fromIntegral size) $ do
             hPutChar stderr '\r'
             hPutStr stderr (replicate width ' ')
