module System.Taffybar.Information.Network where
import Control.Applicative
import qualified Control.Concurrent.MVar as MV
import Control.Exception (catch, SomeException)
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Maybe (MaybeT(..))
import Data.Maybe ( mapMaybe )
import Data.Time.Clock
import Data.Time.Clock.System
import Safe ( atMay, initSafe, readDef )
import System.Taffybar.Information.StreamInfo ( getParsedInfo )
import System.Taffybar.Util
import Prelude
networkInfoFile :: FilePath
networkInfoFile = "/proc/net/dev"
getNetInfo :: String -> IO (Maybe [Int])
getNetInfo iface = runMaybeT $ do
isInterfaceUp iface
handleFailure $ getParsedInfo networkInfoFile parseDevNet' iface
parseDevNet' :: String -> [(String, [Int])]
parseDevNet' input =
map makeList $ parseDevNet input
where makeList (a, (u, d)) = (a, [u, d])
parseDevNet :: String -> [(String, (Int, Int))]
parseDevNet = mapMaybe (getDeviceUpDown . words) . drop 2 . lines
getDeviceUpDown :: [String] -> Maybe (String, (Int, Int))
getDeviceUpDown s = do
dev <- initSafe <$> s `atMay` 0
down <- readDef (-1) <$> s `atMay` 1
up <- readDef (-1) <$> s `atMay` out
return (dev, (down, up))
where
out = length s - 8
isInterfaceUp :: String -> MaybeT IO ()
isInterfaceUp iface = do
state <- handleFailure $ readFile $ "/sys/class/net/" ++ iface ++ "/operstate"
case state of
'u' : _ -> return ()
_ -> mzero
handleFailure :: IO a -> MaybeT IO a
handleFailure action = MaybeT $ catch (Just <$> action) eToNothing
where
eToNothing :: SomeException -> IO (Maybe a)
eToNothing _ = pure Nothing
getDeviceSamples :: IO (Maybe [TxSample])
getDeviceSamples = runMaybeT $ handleFailure $ do
contents <- readFile networkInfoFile
length contents `seq` return ()
time <- liftIO getSystemTime
let mkSample (device, (up, down)) =
TxSample { sampleUp = up
, sampleDown = down
, sampleTime = time
, sampleDevice = device
}
return $ map mkSample $ parseDevNet contents
data TxSample = TxSample
{ sampleUp :: Int
, sampleDown :: Int
, sampleTime :: SystemTime
, sampleDevice :: String
} deriving (Show, Eq)
monitorNetworkInterfaces
:: RealFrac a1
=> a1 -> ([(String, (Rational, Rational))] -> IO ()) -> IO ()
monitorNetworkInterfaces interval onUpdate = void $ do
samplesVar <- MV.newMVar []
let sampleToSpeeds (device, (s1, s2)) = (device, getSpeed s1 s2)
doOnUpdate samples = do
let speedInfo = map sampleToSpeeds samples
onUpdate speedInfo
return samples
doUpdate = MV.modifyMVar_ samplesVar ((>>= doOnUpdate) . updateSamples)
foreverWithDelay interval doUpdate
updateSamples :: [(String, (TxSample, TxSample))] -> IO [(String, (TxSample, TxSample))]
updateSamples currentSamples = do
let getLast sample@TxSample { sampleDevice = device } =
maybe sample fst $ lookup device currentSamples
getSamplePair sample@TxSample { sampleDevice = device } =
(device, (sample, getLast sample))
maybe currentSamples (map getSamplePair) <$> getDeviceSamples
getSpeed :: TxSample -> TxSample -> (Rational, Rational)
getSpeed TxSample { sampleUp = thisUp
, sampleDown = thisDown
, sampleTime = thisTime
}
TxSample { sampleUp = lastUp
, sampleDown = lastDown
, sampleTime = lastTime
} =
let intervalDiffTime =
diffUTCTime
(systemToUTCTime thisTime)
(systemToUTCTime lastTime)
intervalRatio =
if intervalDiffTime == 0
then 0
else toRational $ 1 / intervalDiffTime
in ( fromIntegral (thisDown - lastDown) * intervalRatio
, fromIntegral (thisUp - lastUp) * intervalRatio
)
sumSpeeds :: [(Rational, Rational)] -> (Rational, Rational)
sumSpeeds = foldr1 sumOne
where
sumOne (d1, u1) (d2, u2) = (d1 + d2, u1 + u2)