packages feed

lord (empty) → 1.20130928

raw patch · 5 files changed

+356/−0 lines, 5 filesdep +HUnitdep +aesondep +ansi-terminalsetup-changed

Dependencies added: HUnit, aeson, ansi-terminal, attoparsec-conduit, base, bytestring, case-insensitive, conduit, configurator, daemons, data-default, directory, fast-logger, hspec, html-conduit, http-conduit, http-types, libmpd, optparse-applicative, text, transformers, unix, unordered-containers, utf8-string, xml-conduit, yaml

Files

+ LICENSE view
@@ -0,0 +1,24 @@+This is free and unencumbered software released into the public domain.++Anyone is free to copy, modify, publish, use, compile, sell, or+distribute this software, either in source code form or as a compiled+binary, for any purpose, commercial or non-commercial, and by any+means.++In jurisdictions that recognize copyright laws, the author or authors+of this software dedicate any and all copyright interest in the+software to the public domain. We make this dedication for the benefit+of the public at large and to the detriment of our heirs and+successors. We intend this dedication to be an overt act of+relinquishment in perpetuity of all present and future rights to this+software under copyright law.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR+OTHER DEALINGS IN THE SOFTWARE.++For more information, please refer to <http://unlicense.org/>
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ lord.cabal view
@@ -0,0 +1,87 @@+name:                lord+version:             1.20130928+synopsis:            Lord of radio daemon+description:         +    A unified interface to online radio service providers, use mpd as backend.+    .+    Supported radios:+    <http://cmd.fm>+    <http://douban.fm>+    <http://jing.fm>+homepage:            https://github.com/rnons/lord+bug-reports:         https://github.com/rnons/lord/issues+license:             PublicDomain+license-file:        LICENSE+author:              rnons+maintainer:          remotenonsense@gmail.com+category:            Web+build-type:          Simple+cabal-version:       >=1.10+tested-with:         GHC == 7.6.3++source-repository   head+  type:             git+  location:         git://github.com/rnons/lord.git+ +executable lord+  main-is:              main.hs         +  -- other-modules:       +  ghc-options:          -Wall -fno-warn-unused-do-bind+  build-depends:        base >= 4 && < 5, +                        aeson >= 0.6, +                        ansi-terminal >= 0.6, +                        attoparsec-conduit >= 1.0, +                        bytestring >= 0.9, +                        case-insensitive >= 1.0, +                        conduit >= 1.0, +                        configurator >= 0.2, +                        daemons >= 0.1.2,+                        data-default >= 0.5,+                        directory >= 1.1, +                        fast-logger >= 0.3,+                        html-conduit >= 1.1, +                        http-conduit >= 1.9, +                        http-types >= 0.8, +                        libmpd >= 0.8, +                        optparse-applicative >= 0.5,+                        text >= 0.11, +                        transformers >= 0.3, +                        unix >= 2.5, +                        unordered-containers >= 0.2, +                        utf8-string >= 0.3, +                        xml-conduit >= 1.1,+                        yaml >= 0.8+  default-language:     Haskell2010++test-suite test+  type:                 exitcode-stdio-1.0+  main-is:              test/main.hs+  ghc-options:          -Wall -fno-warn-unused-do-bind+  hs-source-dirs:       .+  build-depends:        base >= 4 && < 5,+                        aeson >= 0.6, +                        ansi-terminal >= 0.6, +                        attoparsec-conduit >= 1.0, +                        bytestring >= 0.9, +                        case-insensitive >= 1.0, +                        conduit >= 1.0, +                        configurator >= 0.2, +                        daemons >= 0.1.2,+                        data-default >= 0.5,+                        directory >= 1.1, +                        fast-logger >= 0.3,+                        hspec >= 1.6,+                        html-conduit >= 1.1, +                        http-conduit >= 1.9, +                        http-types >= 0.8, +                        HUnit >= 1.2,+                        libmpd >= 0.8, +                        optparse-applicative >= 0.5,+                        text >= 0.11, +                        transformers >= 0.3, +                        unix >= 2.5, +                        unordered-containers >= 0.2, +                        utf8-string >= 0.3, +                        xml-conduit >= 1.1,+                        yaml >= 0.8+  default-language:     Haskell2010
+ main.hs view
@@ -0,0 +1,209 @@+import           Control.Monad (when)+import qualified Data.ByteString.Char8 as C+import           Data.Char (isDigit)+import           Data.Default (def)+import           Network.MPD (withMPD, clear, status, stState)+import           Options.Applicative+import           Options.Applicative.Types (ParserPrefs, ParserInfo)+import           System.Directory (createDirectoryIfMissing)+import           System.Environment (getArgs, getProgName)+import           System.Exit (exitWith, exitSuccess, ExitCode(..))+import           System.IO ( openFile, IOMode(AppendMode)+                           , hPutStr, stdout, stderr, SeekMode(..) )+import           System.Log.FastLogger (mkLogger, Logger)+import           System.Posix.Daemon+import           System.Posix.Files (stdFileMode)+import           System.Posix.IO ( fdWrite, createFile, setLock+                                 , LockRequest(..) )+import           System.Posix.Process (getProcessID)++import Radio+import Radio.Cmd+import Radio.Douban+import Radio.Jing++data Options = Options+    { optCommand    :: Command+    , optDaemon     :: Bool+    } deriving (Eq, Show)++data Command = CmdFM CmdSubCommand+             | DoubanFM DoubanSubCommand+             | JingFM JingSubCommand+             | Status+             | Kill+    deriving (Eq, Show)++data CmdSubCommand = CmdListen String+                   | CmdGenreList+    deriving (Eq, Show)++data DoubanSubCommand = DoubanListen String+                      | DoubanHot+                      | DoubanTrending+                      | DoubanSearch String+    deriving (Eq, Show)++data JingSubCommand = JingListen String+    deriving (Eq, Show)++type Keywords = String++optParser :: Parser Options+optParser = Options +    <$> subparser ( command "cmd"           (info (helper <*> cmdOptions)+                        (progDesc "cmd.fm commander"))+                 <> command "douban"        (info (helper <*> doubanOptions)+                        (progDesc "douban.fm commander"))+                 <> command "jing"          (info (helper <*> jingOptions)+                        (progDesc "jing.fm commander"))+                 <> command "status"        (info (pure Status)+                        (progDesc "show current status"))+                 <> command "kill"          (info (pure Kill)+                        (progDesc "kill the current running lord session"))+                  )+    <*> switch (long "no-daemon" <> help "don't detach from console")++main :: IO ()+main = do+    o <- execParser' $ info (helper <*> optParser) +                           (fullDesc <> header "Lord: radio commander")+    let nodaemon = optDaemon o+    case optCommand o of+        CmdFM subCommand ->+            case subCommand of+                CmdListen genre   -> listen nodaemon (cmd genre)+                CmdGenreList      -> cmdGenres+        DoubanFM subCommand -> +            case subCommand of+                 DoubanListen key -> listen nodaemon (douban key)+                 DoubanHot        -> doubanHot+                 DoubanTrending   -> doubanTrending+                 DoubanSearch key -> doubanSearch key+        JingFM (JingListen key) -> jingListen nodaemon key+        Status -> lordStatus+        Kill -> killLord++-- Taken from Options.Applicative.Extra+execParser' :: ParserInfo a -> IO a+execParser' = customExecParser' (prefs idm)++-- Taken from Options.Applicative.Extra+customExecParser' :: ParserPrefs -> ParserInfo a -> IO a+customExecParser' pprefs pinfo = do+    args <- getArgs+    +    -- My modification!+    -- Run lord with no args is equivalent to run lord status.+    when (null args) $ lordStatus >> exitSuccess+  +    case execParserPure pprefs pinfo args of+        Right a -> return a+        Left failure -> do+            progn <- getProgName+            let c = errExitCode failure+            msg <- errMessage failure progn+            case c of+                ExitSuccess -> putStr msg+                _           -> hPutStr stderr msg+            exitWith c++cmdOptions :: Parser Command+cmdOptions = CmdFM <$> subparser+    ( command "listen" +        (info (helper <*> (CmdListen <$> argument str (metavar "GENRE")))+              (progDesc "Provide genre to listen to cmd.fm"))+    <> command "genres" +        (info (pure CmdGenreList) (progDesc "List available genres"))+    )++doubanOptions :: Parser Command+doubanOptions = DoubanFM <$> subparser +    ( command "listen" +        (info (helper <*> (DoubanListen <$> argument str (metavar "[<channel_id> | <musician>]")))+              (progDesc "Provide cid/musician to listen to douban.fm"))+    <> command "search" +        (info (helper <*> (DoubanSearch <$> argument str (metavar "KEYWORDS")))+              (progDesc "search channels"))+    <> command "hot" (info (pure DoubanHot) (progDesc "hot channels"))+    <> command "trending" +        (info (pure DoubanTrending) (progDesc "trending up channels"))+    )++jingOptions :: Parser Command+jingOptions = JingFM <$> subparser +    ( command "listen" +        (info (helper <*> (JingListen <$> argument str (metavar "KEYWORDS")))+              (progDesc "Provide keywords to listen to jing.fm"))+    )++cmdGenres :: IO ()+cmdGenres = genres >>= pprGenres++doubanHot :: IO ()+doubanHot = hot >>= pprChannels++doubanTrending :: IO ()+doubanTrending = trending >>= pprChannels++doubanSearch :: String -> IO ()+doubanSearch key = search key >>= pprChannels++cmd :: Keywords -> Radio.Param Cmd+cmd = Genre++douban :: Keywords -> Radio.Param Douban+douban k+    | isChId k = Cid $ read k+    | otherwise = Musician k+  where+    isChId = and . fmap isDigit++jingListen :: Bool -> Keywords -> IO ()+jingListen nodaemon k = do+    tok <- readToken k+    case tok of+        Just tok' -> do+            putStrLn $ "Welcome back, " ++ C.unpack (nick tok')+            listen nodaemon tok'+        _         -> login k >>= listen nodaemon++listen :: Radio a => Bool -> Radio.Param a -> IO ()+listen nodaemon param = do+    -- Make sure ~/.lord exists+    getLordDir >>= createDirectoryIfMissing False++    pid <- getPidFile+    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'++-- Partially taken from System.Posix.Daemon module+runInForeground :: FilePath -> IO () -> IO ()+runInForeground pidFile program = do+    fd <- createFile pidFile stdFileMode+    setLock fd (WriteLock, AbsoluteSeek, 0, 0)+    pid <- getProcessID+    fdWrite fd (show pid)+    program++killLord :: IO ()+killLord = withMPD clear >> getPidFile >>= kill++lordStatus :: IO ()+lordStatus = do+    running <- getPidFile >>= isRunning+    status <- +        if running then do+            st <- fmap stState <$> withMPD status+            let state = case st of+                    Right s  -> show s+                    Left err -> error $ show err+            song <- getStateFile >>= readFile +            return $ "[" ++ state ++ "] " ++ song+        else return "Not running!"+    putStrLn status
+ test/main.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE OverloadedStrings #-}+import Data.Maybe (fromJust)+import Test.Hspec+import Test.HUnit++import Radio+import Radio.Cmd+import Radio.Jing+import Radio.Douban+++main :: IO ()+main = do+    hspec spec++spec :: Spec+spec = do+    describe "getPlaylist" $ do+        it "cmd: given genre" $ do+            ss <- Radio.getPlaylist (Genre "Dream Pop")+            assert $ length ss > 0++        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 "jing" $ do+            tok <- readToken "postrock"+            ss <- Radio.getPlaylist $ fromJust tok+            assert $ length ss > 0