musicScroll-0.2.3.3: src/MusicScroll/LyricsPipeline.hs
module MusicScroll.LyricsPipeline (lyricsThread, sizeOfQueue) where
-- | Discriminate between getting the lyrics from SQLite or the web.
import Control.Concurrent.Async (async, cancel, concurrently_)
import Control.Concurrent.STM (atomically, orElse)
import Control.Concurrent.STM.TBQueue ( TBQueue, readTBQueue, writeTBQueue,
newTBQueue )
import Control.Applicative (Alternative(..))
import Control.Exception (bracket)
import Control.Monad.Trans.State (StateT, get, put, evalStateT)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Reader (ReaderT, runReaderT)
import Control.Monad (forever, when)
import Numeric.Natural (Natural)
import Data.Maybe (isJust)
import Database.SQLite.Simple
import MusicScroll.DatabaseUtils
import MusicScroll.TrackInfo
import MusicScroll.TrackSuplement
import MusicScroll.Web (getLyricsFromWeb)
import MusicScroll.Providers.AZLyrics (azLyricsInstance)
import MusicScroll.Providers.MusiXMatch (musiXMatchInstance)
import MusicScroll.UIEvent
type TrackQueue = (TBQueue TrackIdentifier, TBQueue TrackSuplement)
type TrackContext a = StateT (Maybe TrackIdentifier) IO a
sizeOfQueue :: Natural
sizeOfQueue = 5
lyricsThread :: TrackQueue -> TBQueue UIEvent -> IO ()
lyricsThread input output =
do middle <- atomically (newTBQueue sizeOfQueue)
let seenSongT' = seenSongsThread input middle
getLyricsT' = getLyricsThread middle output
concurrently_ (evalStateT seenSongT' Nothing) getLyricsT'
-- | This thread works as a model of the MVC pattern. The UI
-- communicates its callbacks to here. The Dbus thread sends what it
-- sees here to no repeat songs.
seenSongsThread :: TrackQueue -> TBQueue TrackIdentifier -> TrackContext a
seenSongsThread input output = forever $
do mTrackIdent <- mergeQueue input
notSeen <- (/=) <$> get <*> pure mTrackIdent
when (notSeen && isJust mTrackIdent) $ do
let Just trackIdent = mTrackIdent
put mTrackIdent
liftIO . atomically $ writeTBQueue output trackIdent
mergeQueue :: TrackQueue -> TrackContext (Maybe TrackIdentifier)
mergeQueue (inputIdent, inputSupl) =
do let cleanInputIdent = cleanTrack <$> readTBQueue inputIdent
mergedInputChan = (Left <$> cleanInputIdent) `orElse`
(Right <$> readTBQueue inputSupl)
mOldTrack <- get
mergedInput <- liftIO $ atomically mergedInputChan
let suplement' :: TrackSuplement -> Maybe TrackIdentifier
suplement' supl = (Right . suplement supl) <$> mOldTrack
newTrack :: Maybe TrackIdentifier
newTrack = either Just suplement' mergedInput
pure newTrack
getLyricsThread :: TBQueue TrackIdentifier -> TBQueue UIEvent -> IO a
getLyricsThread input output =
do dbPath <- getDBPath
bracket (open dbPath) close $ \conn -> do
execute_ conn sqlDBCreate
flip evalStateT Nothing . forever $
do trackIdent <- liftIO $ atomically (readTBQueue input)
get >>= maybe (pure ()) (liftIO . cancel)
asyncId <- liftIO . async $
do event <- flip runReaderT conn $
either caseByPath caseByInfo trackIdent
atomically $ writeTBQueue output event
put (pure asyncId)
caseByInfo :: TrackInfo -> ReaderT Connection IO UIEvent
caseByInfo track =
let tryGetLyrics = getDBLyrics (tUrl track)
<|> getLyricsFromWeb azLyricsInstance track
<|> getLyricsFromWeb musiXMatchInstance track
in (GotLyric track <$> tryGetLyrics) <|> pure (ErrorOn (NoLyricsOnWeb track))
caseByPath :: TrackByPath -> ReaderT Connection IO UIEvent
caseByPath track =
((uncurry GotLyric) <$> getDBSong (tpPath track)) <|>
pure (ErrorOn (NotOnDB track))