{-# LANGUAGE Safe #-}
module Data.Progress.Meter (
ProgressMeter,
simpleNewMeter,
newMeter,
setComponents,
addComponent,
removeComponent,
setWidth,
renderMeter,
displayMeter,
clearMeter,
writeMeterString,
autoDisplayMeter,
killAutoDisplayMeter
) where
import Data.Progress.Tracker
import Control.Concurrent
import Control.Monad (when)
import Data.String.Utils (join)
import System.Time.Utils (renderSecs)
import Data.Quantity (renderNums, binaryOpts)
import System.IO
import Control.Monad (filterM)
data ProgressMeterR =
ProgressMeterR {masterP :: Progress,
components :: [Progress],
width :: Int,
unit :: String,
renderer :: [Integer] -> [String],
autoDisplayers :: [ThreadId]
}
type ProgressMeter = MVar ProgressMeterR
simpleNewMeter :: Progress -> IO ProgressMeter
simpleNewMeter pt = newMeter pt "B" 80 (renderNums binaryOpts 1)
newMeter :: Progress
-> String
-> Int
-> ([Integer] -> [String])
-> IO ProgressMeter
newMeter tracker u w rfunc =
newMVar $ ProgressMeterR {masterP = tracker, components = [],
width = w, renderer = rfunc, autoDisplayers = [],
unit = u}
setComponents :: ProgressMeter -> [Progress] -> IO ()
setComponents meter componentlist = modifyMVar_ meter (\m -> return $ m {components = componentlist})
addComponent :: ProgressMeter -> Progress -> IO ()
addComponent meter component =
modifyMVar_ meter (\m -> return $ m {components = component : components m})
removeComponent :: ProgressMeter -> String -> IO ()
removeComponent meter componentname = modifyMVar_ meter $ \m ->
do newc <- filterM (\x -> withStatus x (\y -> return $ trackerName y /= componentname))
(components m)
return $ m {components = newc}
setWidth :: ProgressMeter -> Int -> IO ()
setWidth meter w = modifyMVar_ meter (\m -> return $ m {width = w})
displayMeter :: Handle -> ProgressMeter -> IO ()
displayMeter h r = withMVar r $ \meter ->
do s <- renderMeterR meter
hPutStr h ("\r" ++ s)
hFlush h
clearMeter :: Handle -> ProgressMeter -> IO ()
clearMeter h pm = withMVar pm $ \m ->
do hPutStr h (clearmeterstr m)
hFlush h
writeMeterString :: Handle -> ProgressMeter -> String -> IO ()
writeMeterString h pm msg = withMVar pm $ \meter ->
do s <- renderMeterR meter
hPutStr h (clearmeterstr meter)
hPutStr h msg
hPutStr h s
hFlush h
clearmeterstr :: ProgressMeterR -> String
clearmeterstr m = "\r" ++ replicate (width m - 1) ' ' ++ "\r"
autoDisplayMeter :: ProgressMeter
-> Int
-> (ProgressMeter -> IO ())
-> IO ThreadId
autoDisplayMeter pm delay displayfunc =
do thread <- forkIO workerthread
modifyMVar_ pm (\p -> return $ p {autoDisplayers = thread : autoDisplayers p})
return thread
where workerthread = do tid <- myThreadId
yield
loop tid
loop tid = do displayfunc pm
threadDelay (delay * 1000000)
c <- doIContinue tid
when c (loop tid)
doIContinue tid = withMVar pm $ \p ->
if tid `elem` autoDisplayers p
then return True
else return False
killAutoDisplayMeter :: ProgressMeter -> ThreadId -> IO ()
killAutoDisplayMeter pm t =
modifyMVar_ pm (\p -> return $ p {autoDisplayers = filter (/= t) (autoDisplayers p)})
renderMeter :: ProgressMeter -> IO String
renderMeter r = withMVar r $ renderMeterR
renderMeterR :: ProgressMeterR -> IO String
renderMeterR meter =
do overallpct <- renderpct $ masterP meter
compnnts <- mapM (rendercomponent $ renderer meter)
(components meter)
let componentstr = case join " " compnnts of
[] -> ""
x -> x ++ " "
rightpart <- renderoverall (renderer meter) (masterP meter)
let leftpart = overallpct ++ " " ++ componentstr
let padwidth = (width meter) - 1 - (length leftpart) - (length rightpart)
if padwidth < 1
then return $ take (width meter - 1) $ leftpart ++ rightpart
else return $ leftpart ++ replicate padwidth ' ' ++ rightpart
where
u = unit meter
renderpct pt =
withStatus pt renderpctpts
renderpctpts pts =
if (totalUnits pts == 0)
then return "0%"
else return $ show (((completedUnits pts) * 100) `div` (totalUnits pts)) ++ "%"
rendercomponent :: ([Integer] -> [String]) -> Progress -> IO String
rendercomponent rfunc pt = withStatus pt $ \pts ->
do pct <- renderpctpts pts
let renders = rfunc [totalUnits pts, completedUnits pts]
return $ "[" ++ trackerName pts ++ " " ++
(renders !! 1) ++ u ++ "/" ++
head renders ++ u ++ " " ++ pct ++ "]"
renderoverall :: (ProgressStatuses a (IO [Char])) => ([Integer] -> [[Char]]) -> a -> IO [Char]
renderoverall rfunc pt = withStatus pt $ \pts ->
do etr <- getETR pts
speed <- getSpeed pts
return $ head (rfunc [floor (speed :: Double)]) ++ u ++
"/s " ++ renderSecs etr