File: Progress.hs

package info (click to toggle)
git-annex 8.20210223-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 68,764 kB
  • sloc: haskell: 70,359; javascript: 9,103; sh: 1,304; makefile: 212; perl: 136; ansic: 44
file content (205 lines) | stat: -rw-r--r-- 6,414 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
{- git-annex progress output
 -
 - Copyright 2010-2020 Joey Hess <id@joeyh.name>
 -
 - Licensed under the GNU AGPL version 3 or higher.
 -}

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}

module Messages.Progress where

import Common
import Messages
import Utility.Metered
import Types
import Types.Messages
import Types.Key
import Types.KeySource
import Utility.InodeCache
import qualified Messages.JSON as JSON
import Messages.Concurrent
import Messages.Internal

import qualified System.Console.Regions as Regions
import qualified System.Console.Concurrent as Console
import Control.Monad.IO.Class (MonadIO)
import Data.IORef

{- Class of things from which a size can be gotten to display a progress
 - meter. -}
class MeterSize t where
	getMeterSize :: t -> Annex (Maybe TotalSize)

instance MeterSize t => MeterSize (Maybe t) where
	getMeterSize Nothing = pure Nothing
	getMeterSize (Just t) = getMeterSize t

instance MeterSize FileSize where
	getMeterSize = pure . Just . TotalSize

instance MeterSize Key where
	getMeterSize = pure . fmap TotalSize . fromKey keySize

instance MeterSize InodeCache where
	getMeterSize = pure . Just . TotalSize . inodeCacheFileSize

instance MeterSize KeySource where
	getMeterSize = maybe (pure Nothing) getMeterSize . inodeCache

{- When the key's size is not known, the file is statted to get the size.
 - This allows uploads of keys without size to still have progress
 - displayed.
 -}
data KeySizer = KeySizer Key (Annex (Maybe RawFilePath))

instance MeterSize KeySizer where
	getMeterSize (KeySizer k getsrcfile) = case fromKey keySize k of
		Just sz -> return (Just (TotalSize sz))
		Nothing -> do
			srcfile <- getsrcfile
			case srcfile of
				Nothing -> return Nothing
				Just f -> catchMaybeIO $ liftIO $
					TotalSize <$> getFileSize f

{- Shows a progress meter while performing an action.
 - The action is passed the meter and a callback to use to update the meter.
 --}
metered
	:: MeterSize sizer
	=> Maybe MeterUpdate
	-> sizer
	-> (Meter -> MeterUpdate -> Annex a)
	-> Annex a
metered othermeter sizer a = withMessageState $ \st -> do
	sz <- getMeterSize sizer
	metered' st othermeter sz showOutput a

metered'
	:: (Monad m, MonadIO m, MonadMask m)
	=> MessageState
	-> Maybe MeterUpdate
	-> Maybe TotalSize
	-> m ()
	-- ^ this should run showOutput
	-> (Meter -> MeterUpdate -> m a)
	-> m a
