packages feed

libmpd 0.9.0.9 → 0.9.0.10

raw patch · 26 files changed

+369/−66 lines, 26 filesdep +safe-exceptionsdep ~QuickCheckdep ~basedep ~containersnew-uploaderPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: safe-exceptions

Dependency ranges changed: QuickCheck, base, containers, network, time

API changes (from Hackage documentation)

- Network.MPD: data State
+ Network.MPD: Priority :: Int -> Priority
+ Network.MPD: addTagId :: MonadMPD m => Id -> Metadata -> Value -> m ()
+ Network.MPD: clearTagId :: MonadMPD m => Id -> Metadata -> m ()
+ Network.MPD: data PlaybackState
+ Network.MPD: data Volume
+ Network.MPD: listMounts :: MonadMPD m => m [(String, String)]
+ Network.MPD: listNeighbors :: MonadMPD m => m [(String, String)]
+ Network.MPD: mount :: MonadMPD m => String -> String -> m ()
+ Network.MPD: newtype Priority
+ Network.MPD: prio :: MonadMPD m => Priority -> (Position, Position) -> m ()
+ Network.MPD: prioId :: MonadMPD m => Priority -> Id -> m ()
+ Network.MPD: rangeId :: MonadMPD m => Id -> (Maybe Double, Maybe Double) -> m ()
+ Network.MPD: readComments :: MonadMPD m => Path -> m [(String, String)]
+ Network.MPD: toggleOutput :: MonadMPD m => Int -> m ()
+ Network.MPD: type FractionalSeconds = Double
+ Network.MPD: unmount :: MonadMPD m => String -> m ()
+ Network.MPD.Applicative.CurrentPlaylist: addTagId :: Id -> Metadata -> Value -> Command ()
+ Network.MPD.Applicative.CurrentPlaylist: clearTagId :: Id -> Metadata -> Command ()
+ Network.MPD.Applicative.CurrentPlaylist: prio :: Priority -> (Position, Position) -> Command ()
+ Network.MPD.Applicative.CurrentPlaylist: prioId :: Priority -> Id -> Command ()
+ Network.MPD.Applicative.CurrentPlaylist: rangeId :: Id -> (Maybe Double, Maybe Double) -> Command ()
+ Network.MPD.Applicative.Database: readComments :: Path -> Command [(String, String)]
+ Network.MPD.Applicative.Mount: listMounts :: Command [(String, String)]
+ Network.MPD.Applicative.Mount: listNeighbors :: Command [(String, String)]
+ Network.MPD.Applicative.Mount: mount :: String -> String -> Command ()
+ Network.MPD.Applicative.Mount: unmount :: String -> Command ()
+ Network.MPD.Applicative.Output: toggleOutput :: Int -> Command ()
- Network.MPD: Paused :: State
+ Network.MPD: Paused :: PlaybackState
- Network.MPD: Playing :: State
+ Network.MPD: Playing :: PlaybackState
- Network.MPD: Status :: State -> Maybe Int -> Bool -> Bool -> Integer -> Integer -> Maybe Position -> Maybe Id -> Maybe Position -> Maybe Id -> Maybe (Double, Seconds) -> Maybe Int -> Seconds -> Double -> Double -> (Int, Int, Int) -> Maybe Integer -> Bool -> Bool -> Maybe String -> Status
+ Network.MPD: Status :: PlaybackState -> Maybe Volume -> Bool -> Bool -> Integer -> Integer -> Maybe Position -> Maybe Id -> Maybe Position -> Maybe Id -> Maybe (FractionalSeconds, FractionalSeconds) -> Maybe Int -> Seconds -> Double -> Double -> (Int, Int, Int) -> Maybe Integer -> Bool -> Bool -> Maybe String -> Status
- Network.MPD: Stopped :: State
+ Network.MPD: Stopped :: PlaybackState
- Network.MPD: [stState] :: Status -> State
+ Network.MPD: [stState] :: Status -> PlaybackState
- Network.MPD: [stTime] :: Status -> Maybe (Double, Seconds)
+ Network.MPD: [stTime] :: Status -> Maybe (FractionalSeconds, FractionalSeconds)
- Network.MPD: [stVolume] :: Status -> Maybe Int
+ Network.MPD: [stVolume] :: Status -> Maybe Volume
- Network.MPD: seek :: MonadMPD m => Position -> Seconds -> m ()
+ Network.MPD: seek :: MonadMPD m => Position -> FractionalSeconds -> m ()
- Network.MPD: seekId :: MonadMPD m => Id -> Seconds -> m ()
+ Network.MPD: seekId :: MonadMPD m => Id -> FractionalSeconds -> m ()
- Network.MPD: setVolume :: MonadMPD m => Int -> m ()
+ Network.MPD: setVolume :: MonadMPD m => Volume -> m ()
- Network.MPD.Applicative.PlaybackControl: seek :: Position -> Seconds -> Command ()
+ Network.MPD.Applicative.PlaybackControl: seek :: Position -> FractionalSeconds -> Command ()
- Network.MPD.Applicative.PlaybackControl: seekId :: Id -> Seconds -> Command ()
+ Network.MPD.Applicative.PlaybackControl: seekId :: Id -> FractionalSeconds -> Command ()
- Network.MPD.Applicative.PlaybackOptions: setVolume :: Int -> Command ()
+ Network.MPD.Applicative.PlaybackOptions: setVolume :: Volume -> Command ()
- Network.MPD.Core: getResponse :: (MonadMPD m) => String -> m [ByteString]
+ Network.MPD.Core: getResponse :: MonadMPD m => String -> m [ByteString]
- Network.MPD.Core: kill :: (MonadMPD m) => m ()
+ Network.MPD.Core: kill :: MonadMPD m => m ()

