packages feed

lord 1.20131130 → 2.20131201

raw patch · 10 files changed

+293/−190 lines, 10 filesdep +process

Dependencies added: process

Files

Radio.hs view
@@ -26,13 +26,15 @@ import           Data.Conduit.Binary (sinkFile) import           Data.Yaml import           Network.HTTP.Conduit hiding (path)-import           Network.MPD hiding (play, Value)+import           Network.MPD hiding (play, pause, Value) import qualified Network.MPD as MPD import           System.Directory (doesFileExist, getHomeDirectory) import           System.IO import           System.IO.Unsafe (unsafePerformIO) import           System.Log.FastLogger+import           System.Process + eof :: MVar () eof = unsafePerformIO newEmptyMVar @@ -40,8 +42,11 @@     { artist    :: String     , album     :: String     , title     :: String-    }+    }  +instance Show SongMeta where+    show meta = artist meta ++ " - " ++ title meta + class FromJSON a => Radio a where     data Param a :: * @@ -77,98 +82,128 @@             Left err -> print err      play :: Logger -> Param a -> [a] -> IO ()-    play logger reqData [] = getPlaylist reqData >>= play logger reqData-    play logger reqData (x:xs)-      | playable x = do-        surl <- songUrl reqData x-        print surl-        when (surl /= "") $ do-            let song = artist (songMeta x) ++ " - " ++ title (songMeta x)-            writeLog logger song -            getStateFile >>= flip writeFile song-            -- Report song played if needed-            when (reportRequired x) $ void (forkIO $ reportLoop reqData x)-            mpdLoad $ Path $ C.pack surl-            takeMVar eof                     -- Finished-        play logger reqData xs-      | otherwise = do-        surl <- songUrl reqData x-        print surl-        req <- parseUrl surl-        home <- getLordDir-        manager <- newManager def-        forkIO $ E.catch -            (do-                let song = artist (songMeta x) ++ " - " ++ title (songMeta x)-                writeLog logger song -                getStateFile >>= flip writeFile song-                -- Report song played if needed-                when (reportRequired x) $ void (forkIO $ reportLoop reqData x)+    play logger reqData xxs = do+        st <- withMPD status+        case st of+            Right _ -> playWithMPD logger reqData xxs+            Left  _ -> playWithMplayer logger reqData xxs -                runResourceT $ do -                    res <- http req manager-                    responseBody res $$+- sinkFile (home ++ "/lord.m4a")-                -- This will block until eof.+playWithMPD :: Radio a => Logger -> Param a -> [a] -> IO ()+playWithMPD logger reqData [] = +    getPlaylist reqData >>= playWithMPD logger reqData+playWithMPD logger reqData (x:xs)+    | playable x = loadAndPlay logger reqData (x:xs)+    | otherwise = downloadAndPlay logger reqData (x:xs) -                putMVar eof ())-            (\e -> do-                print (e :: E.SomeException)-                writeLog logger $ show e-                play logger reqData xs-                )-        threadDelay (3*1000000)-        mpdLoad m4a-        play logger reqData xs-      where-        m4a = "lord/lord.m4a"+loadAndPlay :: Radio a => Logger -> Param a -> [a] -> IO ()+loadAndPlay logger reqData [] = +    getPlaylist reqData >>= loadAndPlay logger reqData+loadAndPlay logger reqData (x:xs) = do+    surl <- songUrl reqData x+    print surl+    when (surl /= "") $ do+        logAndReport logger reqData x+        mpdLoad $ Path $ C.pack surl+        takeMVar eof                     -- Finished+    loadAndPlay logger reqData xs+  where+    mpdLoad :: Path -> IO ()+    mpdLoad path = do+        withMPD $ do+            clear+            add path+        withMPD $ MPD.play Nothing+        mpdPlay -        mpdLoad :: Path -> IO ()-        mpdLoad path-          | playable x = do-            withMPD $ do+    mpdPlay :: IO ()+    mpdPlay = do+        st <- mpdState+        if st == Right Stopped +            then putMVar eof ()+            else mpdPlay++downloadAndPlay :: Radio a => Logger -> Param a -> [a] -> IO ()+downloadAndPlay logger reqData [] = +    getPlaylist reqData >>= downloadAndPlay logger reqData+downloadAndPlay logger reqData (x:xs) = do+    surl <- songUrl reqData x+    print surl+    req <- parseUrl surl+    home <- getLordDir+    manager <- newManager def+    forkIO $ E.catch +        (do+            logAndReport logger reqData x++            runResourceT $ do +                res <- http req manager+                responseBody res $$+- sinkFile (home ++ "/lord.m4a")+            -- This will block until eof.++            putMVar eof ())+        (\e -> do+            print (e :: E.SomeException)+            writeLog logger $ show e+            downloadAndPlay logger reqData xs+            )+    threadDelay (3*1000000)+    mpdLoad m4a+    downloadAndPlay logger reqData xs+  where+    m4a = "lord/lord.m4a"++    mpdLoad :: Path -> IO ()+    mpdLoad path = do+        s <- withMPD $ do                 clear+                update [Path "lord"]                 add path-            withMPD $ MPD.play Nothing-            mpdPlay-          | otherwise = do-            s <- withMPD $ do-                    clear-                    update [Path "lord"]-                    add path-            case s of-                Right _ -> do-                    withMPD $ MPD.play Nothing-                    mpdPlay-                _                  -> mpdLoad path+        case s of+            Right _ -> do+                withMPD $ MPD.play Nothing+                mpdPlay+            _                  -> mpdLoad path -        mpdState :: IO (MPD.Response State)-        mpdState = do-            withMPD $ idle [PlayerS]   -            -- This will block until paused/finished.+    mpdPlay :: IO ()+    mpdPlay = do+        st <- mpdState+        bd <- isEmptyMVar eof+        if st == Right Stopped +            then if bd +                    then do                              -- Slow Network+                        withMPD $ MPD.play Nothing+                        mpdPlay+                    else do+                        withMPD clear+                        takeMVar eof                     -- Finished+            else mpdPlay                                 -- Pause -            st <- liftM stState <$> withMPD status-            print st-            return st+playWithMplayer :: Radio a => Logger -> Param a -> [a] -> IO ()+playWithMplayer logger reqData [] = +    getPlaylist reqData >>= playWithMplayer logger reqData+playWithMplayer logger reqData (x:xs) = do+    surl <- songUrl reqData x+    when (surl /= "") $ do+        logAndReport logger reqData x+        let sh = "mplayer -cache 2048 -cache-min 5 -novideo " ++ surl+        void $ waitForProcess =<< runCommand sh+    playWithMplayer logger reqData xs -        mpdPlay :: IO ()-        mpdPlay-          | playable x = do-            st <- mpdState-            if st == Right Stopped -                then putMVar eof ()-                else mpdPlay-          | otherwise = do-            st <- mpdState-            bd <- isEmptyMVar eof-            if st == Right Stopped -                then if bd -                        then do                              -- Slow Network-                            withMPD $ MPD.play Nothing-                            mpdPlay-                        else do-                            withMPD clear-                            takeMVar eof                     -- Finished-                else mpdPlay                                 -- Pause+logAndReport :: Radio a => Logger -> Param a -> a -> IO ()+logAndReport logger reqData x = do+    writeLog logger (show $ songMeta x) +    getStateFile >>= flip writeFile (show $ songMeta x)+    -- Report song played if needed+    when (reportRequired x) $ void (forkIO $ reportLoop reqData x)++mpdState :: IO (MPD.Response State)+mpdState = do+    withMPD $ idle [PlayerS]   +    -- This will block until paused/finished.++    st <- liftM stState <$> withMPD status+    print st+    return st  class (Radio a, ToJSON (Param a), ToJSON (Config a)) => NeedLogin a where     login :: String -> IO (Param a)
Radio/Cmd.hs view
@@ -19,7 +19,6 @@  import qualified Radio -type Param a = Radio.Param Cmd  data Cmd = Cmd     { sc_id :: Int@@ -42,7 +41,7 @@ instance Radio.Radio Cmd where     data Param Cmd = Genre String -    parsePlaylist val = do+    parsePlaylist val =         case fromJSON val of             Success s -> s             Error err -> error $ "Parse playlist failed: " ++ show err@@ -64,8 +63,8 @@         let req = initReq { method = "GET"                           , queryString = renderQuery False query                           }-        E.catch (do withManager $ \manager -> -                        httpLbs req { redirectCount = 0 } manager >> return "")+        E.catch (withManager $ \manager -> +                    httpLbs req { redirectCount = 0 } manager >> return "")                 (\e -> case e of                     (StatusCodeException s hdr _) ->                         if s == status302 then redirect hdr @@ -116,4 +115,4 @@     putStrLn $ foldr1 f (take 4 gs)     pprGenres $ drop 4 gs   where-    f a b = a ++ (concat $ take (20 - length(a)) $ repeat " ") ++ b+    f a b = a ++ concat (replicate (20 - length a) " ") ++ b
Radio/Douban.hs view
@@ -23,7 +23,6 @@  import qualified Radio -type Param a = Radio.Param Douban  data Douban = Douban      { picture :: String@@ -72,8 +71,8 @@  musicianID :: String -> IO (Maybe String) musicianID mname = do-    let rurl = "http://music.douban.com/search/" ++ -              (C.unpack $ urlEncode True (C.pack $ encodeString mname))+    let rurl = "http://music.douban.com/subject_search/?search_text=" ++ +              C.unpack (urlEncode True (C.pack $ encodeString mname))     rsp <- simpleHttp rurl     let cursor = fromDocument $ parseLBS rsp         href = cursor $// element "a" @@ -174,7 +173,7 @@   where      rurl = "http://douban.fm/j/explore/search?query=" ++            -- encodeString: encode chinese characters-          (C.unpack $ urlEncode True (C.pack $ encodeString key))+          C.unpack (urlEncode True (C.pack $ encodeString key))   search' :: String -> IO [Channel]
Radio/EightTracks.hs view
@@ -16,8 +16,10 @@ import           Data.Maybe (fromJust) import           Data.Yaml hiding (decode) import           Data.CaseInsensitive (mk)+import           Data.Char (isDigit) import           Data.Conduit (($$+-))-import           Data.Conduit.Attoparsec (sinkParser)+import           Data.Conduit.Attoparsec (sinkParser, ParseError)+import qualified Data.List as L import           GHC.Generics (Generic) import           Network.HTTP.Types  import           Network.HTTP.Conduit@@ -41,8 +43,10 @@ verHdr = (mk "X-Api-Version", "3") keyHdr = (mk "X-Api-Key", C.pack apiKey) -type Param a = Radio.Param EightTracks+type ETParam = Radio.Param EightTracks +-- | Response from http://8tracks.com/sets/new.json+-- `play_token` is the value of interest data PlaySession = PlaySession     { play_token            :: String     , status                :: String@@ -50,15 +54,16 @@     , notices               :: Maybe String     , api_version           :: Int     } deriving (Show, Generic)+instance FromJSON PlaySession -data EightTracks = EightTracks -    { id                    :: Int-    , track_file_stream_url :: String-    , name                  :: String-    , performer             :: String-    , release_name          :: String-    , url                   :: String+-- | Response from /play.json /next.json+-- `track` is the value of interest+data PlayResponse = PlayResponse+    { play_set               :: MixSet+    , play_status            :: String     } deriving (Show, Generic)+instance FromJSON PlayResponse where+    parseJSON = genericParseJSON defaultOptions { fieldLabelModifier = drop 5 }  data MixSet = MixSet     { at_beginning          :: Bool@@ -67,18 +72,27 @@     , skip_allowed          :: Bool     , track                 :: EightTracks     } deriving (Show, Generic)+instance FromJSON MixSet -data MixResponse = MixResponse-    { mix_set               :: MixSet-    , mix_status            :: String+data EightTracks = EightTracks +    { id                    :: Int+    , track_file_stream_url :: String+    , name                  :: String+    , performer             :: String+    , release_name          :: String+    , url                   :: String     } deriving (Show, Generic)--instance FromJSON PlaySession instance FromJSON EightTracks-instance FromJSON MixSet -instance FromJSON MixResponse where-    parseJSON = genericParseJSON defaultOptions { fieldLabelModifier = drop 4 }+-- | Response from http://8tracks.com/mixes/14.json or+-- http://8tracks.com/dp/electrominimalicious.json+-- `info_mix` is the value of interest+data MixInfo = MixInfo+    { info_mix               :: Exp.Mix+    , info_status            :: String+    } deriving (Show, Generic)+instance FromJSON MixInfo where+    parseJSON = genericParseJSON defaultOptions { fieldLabelModifier = drop 5 }  instance Radio.Radio EightTracks where     data Param EightTracks = Token@@ -90,7 +104,7 @@      parsePlaylist val =         case fromJSON val of-            Success s -> [track $ mix_set s]+            Success s -> [track $ play_set s]             Error err -> error $ "Parse playlist failed: " ++ show err      -- Request play.json will record current mix to listening history.@@ -101,14 +115,14 @@             rurl <- if justStarted                 then do                     putMVar running ()-                    return $ "http://8tracks.com/sets/" ++ (show $ playToken tok)  ++ "/play.json"+                    return $ "http://8tracks.com/sets/" ++ show (playToken tok) ++ "/play.json"                 else-                    return $ "http://8tracks.com/sets/" ++ (show $ playToken tok)  ++ "/next.json"+                    return $ "http://8tracks.com/sets/" ++ show (playToken tok) ++ "/next.json"             getPlaylist' rurl)         (\e -> do             -- When reached the last track in this mix. Play it again             print (e :: E.SomeException) -            let rurl = "http://8tracks.com/sets/" ++ (show $ playToken tok)  ++ "/play.json"+            let rurl = "http://8tracks.com/sets/" ++ show (playToken tok) ++ "/play.json"             getPlaylist' rurl)       where         usrHdr = (mk "X-User-Token", C.pack $ userToken tok)@@ -143,15 +157,16 @@         res <- withManager $ \manager -> httpLbs req manager         print $ responseBody res       where-        rurl = "http://8tracks.com/sets/" ++ (show $ playToken tok) ++ "/report.json"+        rurl = "http://8tracks.com/sets/" ++ show (playToken tok) ++ "/report.json"         query = [ ("track_id", C.pack $ show $ id x)                 , ("mix_id", C.pack $ show $ mixId tok) ] -instance FromJSON (Radio.Param EightTracks)-instance ToJSON (Radio.Param EightTracks)+instance FromJSON ETParam+instance ToJSON ETParam  instance NeedLogin EightTracks where     createSession strMixId email pwd = do+        mId <- getMixId strMixId         initReq <- parseUrl rurl         let req = initReq { method = "POST"                           , queryString = renderSimpleQuery False query@@ -166,13 +181,13 @@       where         rurl = "http://8tracks.com/sessions.json"         query = [ ("login", C.pack email), ("password", C.pack pwd) ]-        mId = read strMixId :: Int -    data Config EightTracks = Config { eight :: Radio.Param EightTracks } deriving Generic+    data Config EightTracks = Config { eight :: ETParam } deriving Generic -    mkConfig tok = Config tok+    mkConfig = Config -    readToken mid = do+    readToken strMixId = do+        mId <- getMixId strMixId         home <- Radio.getLordDir         let yml = home ++ "/lord.yml"         exist <- doesFileExist yml@@ -183,7 +198,7 @@                     Nothing -> error $ "Invalid YAML file: " ++ show conf                     Just c ->                          case fromJSON c of-                            Success tok -> return $ Just $ (eight tok) { mixId = read mid }+                            Success tok -> return $ Just $ (eight tok) { mixId = mId }                             Error err -> do                                 print $ "Parse token failed: " ++ show err                                 return Nothing@@ -200,23 +215,21 @@   where     rurl = "http://8tracks.com/sets/new.json?api_version=3&api_key=" ++ apiKey  +smartUrl :: String -> String+smartUrl smartId = +    "http://8tracks.com/mix_sets/" ++ smartId ++ ".json?include=mixes"++smartGet :: String -> IO [Exp.Mix]+smartGet smartId = get (smartUrl smartId) (Exp.mixes . Exp.mix_set)+ search :: String -> IO [Exp.Mix] search [] = return []-search key = search' rurl-  where -    rurl = "http://8tracks.com/mix_sets/keyword:" ++ key ++ ".json?include=mixes"--search' :: String -> IO [Exp.Mix]-search' rurl = do-    initReq <- parseUrl rurl-    let req = initReq { requestHeaders = [verHdr, keyHdr] }-    val <- withManager $ \manager -> do-        res <- http req manager-        responseBody res $$+- sinkParser json+search key = smartGet $ "keyword:" ++ key -    case fromJSON val of-        Success v -> return $ Exp.mixes $ Exp.mix_set v-        Error err -> putStrLn err >> return []+featured, trending, newest :: IO [Exp.Mix]+featured = smartGet "collection:homepage"+trending = smartGet "all"+newest   = smartGet "all:recent"  pprMixes :: [Exp.Mix] -> IO () pprMixes mixes =@@ -224,9 +237,47 @@         setSGR [SetConsoleIntensity BoldIntensity]         putStr $ "* " ++ Exp.name m          setSGR [SetColor Foreground Vivid Green]-        putStrLn $ " id=" ++ show (Exp.id m)+        putStr $ " id=" ++ show (Exp.id m)+        putStr $ "  ▶" ++ show (Exp.plays_count m)+        putStr $ "  ♥" ++ show (Exp.likes_count m)+        putStrLn $ "  (" ++ show (Exp.tracks_count m) ++ " tracks)"         setSGR [Reset]-        putStrLn $ "    Description: " ++ Exp.description m+        putStrLn $ "    Description: " ++ +                   unlines (map ("    " ++) (lines $ Exp.description m))         putStrLn $ "    Tags: " ++ Exp.tag_list_cache m         putStrLn ""         )++get :: FromJSON a => String -> (a -> b) -> IO b+get rurl selector = do+    initReq <- parseUrl rurl+    let req = initReq { requestHeaders = [verHdr, keyHdr] }+    val <- E.catches +        (withManager $ \manager -> do+            res <- http req manager+            responseBody res $$+- sinkParser json)+        [ E.Handler (\e -> print (e :: ParseError) >>+           error "The mix you are trying to access may be private.")+        , E.Handler (\e -> case e of+                        (StatusCodeException s _ _) ->+                            error $ show s+                        otherException ->+                            error $ show otherException) +        ]++    case fromJSON val of+        Success v -> return $ selector v+        Error err -> error err++getMixId :: String -> IO Int+getMixId m+  | isNumerical m = return $ read m+  | otherwise = do+    let mixUrl = if domain `L.isPrefixOf` m+                     then m+                     else (domain ++)+                          (if "/" `L.isPrefixOf` m then m else '/' : m)+    get (mixUrl ++ ".json") (Exp.id . info_mix)+  where+    isNumerical = and . fmap isDigit+    domain = "http://8tracks.com"
Radio/EightTracks/Explore.hs view
@@ -5,12 +5,16 @@ import           GHC.Generics (Generic)  data Mix = Mix-    { id        :: Int-    , path      :: String-    , web_path  :: String-    , name      :: String-    , description :: String-    , tag_list_cache :: String+    { id            :: Int+    , path          :: String+    , web_path      :: String+    , name          :: String+    , description   :: String+    , plays_count   :: Int+    , likes_count   :: Int+    , tag_list_cache:: String+    , duration      :: Int+    , tracks_count  :: Int     } deriving (Show, Generic)  instance FromJSON Mix
Radio/Jing.hs view
@@ -28,7 +28,7 @@  import Radio -type Param a = Radio.Param Jing+type JingParam = Radio.Param Jing  data Jing = Jing      { abid :: Int       -- album id@@ -127,8 +127,8 @@          playable _ = False -instance FromJSON (Radio.Param Jing)-instance ToJSON (Radio.Param Jing)+instance FromJSON JingParam+instance ToJSON JingParam  instance NeedLogin Jing where     createSession keywords email pwd = do@@ -140,7 +140,7 @@         let hmap = HM.fromList $ responseHeaders res             atoken = HM.lookup "Jing-A-Token-Header" hmap             rtoken = HM.lookup "Jing-R-Token-Header" hmap-            parseToken :: Value -> Maybe (Radio.Param Jing)+            parseToken :: Value -> Maybe JingParam             parseToken (Object hm) = do                 let user = HM.lookup "result" hm >>=                             \(Object hm') -> HM.lookup "usr" hm'@@ -155,9 +155,9 @@             parseToken _ = error "Unrecognized token format."         liftM parseToken (runResourceT $ responseBody res $$+- sinkParser json) -    data Config Jing = Config { jing :: Radio.Param Jing } deriving Generic+    data Config Jing = Config { jing :: JingParam } deriving Generic -    mkConfig tok = Config tok+    mkConfig = Config       readToken keywords = do         home <- Radio.getLordDir
Radio/Reddit.hs view
@@ -15,8 +15,6 @@  import qualified Radio -type Param a = Radio.Param Reddit- data Reddit = Reddit     { title         :: String     , artist        :: String@@ -61,4 +59,4 @@     putStrLn $ foldr1 f (take 4 gs)     pprGenres $ drop 4 gs   where-    f a b = a ++ (concat $ take (20 - length(a)) $ repeat " ") ++ b+    f a b = a ++ concat (replicate (20 - length a) " ") ++ b
lord.cabal view
@@ -1,8 +1,8 @@ name:                lord-version:             1.20131130+version:             2.20131201 synopsis:            A command line interface to online radios. description:         -    A unified interface to several online radio service providers, use @mpd@ (<http://musicpd.org>) as backend.+    A unified command line interface to several online radios, use @mpd@ (<http://musicpd.org>) as backend by default. Will fallback to mplayer (http://www.mplayerhq.hu) when mpd is unavailable.     .     Supported radios:     .@@ -22,16 +22,16 @@     > lord status     > lord kill     >-    > lord 8tracks listen <mix_id> [--no-daemon]+    > lord 8tracks listen [<mix_id> | <mix_url>] [--no-daemon]     > lord 8tracks search <keywords>+    > lord 8tracks [featured | trending | newest]     >     > lord cmd listen <genre> [--no-daemon]     > lord cmd genres     >     > lord douban listen [<channel_id> | <musician>] [--no-daemon]     > lord douban search <keywords>-    > lord douban hot-    > lord douban trending+    > lord douban [hot | trending]     >     > lord jing listen <keywords> [--no-daemon]     >@@ -82,6 +82,7 @@                         http-types >= 0.8,                          libmpd >= 0.8,                          optparse-applicative >= 0.5,+                        process >= 1.1,                         text >= 0.11,                          transformers >= 0.3,                          unix >= 2.5, @@ -115,6 +116,7 @@                         HUnit >= 1.2,                         libmpd >= 0.8,                          optparse-applicative >= 0.5,+                        process >= 1.1,                         text >= 0.11,                          transformers >= 0.3,                          unix >= 2.5, 
main.hs view
@@ -49,6 +49,9 @@     deriving (Eq, Show)  data ETSubCommand = ETListen String+                  | ETFeatured+                  | ETTrending+                  | ETNewest                   | ETSearch String     deriving (Eq, Show) @@ -82,6 +85,9 @@  main :: IO () main = do+    -- Make sure ~/.lord exists+    getLordDir >>= createDirectoryIfMissing False+     o <- execParser' $ info (helper <*> optParser)                             (fullDesc <> header "Lord: radio commander")     let nodaemon = optDaemon o@@ -99,6 +105,9 @@         EightTracks subCommand ->             case subCommand of                 ETListen mId -> etListen nodaemon mId+                ETFeatured   -> ET.featured >>= ET.pprMixes+                ETTrending   -> ET.trending >>= ET.pprMixes+                ETNewest     -> ET.newest   >>= ET.pprMixes                 ETSearch key -> etSearch key         JingFM (JingListen key) -> jingListen nodaemon key         RedditFM subCommand ->@@ -159,6 +168,9 @@     ( command "listen"         (info (helper <*> (ETListen <$> argument str (metavar "mix id")))               (progDesc "Provide mix id to listen to 8tracks.com"))+    <> command "featured" (info (pure ETFeatured) (progDesc "Featured mixes"))+    <> command "trending" (info (pure ETFeatured) (progDesc "Trending mixes"))+    <> command "newest" (info (pure ETFeatured) (progDesc "Newest mixes"))     <> command "search"         (info (helper <*> (ETSearch <$> argument str (metavar "KEYWORDS")))               (progDesc "search mixes"))@@ -223,17 +235,22 @@  listen :: Radio a => Bool -> Radio.Param a -> IO () listen nodaemon param = do-    -- Make sure ~/.lord exists-    getLordDir >>= createDirectoryIfMissing False+    -- mplayer backend won't work in daemon mode!+    st <- withMPD status+    nodaemon' <- case st of +                     Right _ -> return nodaemon+                     Left  e -> print e >> +                                putStrLn "Lord will run in foreground" >>+                                return True      pid <- getPidFile-    logger <- if nodaemon then mkLogger True stdout +    logger <- if nodaemon' then mkLogger True stdout                else getLogFile >>= flip openFile AppendMode >>= mkLogger True     let listen' = play logger param []     running <- isRunning pid     when running $ killAndWait pid -    if nodaemon then runInForeground pid listen'-                else runDetached (Just pid) def listen'+    if nodaemon' then runInForeground pid listen'+                 else runDetached (Just pid) def listen'  -- Partially taken from System.Posix.Daemon module runInForeground :: FilePath -> IO () -> IO ()
test/main.hs view
@@ -12,34 +12,32 @@   main :: IO ()-main = do-    hspec spec+main = hspec spec  spec :: Spec-spec = do-    describe "getPlaylist" $ do-        it "cmd: given genre" $ do-            ss <- Radio.getPlaylist (Cmd.Genre "Dream Pop")-            assert $ length ss > 0+spec = describe "getPlaylist" $ do+    it "cmd: given genre" $ do+        ss <- Radio.getPlaylist (Cmd.Genre "Dream Pop")+        assert $ not $ null ss -        it "douban: given channel id" $ do-            ss <- Radio.getPlaylist (Cid 6)-            assert $ length ss > 0-    -        it "douban: given musician name" $ do-            ss <- Radio.getPlaylist (Musician "Sigur RóS")-            assert $ length ss > 0+    it "douban: given channel id" $ do+        ss <- Radio.getPlaylist (Cid 6)+        assert $ not $ null ss -        it "8tracks: given mix id" $ do-            tok <- readToken "14" :: IO (Maybe (Radio.Param ET.EightTracks))-            ss <- Radio.getPlaylist $ fromJust tok-            assert $ length ss > 0+    it "douban: given musician name" $ do+        ss <- Radio.getPlaylist (Musician "Sigur RóS")+        assert $ not $ null ss -        it "jing: given keywords" $ do-            tok <- readToken "postrock" :: IO (Maybe (Radio.Param Jing))-            ss <- Radio.getPlaylist $ fromJust tok-            assert $ length ss > 0+    it "8tracks: given mix id" $ do+        tok <- readToken "14" :: IO (Maybe (Radio.Param ET.EightTracks))+        ss <- Radio.getPlaylist $ fromJust tok+        assert $ not $ null ss -        it "reddit: given genre" $ do-            ss <- Radio.getPlaylist (Reddit.Genre "indie")-            assert $ length ss > 0+    it "jing: given keywords" $ do+        tok <- readToken "postrock" :: IO (Maybe (Radio.Param Jing))+        ss <- Radio.getPlaylist $ fromJust tok+        assert $ not $ null ss++    it "reddit: given genre" $ do+        ss <- Radio.getPlaylist (Reddit.Genre "indie")+        assert $ not $ null ss