metered' st othermeter msize showoutput a = go st
  where
	go (MessageState { outputType = QuietOutput }) = nometer
	go (MessageState { outputType = NormalOutput, concurrentOutputEnabled = False }) = do
		showoutput
		meter <- liftIO $ mkMeter msize $ 
			displayMeterHandle stdout bandwidthMeter
		m <- liftIO $ rateLimitMeterUpdate consoleratelimit meter $
			updateMeter meter
		r <- a meter (combinemeter m)
		liftIO $ clearMeterHandle meter stdout
		return r
	go (MessageState { outputType = NormalOutput, concurrentOutputEnabled = True }) =
		withProgressRegion st $ \r -> do
			meter <- liftIO $ mkMeter msize $ \_ msize' old new ->
				let s = bandwidthMeter msize' old new
				in Regions.setConsoleRegion r ('\n' : s)
			m <- liftIO $ rateLimitMeterUpdate consoleratelimit meter $
				updateMeter meter
			a meter (combinemeter m)
	go (MessageState { outputType = JSONOutput jsonoptions })
		| jsonProgress jsonoptions = do
			let buf = jsonBuffer st
			meter <- liftIO $ mkMeter msize $ \_ msize' _old new ->
				JSON.progress buf msize' (meterBytesProcessed new)
			m <- liftIO $ rateLimitMeterUpdate jsonratelimit meter $
				updateMeter meter
			a meter (combinemeter m)
		| otherwise = nometer
	go (MessageState { outputType = SerializedOutput h _ }) = do
		liftIO $ outputSerialized h BeginProgressMeter
		case msize of
			Just sz -> liftIO $ outputSerialized h $ UpdateProgressMeterTotalSize sz
			Nothing -> noop
		szv <- liftIO $ newIORef msize
		meter <- liftIO $ mkMeter msize $ \_ msize' _old new -> do
			case msize' of
				Just sz | msize' /= msize -> do
					psz <- readIORef szv
					when (msize' /= psz) $ do
						writeIORef szv msize'
						outputSerialized h $ UpdateProgressMeterTotalSize sz
				_ -> noop
			outputSerialized h $ UpdateProgressMeter $
				meterBytesProcessed new
		m <- liftIO $ rateLimitMeterUpdate minratelimit meter $
			updateMeter meter
		a meter (combinemeter m)
			`finally` (liftIO $ outputSerialized h EndProgressMeter)
	nometer = do
		dummymeter <- liftIO $ mkMeter Nothing $
			\_ _ _ _ -> return ()
		a dummymeter (combinemeter (const noop))

	combinemeter m = case othermeter of
		Nothing -> m
		Just om -> combineMeterUpdate m om

	consoleratelimit = 0.2

	jsonratelimit = 0.1

	minratelimit = min consoleratelimit jsonratelimit

{- Poll file size to display meter. -}
meteredFile :: FilePath -> Maybe MeterUpdate -> Key -> Annex a -> Annex a
meteredFile file combinemeterupdate key a = 
	metered combinemeterupdate key $ \_ p ->
		watchFileSize file p a

{- Progress dots. -}
showProgressDots :: Annex ()
showProgressDots = outputMessage JSON.none "."

{- Runs a command, that may output progress to either stdout or
 - stderr, as well as other messages.
 -
 - In quiet mode, the output is suppressed, except for error messages.
 -}
progressCommand :: FilePath -> [CommandParam] -> Annex Bool
progressCommand cmd params = progressCommandEnv cmd params Nothing

progressCommandEnv :: FilePath -> [CommandParam] -> Maybe [(String, String)] -> Annex Bool
progressCommandEnv cmd params environ = ifM commandProgressDisabled
	( do
		oh <- mkOutputHandler
		liftIO $ demeterCommandEnv oh cmd params environ
	, liftIO $ boolSystemEnv cmd params environ
	)

mkOutputHandler :: Annex OutputHandler
mkOutputHandler = OutputHandler
	<$> commandProgressDisabled
	<*> mkStderrEmitter

mkOutputHandlerQuiet :: Annex OutputHandler
mkOutputHandlerQuiet = OutputHandler
	<$> pure True
	<*> mkStderrEmitter

mkStderrRelayer :: Annex (ProcessHandle -> Handle -> IO ())
mkStderrRelayer = do
	quiet <- commandProgressDisabled
	emitter <- mkStderrEmitter
	return $ \ph h -> avoidProgress quiet ph h emitter

{- Generates an IO action that can be used to emit stderr.
 -
 - When a progress meter is displayed, this takes care to avoid
 - messing it up with interleaved stderr from a command.
 -}
mkStderrEmitter :: Annex (String -> IO ())
mkStderrEmitter = withMessageState go
  where
	go s
		| concurrentOutputEnabled s = return Console.errorConcurrent
		| otherwise = return (hPutStrLn stderr)