vision-0.0.5.0: src/Medialib.hs
-- -*-haskell-*-
-- Vision (for the Voice): an XMMS2 client.
--
-- Author: Oleg Belozeorov
-- Created: 18 Jun. 2010
--
-- Copyright (C) 2010, 2011 Oleg Belozeorov
--
-- This program is free software; you can redistribute it and/or
-- modify it under the terms of the GNU General Public License as
-- published by the Free Software Foundation; either version 3 of
-- the License, or (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-- General Public License for more details.
--
module Medialib
( Stamp
, MediaInfo
, RequestPriority (..)
, initMedialib
, requestInfo
, retrieveProperties
, mediaInfoChan
) where
import Control.Concurrent
import Control.Concurrent.STM
import Control.Concurrent.STM.TGVar
import Control.Monad (when, void, forever)
import Control.Monad.Trans
import Data.Map (Map)
import Data.IntMap (IntMap)
import qualified Data.IntMap as IntMap
import qualified Data.IntSet as IntSet
import Data.PSQueue (PSQ, Binding (..))
import qualified Data.PSQueue as PSQ
import XMMS2.Client
import XMMS2.Client.Bindings (propdictToDict)
import Context
import XMMS
import Utils
type Stamp = Int
type MediaInfo = Map String Property
data RequestPriority
= Current
| Visible
| Search
| Changed
| Background
deriving (Eq, Ord)
data CacheEntry
= CEReady Stamp MediaInfo
| CERetrieving RequestPriority
data Cache
= Cache { cEntries :: IntMap CacheEntry
, cNextStamp :: Stamp
}
emptyCache =
Cache { cEntries = IntMap.empty
, cNextStamp = 1
}
data MLib
= MLib { mCache :: TVar (Maybe Cache)
, mMediaInfoChan :: TChan (MediaId, Stamp, MediaInfo)
, mReqQ :: TVar (PSQ MediaId RequestPriority)
}
cache = mCache context
reqQ = mReqQ context
mediaInfoChan = mMediaInfoChan context
initMedialib = do
context <- initContext
let ?context = context
xcW <- atomically $ newTGWatch connectedV
let mon xc
| xc = do
atomically $ writeTVar cache $ Just emptyCache
broadcastMedialibEntryChanged xmms >>* do
id <- result
let id' = fromIntegral id
liftIO $ atomically $ do
cc <- readTVar cache
withJust cc $ \cc ->
when (IntMap.member id' $ cEntries cc) $ do
r <- readTVar reqQ
writeTVar reqQ $ PSQ.insert id Changed r
persist
rt <- forkIO infoReqJob
xc <- atomically $ watch xcW
killThread rt
atomically $ do
writeTVar cache Nothing
writeTVar reqQ PSQ.empty
mon xc
| otherwise = do
xc <- atomically $ watch xcW
mon xc
forkIO $ mon False
return ?context
requestInfo prio id = atomically $ do
cc <- readTVar cache
withJust cc $ \cc ->
let id' = fromIntegral id
entries = cEntries cc
in case IntMap.lookup id' entries of
Nothing -> do
r <- readTVar reqQ
writeTVar reqQ $ PSQ.insert id prio r
writeTVar cache $ Just
cc { cEntries = IntMap.insert id' (CERetrieving prio) entries }
Just (CERetrieving old) | old > prio -> do
r <- readTVar reqQ
writeTVar reqQ $ PSQ.update (const $ Just prio) id r
writeTVar cache $ Just
cc { cEntries = IntMap.insert id' (CERetrieving prio) entries }
Just (CEReady s i) -> do
writeTChan mediaInfoChan (id, s, i)
void $ readTChan mediaInfoChan
_ -> return ()
initContext = do
cache <- newTVarIO Nothing
mediaInfoChan <- newTChanIO
reqQ <- newTVarIO PSQ.empty
return $ augmentContext
MLib { mCache = cache
, mMediaInfoChan = mediaInfoChan
, mReqQ = reqQ
}
retrieveProperties ids f = do
let ids' = IntSet.fromList $ map fromIntegral ids
len = IntSet.size ids'
step = len `div` 100
chan <- atomically $ dupTChan mediaInfoChan
let handler st@(ctr, todo, ready) = do
(id, _, info) <- atomically $ readTChan chan
let id' = fromIntegral id
if IntSet.member id' todo
then do
let todo' = IntSet.delete id' todo
if IntSet.null todo'
then do
f . Right $ reverse ((id, info) : ready)
return ()
else do
let ctr' = ctr + 1
when (step == 0 || ctr' `mod` step == 0) $
f . Left $ fromIntegral ctr' / fromIntegral len
handler (ctr', todo', (id, info) : ready)
else
handler st
tid <- forkIOUnmasked $ handler (0, ids', [])
forkIO $ mapM_ (requestInfo Background . fromIntegral) $ IntSet.toList ids'
return $ killThread tid
infoReqJob = do
tv <- newTVarIO 0
forever $ do
id <- atomically $ do
c <- readTVar tv
when (c > 100) retry
r <- readTVar reqQ
case PSQ.minView r of
Nothing -> retry
Just (id :-> _, rest) -> do
writeTVar reqQ rest
writeTVar tv $ c + 1
return id
medialibGetInfo xmms id >>* do
rawv <- resultRawValue
liftIO $ do
info <- valueGet =<< propdictToDict rawv []
stamp <- atomically $ do
c <- readTVar tv
writeTVar tv $ c - 1
cc <- readTVar cache
case cc of
Just cc -> do
let stamp = cNextStamp cc
entries = cEntries cc
entry = CEReady stamp info
writeTVar cache $ Just
Cache { cEntries = IntMap.insert (fromIntegral id) entry entries
, cNextStamp = succ stamp
}
return $ Just stamp
Nothing -> return Nothing
withJust stamp $ \stamp -> atomically $ do
writeTChan mediaInfoChan (id, stamp, info)
void $ readTChan mediaInfoChan