{-# LANGUAGE CPP #-}

{- |
Module      : Network.MPD
Copyright   : (c) Joachim Fasting, Simon Hengel 2012
License     : MIT

Maintainer  : Joachim Fasting <joachifm@fastmail.fm>
Stability   : unstable
Portability : unportable

An MPD client library. MPD is a daemon for playing music that is
controlled over a network socket.

To use the library, do:

> {-# LANGUAGE OverloadedStrings #-}
> import qualified Network.MPD as MPD
-}

module Network.MPD (
    -- * Basic data types
    MonadMPD, MPD, MPDError(..), ACKType(..), Response,
    Host, Port, Password,
    -- * Connections
    withMPD, withMPD_, withMPDEx,
    module Network.MPD.Commands,
#ifdef TEST
    getConnectionSettings, getEnvDefault
#endif
    ) where

import           Prelude
import qualified Control.Exception as E
import           Network.MPD.Commands
import           Network.MPD.Core

import           System.Environment (getEnv)
import           System.IO.Error (isDoesNotExistError)
import           Data.Maybe (listToMaybe)

-- | A wrapper for 'withMPDEx' that uses localhost:6600 as the default
-- host:port, or whatever is found in the environment variables MPD_HOST and
-- MPD_PORT. If MPD_HOST is of the form \"password\@host\" the password
-- will be supplied as well.
--
-- Examples:
--
-- > withMPD $ play Nothing
-- > withMPD $ add_ "tool" >> play Nothing >> currentSong
withMPD :: MPD a -> IO (Response a)
withMPD :: MPD a -> IO (Response a)
withMPD = Maybe String -> Maybe String -> MPD a -> IO (Response a)
forall a. Maybe String -> Maybe String -> MPD a -> IO (Response a)
withMPD_ Maybe String
forall a. Maybe a
Nothing Maybe String
forall a. Maybe a
Nothing

-- | Same as `withMPD`, but takes optional arguments that override MPD_HOST and
-- MPD_PORT.
--
-- This is e.g. useful for clients that optionally take @--port@ and @--host@
-- as command line arguments, and fall back to `withMPD`'s defaults if those
-- arguments are not given.
withMPD_ :: Maybe String -- ^ optional override for MPD_HOST
         -> Maybe String -- ^ optional override for MPD_PORT
         -> MPD a -> IO (Response a)
withMPD_ :: Maybe String -> Maybe String -> MPD a -> IO (Response a)
withMPD_ Maybe String
mHost Maybe String
mPort MPD a
action = do
    Either String (String, Port, String)
settings <- Maybe String
-> Maybe String -> IO (Either String (String, Port, String))
getConnectionSettings Maybe String
mHost Maybe String
mPort
    case Either String (String, Port, String)
settings of
      Right (String
host, Port
port, String
pw) -> String -> Port -> String -> MPD a -> IO (Response a)
forall a. String -> Port -> String -> MPD a -> IO (Response a)
withMPDEx String
host Port
port String
pw MPD a
action
      Left String
err -> (Response a -> IO (Response a)
forall (m :: * -> *) a. Monad m => a -> m a
return (Response a -> IO (Response a))
-> (String -> Response a) -> String -> IO (Response a)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MPDError -> Response a
forall a b. a -> Either a b
Left (MPDError -> Response a)
-> (String -> MPDError) -> String -> Response a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> MPDError
Custom) String
err

getConnectionSettings :: Maybe String -> Maybe String -> IO (Either String (Host, Port, Password))
getConnectionSettings :: Maybe String
-> Maybe String -> IO (Either String (String, Port, String))
getConnectionSettings Maybe String
mHost Maybe String
mPort = do
    (String
host, String
pw) <- String -> (String, String)
parseHost (String -> (String, String)) -> IO String -> IO (String, String)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
`fmap`
        IO String -> (String -> IO String) -> Maybe String -> IO String
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (String -> String -> IO String
getEnvDefault String
"MPD_HOST" String
"localhost") String -> IO String
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe String
mHost
    String
port <- IO String -> (String -> IO String) -> Maybe String -> IO String
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (String -> String -> IO String
getEnvDefault String
"MPD_PORT" String
"6600") String -> IO String
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe String
mPort
    case String -> Maybe Port
forall a. Read a => String -> Maybe a
maybeRead String
port of
      Just Port
p  -> (Either String (String, Port, String)
-> IO (Either String (String, Port, String))
forall (m :: * -> *) a. Monad m => a -> m a
return (Either String (String, Port, String)
 -> IO (Either String (String, Port, String)))
-> ((String, Port, String) -> Either String (String, Port, String))
-> (String, Port, String)
-> IO (Either String (String, Port, String))
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String, Port, String) -> Either String (String, Port, String)
forall a b. b -> Either a b
Right) (String
host, Port
p, String
pw)
      Maybe Port
Nothing -> (Either String (String, Port, String)
-> IO (Either String (String, Port, String))
forall (m :: * -> *) a. Monad m => a -> m a
return (Either String (String, Port, String)
 -> IO (Either String (String, Port, String)))
-> (String -> Either String (String, Port, String))
-> String
-> IO (Either String (String, Port, String))
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Either String (String, Port, String)
forall a b. a -> Either a b
Left) (String -> String
forall a. Show a => a -> String
show String
port String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" is not a valid port!")
    where
        parseHost :: String -> (String, String)
parseHost String
s = case Char -> String -> (String, String)
breakChar Char
'@' String
s of
                          (String
host, String
"") -> (String
host, String
"")
                          (String
pw, String
host) -> (String
host, String
pw)

getEnvDefault :: String -> String -> IO String
getEnvDefault :: String -> String -> IO String
getEnvDefault String
x String
dflt =
    IO String -> (IOError -> IO String) -> IO String
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
E.catch (String -> IO String
getEnv String
x) (\IOError
e -> if IOError -> Bool
isDoesNotExistError IOError
e
                            then String -> IO String
forall (m :: * -> *) a. Monad m => a -> m a
return String
dflt else IOError -> IO String
forall a. IOError -> IO a
ioError IOError
e)

-- Break a string by character, removing the separator.
breakChar :: Char -> String -> (String, String)
breakChar :: Char -> String -> (String, String)
breakChar Char
c String
s = let (String
x, String
y) = (Char -> Bool) -> String -> (String, String)
forall a. (a -> Bool) -> [a] -> ([a], [a])
break (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
c) String
s in (String
x, Int -> String -> String
forall a. Int -> [a] -> [a]
drop Int
1 String
y)

maybeRead :: Read a => String -> Maybe a
maybeRead :: String -> Maybe a
maybeRead = ((a, String) -> a) -> Maybe (a, String) -> Maybe a
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (a, String) -> a
forall a b. (a, b) -> a
fst (Maybe (a, String) -> Maybe a)
-> (String -> Maybe (a, String)) -> String -> Maybe a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [(a, String)] -> Maybe (a, String)
forall a. [a] -> Maybe a
listToMaybe ([(a, String)] -> Maybe (a, String))
-> (String -> [(a, String)]) -> String -> Maybe (a, String)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> [(a, String)]
forall a. Read a => ReadS a
reads