Files

README.md view
@@ -1,5 +1,8 @@ # libmpd-haskell: a client library for MPD +[![Hackage](https://budueba.com/hackage/libmpd)](http://hackage.haskell.org/package/libmpd)+[![Build Status](https://secure.travis-ci.org/vimus/libmpd-haskell.png?branch=master)](http://travis-ci.org/vimus/libmpd-haskell)+ ## About libmpd-haskell is a pure [Haskell] client library for [MPD], the music playing daemon.@@ -11,14 +14,14 @@ * [Latest release on Hackage] * `git clone git://github.com/vimus/libmpd-haskell.git` -[Latest release on Hackage]: http://hackage.haskell.org/package/libmpd "libmpd-haskell on Hackage"+[Latest release on Hackage]: https://hackage.haskell.org/package/libmpd "libmpd-haskell on Hackage"  ## Installation With [cabal-install], do  `cd libmpd-haskell && cabal install` -[cabal-install]: http://hackage.haskell.org/package/cabal-install+[cabal-install]: https://hackage.haskell.org/package/cabal-install  ## Usage With GHCi:@@ -46,25 +49,31 @@  Well-formatted patches are appreciated. New features should have a test case. +## See also+* [vimus], an MPD client with vim-like keybindings++[vimus]: https://github.com/vimus/vimus+ ## Resources * [API documentation] * [MPD protocol reference] * [Using GitHub] * \#vimus @ irc.freenode.net -[bug tracker]: http://github.com/vimus/libmpd-haskell/issues-[GitHub]: http://www.github.com-[GitHub repository]: http://www.github.com/vimus/libmpd-haskell-[API documentation]: http://hackage.haskell.org/packages/archive/libmpd/latest/doc/html/Network-MPD.html+[bug tracker]: https://github.com/vimus/libmpd-haskell/issues+[GitHub]: https://github.com+[GitHub repository]: https://github.com/vimus/libmpd-haskell+[API documentation]: https://hackage.haskell.org/packages/archive/libmpd/latest/doc/html/Network-MPD.html [MPD protocol reference]: http://www.musicpd.org/doc/protocol/-[Using GitHub]: http://help.github.com+[Using GitHub]: https://help.github.com  ## License libmpd-haskell is distributed under the [MIT license]. -[MIT license]: http://www.opensource.org/licenses/MIT+[MIT license]: http://opensource.org/licenses/MIT  ## Contributors-See `CONTRIBUTORS` in the source distribution.-Feel free to add yourself to this list if you deem it appropriate-to do so.+See [CONTRIBUTORS](https://github.com/vimus/libmpd-haskell/blob/master/CONTRIBUTORS) in the+source distribution.+Feel free to add yourself to this list if you deem it appropriate to do+so.
changelog.md view
@@ -1,3 +1,6 @@+* v0.9.10+    - Port it for newer network library+ * v0.9.0, 2014-09-21     - New commands: `deleteRange`, `moveRange`, `playlistInfoRange`,       `searchAdd`, `searchAddpl`.
libmpd.cabal view
@@ -1,5 +1,5 @@ Name:               libmpd-Version:            0.9.0.9+Version:            0.9.0.10 Synopsis:           An MPD client library. Description:        A client library for MPD, the Music Player Daemon. Category:           Network, Sound@@ -19,7 +19,7 @@ Homepage:           http://github.com/vimus/libmpd-haskell#readme Bug-reports:        http://github.com/vimus/libmpd-haskell/issues -Tested-With:        GHC ==7.6.3, GHC ==7.8.4, GHC ==7.10.1, GHC ==8.0.1+Tested-With:        GHC ==8.0.1, GHC==8.2.2, GHC==8.4.3, GHC==8.6.1 Build-Type:         Simple Cabal-Version:      >= 1.10 @@ -39,7 +39,7 @@      Build-Depends:         -- Platform dependencies-        base >= 4 && < 5+        base >= 4.9 && < 5       , attoparsec >= 0.10.1 && < 1       , bytestring >= 0.9 && < 1       , containers >= 0.3 && < 1@@ -50,12 +50,13 @@          -- Additional dependencies       , data-default-class >= 0.0.1 && < 1-      , network >= 2.1 && < 3+      , network >= 2.6.3.5+      , safe-exceptions >= 0.1 && < 0.2       , utf8-string >= 0.3.1 && < 1.1      if impl(ghc >= 7.10.0)         Build-Depends:-            time >= 1.5 && <1.9+            time >= 1.5     else         Build-Depends:             time >= 1.1 && <1.5@@ -67,6 +68,7 @@       , Network.MPD.Applicative.Connection       , Network.MPD.Applicative.CurrentPlaylist       , Network.MPD.Applicative.Database+      , Network.MPD.Applicative.Mount       , Network.MPD.Applicative.Output       , Network.MPD.Applicative.PlaybackControl       , Network.MPD.Applicative.PlaybackOptions@@ -96,6 +98,7 @@       , Network.MPD.Commands.Connection       , Network.MPD.Commands.Output       , Network.MPD.Commands.Reflection+      , Network.MPD.Commands.Mount       , Network.MPD.Applicative.Util       , Network.MPD.Applicative.Internal       , Network.MPD.Util@@ -125,9 +128,10 @@         -- Additional dependencies       , data-default-class       , network+      , safe-exceptions       , utf8-string          -- Test dependencies       , unix-      , QuickCheck >= 2.1+      , QuickCheck >= 2.10       , hspec >= 1.3
src/Network/MPD/Applicative.hs view
@@ -31,13 +31,21 @@  -- * Reflection , module Network.MPD.Applicative.Reflection++-- * Mounting+, module Network.MPD.Applicative.Mount++-- * Client-to-client+, module Network.MPD.Applicative.ClientToClient ) where  import Network.MPD.Applicative.Internal +import Network.MPD.Applicative.ClientToClient import Network.MPD.Applicative.Connection import Network.MPD.Applicative.CurrentPlaylist import Network.MPD.Applicative.Database+import Network.MPD.Applicative.Mount import Network.MPD.Applicative.Output import Network.MPD.Applicative.PlaybackControl import Network.MPD.Applicative.PlaybackOptions
src/Network/MPD/Applicative/CurrentPlaylist.hs view
@@ -29,9 +29,14 @@     , playlistSearch     , plChanges     , plChangesPosId+    , prio+    , prioId     , shuffle     , swap     , swapId+    , addTagId+    , clearTagId+    , rangeId     ) where  import           Network.MPD.Commands.Arg hiding (Command)@@ -135,6 +140,14 @@              = Right (x', Id y')              | otherwise = Left "" +-- | Set the priority of the specified songs.+prio :: Priority -> (Position, Position) -> Command ()+prio p range = Command emptyResponse ["prio" <@> p <++> range]++-- | Set priority by song id.+prioId :: Priority -> Id -> Command ()+prioId p ids = Command emptyResponse ["prioid" <@> p <++> ids]+ -- | Shuffle the current playlist. -- Optionally restrict to a range of songs. shuffle :: Maybe (Position, Position) -> Command ()@@ -147,3 +160,17 @@ -- | Swap songs by id. swapId :: Id -> Id -> Command () swapId id1 id2 = Command emptyResponse ["swapid" <@> id1 <++> id2]++-- | Add tag to specified (remote) song.+addTagId :: Id -> Metadata -> Value -> Command ()+addTagId id' tag val = Command emptyResponse ["addtagid" <@> id' <++> tag <++> val]++-- | Remove tag from specified (remote) song.+clearTagId :: Id -> Metadata -> Command ()+clearTagId id' tags = Command emptyResponse ["cleartagid" <@> id' <++> tags]++-- | Specify portion of song that shall be played.+-- Both ends of the range are optional; omitting both plays everything.+rangeId :: Id -> (Maybe Double, Maybe Double) -> Command ()+rangeId id' (mbStart, mbEnd) = Command emptyResponse ["rangeid " ++ show id' ++ " " ++ arg ]+  where arg = maybe "" show mbStart ++ ":" ++ maybe "" show mbEnd
src/Network/MPD/Applicative/Database.hs view
@@ -77,6 +77,11 @@ lsInfo :: Path -> Command [LsResult] lsInfo = lsInfo' "lsinfo" +-- | Read comments from the file at the specified path.+readComments :: Path -> Command [(String, String)]+readComments uri = Command p ["readcomments" <@> uri]+  where p = map decodePair . toAssocList <$> getResponse+ -- | Like 'find' but with inexact matching. search :: Query -> Command [Song] search q = Command p ["search" <@> q]
src/Network/MPD/Applicative/Internal.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TupleSections #-}+{-# LANGUAGE CPP #-}  {- | Module      : Network.MPD.Applicative.Internal@@ -38,6 +39,7 @@ import           Network.MPD.Core hiding (getResponse) import qualified Network.MPD.Core as Core import           Control.Monad.Error+import qualified Control.Monad.Fail as Fail  -- | A line-oriented parser that returns a value along with any remaining input. newtype Parser a@@ -45,9 +47,11 @@       deriving Functor  instance Monad Parser where-    fail      = Parser . const . Left     return a  = Parser $ \input -> Right (a, input)     p1 >>= p2 = Parser $ \input -> runParser p1 input >>= uncurry (runParser . p2)++instance Fail.MonadFail Parser where+    fail = Prelude.fail  instance Applicative Parser where     pure  = return
+ src/Network/MPD/Applicative/Mount.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE OverloadedStrings #-}++{- |+Module      : Network.MPD.Applicative.Mount+Copyright   : (c) Joachim Fasting 2014+License     : MIT++Maintainer  : joachifm@fastmail.fm+Stability   : stable+Portability : unportable++Mounting remote storage.+-}++module Network.MPD.Applicative.Mount+  ( mount+  , unmount+  , listMounts+  , listNeighbors+  ) where++import           Network.MPD.Commands.Arg hiding (Command)+import           Network.MPD.Applicative.Internal+import           Network.MPD.Util++import           Data.ByteString.Char8 (ByteString)+import qualified Data.ByteString.UTF8 as UTF8++mount :: String -- Path+      -> String -- Uri+      -> Command ()+mount p u = Command emptyResponse ["mount" <@> p <++> u]++unmount :: String -- Path+        -> Command ()+unmount p = Command emptyResponse ["unmount" <@> p]++listMounts :: Command [(String, String)] -- (Path, Uri)+listMounts = Command (liftParser p) ["listmounts"]+  where+    p = mapM parseMount . splitGroups ["mount"] . toAssocList+    parseMount :: [(ByteString, ByteString)] -> Either String (String, String)+    parseMount [("mount", mo), ("storage", st)] = Right (UTF8.toString mo, UTF8.toString st)+    parseMount _ = Left "Unexpected result from listMounts"++listNeighbors :: Command [(String, String)] -- (Uri, Name)+listNeighbors = Command (liftParser p) ["listneighbors"]+  where+    p = mapM parseNeighbor . splitGroups ["neighbor"] . toAssocList+    parseNeighbor :: [(ByteString, ByteString)] -> Either String (String, String)+    parseNeighbor [("neighbor", ne), ("name", na)] = Right (UTF8.toString ne, UTF8.toString na)+    parseNeighbor _ = Left "Unexpected result from listNeighbors"
src/Network/MPD/Applicative/Output.hs view
@@ -15,6 +15,7 @@ module Network.MPD.Applicative.Output     ( disableOutput     , enableOutput+    , toggleOutput     , outputs     ) where @@ -30,6 +31,10 @@ -- | Turn on output. enableOutput :: Int -> Command () enableOutput n = Command emptyResponse ["enableoutput" <@> n]++-- | Toggle output.+toggleOutput :: Int -> Command ()+toggleOutput n = Command emptyResponse ["toggleoutput" <@> n]  -- | Get information about all available output devices. outputs :: Command [Device]
src/Network/MPD/Applicative/PlaybackControl.hs view
@@ -50,11 +50,11 @@ previous = Command emptyResponse ["previous"]  -- | Seek to time in the song at the given position.-seek :: Position -> Seconds -> Command ()+seek :: Position -> FractionalSeconds -> Command () seek pos time = Command emptyResponse ["seek" <@> pos <++> time]  -- | Seek to time in the song with the given id.-seekId :: Id -> Seconds -> Command ()+seekId :: Id -> FractionalSeconds -> Command () seekId id' time = Command emptyResponse ["seekid" <@> id' <++> time]  -- | Stop playback.
src/Network/MPD/Applicative/PlaybackOptions.hs view
@@ -50,8 +50,8 @@ repeat :: Bool -> Command () repeat f = Command emptyResponse ["repeat" <@> f] --- | Set volume in percent.-setVolume :: Int -> Command ()+-- | Set volume.+setVolume :: Volume -> Command () setVolume vol = Command emptyResponse ["setvol" <@> vol]  -- | Toggle single mode.
src/Network/MPD/Applicative/Status.hs view
@@ -96,7 +96,7 @@                 "nextsongid"     -> int   $ \x -> a { stNextSongID      = Just $ Id x }                 "time"           -> time  $ \x -> a { stTime            = Just x }                 "elapsed"        -> frac  $ \x -> a { stTime            = fmap ((x,) . snd) (stTime a) }-                "duration"       -> frac  $ \x -> a { stTime            = fmap ((, round x) . fst) (stTime a) }+                "duration"       -> frac  $ \x -> a { stTime            = fmap ((,x) . fst) (stTime a) }                 "bitrate"        -> int   $ \x -> a { stBitrate         = Just x }                 "xfade"          -> num   $ \x -> a { stXFadeWidth      = x }                 "mixrampdb"      -> frac  $ \x -> a { stMixRampdB       = x }@@ -116,7 +116,7 @@                     -- errors.                     audio f = Right $ maybe a f (parseTriple ':' parseNum v) -                    time f = case parseFrac *** parseNum $ breakChar ':' v of+                    time f = case parseFrac *** parseFrac $ breakChar ':' v of                                  (Just a_, Just b) -> (Right . f) (a_, b)                                  _                 -> unexpectedPair @@ -127,8 +127,8 @@                         _       -> unexpectedPair                      -- A volume of -1 indicates an audio backend w/o a mixer-                    vol f = case parseNum v of+                    vol f = case (parseNum v :: Maybe Int) of                       Nothing -> unexpectedPair -- does it really make sense to fail here? when does this occur?                       Just v' -> (Right . f) (g v')                       where g n | n < 0     = Nothing-                                | otherwise = Just (n::Int)+                                | otherwise = Just $ fromIntegral n
src/Network/MPD/Commands.hs view
@@ -23,15 +23,18 @@     , Value     , ObjectType(..)     , Seconds+    , FractionalSeconds     , Decibels-    , State(..)+    , PlaybackState(..)     , Subsystem(..)     , ReplayGainMode(..)     , Count(..)     , LsResult(..)     , Device(..)     , Song(..)+    , Priority(..)     , Position+    , Volume     , Id(..)     , sgGetTag     , sgAddTag@@ -50,6 +53,7 @@     , module Network.MPD.Commands.Output     , module Network.MPD.Commands.Reflection     , module Network.MPD.Commands.ClientToClient+    , module Network.MPD.Commands.Mount     ) where  import           Network.MPD.Commands.Query@@ -66,3 +70,4 @@ import           Network.MPD.Commands.Output import           Network.MPD.Commands.Reflection import           Network.MPD.Commands.ClientToClient+import           Network.MPD.Commands.Mount
src/Network/MPD/Commands/CurrentPlaylist.hs view
@@ -28,9 +28,14 @@     , playlistSearch     , plChanges     , plChangesPosId+    , prio+    , prioId     , shuffle     , swap     , swapId+    , addTagId+    , clearTagId+    , rangeId     ) where  import qualified Network.MPD.Applicative.Internal as A@@ -114,6 +119,14 @@ plChangesPosId :: MonadMPD m => Integer -> m [(Position, Id)] plChangesPosId = A.runCommand . A.plChangesPosId +-- | Set the priority of the specified songs.+prio :: MonadMPD m => Priority -> (Position, Position) -> m ()+prio p = A.runCommand . A.prio p++-- | Set priority by song id.+prioId :: MonadMPD m => Priority -> Id -> m ()+prioId p = A.runCommand . A.prioId p+ -- | Shuffle the playlist. shuffle :: MonadMPD m => Maybe (Position, Position) -- ^ Optional range (start, end)         -> m ()@@ -126,3 +139,16 @@ -- | Swap the positions of two songs (Id version) swapId :: MonadMPD m => Id -> Id -> m () swapId id1 = A.runCommand . A.swapId id1++-- | Add tag to (remote) song.+addTagId :: (MonadMPD m) => Id -> Metadata -> Value -> m ()+addTagId id' tag = A.runCommand . A.addTagId id' tag++-- | Remove tag from (remote) song.+clearTagId :: (MonadMPD m) => Id -> Metadata -> m ()+clearTagId id' = A.runCommand . A.clearTagId id'++-- | Specify portion of song that shall be played.+-- Both ends of the range are optional; omitting both plays everything.+rangeId :: (MonadMPD m) => Id -> (Maybe Double, Maybe Double) -> m ()+rangeId id' = A.runCommand . A.rangeId id'
src/Network/MPD/Commands/Database.hs view
@@ -20,6 +20,7 @@     , listAll     , listAllInfo     , lsInfo+    , readComments     , search     , update     , rescan@@ -60,6 +61,10 @@ -- | Non-recursively list the contents of a database directory. lsInfo :: MonadMPD m => Path -> m [LsResult] lsInfo = A.runCommand . A.lsInfo++-- | Read comments from file at path.+readComments :: MonadMPD m => Path -> m [(String, String)]+readComments = A.runCommand . A.readComments  -- | Search the database using case insensitive matching. search :: MonadMPD m => Query -> m [Song]
+ src/Network/MPD/Commands/Mount.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE OverloadedStrings #-}++{- |+Module      : Network.MPD.Commands.Mount+Copyright   : (c) Joachim Fasting 2014+License     : MIT++Maintainer  : joachifm@fastmail.fm+Stability   : stable+Portability : unportable++Mounting remote storage.+-}++module Network.MPD.Commands.Mount+  ( mount+  , unmount+  , listMounts+  , listNeighbors+  ) where++import qualified Network.MPD.Applicative.Internal as A+import qualified Network.MPD.Applicative.Mount as A+import           Network.MPD.Core++mount :: (MonadMPD m) => String -> String -> m ()+mount p = A.runCommand . A.mount p++unmount :: (MonadMPD m) => String -> m ()+unmount = A.runCommand . A.unmount++listMounts :: (MonadMPD m) => m [(String, String)]+listMounts = A.runCommand A.listMounts++listNeighbors :: (MonadMPD m) => m [(String, String)]+listNeighbors = A.runCommand A.listNeighbors
src/Network/MPD/Commands/Output.hs view
@@ -15,6 +15,7 @@ module Network.MPD.Commands.Output     ( disableOutput     , enableOutput+    , toggleOutput     , outputs     ) where @@ -30,6 +31,10 @@ -- | Turn on an output device. enableOutput :: MonadMPD m => Int -> m () enableOutput = A.runCommand . A.enableOutput++-- | Toggle output device.+toggleOutput :: MonadMPD m => Int -> m ()+toggleOutput = A.runCommand . A.toggleOutput  -- | Retrieve information for all output devices. outputs :: MonadMPD m => m [Device]
src/Network/MPD/Commands/PlaybackControl.hs view
@@ -49,11 +49,11 @@ previous = A.runCommand A.previous  -- | Seek to some point in a song.-seek :: MonadMPD m => Position -> Seconds -> m ()+seek :: MonadMPD m => Position -> FractionalSeconds -> m () seek pos = A.runCommand . A.seek pos  -- | Seek to some point in a song (id version)-seekId :: MonadMPD m => Id -> Seconds -> m ()+seekId :: MonadMPD m => Id -> FractionalSeconds -> m () seekId id' = A.runCommand . A.seekId id'  -- | Stop playing.
src/Network/MPD/Commands/PlaybackOptions.hs view
@@ -46,8 +46,8 @@ repeat :: MonadMPD m => Bool -> m () repeat = A.runCommand . A.repeat --- | Set the volume (0-100 percent).-setVolume :: MonadMPD m => Int -> m ()+-- | Set the volume.+setVolume :: MonadMPD m => Volume -> m () setVolume = A.runCommand . A.setVolume  -- | Set single mode
src/Network/MPD/Commands/Types.hs view
@@ -18,8 +18,9 @@     , Value(..)     , ObjectType(..)     , Seconds+    , FractionalSeconds     , Decibels-    , State(..)+    , PlaybackState(..)     , Subsystem(..)     , ReplayGainMode(..)     , Count(..)@@ -28,8 +29,10 @@     , Song(..)     , Position     , Id(..)+    , Priority(..)     , sgGetTag     , sgAddTag+    , Volume(..)     , Stats(..)     , Status(..)     , def@@ -138,15 +141,18 @@ instance MPDArg ObjectType where     prep SongObj = Args ["song"] +type FractionalSeconds = Double+ type Seconds = Integer  type Decibels = Integer  -- | Represents the different playback states.-data State = Playing-           | Stopped-           | Paused-    deriving (Show, Eq)+data PlaybackState+  = Playing+  | Stopped+  | Paused+    deriving (Eq, Enum, Ord, Bounded, Show)  -- | Represents the various MPD subsystems. data Subsystem@@ -161,7 +167,7 @@     | StickerS           -- ^ Sticker database     | SubscriptionS      -- ^ Subscription     | MessageS           -- ^ Message on subscribed channel-      deriving (Eq, Show)+      deriving (Eq, Enum, Ord, Bounded, Show)  instance MPDArg Subsystem where     prep DatabaseS = Args ["database"]@@ -180,7 +186,7 @@     = Off       -- ^ Disable replay gain     | TrackMode -- ^ Per track mode     | AlbumMode -- ^ Per album mode-      deriving (Eq, Show)+      deriving (Eq, Enum, Ord, Bounded, Show)  instance MPDArg ReplayGainMode where     prep Off = Args ["off"]@@ -246,6 +252,12 @@ instance (MPDArg Id) where     prep (Id x) = prep x +newtype Priority = Priority Int+  deriving (Eq, Show)++instance (MPDArg Priority) where+  prep (Priority x) = prep x+ -- | Get list of specific tag type sgGetTag :: Metadata -> Song -> Maybe [Value] sgGetTag meta s = M.lookup meta $ sgTags s@@ -280,13 +292,56 @@ instance Default Stats where     def = defaultStats +-- | Volume values.+--+-- Values of this type are always in the range 0-100.+--+-- Arithmetic on volumes has the property that:+--+-- @current + new = 100 if current + new > 100@+--+-- @current - new = 0   if current - new < 0@+--+-- but @current / 0@ still yields a division by zero exception.+newtype Volume = Volume Int deriving (Eq, Ord, Show)++instance Enum Volume where+    toEnum = Volume . min 100 . max 0+    fromEnum (Volume x) = x++instance Bounded Volume where+    minBound = 0+    maxBound = 100++instance Num Volume where+    Volume x + Volume y = toEnum (x + y)+    Volume x - Volume y = toEnum (x - y)+    Volume x * Volume y = toEnum (x * y)++    negate = id+    abs    = id+    signum = const 0++    fromInteger = toEnum . fromIntegral++instance Integral Volume where+    quotRem (Volume x) (Volume y) =+        let (x', y') = x `quotRem` y in (Volume x', Volume y')+    toInteger (Volume x) = fromIntegral x++instance Real Volume where+    toRational (Volume x) = toRational x++instance MPDArg Volume where+    prep (Volume x) = prep x+ -- | Container for MPD status. data Status =-    Status { stState :: State+    Status { stState :: PlaybackState              -- | A percentage (0-100).              --              -- 'Nothing' indicates that the output lacks mixer support.-           , stVolume          :: Maybe Int+           , stVolume          :: Maybe Volume            , stRepeat          :: Bool            , stRandom          :: Bool              -- | A value that is incremented by the server every time the@@ -303,7 +358,7 @@              -- | Next song's playlist ID.            , stNextSongID      :: Maybe Id              -- | Time elapsed\/total time of playing song (if any).-           , stTime            :: Maybe (Double, Seconds)+           , stTime            :: Maybe (FractionalSeconds, FractionalSeconds)              -- | Bitrate (in kilobytes per second) of playing song (if any).            , stBitrate         :: Maybe Int              -- | Crossfade time.
src/Network/MPD/Core.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE FlexibleContexts, GeneralizedNewtypeDeriving, OverloadedStrings #-}+{-# LANGUAGE FlexibleContexts, GeneralizedNewtypeDeriving, OverloadedStrings, CPP #-} {-# LANGUAGE ScopedTypeVariables #-}  -- | Module    : Network.MPD.Core@@ -28,12 +28,26 @@ import           Data.Char (isDigit) import           Control.Applicative (Applicative(..), (<$>), (<*)) import qualified Control.Exception as E+import           Control.Exception.Safe (catch, catchAny) import           Control.Monad (ap, unless) import           Control.Monad.Error (ErrorT(..), MonadError(..)) import           Control.Monad.Reader (ReaderT(..), ask) import           Control.Monad.State (StateT, MonadIO(..), modify, gets, evalStateT) import qualified Data.Foldable as F-import           Network (PortID(..), withSocketsDo, connectTo)+import           System.IO (IOMode(..))+import Network.Socket+  ( SockAddr(..)+  , addrAddress+  , addrFamily+  , addrProtocol+  , addrSocketType+  , connect+  , defaultHints+  , getAddrInfo+  , socket+  , socketToHandle+  , withSocketsDo+  ) import           System.IO (Handle, hPutStrLn, hReady, hClose, hFlush) import           System.IO.Error (isEOFError, tryIOError, ioeGetErrorType) import           Text.Printf (printf)@@ -105,16 +119,15 @@ mpdOpen = MPD $ do     (host, port) <- ask     runMPD close-    mHandle <- liftIO (safeConnectTo host port)+    addr:_ <- liftIO $ getAddrInfo (Just defaultHints) (Just host) (Just $ show port)+    sock <- liftIO $ socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)+    mHandle <- liftIO (safeConnectTo (sock,(addrAddress addr)))     modify (\st -> st { stHandle = mHandle })     F.forM_ mHandle $ \_ -> runMPD checkConn >>= (`unless` runMPD close)     where-        safeConnectTo host@('/':_) _ =-            (Just <$> connectTo "" (UnixSocket host))-            `E.catch` (\(_ :: E.SomeException) -> return Nothing)-        safeConnectTo host port =-            (Just <$> connectTo host (PortNumber $ fromInteger port))-            `E.catch` (\(_ :: E.SomeException) -> return Nothing)+        safeConnectTo (sock,addr) =+            (connect sock addr) >> (Just <$> socketToHandle sock ReadWriteMode)+            `catchAny` const (return Nothing)         checkConn = do             singleMsg <- send ""             let [msg] = singleMsg@@ -151,7 +164,7 @@     where         sendClose handle =             (hPutStrLn handle "close" >> hReady handle >> hClose handle >> return Nothing)-            `E.catch` handler+            `catch` handler          handler err             | isEOFError err = return Nothing
tests/Arbitrary.hs view
@@ -20,14 +20,17 @@ import qualified Data.Map as M import           Data.Time import           Test.QuickCheck+import           Test.QuickCheck.Arbitrary+import           Test.QuickCheck.Gen  import           Network.MPD.Commands.Types  import           Data.ByteString (ByteString) import qualified Data.ByteString.UTF8 as UTF8 + instance Arbitrary ByteString where-  arbitrary = UTF8.fromString <$> arbitrary+  arbitrary = UTF8.fromString <$> listOf1 arbitraryPrintableChar  -- No longer provided by QuickCheck 2 -- two :: Monad m => m a -> m (a, a)@@ -46,7 +49,7 @@  -- MPD fields can't contain newlines and the parser skips initial spaces. field :: Gen String-field = (filter (/= '\n') . dropWhile isSpace) <$> arbitrary+field = (filter (/= '\n') . dropWhile isSpace) <$> listOf1 arbitraryPrintableChar  fieldBS :: Gen ByteString fieldBS = UTF8.fromString <$> field
tests/ParserSpec.hs view
@@ -41,25 +41,25 @@ -- This property also ensures, that (instance Arbitrary UTCTime) is sound. -- Indeed, a bug in the instance declaration was the primary motivation to add -- this property.-prop_parseIso8601 :: UTCTime -> Bool-prop_parseIso8601 t = Just t == (parseIso8601 . UTF8.fromString . formatIso8601) t+prop_parseIso8601 :: UTCTime -> Expectation+prop_parseIso8601 t = Just t `shouldBe` (parseIso8601 . UTF8.fromString . formatIso8601) t -prop_parseCount :: Count -> Bool-prop_parseCount c = Right c == (parseCount . map UTF8.fromString . lines . unparse) c+prop_parseCount :: Count -> Expectation+prop_parseCount c = Right c `shouldBe` (parseCount . map UTF8.fromString . lines . unparse) c -prop_parseOutputs :: [Device] -> Bool+prop_parseOutputs :: [Device] -> Expectation prop_parseOutputs ds =-    Right ds == (parseOutputs . map UTF8.fromString . lines . concatMap unparse) ds+    Right ds `shouldBe` (parseOutputs . map UTF8.fromString . lines . concatMap unparse) ds  deriving instance Ord Value -prop_parseSong :: Song -> Bool-prop_parseSong s = Right (sortTags s) == sortTags `fmap` (parseSong . toAssocList . map UTF8.fromString . lines . unparse) s+prop_parseSong :: Song -> Expectation+prop_parseSong s = Right (sortTags s) `shouldBe` sortTags `fmap` (parseSong . toAssocList . map UTF8.fromString . lines . unparse) s   where     -- We consider lists of tag values equal if they contain the same elements.     -- To ensure that two lists with the same elements are equal, we bring the     -- elements in a deterministic order.     sortTags song = song { sgTags = M.map sort $ sgTags song } -prop_parseStats :: Stats -> Bool-prop_parseStats s = Right s == (parseStats . map UTF8.fromString . lines . unparse) s+prop_parseStats :: Stats -> Expectation+prop_parseStats s = Right s `shouldBe` (parseStats . map UTF8.fromString . lines . unparse) s
tests/StringConn.hs view
@@ -1,10 +1,10 @@ {-# LANGUAGE GeneralizedNewtypeDeriving, MultiParamTypeClasses #-} {-# OPTIONS_GHC -Wwarn #-} --- | Module    : StringConn+-- |+-- Module      : StringConn -- Copyright   : (c) Ben Sinclair 2005-2009 -- License     : MIT (see LICENSE)--- Maintainer  : bsinclai@turing.une.edu.au -- Stability   : alpha -- -- A testing scaffold for MPD commands
+ tests/TypeSpec.hs view
@@ -0,0 +1,34 @@+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module TypeSpec (main, spec) where++import           Test.Hspec+import           Test.Hspec.QuickCheck (prop)+import qualified Test.QuickCheck as QC++import           Network.MPD.Commands.Types (Volume)++main :: IO ()+main = hspec spec++spec :: Spec+spec = do+    describe "Volume arithmetic is closed over 0-100" $ do+        prop "for addition" prop_volume_add+        prop "for subtraction" prop_volume_sub+        prop "for multiplication" prop_volume_mul+        prop "for division" prop_volume_div++instance QC.Arbitrary Volume where+    arbitrary = QC.elements [0..100]++inRange :: Ord a => a -> a -> a -> Bool+inRange l h x = l <= x && x <= h++prop_volume_arith op cur new = inRange 0 100 ((cur :: Volume) `op` new)++prop_volume_add = prop_volume_arith (+)+prop_volume_sub = prop_volume_arith (-)+prop_volume_mul = prop_volume_arith (*)+prop_volume_div cur new = new /= 0 QC.==> prop_volume_arith div cur new
tests/Unparse.hs view
@@ -53,12 +53,16 @@         , "db_update: " ++ show (stsDbUpdate s)         ] +instance Unparse Volume where+    unparse (Volume x) = show x+ instance Unparse Status where     unparse s = unlines $         [ "state: " ++ (case stState s of Playing -> "play"                                           Paused  -> "pause"                                           _       -> "stop")-        , "volume: " ++ maybe "-1" show (stVolume s)+        , "volume: " ++ maybe "-1" unparse (stVolume s)+        , "volume: " ++ unparse (stVolume s)         , "repeat: " ++ showBool (stRepeat s)         , "random: " ++ showBool (stRandom s)         , "playlist: " ++ show (stPlaylistVersion s)