packages feed

libmpd 0.4.2 → 0.5.0

raw patch · 12 files changed

+723/−536 lines, 12 filesdep +containersdep +old-localedep +timePVP ok

version bump matches the API change (PVP)

Dependencies added: containers, old-locale, time

API changes (from Hackage documentation)

- Network.MPD: Any :: Meta
- Network.MPD: Database :: Subsystem
- Network.MPD: Device :: Int -> String -> Bool -> Device
- Network.MPD: Filename :: Meta
- Network.MPD: ID :: Integer -> PLIndex
- Network.MPD: Mixer :: Subsystem
- Network.MPD: Options :: Subsystem
- Network.MPD: Player :: Subsystem
- Network.MPD: Pos :: Integer -> PLIndex
- Network.MPD: StoredPlaylist :: Subsystem
- Network.MPD: Update :: Subsystem
- Network.MPD: addMany :: (MonadMPD m) => PlaylistName -> [Path] -> m ()
- Network.MPD: complete :: (MonadMPD m) => String -> m [Either Path Song]
- Network.MPD: crop :: (MonadMPD m) => Maybe PLIndex -> Maybe PLIndex -> m ()
- Network.MPD: dOutputEnabled :: Device -> Bool
- Network.MPD: dOutputID :: Device -> Int
- Network.MPD: dOutputName :: Device -> String
- Network.MPD: data Device
- Network.MPD: data Meta
- Network.MPD: data PLIndex
- Network.MPD: defaultCount :: Count
- Network.MPD: defaultDevice :: Device
- Network.MPD: defaultSong :: Song
- Network.MPD: defaultStats :: Stats
- Network.MPD: defaultStatus :: Status
- Network.MPD: deleteMany :: (MonadMPD m) => PlaylistName -> [PLIndex] -> m ()
- Network.MPD: getPlaylist :: (MonadMPD m) => m [Song]
- Network.MPD: listAlbum :: (MonadMPD m) => Artist -> Album -> m [Song]
- Network.MPD: listAlbums :: (MonadMPD m) => Maybe Artist -> m [Album]
- Network.MPD: listArtists :: (MonadMPD m) => m [Artist]
- Network.MPD: lsDirs :: (MonadMPD m) => Path -> m [Path]
- Network.MPD: lsFiles :: (MonadMPD m) => Path -> m [Path]
- Network.MPD: lsPlaylists :: (MonadMPD m) => m [PlaylistName]
- Network.MPD: prune :: (MonadMPD m) => m ()
- Network.MPD: sgAlbum :: Song -> String
- Network.MPD: sgArtist :: Song -> String
- Network.MPD: sgAux :: Song -> [(String, String)]
- Network.MPD: sgComposer :: Song -> String
- Network.MPD: sgDate :: Song -> Int
- Network.MPD: sgDisc :: Song -> Maybe (Int, Int)
- Network.MPD: sgGenre :: Song -> String
- Network.MPD: sgName :: Song -> String
- Network.MPD: sgPerformer :: Song -> String
- Network.MPD: sgTitle :: Song -> String
- Network.MPD: sgTrack :: Song -> (Int, Int)
- Network.MPD: stPlaylistVersion :: Status -> Integer
- Network.MPD: toggle :: (MonadMPD m) => m ()
- Network.MPD: updateId :: (MonadMPD m) => [Path] -> m Integer
- Network.MPD: volume :: (MonadMPD m) => Int -> m ()
+ Network.MPD: AlbumArtist :: Metadata
+ Network.MPD: AlbumArtistSort :: Metadata
+ Network.MPD: ArtistSort :: Metadata
+ Network.MPD: Comment :: Metadata
+ Network.MPD: DatabaseS :: Subsystem
+ Network.MPD: DirectoryE :: String -> Entry
+ Network.MPD: MUSICBRAINZ_ALBUMARTISTID :: Metadata
+ Network.MPD: MUSICBRAINZ_ALBUMID :: Metadata
+ Network.MPD: MUSICBRAINZ_ARTISTID :: Metadata
+ Network.MPD: MUSICBRAINZ_TRACKID :: Metadata
+ Network.MPD: MixerS :: Subsystem
+ Network.MPD: OptionsS :: Subsystem
+ Network.MPD: OutputS :: Subsystem
+ Network.MPD: PlayerS :: Subsystem
+ Network.MPD: PlaylistE :: Playlist -> Entry
+ Network.MPD: PlaylistS :: Subsystem
+ Network.MPD: SongE :: Song -> Entry
+ Network.MPD: StoredPlaylistS :: Subsystem
+ Network.MPD: UpdateS :: Subsystem
+ Network.MPD: data Entry
+ Network.MPD: data Metadata
+ Network.MPD: data Output
+ Network.MPD: data Playlist
+ Network.MPD: deleteId :: MonadMPD m => Int -> m ()
+ Network.MPD: getIdle :: MonadMPD m => m [Subsystem]
+ Network.MPD: moveId :: MonadMPD m => Int -> Int -> m ()
+ Network.MPD: outEnabled :: Output -> Bool
+ Network.MPD: outID :: Output -> Int
+ Network.MPD: outName :: Output -> String
+ Network.MPD: plLastModified :: Playlist -> Maybe UTCTime
+ Network.MPD: plName :: Playlist -> String
+ Network.MPD: playId :: MonadMPD m => Int -> m ()
+ Network.MPD: playlistId :: MonadMPD m => Maybe Int -> m [Song]
+ Network.MPD: seekId :: MonadMPD m => Int -> Seconds -> m ()
+ Network.MPD: sgGet :: Metadata -> Song -> Maybe [String]
+ Network.MPD: sgLastModified :: Song -> Maybe UTCTime
+ Network.MPD: sgTags :: Song -> Map Metadata [String]
+ Network.MPD: stMixRampDelay :: Status -> Double
+ Network.MPD: stMixRampdB :: Status -> Double
+ Network.MPD: stPlaylistID :: Status -> Integer
+ Network.MPD: swapId :: MonadMPD m => Int -> Int -> m ()
+ Network.MPD.Commands.Extensions: addMany :: MonadMPD m => PlaylistName -> [Path] -> m ()
+ Network.MPD.Commands.Extensions: getPlaylist :: MonadMPD m => m [Song]
+ Network.MPD.Commands.Extensions: listAlbum :: MonadMPD m => Artist -> Album -> m [Song]
+ Network.MPD.Commands.Extensions: listAlbums :: MonadMPD m => Maybe Artist -> m [Album]
+ Network.MPD.Commands.Extensions: listArtists :: MonadMPD m => m [Artist]
+ Network.MPD.Commands.Extensions: toggle :: MonadMPD m => m ()
+ Network.MPD.Commands.Extensions: updateId :: MonadMPD m => [Path] -> m Integer
+ Network.MPD.Commands.Extensions: volume :: MonadMPD m => Int -> m ()
+ Network.MPD.Core: getHandle :: MonadMPD m => m (Maybe Handle)
+ Network.MPD.Core: getVersion :: MonadMPD m => m (Int, Int, Int)
+ Network.MPD.Core: receive :: MonadMPD m => m [String]
+ Network.MPD.Core: setPassword :: MonadMPD m => String -> m ()
- Network.MPD: (=?) :: Meta -> String -> Query
+ Network.MPD: (=?) :: Metadata -> String -> Query
- Network.MPD: Album :: Meta
+ Network.MPD: Album :: Metadata
- Network.MPD: Artist :: Meta
+ Network.MPD: Artist :: Metadata
- Network.MPD: Composer :: Meta
+ Network.MPD: Composer :: Metadata
- Network.MPD: Date :: Meta
+ Network.MPD: Date :: Metadata
- Network.MPD: Disc :: Meta
+ Network.MPD: Disc :: Metadata
- Network.MPD: Genre :: Meta
+ Network.MPD: Genre :: Metadata
- Network.MPD: Name :: Meta
+ Network.MPD: Name :: Metadata
- Network.MPD: Output :: Subsystem
+ Network.MPD: Output :: Int -> String -> Bool -> Output
- Network.MPD: Performer :: Meta
+ Network.MPD: Performer :: Metadata
- Network.MPD: Playlist :: Subsystem
+ Network.MPD: Playlist :: String -> Maybe UTCTime -> Playlist
- Network.MPD: Song :: String -> String -> String -> String -> String -> String -> String -> String -> Seconds -> Int -> (Int, Int) -> Maybe (Int, Int) -> Maybe PLIndex -> [(String, String)] -> Song
+ Network.MPD: Song :: String -> Map Metadata [String] -> Maybe UTCTime -> Seconds -> Maybe (Int, Int) -> Song
- Network.MPD: Status :: State -> Int -> Bool -> Bool -> Integer -> Integer -> Maybe PLIndex -> Maybe PLIndex -> Maybe PLIndex -> Maybe PLIndex -> (Seconds, Seconds) -> Int -> Seconds -> (Int, Int, Int) -> Integer -> Bool -> Bool -> String -> Status
+ Network.MPD: Status :: State -> Int -> Bool -> Bool -> Integer -> Integer -> Maybe Int -> Maybe Int -> Maybe Int -> Maybe Int -> (Double, Seconds) -> Int -> Seconds -> Double -> Double -> (Int, Int, Int) -> Integer -> Bool -> Bool -> Maybe String -> Status
- Network.MPD: Title :: Meta
+ Network.MPD: Title :: Metadata
- Network.MPD: Track :: Meta
+ Network.MPD: Track :: Metadata
- Network.MPD: add :: (MonadMPD m) => Path -> m [Path]
+ Network.MPD: add :: MonadMPD m => Path -> m [Path]
- Network.MPD: addId :: (MonadMPD m) => Path -> Maybe Integer -> m Integer
+ Network.MPD: addId :: MonadMPD m => Path -> Maybe Integer -> m Int
- Network.MPD: add_ :: (MonadMPD m) => Path -> m ()
+ Network.MPD: add_ :: MonadMPD m => Path -> m ()
- Network.MPD: clear :: (MonadMPD m) => m ()
+ Network.MPD: clear :: MonadMPD m => m ()
- Network.MPD: clearError :: (MonadMPD m) => m ()
+ Network.MPD: clearError :: MonadMPD m => m ()
- Network.MPD: close :: (MonadMPD m) => m ()
+ Network.MPD: close :: MonadMPD m => m ()
- Network.MPD: commands :: (MonadMPD m) => m [String]
+ Network.MPD: commands :: MonadMPD m => m [String]
- Network.MPD: consume :: (MonadMPD m) => Bool -> m ()
+ Network.MPD: consume :: MonadMPD m => Bool -> m ()
- Network.MPD: count :: (MonadMPD m) => Query -> m Count
+ Network.MPD: count :: MonadMPD m => Query -> m Count
- Network.MPD: crossfade :: (MonadMPD m) => Seconds -> m ()
+ Network.MPD: crossfade :: MonadMPD m => Seconds -> m ()
- Network.MPD: currentSong :: (Functor m, MonadMPD m) => m (Maybe Song)
+ Network.MPD: currentSong :: MonadMPD m => m (Maybe Song)
- Network.MPD: decoders :: (MonadMPD m) => m [(String, [(String, String)])]
+ Network.MPD: decoders :: MonadMPD m => m [(String, [(String, String)])]
- Network.MPD: delete :: (MonadMPD m) => PLIndex -> m ()
+ Network.MPD: delete :: MonadMPD m => Int -> m ()
- Network.MPD: disableOutput :: (MonadMPD m) => Int -> m ()
+ Network.MPD: disableOutput :: MonadMPD m => Int -> m ()
- Network.MPD: enableOutput :: (MonadMPD m) => Int -> m ()
+ Network.MPD: enableOutput :: MonadMPD m => Int -> m ()
- Network.MPD: find :: (MonadMPD m) => Query -> m [Song]
+ Network.MPD: find :: MonadMPD m => Query -> m [Song]
- Network.MPD: findAdd :: (MonadMPD m) => Query -> m ()
+ Network.MPD: findAdd :: MonadMPD m => Query -> m ()
- Network.MPD: idle :: (MonadMPD m) => m [Subsystem]
+ Network.MPD: idle :: MonadMPD m => [Subsystem] -> m ()
- Network.MPD: kill :: (MonadMPD m) => m ()
+ Network.MPD: kill :: MonadMPD m => m ()
- Network.MPD: list :: (MonadMPD m) => Meta -> Query -> m [String]
+ Network.MPD: list :: MonadMPD m => Metadata -> Query -> m [String]
- Network.MPD: listAll :: (MonadMPD m) => Path -> m [Path]
+ Network.MPD: listAll :: MonadMPD m => Path -> m [Path]
- Network.MPD: listAllInfo :: (MonadMPD m) => Path -> m [Either Path Song]
+ Network.MPD: listAllInfo :: MonadMPD m => Path -> m [Song]
- Network.MPD: listPlaylist :: (MonadMPD m) => PlaylistName -> m [Path]
+ Network.MPD: listPlaylist :: MonadMPD m => PlaylistName -> m [Path]
- Network.MPD: listPlaylistInfo :: (MonadMPD m) => PlaylistName -> m [Song]
+ Network.MPD: listPlaylistInfo :: MonadMPD m => PlaylistName -> m [Song]
- Network.MPD: listPlaylists :: (MonadMPD m) => m [PlaylistName]
+ Network.MPD: listPlaylists :: MonadMPD m => m [PlaylistName]
- Network.MPD: load :: (MonadMPD m) => PlaylistName -> m ()
+ Network.MPD: load :: MonadMPD m => PlaylistName -> m ()
- Network.MPD: lsInfo :: (MonadMPD m) => Path -> m [Either Path Song]
+ Network.MPD: lsInfo :: MonadMPD m => Path -> m [Entry]
- Network.MPD: move :: (MonadMPD m) => PLIndex -> Integer -> m ()
+ Network.MPD: move :: MonadMPD m => Int -> Int -> m ()
- Network.MPD: next :: (MonadMPD m) => m ()
+ Network.MPD: next :: MonadMPD m => m ()
- Network.MPD: noidle :: (MonadMPD m) => m ()
+ Network.MPD: noidle :: MonadMPD m => m ()
- Network.MPD: notCommands :: (MonadMPD m) => m [String]
+ Network.MPD: notCommands :: MonadMPD m => m [String]
- Network.MPD: outputs :: (MonadMPD m) => m [Device]
+ Network.MPD: outputs :: MonadMPD m => m [Output]
- Network.MPD: password :: (MonadMPD m) => String -> m ()
+ Network.MPD: password :: MonadMPD m => String -> m ()
- Network.MPD: pause :: (MonadMPD m) => Bool -> m ()
+ Network.MPD: pause :: MonadMPD m => Bool -> m ()
- Network.MPD: ping :: (MonadMPD m) => m ()
+ Network.MPD: ping :: MonadMPD m => m ()
- Network.MPD: plChanges :: (MonadMPD m) => Integer -> m [Song]
+ Network.MPD: plChanges :: MonadMPD m => Integer -> m [Song]
- Network.MPD: plChangesPosId :: (MonadMPD m) => Integer -> m [(PLIndex, PLIndex)]
+ Network.MPD: plChangesPosId :: MonadMPD m => Integer -> m [(Int, Int)]
- Network.MPD: play :: (MonadMPD m) => Maybe PLIndex -> m ()
+ Network.MPD: play :: MonadMPD m => Maybe Int -> m ()
- Network.MPD: playlist :: (MonadMPD m) => m [(PLIndex, Path)]
+ Network.MPD: playlist :: MonadMPD m => m [(Int, Path)]
- Network.MPD: playlistAdd :: (MonadMPD m) => PlaylistName -> Path -> m [Path]
+ Network.MPD: playlistAdd :: MonadMPD m => PlaylistName -> Path -> m [Path]
- Network.MPD: playlistAdd_ :: (MonadMPD m) => PlaylistName -> Path -> m ()
+ Network.MPD: playlistAdd_ :: MonadMPD m => PlaylistName -> Path -> m ()
- Network.MPD: playlistClear :: (MonadMPD m) => PlaylistName -> m ()
+ Network.MPD: playlistClear :: MonadMPD m => PlaylistName -> m ()
- Network.MPD: playlistDelete :: (MonadMPD m) => PlaylistName -> Integer -> m ()
+ Network.MPD: playlistDelete :: MonadMPD m => PlaylistName -> Integer -> m ()
- Network.MPD: playlistFind :: (MonadMPD m) => Query -> m [Song]
+ Network.MPD: playlistFind :: MonadMPD m => Query -> m [Song]
- Network.MPD: playlistInfo :: (MonadMPD m) => Maybe (Either PLIndex (Int, Int)) -> m [Song]
+ Network.MPD: playlistInfo :: MonadMPD m => Maybe (Int, Int) -> m [Song]
- Network.MPD: playlistMove :: (MonadMPD m) => PlaylistName -> Integer -> Integer -> m ()
+ Network.MPD: playlistMove :: MonadMPD m => PlaylistName -> Integer -> Integer -> m ()
- Network.MPD: playlistSearch :: (MonadMPD m) => Query -> m [Song]
+ Network.MPD: playlistSearch :: MonadMPD m => Query -> m [Song]
- Network.MPD: previous :: (MonadMPD m) => m ()
+ Network.MPD: previous :: MonadMPD m => m ()
- Network.MPD: random :: (MonadMPD m) => Bool -> m ()
+ Network.MPD: random :: MonadMPD m => Bool -> m ()
- Network.MPD: rename :: (MonadMPD m) => PlaylistName -> PlaylistName -> m ()
+ Network.MPD: rename :: MonadMPD m => PlaylistName -> PlaylistName -> m ()
- Network.MPD: repeat :: (MonadMPD m) => Bool -> m ()
+ Network.MPD: repeat :: MonadMPD m => Bool -> m ()
- Network.MPD: replayGainMode :: (MonadMPD m) => ReplayGainMode -> m ()
+ Network.MPD: replayGainMode :: MonadMPD m => ReplayGainMode -> m ()
- Network.MPD: replayGainStatus :: (MonadMPD m) => m [String]
+ Network.MPD: replayGainStatus :: MonadMPD m => m [String]
- Network.MPD: rescan :: (MonadMPD m) => [Path] -> m ()
+ Network.MPD: rescan :: MonadMPD m => [Path] -> m ()
- Network.MPD: rm :: (MonadMPD m) => PlaylistName -> m ()
+ Network.MPD: rm :: MonadMPD m => PlaylistName -> m ()
- Network.MPD: save :: (MonadMPD m) => PlaylistName -> m ()
+ Network.MPD: save :: MonadMPD m => PlaylistName -> m ()
- Network.MPD: search :: (MonadMPD m) => Query -> m [Song]
+ Network.MPD: search :: MonadMPD m => Query -> m [Song]
- Network.MPD: seek :: (MonadMPD m) => Maybe PLIndex -> Seconds -> m ()
+ Network.MPD: seek :: MonadMPD m => Int -> Seconds -> m ()
- Network.MPD: setVolume :: (MonadMPD m) => Int -> m ()
+ Network.MPD: setVolume :: MonadMPD m => Int -> m ()
- Network.MPD: sgIndex :: Song -> Maybe PLIndex
+ Network.MPD: sgIndex :: Song -> Maybe (Int, Int)
- Network.MPD: shuffle :: (MonadMPD m) => Maybe (Int, Int) -> m ()
+ Network.MPD: shuffle :: MonadMPD m => Maybe (Int, Int) -> m ()
- Network.MPD: single :: (MonadMPD m) => Bool -> m ()
+ Network.MPD: single :: MonadMPD m => Bool -> m ()
- Network.MPD: stError :: Status -> String
+ Network.MPD: stError :: Status -> Maybe String
- Network.MPD: stNextSongID :: Status -> Maybe PLIndex
+ Network.MPD: stNextSongID :: Status -> Maybe Int
- Network.MPD: stNextSongPos :: Status -> Maybe PLIndex
+ Network.MPD: stNextSongPos :: Status -> Maybe Int
- Network.MPD: stSongID :: Status -> Maybe PLIndex
+ Network.MPD: stSongID :: Status -> Maybe Int
- Network.MPD: stSongPos :: Status -> Maybe PLIndex
+ Network.MPD: stSongPos :: Status -> Maybe Int
- Network.MPD: stTime :: Status -> (Seconds, Seconds)
+ Network.MPD: stTime :: Status -> (Double, Seconds)
- Network.MPD: stats :: (MonadMPD m) => m Stats
+ Network.MPD: stats :: MonadMPD m => m Stats
- Network.MPD: status :: (MonadMPD m) => m Status
+ Network.MPD: status :: MonadMPD m => m Status
- Network.MPD: stickerDelete :: (MonadMPD m) => ObjectType -> String -> String -> m ()
+ Network.MPD: stickerDelete :: MonadMPD m => ObjectType -> String -> String -> m ()
- Network.MPD: stickerFind :: (MonadMPD m) => ObjectType -> String -> String -> m [(String, String)]
+ Network.MPD: stickerFind :: MonadMPD m => ObjectType -> String -> String -> m [(String, String)]
- Network.MPD: stickerGet :: (MonadMPD m) => ObjectType -> String -> String -> m [String]
+ Network.MPD: stickerGet :: MonadMPD m => ObjectType -> String -> String -> m [String]
- Network.MPD: stickerList :: (MonadMPD m) => ObjectType -> String -> m [(String, String)]
+ Network.MPD: stickerList :: MonadMPD m => ObjectType -> String -> m [(String, String)]
- Network.MPD: stickerSet :: (MonadMPD m) => ObjectType -> String -> String -> String -> m ()
+ Network.MPD: stickerSet :: MonadMPD m => ObjectType -> String -> String -> String -> m ()
- Network.MPD: stop :: (MonadMPD m) => m ()
+ Network.MPD: stop :: MonadMPD m => m ()
- Network.MPD: swap :: (MonadMPD m) => PLIndex -> PLIndex -> m ()
+ Network.MPD: swap :: MonadMPD m => Int -> Int -> m ()
- Network.MPD: tagTypes :: (MonadMPD m) => m [String]
+ Network.MPD: tagTypes :: MonadMPD m => m [String]
- Network.MPD: update :: (MonadMPD m) => [Path] -> m ()
+ Network.MPD: update :: MonadMPD m => [Path] -> m ()
- Network.MPD: urlHandlers :: (MonadMPD m) => m [String]
+ Network.MPD: urlHandlers :: MonadMPD m => m [String]
- Network.MPD.Core: close :: (MonadMPD m) => m ()
+ Network.MPD.Core: close :: MonadMPD m => m ()
- Network.MPD.Core: getPassword :: (MonadMPD m) => m Password
+ Network.MPD.Core: getPassword :: MonadMPD m => m Password
- Network.MPD.Core: getResponse :: (MonadMPD m) => String -> m [String]
+ Network.MPD.Core: getResponse :: MonadMPD m => String -> m [String]
- Network.MPD.Core: kill :: (MonadMPD m) => m ()
+ Network.MPD.Core: kill :: MonadMPD m => m ()
- Network.MPD.Core: open :: (MonadMPD m) => m ()
+ Network.MPD.Core: open :: MonadMPD m => m ()
- Network.MPD.Core: send :: (MonadMPD m) => String -> m String
+ Network.MPD.Core: send :: MonadMPD m => String -> m ()

