{-# 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 :: forall a. MPD a -> IO (Response a)
withMPD = forall a. Maybe String -> Maybe String -> MPD a -> IO (Response a)
withMPD_ forall a. Maybe a
Nothing 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_ :: forall a. 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) -> forall a. String -> Port -> String -> MPD a -> IO (Response a)
withMPDEx String
host Port
port String
pw MPD a
action
      Left String
err -> (forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. a -> Either a b
Left 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 forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
`fmap`
        forall b a. b -> (a -> b) -> Maybe a -> b
maybe (String -> String -> IO String
getEnvDefault String
"MPD_HOST" String
"localhost") forall (m :: * -> *) a. Monad m => a -> m a
return Maybe String
mHost
    String
port <- forall b a. b -> (a -> b) -> Maybe a -> b
maybe (String -> String -> IO String
getEnvDefault String
"MPD_PORT" String
"6600") forall (m :: * -> *) a. Monad m => a -> m a
return Maybe String
mPort
    case forall a. Read a => String -> Maybe a
maybeRead String
port of
      Just Port
p  -> (forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. b -> Either a b
Right) (String
host, Port
p, String
pw)
      Maybe Port
Nothing -> (forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. a -> Either a b
Left) (forall a. Show a => a -> String
show String
port 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 =
    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 forall (m :: * -> *) a. Monad m => a -> m a
return String
dflt else 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) = forall a. (a -> Bool) -> [a] -> ([a], [a])
break (forall a. Eq a => a -> a -> Bool
== Char
c) String
s in (String
x, forall a. Int -> [a] -> [a]
drop Int
1 String
y)

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