Files

ChangeLog view
@@ -1,3 +1,16 @@+* v0.5.0, 2010-09-08+	- Moved extensions to Network.MPD.Commands.Extensions+	These might be removed in a later version+	- Non-blocking `idle'+	- The API is closer to the MPD spec, by untangling functionality+	- Better MPD API coverage+	- Improved parser implementation, now runs in constant space+	- Constructors of the `Subsystem' type have been renamed+	- Passwords can be changed using `setPassword'+	- The connection handle can be accessed via `getHandle'+	- The version of the MPD server is available via `getVersion'+	- Added support for connecting via unix sockets+ * v0.4.1, 2010-08-31 	- Only depend on QuickCheck when building the test target 
Network/MPD/Commands.hs view
@@ -16,18 +16,19 @@     module Network.MPD.Commands.Query,      -- * Querying MPD's status-    clearError, currentSong, idle, noidle, status, stats,+    clearError, currentSong, idle, getIdle, noidle, status, stats,      -- * Playback options     consume, crossfade, random, repeat, setVolume, single, replayGainMode,     replayGainStatus,      -- * Controlling playback-    next, pause, play, previous, seek, stop,+    next, pause, play, playId, previous, seek, seekId, stop,      -- * The current playlist-    add, add_, addId, clear, delete, move, playlist, playlistFind,-    playlistInfo, playlistSearch, plChanges, plChangesPosId, shuffle, swap,+    add, add_, addId, clear, delete, deleteId, move, moveId, playlist,+    playlistId, playlistFind, playlistInfo, playlistSearch, plChanges,+    plChangesPosId, shuffle, swap, swapId,      -- * Stored playlist     listPlaylist, listPlaylistInfo, listPlaylists, load, playlistAdd,@@ -49,25 +50,20 @@      -- * Reflection     commands, notCommands, tagTypes, urlHandlers, decoders,--    -- * Extensions\/shortcuts-    addMany, deleteMany, complete, crop, prune, lsDirs, lsFiles, lsPlaylists,-    listArtists, listAlbums, listAlbum, getPlaylist, toggle, updateId, volume     ) where  import Network.MPD.Commands.Arg import Network.MPD.Commands.Parse import Network.MPD.Commands.Query import Network.MPD.Commands.Types+import Network.MPD.Commands.Util import Network.MPD.Core import Network.MPD.Utils -import Control.Monad (liftM, unless)+import Control.Monad (liftM) import Control.Monad.Error (throwError) import Prelude hiding (repeat)-import Data.List (findIndex, intersperse, isPrefixOf) import Data.Maybe-import System.FilePath (dropFileName)  -- -- Querying MPD's status@@ -77,42 +73,34 @@ clearError :: MonadMPD m => m () clearError = getResponse_ "clearerror" --- | Get the currently playing song.-currentSong :: (Functor m, MonadMPD m) => m (Maybe Song)-currentSong = do-    cs <- status-    if stState cs == Stopped-       then return Nothing-       else getResponse1 "currentsong" >>=-            fmap Just . runParser parseSong . toAssocList+-- | Get the current song.+currentSong :: MonadMPD m => m (Maybe Song)+currentSong = getResponse "currentsong" >>= takeSongs >>= return . listToMaybe --- | Wait until there is a noteworthy change in one or more of MPD's--- susbystems. Note that running this command will block until either 'idle'--- returns or is cancelled by 'noidle'.-idle :: MonadMPD m => m [Subsystem]-idle =-    mapM (\("changed", system) -> case system of "database" -> return Database-                                                 "update"   -> return Update-                                                 "stored_playlist" -> return StoredPlaylist-                                                 "playlist" -> return Playlist-                                                 "player" -> return Player-                                                 "mixer" -> return Mixer-                                                 "output" -> return Output-                                                 "options" -> return Options-                                                 k -> fail ("Unknown subsystem: " ++ k))-         =<< toAssocList `liftM` getResponse "idle"+-- | Make MPD server notify the client if there is a noteworthy change+--   in one or more of its subsystems. Note that after running this command+--   you can either monitor handle for incoming notifications or cancel this+--   by 'noidle'. Any command other than 'noidle' sent to MPD server while+--   idle is active will be ignored.+idle :: MonadMPD m => [Subsystem] -> m ()+idle ss = send ("idle" <$> foldr (<++>) (Args []) ss) +-- | Get idle notifications. If there is no notifications ready+--   at the moment, this function will block until they show up.+getIdle :: MonadMPD m => m [Subsystem]+getIdle = getResponse "" >>= return . parseIdle . toAssocList+ -- | Cancel 'idle'. noidle :: MonadMPD m => m () noidle = getResponse_ "noidle"  -- | Get server statistics. stats :: MonadMPD m => m Stats-stats = getResponse "stats" >>= runParser parseStats+stats = getResponse "stats" >>= return . parseStats . toAssocList  -- | Get the server's status. status :: MonadMPD m => m Status-status = getResponse "status" >>= runParser parseStatus+status = getResponse "status" >>= return . parseStatus . toAssocList  -- -- Playback options@@ -163,24 +151,26 @@ pause = getResponse_ . ("pause" <$>)  -- | Begin\/continue playing.-play :: MonadMPD m => Maybe PLIndex -> m ()-play Nothing        = getResponse_  "play"-play (Just (Pos x)) = getResponse_ ("play"   <$> x)-play (Just (ID x))  = getResponse_ ("playid" <$> x)+play :: MonadMPD m => Maybe Int -> m ()+play (Just pos) = getResponse_ ("play" <$> pos)+play _          = getResponse_  "play" +-- | Play a file with given id.+playId :: MonadMPD m => Int -> m ()+playId id' = getResponse_ ("playid" <$> id')+ -- | Play the previous song. previous :: MonadMPD m => m () previous = getResponse_ "previous"  -- | Seek to some point in a song.--- Seeks in current song if no position is given.-seek :: MonadMPD m => Maybe PLIndex -> Seconds -> m ()-seek (Just (Pos x)) time = getResponse_ ("seek" <$> x <++> time)-seek (Just (ID x)) time = getResponse_ ("seekid" <$> x <++> time)-seek Nothing time = do-    st <- status-    unless (stState st == Stopped) (seek (stSongID st) time)+seek :: MonadMPD m => Int -> Seconds -> m ()+seek pos time = getResponse_ ("seek" <$> pos <++> time) +-- | Seek to some point in a song (id version)+seekId :: MonadMPD m => Int -> Seconds -> m ()+seekId id' time = getResponse_ ("seekid" <$> id' <++> time)+ -- | Stop playing. stop :: MonadMPD m => m () stop = getResponse_ "stop"@@ -192,7 +182,7 @@ -- This might do better to throw an exception than silently return 0. -- | Like 'add', but returns a playlist id. addId :: MonadMPD m => Path -> Maybe Integer -- ^ Optional playlist position-      -> m Integer+      -> m Int addId p pos = liftM (parse parseNum id 0 . snd . head . toAssocList)               $ getResponse1 ("addid" <$> p <++> pos) @@ -209,24 +199,31 @@ clear = getResponse_ "clear"  -- | Remove a song from the current playlist.-delete :: MonadMPD m => PLIndex -> m ()-delete (Pos x) = getResponse_ ("delete" <$> x)-delete (ID x)  = getResponse_ ("deleteid" <$> x)+delete :: MonadMPD m => Int -> m ()+delete pos = getResponse_ ("delete" <$> pos) +-- | Remove a song from the current playlist.+deleteId :: MonadMPD m => Int -> m ()+deleteId id' = getResponse_ ("deleteid" <$> id')+ -- | Move a song to a given position in the current playlist.-move :: MonadMPD m => PLIndex -> Integer -> m ()-move (Pos from) to = getResponse_ ("move" <$> from <++> to)-move (ID from) to = getResponse_ ("moveid" <$> from <++> to)+move :: MonadMPD m => Int -> Int -> m ()+move pos to = getResponse_ ("move" <$> pos <++> to) +-- | Move a song from (songid) to (playlist index) in the playlist. If to is+-- negative, it is relative to the current song in the playlist (if there is one).+moveId :: MonadMPD m => Int -> Int -> m ()+moveId id' to = getResponse_ ("moveid" <$> id' <++> to)+ -- | Retrieve file paths and positions of songs in the current playlist. -- Note that this command is only included for completeness sake; it's -- deprecated and likely to disappear at any time, please use 'playlistInfo' -- instead.-playlist :: MonadMPD m => m [(PLIndex, Path)]+playlist :: MonadMPD m => m [(Int, Path)] playlist = mapM f =<< getResponse "playlist"     where f s | (pos, name) <- breakChar ':' s               , Just pos'   <- parseNum pos-              = return (Pos pos', name)+              = return (pos', name)               | otherwise = throwError . Unexpected $ show s  -- | Search for songs in the current playlist with strict matching.@@ -234,14 +231,14 @@ playlistFind q = takeSongs =<< getResponse ("playlistfind" <$> q)  -- | Retrieve metadata for songs in the current playlist.-playlistInfo :: MonadMPD m => Maybe (Either PLIndex (Int, Int)) -> m [Song]-playlistInfo x = getResponse cmd >>= takeSongs-    where cmd = case x of-                    Nothing -> "playlistinfo"-                    Just (Left (Pos x')) -> "playlistinfo" <$> x'-                    Just (Left (ID x'))  -> "playlistid" <$> x'-                    Just (Right range)   -> "playlistinfo" <$> range+playlistInfo :: MonadMPD m => Maybe (Int, Int) -> m [Song]+playlistInfo range = takeSongs =<< getResponse ("playlistinfo" <$> range) +-- | Displays a list of songs in the playlist.+-- If id is specified, only its info is returned.+playlistId :: MonadMPD m => Maybe Int -> m [Song]+playlistId id' = takeSongs =<< getResponse ("playlistinfo" <$> id')+ -- | Search case-insensitively with partial matches for songs in the -- current playlist. playlistSearch :: MonadMPD m => Query -> m [Song]@@ -253,13 +250,13 @@ plChanges version = takeSongs =<< getResponse ("plchanges" <$> version)  -- | Like 'plChanges' but only returns positions and ids.-plChangesPosId :: MonadMPD m => Integer -> m [(PLIndex, PLIndex)]+plChangesPosId :: MonadMPD m => Integer -> m [(Int, Int)] plChangesPosId plver =     getResponse ("plchangesposid" <$> plver) >>=     mapM f . splitGroups [("cpos",id)] . toAssocList     where f xs | [("cpos", x), ("Id", y)] <- xs                , Just (x', y') <- pair parseNum (x, y)-               = return (Pos x', ID y')+               = return (x', y')                | otherwise = throwError . Unexpected $ show xs  -- | Shuffle the playlist.@@ -268,13 +265,13 @@ shuffle range = getResponse_ ("shuffle" <$> range)  -- | Swap the positions of two songs.--- Note that the positions must be of the same type, i.e. mixing 'Pos' and 'ID'--- will result in a no-op.-swap :: MonadMPD m => PLIndex -> PLIndex -> m ()-swap (Pos x) (Pos y) = getResponse_ ("swap" <$> x <++> y)-swap (ID x)  (ID y)  = getResponse_ ("swapid" <$> x <++> y)-swap _ _ = fail "'swap' cannot mix position and ID arguments"+swap :: MonadMPD m => Int -> Int -> m ()+swap pos1 pos2 = getResponse_ ("swap" <$> pos1 <++> pos2) +-- | Swap the positions of two songs (Id version+swapId :: MonadMPD m => Int -> Int -> m ()+swapId id1 id2 = getResponse_ ("swapid" <$> id1 <++> id2)+ -- -- Stored playlists --@@ -348,7 +345,7 @@  -- | Count the number of entries matching a query. count :: MonadMPD m => Query -> m Count-count query = getResponse ("count" <$>  query) >>= runParser parseCount+count query = getResponse ("count" <$>  query) >>= return . parseCount . toAssocList  -- | Search the database for entries exactly matching a query. find :: MonadMPD m => Query -> m [Song]@@ -360,7 +357,7 @@  -- | List all metadata of metadata (sic). list :: MonadMPD m-     => Meta -- ^ Metadata to list+     => Metadata -- ^ Metadata to list      -> Query -> m [String] list mtype query = liftM takeValues $ getResponse ("list" <$> mtype <++> query) @@ -369,23 +366,17 @@ listAll path = liftM (map snd . filter ((== "file") . fst) . toAssocList)                      (getResponse $ "listall" <$> path) --- Helper for lsInfo and listAllInfo.-lsInfo' :: MonadMPD m => String -> Path -> m [Either Path Song]-lsInfo' cmd path =-    liftM (extractEntries (Just . Right, const Nothing, Just . Left)) $-         takeEntries =<< getResponse (cmd <$> path)- -- | Recursive 'lsInfo'.-listAllInfo :: MonadMPD m => Path -> m [Either Path Song]-listAllInfo = lsInfo' "listallinfo"+listAllInfo :: MonadMPD m => Path -> m [Song]+listAllInfo path = takeSongs =<< getResponse ("listallinfo" <$> path)  -- | Non-recursively list the contents of a database directory.-lsInfo :: MonadMPD m => Path -> m [Either Path Song]-lsInfo = lsInfo' "lsinfo"+lsInfo :: MonadMPD m => Path -> m [Entry]+lsInfo path = takeEntries =<< getResponse ("lsinfo" <$> path)  -- | Search the database using case insensitive matching. search :: MonadMPD m => Query -> m [Song]-search query = getResponse ("search" <$> query) >>= takeSongs+search query = takeSongs =<< getResponse ("search" <$> query)  -- | Update the server's database. -- If no paths are given, all paths will be scanned.@@ -474,8 +465,8 @@ enableOutput = getResponse_ . ("enableoutput" <$>)  -- | Retrieve information for all output devices.-outputs :: MonadMPD m => m [Device]-outputs = getResponse "outputs" >>= runParser parseOutputs+outputs :: MonadMPD m => m [Output]+outputs = getResponse "outputs" >>= return . parseOutputs . toAssocList  -- -- Reflection@@ -505,197 +496,3 @@         takeDecoders ((_, p):xs) =             let (info, rest) = break ((==) "plugin" . fst) xs             in (p, info) : takeDecoders rest------- Extensions\/shortcuts.------- | Like 'update', but returns the update job id.-updateId :: MonadMPD m => [Path] -> m Integer-updateId paths = liftM (read . head . takeValues) cmd-  where cmd = case paths of-                []  -> getResponse  "update"-                [x] -> getResponse ("update" <$> x)-                xs  -> getResponses $ map ("update" <$>) xs---- | Toggles play\/pause. Plays if stopped.-toggle :: MonadMPD m => m ()-toggle = status >>= \st -> case stState st of Playing -> pause True-                                              _       -> play Nothing---- | Add a list of songs\/folders to a playlist.--- Should be more efficient than running 'add' many times.-addMany :: MonadMPD m => PlaylistName -> [Path] -> m ()-addMany _ [] = return ()-addMany "" [x] = add_ x-addMany plname [x] = playlistAdd_ plname x-addMany plname xs = getResponses (map cmd xs) >> return ()-    where cmd x = case plname of-                      "" -> "add" <$> x-                      pl -> "playlistadd" <$> pl <++> x---- | Delete a list of songs from a playlist.--- If there is a duplicate then no further songs will be deleted, so--- take care to avoid them (see 'prune' for this).-deleteMany :: MonadMPD m => PlaylistName -> [PLIndex] -> m ()-deleteMany _ [] = return ()-deleteMany plname [(Pos x)] = playlistDelete plname x-deleteMany "" xs = getResponses (map cmd xs) >> return ()-    where cmd (Pos x) = "delete"   <$> x-          cmd (ID x)  = "deleteid" <$> x-deleteMany plname xs = getResponses (map cmd xs) >> return ()-    where cmd (Pos x) = "playlistdelete" <$> plname <++> x-          cmd _       = ""---- | Returns all songs and directories that match the given partial--- path name.-complete :: MonadMPD m => String -> m [Either Path Song]-complete path = do-    xs <- liftM matches . lsInfo $ dropFileName path-    case xs of-        [Left dir] -> complete $ dir ++ "/"-        _          -> return xs-    where-        matches = filter (isPrefixOf path . takePath)-        takePath = either id sgFilePath---- | Crop playlist.--- The bounds are inclusive.--- If 'Nothing' is passed the cropping will leave your playlist alone--- on that side.--- Using 'ID' will automatically find the absolute playlist position and use--- that as the cropping boundary.-crop :: MonadMPD m => Maybe PLIndex -> Maybe PLIndex -> m ()-crop x y = do-    pl <- playlistInfo Nothing-    let x' = case x of Just (Pos p) -> fromInteger p-                       Just (ID i)  -> fromMaybe 0 (findByID i pl)-                       Nothing      -> 0-        -- ensure that no songs are deleted twice with 'max'.-        ys = case y of Just (Pos p) -> drop (max (fromInteger p) x') pl-                       Just (ID i)  -> maybe [] (flip drop pl . max x' . (+1))-                                      (findByID i pl)-                       Nothing      -> []-    deleteMany "" . mapMaybe sgIndex $ take x' pl ++ ys-    where findByID i = findIndex ((==) i . (\(ID j) -> j) . fromJust . sgIndex)---- | Remove duplicate playlist entries.-prune :: MonadMPD m => m ()-prune = findDuplicates >>= deleteMany ""---- Find duplicate playlist entries.-findDuplicates :: MonadMPD m => m [PLIndex]-findDuplicates =-    liftM (map ((\(ID x) -> ID x) . fromJust . sgIndex) . flip dups ([],[])) $-        playlistInfo Nothing-    where dups [] (_, dup) = dup-          dups (x:xs) (ys, dup)-              | x `mSong` xs && not (x `mSong` ys) = dups xs (ys, x:dup)-              | otherwise                          = dups xs (x:ys, dup)-          mSong x = let m = sgFilePath x in any ((==) m . sgFilePath)---- | List directories non-recursively.-lsDirs :: MonadMPD m => Path -> m [Path]-lsDirs path =-    liftM (extractEntries (const Nothing,const Nothing, Just)) $-        takeEntries =<< getResponse ("lsinfo" <$> path)---- | List files non-recursively.-lsFiles :: MonadMPD m => Path -> m [Path]-lsFiles path =-    liftM (extractEntries (Just . sgFilePath, const Nothing, const Nothing)) $-        takeEntries =<< getResponse ("lsinfo" <$> path)---- | List all playlists.-lsPlaylists :: MonadMPD m => m [PlaylistName]-lsPlaylists = liftM (extractEntries (const Nothing, Just, const Nothing)) $-                    takeEntries =<< getResponse "lsinfo"---- | List the artists in the database.-listArtists :: MonadMPD m => m [Artist]-listArtists = liftM takeValues (getResponse "list artist")---- | List the albums in the database, optionally matching a given--- artist.-listAlbums :: MonadMPD m => Maybe Artist -> m [Album]-listAlbums artist = liftM takeValues $-                    getResponse ("list album" <$> fmap ("artist" <++>) artist)---- | List the songs in an album of some artist.-listAlbum :: MonadMPD m => Artist -> Album -> m [Song]-listAlbum artist album = find (Artist =? artist <&> Album =? album)---- | Retrieve the current playlist.--- Equivalent to @playlistinfo Nothing@.-getPlaylist :: MonadMPD m => m [Song]-getPlaylist = playlistInfo Nothing---- | Increase or decrease volume by a given percent, e.g.--- 'volume 10' will increase the volume by 10 percent, while--- 'volume (-10)' will decrease it by the same amount.-volume :: MonadMPD m => Int -> m ()-volume n = do-    current <- (fromIntegral . stVolume) `liftM` status-    setVolume . round $ (fromIntegral n / 100) * current + current------- Miscellaneous functions.------- Run getResponse but discard the response.-getResponse_ :: MonadMPD m => String -> m ()-getResponse_ x = getResponse x >> return ()---- Get the lines of the daemon's response to a list of commands.-getResponses :: MonadMPD m => [String] -> m [String]-getResponses cmds = getResponse . concat $ intersperse "\n" cmds'-    where cmds' = "command_list_begin" : cmds ++ ["command_list_end"]---- Helper that throws unexpected error if input is empty.-failOnEmpty :: MonadMPD m => [String] -> m [String]-failOnEmpty [] = throwError $ Unexpected "Non-empty response expected."-failOnEmpty xs = return xs---- A wrapper for getResponse that fails on non-empty responses.-getResponse1 :: MonadMPD m => String -> m [String]-getResponse1 x = getResponse x >>= failOnEmpty------- Parsing.------- Run 'toAssocList' and return only the values.-takeValues :: [String] -> [String]-takeValues = snd . unzip . toAssocList--data EntryType-    = SongEntry Song-    | PLEntry   String-    | DirEntry  String-      deriving (Eq, Show)---- Separate the result of an lsinfo\/listallinfo call into directories,--- playlists, and songs.-takeEntries :: MonadMPD m => [String] -> m [EntryType]-takeEntries = mapM toEntry . splitGroups wrappers . toAssocList-    where-        toEntry xs@(("file",_):_)   = liftM SongEntry $ runParser parseSong xs-        toEntry (("directory",d):_) = return $ DirEntry d-        toEntry (("playlist",pl):_) = return $ PLEntry  pl-        toEntry _ = error "takeEntries: splitGroups is broken"-        wrappers = [("file",id), ("directory",id), ("playlist",id)]---- Extract a subset of songs, directories, and playlists.-extractEntries :: (Song -> Maybe a, String -> Maybe a, String -> Maybe a)-               -> [EntryType] -> [a]-extractEntries (fSong,fPlayList,fDir) = mapMaybe f-    where-        f (SongEntry s) = fSong s-        f (PLEntry pl)  = fPlayList pl-        f (DirEntry d)  = fDir d---- Build a list of song instances from a response.-takeSongs :: MonadMPD m => [String] -> m [Song]-takeSongs = mapM (runParser parseSong)-          . splitGroups [("file",id)]-          . toAssocList
+ Network/MPD/Commands/Extensions.hs view
@@ -0,0 +1,147 @@+-- | Module    : Network.MPD.Commands.Extensions+-- Copyright   : (c) Ben Sinclair 2005-2009, Joachim Fasting 2010+-- License     : LGPL (see LICENSE)+-- Maintainer  : Joachim Fasting <joachim.fasting@gmail.com>+-- Stability   : alpha+--+-- Extensions and shortcuts to the standard MPD command set.++module Network.MPD.Commands.Extensions where++import Network.MPD.Commands.Arg+import Network.MPD.Commands.Query+import Network.MPD.Commands.Types+import Network.MPD.Commands.Util+import Network.MPD.Commands+import Network.MPD.Core++import Control.Monad (liftM)+import Prelude hiding (repeat)++-- | Like 'update', but returns the update job id.+updateId :: MonadMPD m => [Path] -> m Integer+updateId paths = liftM (read . head . takeValues) cmd+  where cmd = case paths of+                []  -> getResponse  "update"+                [x] -> getResponse ("update" <$> x)+                xs  -> getResponses $ map ("update" <$>) xs++-- | Toggles play\/pause. Plays if stopped.+toggle :: MonadMPD m => m ()+toggle = status >>= \st -> case stState st of Playing -> pause True+                                              _       -> play Nothing++-- | Add a list of songs\/folders to a playlist.+-- Should be more efficient than running 'add' many times.+addMany :: MonadMPD m => PlaylistName -> [Path] -> m ()+addMany _ [] = return ()+addMany "" [x] = add_ x+addMany plname [x] = playlistAdd_ plname x+addMany plname xs = getResponses (map cmd xs) >> return ()+    where cmd x = case plname of+                      "" -> "add" <$> x+                      pl -> "playlistadd" <$> pl <++> x++-- | Delete a list of songs from a playlist.+-- If there is a duplicate then no further songs will be deleted, so+-- take care to avoid them (see 'prune' for this).+{- deleteMany :: MonadMPD m => PlaylistName -> [PLIndex] -> m ()+deleteMany _ [] = return ()+deleteMany plname [(Pos x)] = playlistDelete plname x+deleteMany "" xs = getResponses (map cmd xs) >> return ()+    where cmd (Pos x) = "delete"   <$> x+          cmd (ID x)  = "deleteid" <$> x+deleteMany plname xs = getResponses (map cmd xs) >> return ()+    where cmd (Pos x) = "playlistdelete" <$> plname <++> x+          cmd _       = ""++-- | Returns all songs and directories that match the given partial+-- path name.+complete :: MonadMPD m => String -> m [Either Path Song]+complete path = do+    xs <- liftM matches . lsInfo $ dropFileName path+    case xs of+        [Left dir] -> complete $ dir ++ "/"+        _          -> return xs+    where+        matches = filter (isPrefixOf path . takePath)+        takePath = either id sgFilePath++-- | Crop playlist.+-- The bounds are inclusive.+-- If 'Nothing' is passed the cropping will leave your playlist alone+-- on that side.+-- Using 'ID' will automatically find the absolute playlist position and use+-- that as the cropping boundary.+crop :: MonadMPD m => Maybe PLIndex -> Maybe PLIndex -> m ()+crop x y = do+    pl <- playlistInfo Nothing+    let x' = case x of Just (Pos p) -> fromInteger p+                       Just (ID i)  -> fromMaybe 0 (findByID i pl)+                       Nothing      -> 0+        -- ensure that no songs are deleted twice with 'max'.+        ys = case y of Just (Pos p) -> drop (max (fromInteger p) x') pl+                       Just (ID i)  -> maybe [] (flip drop pl . max x' . (+1))+                                      (findByID i pl)+                       Nothing      -> []+    deleteMany "" . mapMaybe sgIndex $ take x' pl ++ ys+    where findByID i = findIndex ((==) i . (\(ID j) -> j) . fromJust . sgIndex)++-- | Remove duplicate playlist entries.+prune :: MonadMPD m => m ()+prune = findDuplicates >>= deleteMany ""++-- Find duplicate playlist entries.+findDuplicates :: MonadMPD m => m [PLIndex]+findDuplicates =+    liftM (map ((\(ID x) -> ID x) . fromJust . sgIndex) . flip dups ([],[])) $+        playlistInfo Nothing+    where dups [] (_, dup) = dup+          dups (x:xs) (ys, dup)+              | x `mSong` xs && not (x `mSong` ys) = dups xs (ys, x:dup)+              | otherwise                          = dups xs (x:ys, dup)+          mSong x = let m = sgFilePath x in any ((==) m . sgFilePath)++-- | List directories non-recursively.+lsDirs :: MonadMPD m => Path -> m [Path]+lsDirs path =+    liftM (extractEntries (const Nothing,const Nothing, Just)) $+        takeEntries =<< getResponse ("lsinfo" <$> path)++-- | List files non-recursively.+lsFiles :: MonadMPD m => Path -> m [Path]+lsFiles path =+    liftM (extractEntries (Just . sgFilePath, const Nothing, const Nothing)) $+        takeEntries =<< getResponse ("lsinfo" <$> path)++-- | List all playlists.+lsPlaylists :: MonadMPD m => m [PlaylistName]+lsPlaylists = liftM (extractEntries (const Nothing, Just, const Nothing)) $+                    takeEntries =<< getResponse "lsinfo" -}++-- | List the artists in the database.+listArtists :: MonadMPD m => m [Artist]+listArtists = liftM takeValues (getResponse "list artist")++-- | List the albums in the database, optionally matching a given+-- artist.+listAlbums :: MonadMPD m => Maybe Artist -> m [Album]+listAlbums artist = liftM takeValues $+                    getResponse ("list album" <$> fmap ("artist" <++>) artist)++-- | List the songs in an album of some artist.+listAlbum :: MonadMPD m => Artist -> Album -> m [Song]+listAlbum artist album = find (Artist =? artist <&> Album =? album)++-- | Retrieve the current playlist.+-- Equivalent to @playlistinfo Nothing@.+getPlaylist :: MonadMPD m => m [Song]+getPlaylist = playlistInfo Nothing++-- | Increase or decrease volume by a given percent, e.g.+-- 'volume 10' will increase the volume by 10 percent, while+-- 'volume (-10)' will decrease it by the same amount.+volume :: MonadMPD m => Int -> m ()+volume n = do+    current <- (fromIntegral . stVolume) `liftM` status+    setVolume . round $ (fromIntegral n / 100) * current + current
Network/MPD/Commands/Parse.hs view
@@ -8,154 +8,266 @@ -- -- Parsers for MPD data types. -module Network.MPD.Commands.Parse where+module Network.MPD.Commands.Parse (+    -- * Parsers for many objects+    parseSongs, parseEntries, parseOutputs,+    -- * Parsers for one object+    parseCount, parseStats, parseStatus,+    -- * Other parsers+    parseIdle,+    -- * Misc utilities+    parse, pair+    ) where  import Network.MPD.Commands.Types -import Control.Monad.Error+import qualified Data.Map as M+import Data.Maybe (fromMaybe)+import Control.Arrow (first, (***)) import Network.MPD.Utils-import Network.MPD.Core (MonadMPD, MPDError(Unexpected)) +type ItemGen a = [(String, String)] -- ^ list of (key,value) pairs+               -> a                 -- ^ intermediate object+               -> (a, Int)          -- ^ result object along with information+                                    --   about amount of lines it consumed++-- | Generate Output object.+parseOutput :: ItemGen Output+parseOutput ls d = f ls d 0+    where f []                d' n = (d', n)+          f ((key, value):ls') d' n =+              case key of+                   "outputname"    -> f ls' d' { outName = value }      (succ n)+                   "outputenabled" -> f ls' (up outenabled' parseBool) (succ n)+                   "outputid"      -> (d', n) -- new output, we're done here+                   _               -> f ls' d' (succ n) -- ignore unknown keys++              where up g parser = maybe d g $ parser value+                    outenabled' v = d' { outEnabled = v }++-- | Generate Entry object+parseEntry :: ItemGen Entry+parseEntry ls (SongE s) = first SongE $ parseSong ls s+parseEntry _  d@(DirectoryE _) = (d, 0)+parseEntry ls (PlaylistE p) = first PlaylistE $ parsePlaylist ls p 0+    where parsePlaylist [] s n = (s, n)+          parsePlaylist ((key, value):ls') p' n =+              case key of+                   "Last-Modified" -> parsePlaylist ls' p'+                                        { plLastModified = parseIso8601 value }+                                        (succ n)+                   "playlist"      -> (p', n) -- new playlist, we're done here+                   _               -> parsePlaylist ls' p' (succ n) -- ignore unknown keys++-- | Generate Song object+parseSong :: ItemGen Song+parseSong ls s = f ls s 0+    where f []                s' n = (s', n)+          f ((key, value):ls') s' n =+              case key of+                   "Last-Modified" -> f ls' s' { sgLastModified = lm   } (succ n)+                   "Time"          -> f ls' s' { sgLength       = time } (succ n)+                   "Id"            -> f ls' s' { sgIndex        = id'  } (succ n)+                   "Pos"           -> f ls' s' { sgIndex        = pos  } (succ n)+                   "file"          -> (s', n) -- new item, we're done here+                   "playlist"      -> (s', n) -- new item, we're done here+                   -- "directory" is not needed, files are always after dirs+                   _ -> case tagValue key of+                            Just meta -> f ls' s'+                                           { sgTags = M.insertWith' (++)+                                                 meta [value] (sgTags s')+                                           } (succ n)+                            Nothing   -> f ls' s' (succ n) -- ignore unknown keys++              where lm     = parseIso8601 value+                    time   = maybe 0 id $ parseNum value+                    id'    = Just (maybe 0 fst $ sgIndex s', maybe 0 id $ parseNum value)+                    pos    = Just (maybe 0 id $ parseNum value, maybe 0 snd $ sgIndex s')++                    -- Why not just derive instance of Read? Because+                    -- using read is a few times slower than this.+                    tagValue "Artist" = Just Artist+                    tagValue "ArtistSort" = Just ArtistSort+                    tagValue "Album" = Just Album+                    tagValue "AlbumArtist" = Just AlbumArtist+                    tagValue "AlbumArtistSort" = Just AlbumArtistSort+                    tagValue "Title" = Just Title+                    tagValue "Track" = Just Track+                    tagValue "Name" = Just Name+                    tagValue "Genre" = Just Genre+                    tagValue "Date" = Just Date+                    tagValue "Composer" = Just Composer+                    tagValue "Performer" = Just Performer+                    tagValue "Disc" = Just Disc+                    tagValue "MUSICBRAINZ_ARTISTID" = Just MUSICBRAINZ_ARTISTID+                    tagValue "MUSICBRAINZ_ALBUMID" = Just MUSICBRAINZ_ALBUMID+                    tagValue "MUSICBRAINZ_ALBUMARTISTID" = Just MUSICBRAINZ_ALBUMARTISTID+                    tagValue "MUSICBRAINZ_TRACKID" = Just MUSICBRAINZ_TRACKID+                    tagValue _ = Nothing++-------------------------------------------------------------------++-- | Parser generator.+genParser :: [(String, String -> a)] -> ItemGen a -> [(String, String)] -> [a]+genParser _    _      []                = []+genParser ptrs parser ((key, value):ls) =+    case key `lookup` ptrs of+        Just ctr -> let (item, n) = parser ls (ctr value) in+                        item : genParser ptrs parser (drop n ls)+        Nothing  ->            genParser ptrs parser ls++-- | Parser for songs.+parseSongs :: [(String, String)] -> [Song]+parseSongs = genParser [ ("file", initSong)+                       ] parseSong++initSong :: String -> Song+initSong filepath =+    Song { sgFilePath = filepath, sgTags = M.empty+         , sgLastModified = Nothing, sgLength = 0+         , sgIndex = Nothing }++-- | Parser for database entries.+parseEntries :: [(String, String)] -> [Entry]+parseEntries = genParser [ ("directory", DirectoryE)+                         , ("file", SongE . initSong)+                         , ("playlist", PlaylistE . initPlaylist)+                         ] parseEntry++initPlaylist :: String -> Playlist+initPlaylist name =+    Playlist { plName = name, plLastModified = Nothing }++-- | Parser for outputs.+parseOutputs :: [(String, String)] -> [Output]+parseOutputs = genParser [ ("outputid", initOutput . fromMaybe (-1) . parseNum )+                         ] parseOutput++initOutput :: Int -> Output+initOutput id' =+    Output { outID = id', outName = "", outEnabled = False }++------------------------------------------------------------------+ -- | Builds a 'Count' instance from an assoc. list.-parseCount :: [String] -> Either String Count-parseCount = foldM f defaultCount . toAssocList-        where f :: Count -> (String, String) -> Either String Count-              f a ("songs", x)    = return $ parse parseNum-                                    (\x' -> a { cSongs = x'}) a x-              f a ("playtime", x) = return $ parse parseNum-                                    (\x' -> a { cPlaytime = x' }) a x-              f _ x               = Left $ show x+parseCount :: [(String, String)] -> Count+parseCount = flip parseCount' defaultCount --- | Builds a list of 'Device' instances from an assoc. list-parseOutputs :: [String] -> Either String [Device]-parseOutputs = mapM (foldM f defaultDevice)-             . splitGroups [("outputid",id)]-             . toAssocList-    where f a ("outputid", x)      = return $ parse parseNum-                                     (\x' -> a { dOutputID = x' }) a x-          f a ("outputname", x)    = return a { dOutputName = x }-          f a ("outputenabled", x) = return $ parse parseBool-                                     (\x' -> a { dOutputEnabled = x'}) a x-          f _ x                    = fail $ show x+-- | Helper function.+parseCount' :: [(String, String)] -> Count -> Count+parseCount' []                c = c+parseCount' ((key, value):ls) c =+    case key of+        "songs"    -> modify parseNum $ \v -> c { cSongs = v }+        "playtime" -> modify parseNum $ \v -> c { cPlaytime = v }+        _          -> parseCount' ls c +    where modify p f = parseCount' ls $ parse p f c value++defaultCount =+    Count { cSongs = 0, cPlaytime = 0 }++-------------------------------------------------------------------+ -- | Builds a 'Stats' instance from an assoc. list.-parseStats :: [String] -> Either String Stats-parseStats = foldM f defaultStats . toAssocList-    where-        f a ("artists", x)     = return $ parse parseNum-                                 (\x' -> a { stsArtists  = x' }) a x-        f a ("albums", x)      = return $ parse parseNum-                                 (\x' -> a { stsAlbums   = x' }) a x-        f a ("songs", x)       = return $ parse parseNum-                                 (\x' -> a { stsSongs    = x' }) a x-        f a ("uptime", x)      = return $ parse parseNum-                                 (\x' -> a { stsUptime   = x' }) a x-        f a ("playtime", x)    = return $ parse parseNum-                                 (\x' -> a { stsPlaytime = x' }) a x-        f a ("db_playtime", x) = return $ parse parseNum-                                 (\x' -> a { stsDbPlaytime = x' }) a x-        f a ("db_update", x)   = return $ parse parseNum-                                 (\x' -> a { stsDbUpdate = x' }) a x-        f _ x = fail $ show x+parseStats :: [(String, String)] -> Stats+parseStats = flip parseStats' defaultStats --- | Builds a 'Song' instance from an assoc. list.-parseSong :: [(String, String)] -> Either String Song-parseSong xs = foldM f defaultSong xs-    where f a ("Artist", x)    = return a { sgArtist = x }-          f a ("Album", x)     = return a { sgAlbum  = x }-          f a ("Title", x)     = return a { sgTitle = x }-          f a ("Genre", x)     = return a { sgGenre = x }-          f a ("Name", x)      = return a { sgName = x }-          f a ("Composer", x)  = return a { sgComposer = x }-          f a ("Performer", x) = return a { sgPerformer = x }-          f a ("Date", x)      = return $ parse parseDate-                                 (\x' -> a { sgDate = x' }) a x-          f a ("Track", x)     = return $ parse parseTuple-                                 (\x' -> a { sgTrack = x'}) a x-          f a ("Disc", x)      = return a { sgDisc = parseTuple x }-          f a ("file", x)      = return a { sgFilePath = x }-          f a ("Time", x)      = return $ parse parseNum-                                 (\x' -> a { sgLength = x'}) a x-          f a ("Id", x)        = return $ parse parseNum-                                 (\x' -> a { sgIndex = Just (ID x') }) a x-          -- We prefer Id but take Pos if no Id has been found.-          f a ("Pos", x)       =-              maybe (return $ parse parseNum-                           (\x' -> a { sgIndex = Just (Pos x') }) a x)-                    (const $ return a)-                    (sgIndex a)-          -- Collect auxiliary keys-          f a (k, v)           = return a { sgAux = (k, v) : sgAux a }+-- | Helper function.+parseStats' :: [(String, String)] -> Stats -> Stats+parseStats' []                s = s+parseStats' ((key, value):ls) s =+    case key of+        "artists"     -> modify parseNum $ \v -> s { stsArtists = v }+        "albums"      -> modify parseNum $ \v -> s { stsAlbums = v }+        "songs"       -> modify parseNum $ \v -> s { stsSongs = v }+        "uptime"      -> modify parseNum $ \v -> s { stsUptime = v }+        "playtime"    -> modify parseNum $ \v -> s { stsPlaytime = v }+        "db_playtime" -> modify parseNum $ \v -> s { stsDbPlaytime = v }+        "db_update"   -> modify parseNum $ \v -> s { stsDbUpdate = v }+        _             -> parseStats' ls s -          parseTuple s = let (x, y) = breakChar '/' s in-                         -- Handle incomplete values. For example, songs might-                         -- have a track number, without specifying the total-                         -- number of tracks, in which case the resulting-                         -- tuple will have two identical parts.-                         case (parseNum x, parseNum y) of-                             (Just x', Nothing) -> Just (x', x')-                             (Just x', Just y') -> Just (x', y')-                             _                  -> Nothing+    where modify p f = parseStats' ls $ parse p f s value +defaultStats =+     Stats { stsArtists = 0, stsAlbums = 0, stsSongs = 0, stsUptime = 0+           , stsPlaytime = 0, stsDbPlaytime = 0, stsDbUpdate = 0 }++-------------------------------------------------------------------+ -- | Builds a 'Status' instance from an assoc. list.-parseStatus :: [String] -> Either String Status-parseStatus = foldM f defaultStatus . toAssocList-    where f a ("state", x)-              = return $ parse state     (\x' -> a { stState = x' }) a x-          f a ("volume", x)-              = return $ parse parseNum  (\x' -> a { stVolume = x' }) a x-          f a ("repeat", x)-              = return $ parse parseBool (\x' -> a { stRepeat = x' }) a x-          f a ("random", x)-              = return $ parse parseBool (\x' -> a { stRandom = x' }) a x-          f a ("playlist", x)-              = return $ parse parseNum  (\x' -> a { stPlaylistVersion = x' }) a x-          f a ("playlistlength", x)-              = return $ parse parseNum  (\x' -> a { stPlaylistLength = x' }) a x-          f a ("xfade", x)-              = return $ parse parseNum  (\x' -> a { stXFadeWidth = x' }) a x-          f a ("song", x)-              = return $ parse parseNum  (\x' -> a { stSongPos = Just (Pos x') }) a x-          f a ("songid", x)-              = return $ parse parseNum  (\x' -> a { stSongID = Just (ID x') }) a x-          f a ("time", x)-              = return $ parse time      (\x' -> a { stTime = x' }) a x-          f a ("bitrate", x)-              = return $ parse parseNum  (\x' -> a { stBitrate = x' }) a x-          f a ("audio", x)-              = return $ parse audio     (\x' -> a { stAudio = x' }) a x-          f a ("updating_db", x)-              = return $ parse parseNum  (\x' -> a { stUpdatingDb = x' }) a x-          f a ("error", x)-              = return a { stError = x }-          f a ("single", x)-              = return $ parse parseBool (\x' -> a { stSingle = x' }) a x-          f a ("consume", x)-              = return $ parse parseBool (\x' -> a { stConsume = x' }) a x-          f a ("nextsong", x)-              = return $ parse parseNum  (\x' -> a { stNextSongPos = Just (Pos x') }) a x-          f a ("nextsongid", x)-              = return $ parse parseNum  (\x' -> a { stNextSongID = Just (ID x') }) a x-          f _ x-              = fail $ show x+parseStatus :: [(String, String)] -> Status+parseStatus = flip parseStatus' defaultStatus -          state "play"  = Just Playing-          state "pause" = Just Paused-          state "stop"  = Just Stopped-          state _       = Nothing+-- | Helper function.+parseStatus' :: [(String, String)] -> Status -> Status+parseStatus' []                s = s+parseStatus' ((key, value):ls) s =+    case key of+       "volume"         -> modify parseNum   $ \v -> s { stVolume = v }+       "repeat"         -> modify parseBool  $ \v -> s { stRepeat = v }+       "random"         -> modify parseBool  $ \v -> s { stRandom = v }+       "single"         -> modify parseBool  $ \v -> s { stSingle = v }+       "consume"        -> modify parseBool  $ \v -> s { stConsume = v }+       "playlist"       -> modify parseNum   $ \v -> s { stPlaylistID = v }+       "playlistlength" -> modify parseNum   $ \v -> s { stPlaylistLength = v }+       "xfade"          -> modify parseNum   $ \v -> s { stXFadeWidth = v }+       "mixrampdb"      -> modify parseFrac  $ \v -> s { stMixRampdB = v }+       "mixrampdelay"   -> modify parseFrac  $ \v -> s { stMixRampDelay = v }+       "state"          -> modify parseState $ \v -> s { stState = v }+       "song"           -> parseStatus' ls s { stSongPos = parseNum value }+       "songid"         -> parseStatus' ls s { stSongID = parseNum value }+       "time"           -> modify parseTime  $ \v -> s { stTime = v }+       "elapsed"        -> modify parseFrac  $ \v -> s { stTime = (v, snd $ stTime s) }+       "bitrate"        -> modify parseNum   $ \v -> s { stBitrate = v }+       "audio"          -> modify parseAudio $ \v -> s { stAudio = v }+       "nextsong"       -> parseStatus' ls s { stNextSongPos = parseNum value }+       "nextsongid"     -> parseStatus' ls s { stNextSongID = parseNum value }+       "error"          -> parseStatus' ls s { stError = Just value }+       _                -> parseStatus' ls s -          time = pair parseNum . breakChar ':'+    where modify p f = parseStatus' ls $ parse p f s value -          audio s = let (u, u') = breakChar ':' s-                        (v, w)  = breakChar ':' u' in-                    case (parseNum u, parseNum v, parseNum w) of-                        (Just a, Just b, Just c) -> Just (a, b, c)-                        _                        -> Nothing+          parseAudio = parseTriple ':' parseNum --- | Run a parser and lift the result into the 'MPD' monad-runParser :: (MonadMPD m, MonadError MPDError m)-          => (input -> Either String a) -> input -> m a-runParser f = either (throwError . Unexpected) return . f+          parseState "play"  = Just Playing+          parseState "pause" = Just Paused+          parseState "stop"  = Just Stopped+          parseState _       = Nothing++          parseTime s' =+              case parseFrac *** parseNum $ breakChar ':' s' of+                 (Just a, Just b) -> Just (a, b)+                 _                -> Nothing++defaultStatus =+    Status { stState = Stopped, stVolume = 0, stRepeat = False+           , stRandom = False, stPlaylistID = 0, stPlaylistLength = 0+           , stSongPos = Nothing, stSongID = Nothing, stTime = (0, 0)+           , stNextSongPos = Nothing, stNextSongID = Nothing+           , stBitrate = 0, stXFadeWidth = 0, stMixRampdB = 0+           , stMixRampDelay = 0, stAudio = (0, 0, 0), stUpdatingDb = 0+           , stSingle = False, stConsume = False, stError = Nothing }++-------------------------------------------------------------------++-- | Builds list of idle subsystems.+parseIdle :: [(String, String)] -> [Subsystem]+parseIdle [] = []+parseIdle (("changed", value):ls) =+    case value of+        "database"        -> DatabaseS       : parseIdle ls+        "update"          -> UpdateS         : parseIdle ls+        "stored_playlist" -> StoredPlaylistS : parseIdle ls+        "playlist"        -> PlaylistS       : parseIdle ls+        "player"          -> PlayerS         : parseIdle ls+        "mixer"           -> MixerS          : parseIdle ls+        "output"          -> OutputS         : parseIdle ls+        "options"         -> OptionsS        : parseIdle ls+        _                 ->                   parseIdle ls+parseIdle (_:ls) = parseIdle ls++-------------------------------------------------------------------  -- | A helper that runs a parser on a string and, depending on the -- outcome, either returns the result of some command applied to the
Network/MPD/Commands/Query.hs view
@@ -28,7 +28,7 @@ newtype Query = Query [Match] deriving Show  -- A single query clause, comprising a metadata key and a desired value.-data Match = Match Meta String+data Match = Match Metadata String  instance Show Match where     show (Match meta query) = show meta ++ " \"" ++ query ++ "\""@@ -47,7 +47,7 @@ anything = mempty  -- | Create a query.-(=?) :: Meta -> String -> Query+(=?) :: Metadata -> String -> Query m =? s = Query [Match m s]  -- | Combine queries.
Network/MPD/Commands/Types.hs view
@@ -10,6 +10,9 @@  import Network.MPD.Commands.Arg (MPDArg(prep), Args(Args)) +import qualified Data.Map as M+import Data.Time.Clock (UTCTime)+ type Artist       = String type Album        = String type Title        = String@@ -24,11 +27,14 @@  -- | Available metadata types\/scope modifiers, used for searching the -- database for entries with certain metadata values.-data Meta = Artist | Album | Title | Track | Name | Genre | Date-    | Composer | Performer | Disc | Any | Filename-      deriving Show+data Metadata = Artist | ArtistSort | Album | AlbumArtist+              | AlbumArtistSort | Title | Track | Name | Genre+              | Date | Composer | Performer | Comment | Disc+              | MUSICBRAINZ_ARTISTID | MUSICBRAINZ_ALBUMID+              | MUSICBRAINZ_ALBUMARTISTID | MUSICBRAINZ_TRACKID+              deriving (Eq, Ord, Show) -instance MPDArg Meta+instance MPDArg Metadata  -- | Object types. data ObjectType = SongObj@@ -39,12 +45,6 @@  type Seconds = Integer --- | Represents a song's playlist index.-data PLIndex = Pos Integer -- ^ A playlist position index (starting from 0)-             | ID Integer  -- ^ A playlist ID number that more robustly-                           --   identifies a song.-    deriving (Show, Eq)- -- | Represents the different playback states. data State = Playing            | Stopped@@ -53,25 +53,25 @@  -- | Represents the various MPD subsystems. data Subsystem-    = Database          -- ^ The song database-    | Update            -- ^ Database updates-    | StoredPlaylist    -- ^ Stored playlists-    | Playlist          -- ^ The current playlist-    | Player            -- ^ The player-    | Mixer             -- ^ The volume mixer-    | Output            -- ^ Audio outputs-    | Options           -- ^ Playback options+    = DatabaseS          -- ^ The song database+    | UpdateS            -- ^ Database updates+    | StoredPlaylistS    -- ^ Stored playlists+    | PlaylistS          -- ^ The current playlist+    | PlayerS            -- ^ The player+    | MixerS             -- ^ The volume mixer+    | OutputS            -- ^ Audio outputs+    | OptionsS           -- ^ Playback options       deriving (Eq, Show)  instance MPDArg Subsystem where-    prep Database = Args ["database"]-    prep Update = Args ["update"]-    prep StoredPlaylist = Args ["stored_playlist"]-    prep Playlist = Args ["playlist"]-    prep Player = Args ["player"]-    prep Mixer = Args ["mixer"]-    prep Output = Args ["output"]-    prep Options = Args ["options"]+    prep DatabaseS = Args ["database"]+    prep UpdateS = Args ["update"]+    prep StoredPlaylistS = Args ["stored_playlist"]+    prep PlaylistS = Args ["playlist"]+    prep PlayerS = Args ["player"]+    prep MixerS = Args ["mixer"]+    prep OutputS = Args ["output"]+    prep OptionsS = Args ["options"]  data ReplayGainMode     = Off       -- ^ Disable replay gain@@ -84,6 +84,8 @@     prep TrackMode = Args ["track"]     prep AlbumMode = Args ["album"] +------------------------------------------------------------------+ -- | Represents the result of running 'count'. data Count =     Count { cSongs    :: Integer -- ^ Number of songs matching the query@@ -91,21 +93,45 @@           }     deriving (Eq, Show) -defaultCount :: Count-defaultCount = Count { cSongs = 0, cPlaytime = 0 }- -- | Represents an output device.-data Device =-    Device { dOutputID      :: Int    -- ^ Output's ID number-           , dOutputName    :: String -- ^ Output's name as defined in the MPD-                                      --   configuration file-           , dOutputEnabled :: Bool }-    deriving (Eq, Show)+data Output =+    Output { outID      :: Int    -- ^ Output's ID number+           , outName    :: String -- ^ Output's name as defined in+                                  --   the MPD configuration file+           , outEnabled :: Bool+           } deriving (Eq, Show) -defaultDevice :: Device-defaultDevice =-    Device { dOutputID = 0, dOutputName = "", dOutputEnabled = False }+-- | Represents a single song item.+data Song = Song+         { sgFilePath     :: String+         -- | Map of available tags (multiple occurences of one tag type allowed)+         , sgTags         :: M.Map Metadata [String]+         -- | Last modification date+         , sgLastModified :: Maybe UTCTime+         -- | Length of the song in seconds+         , sgLength       :: Seconds+         -- | Position/ID in playlist+         , sgIndex        :: Maybe (Int, Int)+         } deriving (Eq, Show) +-- | Get list of specific tag type+sgGet :: Metadata -> Song -> Maybe [String]+sgGet meta s = M.lookup meta $ sgTags s++-- | Represents a single playlist item.+data Playlist = Playlist {+               plName         :: String+             , plLastModified :: Maybe UTCTime+             } deriving (Eq, Show)++-- | Represents a single item in database.+data Entry = SongE Song+           | PlaylistE Playlist+           | DirectoryE String+           deriving (Eq, Show)++------------------------------------------------------------------+ -- | Container for database statistics. data Stats =     Stats { stsArtists    :: Integer -- ^ Number of artists.@@ -119,31 +145,6 @@           }     deriving (Eq, Show) -defaultStats :: Stats-defaultStats =-     Stats { stsArtists = 0, stsAlbums = 0, stsSongs = 0, stsUptime = 0-           , stsPlaytime = 0, stsDbPlaytime = 0, stsDbUpdate = 0 }---- | Represents a single song item.-data Song =-    Song { sgArtist, sgAlbum, sgTitle, sgFilePath, sgGenre, sgName, sgComposer-         , sgPerformer :: String-         , sgLength    :: Seconds          -- ^ Length in seconds-         , sgDate      :: Int              -- ^ Year-         , sgTrack     :: (Int, Int)       -- ^ Track number\/total tracks-         , sgDisc      :: Maybe (Int, Int) -- ^ Position in set\/total in set-         , sgIndex     :: Maybe PLIndex-         , sgAux       :: [(String, String)] } -- ^ Auxiliary song fields-    deriving (Eq, Show)--defaultSong :: Song-defaultSong =-    Song { sgArtist = "", sgAlbum = "", sgTitle = ""-         , sgGenre = "", sgName = "", sgComposer = ""-         , sgPerformer = "", sgDate = 0, sgTrack = (0,0)-         , sgDisc = Nothing, sgFilePath = "", sgLength = 0-         , sgIndex = Nothing, sgAux = [] }- -- | Container for MPD status. data Status =     Status { stState :: State@@ -153,23 +154,27 @@            , stRandom          :: Bool              -- | A value that is incremented by the server every time the              --   playlist changes.-           , stPlaylistVersion :: Integer+           , stPlaylistID      :: Integer              -- | The number of items in the current playlist.            , stPlaylistLength  :: Integer              -- | Current song's position in the playlist.-           , stSongPos         :: Maybe PLIndex+           , stSongPos         :: Maybe Int              -- | Current song's playlist ID.-           , stSongID          :: Maybe PLIndex+           , stSongID          :: Maybe Int              -- | Next song's position in the playlist.-           , stNextSongPos     :: Maybe PLIndex+           , stNextSongPos     :: Maybe Int              -- | Next song's playlist ID.-           , stNextSongID      :: Maybe PLIndex+           , stNextSongID      :: Maybe Int              -- | Time elapsed\/total time.-           , stTime            :: (Seconds, Seconds)+           , stTime            :: (Double, Seconds)              -- | Bitrate (in kilobytes per second) of playing song (if any).            , stBitrate         :: Int              -- | Crossfade time.            , stXFadeWidth      :: Seconds+             -- | MixRamp threshold in dB+           , stMixRampdB       :: Double+             -- | MixRamp extra delay in seconds+           , stMixRampDelay    :: Double              -- | Samplerate\/bits\/channels for the chosen output device              --   (see mpd.conf).            , stAudio           :: (Int, Int, Int)@@ -180,15 +185,6 @@              -- | If True, a song will be removed after it has been played.            , stConsume         :: Bool              -- | Last error message (if any).-           , stError           :: String }+           , stError           :: Maybe String }     deriving (Eq, Show) -defaultStatus :: Status-defaultStatus =-    Status { stState = Stopped, stVolume = 0, stRepeat = False-           , stRandom = False, stPlaylistVersion = 0, stPlaylistLength = 0-           , stSongPos = Nothing, stSongID = Nothing, stTime = (0,0)-           , stNextSongPos = Nothing, stNextSongID = Nothing-           , stBitrate = 0, stXFadeWidth = 0, stAudio = (0,0,0)-           , stUpdatingDb = 0, stSingle = False, stConsume = False-           , stError = "" }
+ Network/MPD/Commands/Util.hs view
@@ -0,0 +1,47 @@+-- | Module    : Network.MPD.Commands.Util+-- Copyright   : (c) Ben Sinclair 2005-2009, Joachim Fasting 2010+-- License     : LGPL (see LICENSE)+-- Maintainer  : Joachim Fasting <joachim.fasting@gmail.com>+-- Stability   : alpha+--+-- Internal utilities for implementing MPD commands.++module Network.MPD.Commands.Util where++import Network.MPD.Commands.Parse+import Network.MPD.Commands.Types+import Network.MPD.Core+import Network.MPD.Utils++import Control.Monad.Error (throwError)+import Data.List (intersperse)++-- Run getResponse but discard the response.+getResponse_ :: MonadMPD m => String -> m ()+getResponse_ x = getResponse x >> return ()++-- Get the lines of the daemon's response to a list of commands.+getResponses :: MonadMPD m => [String] -> m [String]+getResponses cmds = getResponse . concat $ intersperse "\n" cmds'+    where cmds' = "command_list_begin" : cmds ++ ["command_list_end"]++-- Helper that throws unexpected error if input is empty.+failOnEmpty :: MonadMPD m => [String] -> m [String]+failOnEmpty [] = throwError $ Unexpected "Non-empty response expected."+failOnEmpty xs = return xs++-- A wrapper for getResponse that fails on non-empty responses.+getResponse1 :: MonadMPD m => String -> m [String]+getResponse1 x = getResponse x >>= failOnEmpty++-- Run 'toAssocList' and return only the values.+takeValues :: [String] -> [String]+takeValues = map snd . toAssocList++-- Build a list of Song instances from a response.+takeSongs :: MonadMPD m => [String] -> m [Song]+takeSongs = return . parseSongs . toAssocList++-- Build a list of Entry instances from a response.+takeEntries :: MonadMPD m => [String] -> m [Entry]+takeEntries = return . parseEntries . toAssocList
Network/MPD/Core.hs view
@@ -20,19 +20,22 @@     getResponse, kill,     ) where +import Network.MPD.Utils import Network.MPD.Core.Class import Network.MPD.Core.Error +import Data.Char (isDigit) import Control.Applicative (Applicative(..), (<$>)) import Control.Monad (ap, unless) import Control.Monad.Error (ErrorT(..), MonadError(..)) import Control.Monad.Reader (ReaderT(..), ask)-import Control.Monad.State (StateT, MonadIO(..), put, get, evalStateT)+import Control.Monad.State (StateT, MonadIO(..), modify, get, evalStateT) import qualified Data.Foldable as F import Data.List (isPrefixOf) import Network (PortID(..), withSocketsDo, connectTo) import System.IO (Handle, hPutStrLn, hReady, hClose, hFlush) import System.IO.Error (isEOFError)+import System.IO.Unsafe (unsafeInterleaveIO) import qualified System.IO.UTF8 as U  --@@ -56,10 +59,11 @@ -- To run IO actions within the MPD monad: -- -- > import Control.Monad.Trans (liftIO)+ newtype MPD a =     MPD { runMPD :: ErrorT MPDError-                    (StateT (Maybe Handle)-                     (ReaderT (Host, Port, Password) IO)) a+                    (StateT MPDState+                     (ReaderT (Host, Port) IO)) a         } deriving (Functor, Monad, MonadIO, MonadError MPDError)  instance Applicative MPD where@@ -70,35 +74,66 @@     open  = mpdOpen     close = mpdClose     send  = mpdSend-    getPassword = MPD $ ask >>= \(_,_,pw) -> return pw+    receive = mpdReceive+    getHandle = MPD $ get >>= return . stHandle+    getPassword = MPD $ get >>= return . stPassword+    setPassword pw = MPD $ modify (\st -> st { stPassword = pw })+    getVersion = MPD $ get >>= return . stVersion +-- | Inner state for MPD+data MPDState =+    MPDState { stHandle   :: Maybe Handle+             , stPassword :: String+             , stVersion  :: (Int, Int, Int)+             }+ -- | A response is either an 'MPDError' or some result. type Response = Either MPDError  -- | The most configurable API for running an MPD action. withMPDEx :: Host -> Port -> Password -> MPD a -> IO (Response a) withMPDEx host port pw x = withSocketsDo $-    runReaderT (evalStateT (runErrorT . runMPD $ open >> x) Nothing)-               (host, port, pw)+    runReaderT (evalStateT (runErrorT . runMPD $ open >> x) initState)+               (host, port)+                   where initState = MPDState Nothing pw (0, 0, 0)  mpdOpen :: MPD () mpdOpen = MPD $ do-    (host, port, _) <- ask+    (host, port) <- ask     runMPD close     handle <- liftIO (safeConnectTo host port)-    put handle+    modify (\st -> st { stHandle = handle })     F.forM_ handle (const $ runMPD checkConn >>= flip unless (runMPD close))     where+        safeConnectTo host@('/':_) _ =+            (Just <$> connectTo "" (UnixSocket host))+            `catch` const (return Nothing)         safeConnectTo host port =             (Just <$> connectTo host (PortNumber $ fromInteger port))             `catch` const (return Nothing) -        checkConn =-            isPrefixOf "OK MPD" <$> send ""+        checkConn = do+            msg <- mpdReceive >>= checkMsg+            if isPrefixOf "OK MPD" msg+               then do+                   MPD $ maybe (throwError $ Custom "Couldn't determine MPD version")+                               (\v -> modify (\st -> st { stVersion = v }))+                               (parseVersion msg)+                   return True+               else return False +        checkMsg ls =+            if null ls+               then throwError $ Custom "No welcome message"+               else return $ head ls++        parseVersion = parseTriple '.' parseNum . dropWhile (not . isDigit)+ mpdClose :: MPD () mpdClose =-    MPD $ get >>= F.mapM_ (liftIO . sendClose) >> put Nothing+    MPD $ do+        get >>= F.mapM_ (liftIO . sendClose) . stHandle+        modify (\st -> st { stHandle = Nothing })     where         sendClose handle =             (hPutStrLn handle "close" >> hReady handle >> hClose handle)@@ -108,29 +143,30 @@             | isEOFError err = result             | otherwise      = ioError err -mpdSend :: String -> MPD String-mpdSend str = send' `catchError` handler+mpdSend :: String -> MPD ()+mpdSend str = MPD $ get >>= maybe (throwError NoMPD) go . stHandle     where-        handler TimedOut = mpdOpen >> send'-        handler err      = throwError err--        send' = MPD $ get >>= maybe (throwError NoMPD) go--        go handle = do+        go handle =             unless (null str) $                 liftIO $ U.hPutStrLn handle str >> hFlush handle-            liftIO ((Right <$> getLines handle []) `catch` (return . Left))++mpdReceive :: MPD [String]+mpdReceive = getHandle >>= maybe (throwError NoMPD) recv+    where+        recv handle = MPD $+            liftIO ((Right <$> getLines handle) `catch` (return . Left))                 >>= either (\err -> if isEOFError err then-                                        put Nothing >> throwError TimedOut+                                        modify (\st -> st { stHandle = Nothing })+                                        >> throwError TimedOut                                       else liftIO (ioError err))                            return -        getLines handle acc = do+        getLines handle = do             l <- U.hGetLine handle             if "OK" `isPrefixOf` l || "ACK" `isPrefixOf` l-                then return . unlines $ reverse (l:acc)-                else getLines handle (l:acc)-+                then return [l]+                else do ls <- unsafeInterleaveIO $ getLines handle+                        return (l:ls)  -- -- Other operations.@@ -147,24 +183,23 @@  -- | Send a command to the MPD server and return the result. getResponse :: (MonadMPD m) => String -> m [String]-getResponse cmd = (send cmd >>= parseResponse) `catchError` sendpw+getResponse cmd = (send cmd >> receive >>= parseResponse) `catchError` sendpw     where         sendpw e@(ACK Auth _) = do             pw <- getPassword-            if null pw then throwError e-                else send ("password " ++ pw) >>= parseResponse-                  >> send cmd >>= parseResponse+            if null pw+                then throwError e+                else do send ("password " ++ pw) >> receive >>= parseResponse+                        send cmd                 >> receive >>= parseResponse         sendpw e =             throwError e  -- Consume response and return a Response.-parseResponse :: (MonadError MPDError m) => String -> m [String]-parseResponse s+parseResponse :: (MonadError MPDError m) => [String] -> m [String]+parseResponse xs     | null xs                    = throwError $ NoMPD-    | isPrefixOf "ACK" (head xs) = throwError $ parseAck s+    | isPrefixOf "ACK" (head xs) = throwError $ parseAck (head xs)     | otherwise                  = return $ Prelude.takeWhile ("OK" /=) xs-    where-        xs = lines s  -- Turn MPD ACK into the corresponding 'MPDError' parseAck :: String -> MPDError
Network/MPD/Core/Class.hs view
@@ -10,6 +10,8 @@  module Network.MPD.Core.Class where +import System.IO (Handle)+ import Network.MPD.Core.Error (MPDError)  import Control.Monad.Error (MonadError)@@ -23,8 +25,16 @@     open  :: m ()     -- | Close the connection.     close :: m ()-    -- | Send a string to the server and return its response.-    send  :: String -> m String+    -- | Send a string to the server.+    send  :: String -> m ()+    -- | Get response from the server.+    receive :: m [String]+    -- | Get underlying Handle (or Nothing, if no connection is estabilished)+    getHandle :: m (Maybe Handle)     -- | Produce a password to send to the server should it ask for     --   one.     getPassword :: m Password+    -- | Alters password to be sent to the server.+    setPassword :: String -> m ()+    -- | Get MPD protocol version+    getVersion :: m (Int, Int, Int)
Network/MPD/Utils.hs view
@@ -7,12 +7,15 @@ -- Utilities.  module Network.MPD.Utils (-    parseDate, parseNum, parseBool, showBool,-    breakChar, toAssoc, toAssocList, splitGroups+    parseDate, parseIso8601, parseNum, parseFrac,+    parseBool, showBool, breakChar, parseTriple,+    toAssoc, toAssocList, splitGroups     ) where  import Data.Char (isDigit) import Data.Maybe (fromMaybe)+import Data.Time.Format (ParseTime, parseTime)+import System.Locale (defaultTimeLocale)  -- Break a string by character, removing the separator. breakChar :: Char -> String -> (String, String)@@ -25,12 +28,26 @@ parseDate :: String -> Maybe Int parseDate = parseNum . takeWhile isDigit +-- Parse date in iso 8601 format+parseIso8601 :: (ParseTime t) => String -> Maybe t+parseIso8601 = parseTime defaultTimeLocale "%FT%TZ"+ -- Parse a positive or negative integer value, returning 'Nothing' on failure. parseNum :: (Read a, Integral a) => String -> Maybe a parseNum s = do     [(x, "")] <- return (reads s)     return x +-- Parse C style floating point value, returning 'Nothing' on failure.+parseFrac :: (Fractional a, Read a) => String -> Maybe a+parseFrac s =+    case s of+        "nan"  -> return $ read "NaN"+        "inf"  -> return $ read "Infinity"+        "-inf" -> return $ read "-Infinity"+        _      -> do [(x, "")] <- return $ reads s+                     return x+ -- Inverts 'parseBool'. showBool :: Bool -> String showBool x = if x then "1" else "0"@@ -41,6 +58,14 @@                   "1" -> Just True                   "0" -> Just False                   _   -> Nothing++-- Break a string into triple.+parseTriple :: Char -> (String -> Maybe a) -> String -> Maybe (a, a, a)+parseTriple c f s = let (u, u') = breakChar c s+                        (v, w)  = breakChar c u' in+    case (f u, f v, f w) of+        (Just x, Just y, Just z) -> Just (x, y, z)+        _                        -> Nothing  -- Break a string into an key-value pair, separating at the first ':'. toAssoc :: String -> (String, String)
README.md view
@@ -81,7 +81,6 @@  ### Resources * [API documentation]-* [Code coverage] * [Protocol reference] * [Using GitHub] * \#libmpd-haskell @ irc.freenode.net@@ -89,8 +88,7 @@ [bug tracker]: http://trac.haskell.org/libmpd/ [GitHub]: http://www.github.com [repository]: http://www.github.com/joachifm/libmpd-haskell-[API documentation]: http://projects.haskell.org/libmpd/doc/-[Code coverage]: http://projects.haskell.org/libmpd/coverage/hpc_index.html+[API documentation]: http://hackage.haskell.org/packages/archive/libmpd/0.4.2/doc/html/Network-MPD.html [Protocol reference]: http://www.musicpd.org/doc/protocol/ [Using GitHub]: http://help.github.com @@ -103,3 +101,5 @@ Joachim Fasting \<joachim.fasting@gmail.com\>  Daniel Schoepe \<daniel.schoepe@googlemail.com\>++Andrzej Rybczak \<electricityispower@gmail.com\>
libmpd.cabal view
@@ -1,5 +1,5 @@ Name:               libmpd-Version:            0.4.2+Version:            0.5.0 License:            LGPL License-file:       LICENSE Copyright:          Ben Sinclair 2005-2009, Joachim Fasting 2010@@ -43,8 +43,12 @@      Build-Depends:      network >= 2.1 && < 2.3,                         mtl >= 1.1 && < 1.2, filepath >= 1.0 && < 1.2,-                        utf8-string >= 0.3.1 && < 0.4-    Exposed-Modules:    Network.MPD, Network.MPD.Core+                        utf8-string >= 0.3.1 && < 0.4,+                        containers >= 0.3 && < 0.4,+                        time >= 1.1 && < 1.2,+                        old-locale >= 1.0 && < 1.1+    Exposed-Modules:    Network.MPD, Network.MPD.Commands.Extensions,+                        Network.MPD.Core     Other-Modules:      Network.MPD.Core.Class,                         Network.MPD.Core.Error,                         Network.MPD.Commands,@@ -52,6 +56,7 @@                         Network.MPD.Commands.Parse,                         Network.MPD.Commands.Query,                         Network.MPD.Commands.Types,+                        Network.MPD.Commands.Util,                         Network.MPD.Utils     ghc-prof-options:   -auto-all -prof