2 libmpd for Haskell, a MPD client library.
3 Copyright (C) 2005 Ben Sinclair <bsinclai@turing.une.edu.au>
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 -- Copyright : (c) Ben Sinclair 2005
23 -- Maintainer : bsinclai@turing.une.edu.au
25 -- Portability : Haskell 98
27 -- MPD client library.
32 State
(..), Status
(..), Stats
(..),
34 Artist
, Album
, Title
, Seconds
, PLIndex
(..),
41 disableoutput
, enableoutput
, kill
, outputs
, update
,
43 -- * Database commands
45 find, list, listAll
, listAllinfo
, lsinfo
, search
, count
,
47 -- * Playlist commands
49 add
, add_
, addid
, clear
, currentSong
, delete, load
, move
,
50 playlistinfo
, listplaylist
, listplaylistinfo
, playlist
, plchanges
,
51 plchangesposid
, rm
, rename
, save
, shuffle
, swap
,
53 -- * Playback commands
54 crossfade
, next, pause
, play
, previous
, random, repeat, seek
,
55 setVolume
, volume
, stop
,
57 -- * Miscellaneous commands
58 clearerror
, close
, commands
, notcommands
, tagtypes
, urlhandlers
,
59 password
, ping
, stats
, status
,
61 -- * Extensions\/shortcuts
62 addMany
, crop
, findArtist
, findAlbum
, findTitle
, listArtists
,
63 listAlbums
, listAlbum
, searchArtist
, searchAlbum
, searchTitle
,
67 import Control
.Monad
(liftM, unless)
68 import Prelude
hiding (repeat)
69 import Data
.List
(isPrefixOf)
78 -- | A connection to a MPD.
79 newtype Connection
= Conn
Handle
84 type Seconds
= Integer
86 -- | Represents a song's playlist index.
87 data PLIndex
= PLNone
-- ^ No index.
88 | Pos
Integer -- ^ A playlist position index (starting from 1).
89 | ID
Integer -- ^ A playlist ID number.
92 -- | Represents the different playback states.
98 -- | Container for MPD status.
100 Status
{ stState
:: State
,
101 -- | A percentage (0-100).
103 stRepeat
, stRandom
:: Bool,
104 -- | This value gets incremented by the server every time the
106 stPlaylistVersion
:: Integer,
107 stPlaylistLength
:: Integer,
108 -- | Current song's position in the playlist (starting from 1).
109 stSongPos
:: PLIndex
,
110 -- | Each song in the playlist has an identifier to more
111 -- robustly identify it.
113 -- | (Seconds played, song length in seconds).
114 stTime
:: (Seconds
,Seconds
),
115 -- | Bitrate of playing song in kilobytes per second.
117 -- | MPD can fade between tracks. This is the time it takes to
119 stXFadeWidth
:: Seconds
,
120 -- | (samplerate, bits, channels)
121 stAudio
:: (Int,Int,Int),
122 -- | Job id of currently running update (if any).
123 stUpdatingDb
:: Integer,
124 -- | Last error message (if any)
128 -- | Container for database statistics.
130 Stats
{ stsArtists
:: Integer -- ^ Number of artists.
131 , stsAlbums
:: Integer -- ^ Number of albums.
132 , stsSongs
:: Integer -- ^ Number of songs.
133 , stsUptime
:: Seconds
-- ^ Daemon uptime in seconds.
134 , stsPlaytime
:: Seconds
-- ^ Time length of music played.
135 , stsDbPlaytime
:: Seconds
-- ^ Sum of all song times in db.
136 , stsDbUpdate
:: Integer -- ^ Last db update in UNIX time.
140 -- | Description of a song.
141 data Song
= Song
{ sgArtist
, sgAlbum
, sgTitle
, sgFilePath
, sgGenre
, sgName
142 ,sgComposer
, sgPerformer
:: String
143 ,sgLength
:: Seconds
-- ^ length in seconds
144 ,sgDate
:: Int -- ^ year
145 ,sgTrack
:: (Int, Int) -- ^ (track number, total tracks)
146 ,sgDisc
:: (Int, Int) -- ^ (pos. in set, total in set)
147 ,sgIndex
:: PLIndex
}
150 -- | Describes a 'count'.
151 data Count
= Count
{ cSongs
:: Integer -- ^ Number of songs that matches
153 , cPlaytime
:: Seconds
-- ^ Total play time of matching
158 -- | Represents an output device.
160 Device
{ dOutputID
:: Int -- ^ Output's id number
161 , dOutputName
:: String -- ^ Output's name as defined in the MPD
162 -- configuration file
163 , dOutputEnabled
:: Bool }
167 -- Basic connection functions
170 -- | Create a MPD connection.
171 connect
:: String -- ^ Hostname.
172 -> PortNumber
-- ^ Port number.
174 connect host port
= withSocketsDo
$ do
175 conn
<- liftM Conn
. connectTo host
$ PortNumber port
176 mpd
<- checkConn conn
177 if mpd
then return conn
178 else close conn
>> fail ("no MPD at " ++ host
++ ":" ++ show port
)
180 -- | Check that a MPD daemon is at the other end of a connection.
181 checkConn
:: Connection
-> IO Bool
182 checkConn
(Conn h
) = liftM (isPrefixOf "OK MPD") (hGetLine h
)
188 -- | Turn off an output device.
189 disableoutput
:: Connection
-> Int -> IO ()
190 disableoutput conn
= getResponse_ conn
. ("disableoutput " ++) . show
192 -- | Turn on an output device.
193 enableoutput
:: Connection
-> Int -> IO ()
194 enableoutput conn
= getResponse_ conn
. ("enableoutput " ++) . show
196 -- | Kill the server. Obviously, the connection is then invalid.
197 kill
:: Connection
-> IO ()
198 kill
(Conn h
) = hPutStrLn h
"kill" >> hClose h
200 -- | Retrieve information for all output devices.
201 outputs
:: Connection
-> IO [Device
]
202 outputs conn
= liftM (map takeDevInfo
. splitGroups
. kvise
)
203 (getResponse conn
"outputs")
205 takeDevInfo xs
= Device
{
206 dOutputID
= takeNum
"outputid" xs
,
207 dOutputName
= takeString
"outputname" xs
,
208 dOutputEnabled
= takeBool
"outputenabled" xs
211 -- | Update the server's database.
212 update
:: Connection
-> [String] -> IO ()
213 update conn
[] = getResponse_ conn
"update"
214 update conn
[x
] = getResponse_ conn
("update " ++ x
)
215 update conn xs
= getResponses conn
(map ("update " ++) xs
) >> return ()
221 -- All scope modifiers (i.e. metadata to match against when searching for
222 -- database entries with certain metadata values) may be any of the
223 -- values listed by 'tagtypes'.
224 -- Also one may use \"any\" or \"filename\".
226 -- | List all metadata of metadata (sic).
228 -> String -- ^ Metadata to list.
229 -> Maybe String -- ^ Optionally specify a scope modifier
230 -> String -- ^ Query (requires optional arg).
232 list conn metaType metaQuery query
= liftM takeValues
(getResponse conn cmd
)
233 where cmd
= "list " ++ metaType
++
234 maybe "" (\x
-> " " ++ x
++ " " ++ show query
) metaQuery
236 -- | Non-recursivly list the contents of a database directory.
237 lsinfo
:: Connection
-> Maybe String -- ^ Optionally specify a path.
238 -> IO [Either String Song
]
239 lsinfo conn path
= liftM takeEntries
240 (getResponse conn
("lsinfo " ++ maybe "" show path
))
242 -- | List the songs (without metadata) in a database directory recursively.
243 listAll
:: Connection
-> Maybe String -> IO [String]
244 listAll conn path
= liftM (map snd . filter ((== "file") . fst) . kvise
)
245 (getResponse conn
("listall " ++ maybe "" show path
))
247 -- | Recursive 'lsinfo'.
248 listAllinfo
:: Connection
-> Maybe String -- ^ Optionally specify a path
249 -> IO [Either String Song
]
250 listAllinfo conn path
= liftM takeEntries
251 (getResponse conn
("listallinfo " ++ maybe "" show path
))
253 -- | Search the database for entries exactly matching a query.
255 -> String -- ^ Scope modifier
258 find conn searchType query
= liftM takeSongs
259 (getResponse conn
("find " ++ searchType
++ " " ++ show query
))
261 -- | Search the database using case insensitive matching.
263 -> String -- ^ Scope modifier
266 search conn searchType query
= liftM takeSongs
267 (getResponse conn
("search " ++ searchType
++ " " ++ show query
))
269 -- | Count the number of entries matching a query.
271 -> String -- ^ Scope modifier
274 count conn countType query
= liftM (takeCountInfo
. kvise
)
275 (getResponse conn
("count " ++ countType
++ " " ++ show query
))
276 where takeCountInfo xs
= Count
{ cSongs
= takeNum
"songs" xs
,
277 cPlaytime
= takeNum
"playtime" xs
}
283 -- Unless otherwise noted all playlist commands operate on the current
286 -- | Like 'add', but returns a playlist id.
287 addid
:: Connection
-> String -> IO Integer
289 liftM (read . snd . head . kvise
) (getResponse conn
("addid " ++ show x
))
291 -- | Like 'add_' but returns a list of the files added.
292 add
:: Connection
-> Maybe String -> String -> IO [String]
293 add conn plname x
= add_ conn plname x
>> listAll conn
(Just x
)
295 -- | Add a song (or a whole directory) to a playlist.
296 -- Adds to current if no playlist is specified.
297 -- Will create a new playlist if the one specified does not already exist.
299 -> Maybe String -- ^ Optionally specify a playlist to operate on
302 add_ conn plname x
= getResponse_ conn cmd
303 where cmd
= maybe ("add " ++ path
)
304 (\pl
-> "playlistadd " ++ show pl
++ " " ++ path
)
308 -- | Clear a playlist. Clears current playlist if no playlist is specified.
309 -- If the specified playlist does not exist, it will be created.
311 -> Maybe String -- ^ Optional name of a playlist to clear.
313 clear conn Nothing
= getResponse_ conn
"clear"
314 clear conn
(Just plname
) = getResponse_ conn
("playlistclear " ++ show plname
)
316 -- | Remove a song from a playlist.
317 -- If no playlist is specified, current playlist is used.
319 -> Maybe String -- ^ Optionally specify a playlist to operate on
321 delete _ _ PLNone
= return ()
322 delete conn Nothing
(Pos x
) = getResponse_ conn
("delete " ++ show (x
- 1))
323 delete conn Nothing
(ID x
) = getResponse_ conn
("deleteid " ++ show x
)
324 -- XXX assume that playlistdelete expects positions and not ids.
325 delete conn
(Just plname
) (Pos x
) =
326 getResponse_ conn
("playlistdelete " ++ show plname
++ " " ++ show (x
- 1))
327 delete _ _ _
= return ()
329 -- | Load an existing playlist.
330 load
:: Connection
-> String -> IO ()
331 load conn
= getResponse_ conn
. ("load " ++) . show
333 -- | Move a song to a given position.
335 -> Maybe String -- ^ Optionally specify a playlist to operate on
336 -> PLIndex
-> Integer -> IO ()
337 move _ _ PLNone _
= return ()
338 move conn Nothing
(Pos from
) to
=
339 getResponse_ conn
("move " ++ show (from
- 1) ++ " " ++ show to
)
340 move conn Nothing
(ID from
) to
=
341 getResponse_ conn
("moveid " ++ show from
++ " " ++ show to
)
342 -- XXX assumes that playlistmove expects positions and not ids
343 move conn
(Just plname
) (Pos from
) to
=
344 getResponse_ conn
("playlistmove " ++ show plname
++ " " ++ show (from
- 1)
346 move _ _ _ _
= return ()
348 -- | Delete existing playlist.
349 rm
:: Connection
-> String -> IO ()
350 rm conn
= getResponse_ conn
. ("rm " ++) . show
352 -- | Rename an existing playlist.
354 -> String -- ^ Name of playlist to be renamed
355 -> String -- ^ New playlist name
357 rename conn plname new
=
358 getResponse_ conn
("rename " ++ show plname
++ " " ++ show new
)
360 -- | Save the current playlist.
361 save
:: Connection
-> String -> IO ()
362 save conn
= getResponse_ conn
. ("save " ++) . show
364 -- | Swap the positions of two songs.
365 swap
:: Connection
-> PLIndex
-> PLIndex
-> IO ()
366 swap conn
(Pos x
) (Pos y
) =
367 getResponse_ conn
("swap " ++ show (x
- 1) ++ " " ++ show (y
- 1))
368 swap conn
(ID x
) (ID y
) =
369 getResponse_ conn
("swapid " ++ show x
++ " " ++ show y
)
370 swap _ _ _
= return ()
372 -- | Shuffle the playlist.
373 shuffle
:: Connection
-> IO ()
374 shuffle
= flip getResponse_
"shuffle"
376 -- | Retrieve metadata for songs in the current playlist.
377 playlistinfo
:: Connection
378 -> PLIndex
-- ^ Optional playlist index.
380 playlistinfo conn x
= liftM takeSongs
(getResponse conn cmd
)
381 where cmd
= case x
of
382 Pos x
' -> "playlistinfo " ++ show (x
' - 1)
383 ID x
' -> "playlistid " ++ show x
'
386 -- | Retrieve metadata for files in a given playlist.
387 listplaylistinfo
:: Connection
-> String -> IO [Song
]
388 listplaylistinfo conn
= liftM takeSongs
. getResponse conn
.
389 ("listplaylistinfo " ++) . show
391 -- | Retrieve a list of files in a given playlist.
392 listplaylist
:: Connection
-> String -> IO [String]
393 listplaylist conn
= liftM takeValues
. getResponse conn
.
394 ("listplaylist " ++) . show
396 -- | Retrieve file paths and positions of songs in the current playlist.
397 -- Note that this command is only included for completeness sake; it's
398 -- deprecated and likely to disappear at any time.
399 playlist
:: Connection
-> IO [(PLIndex
, String)]
400 playlist
= liftM (map f
) . flip getResponse
"playlist"
401 -- meh, the response here deviates from just about all other commands
402 where f s
= let (pos
, name
) = break (== ':') s
403 in (Pos
. (+1) $ read pos
, drop 1 name
)
405 -- | Retrieve a list of changed songs currently in the playlist since
406 -- a given playlist version.
407 plchanges
:: Connection
-> Integer -> IO [Song
]
408 plchanges conn
= liftM takeSongs
. getResponse conn
. ("plchanges " ++) . show
410 -- | Like 'plchanges' but only returns positions and ids.
411 plchangesposid
:: Connection
-> Integer -> IO [(PLIndex
, PLIndex
)]
412 plchangesposid conn plver
=
413 liftM (map takePosid
. splitGroups
. kvise
) (getResponse conn cmd
)
414 where cmd
= "plchangesposid " ++ show plver
415 takePosid xs
= (Pos
. (+1) $ takeNum
"cpos" xs
, ID
$ takeNum
"Id" xs
)
417 -- | Get the currently playing song.
418 currentSong
:: Connection
-> IO (Maybe Song
)
419 currentSong conn
= do
420 currStatus
<- status conn
421 if stState currStatus
== Stopped
423 else do ls
<- liftM kvise
(getResponse conn
"currentsong")
424 return $ if null ls
then Nothing
425 else Just
(takeSongInfo ls
)
431 -- | Set crossfading between songs.
432 crossfade
:: Connection
-> Seconds
-> IO ()
433 crossfade conn
= getResponse_ conn
. ("crossfade " ++) . show
435 -- | Begin\/continue playing.
436 play
:: Connection
-> PLIndex
-> IO ()
437 play conn PLNone
= getResponse_ conn
"play"
438 play conn
(Pos x
) = getResponse_ conn
("play " ++ show (x
-1))
439 play conn
(ID x
) = getResponse_ conn
("playid " ++ show x
)
442 pause
:: Connection
-> Bool -> IO ()
443 pause conn
= getResponse_ conn
. ("pause " ++) . showBool
446 stop
:: Connection
-> IO ()
447 stop
= flip getResponse_
"stop"
449 -- | Play the next song.
450 next :: Connection
-> IO ()
451 next = flip getResponse_
"next"
453 -- | Play the previous song.
454 previous
:: Connection
-> IO ()
455 previous
= flip getResponse_
"previous"
457 -- | Seek to some point in a song.
458 -- Seeks in current song if no position is given.
459 seek
:: Connection
-> PLIndex
-> Seconds
-> IO ()
460 seek conn
(Pos x
) time
=
461 getResponse_ conn
("seek " ++ show (x
- 1) ++ " " ++ show time
)
462 seek conn
(ID x
) time
=
463 getResponse_ conn
("seekid " ++ show x
++ " " ++ show time
)
464 seek conn PLNone time
= do
466 unless (stState st
== Stopped
) (seek conn
(stSongID st
) time
)
468 -- | Set random playing.
469 random :: Connection
-> Bool -> IO ()
470 random conn
= getResponse_ conn
. ("random " ++) . showBool
473 repeat :: Connection
-> Bool -> IO ()
474 repeat conn
= getResponse_ conn
. ("repeat " ++) . showBool
477 setVolume
:: Connection
-> Int -> IO ()
478 setVolume conn
= getResponse_ conn
. ("setvol " ++) . show
480 -- | Increase or decrease volume by a given percent, e.g.
481 -- 'volume 10' will increase the volume by 10 percent, while
482 -- 'volume (-10)' will decrease it by the same amount.
483 -- Note that this command is only included for completeness sake ; it's
484 -- deprecated and may disappear at any time.
485 volume
:: Connection
-> Int -> IO ()
486 volume conn
= getResponse_ conn
. ("volume " ++) . show
489 -- Miscellaneous commands
492 -- | Clear the current error message in status.
493 clearerror
:: Connection
-> IO ()
494 clearerror
(Conn h
) = hPutStrLn h
"clearerror" >> hClose h
496 -- | Close a MPD connection.
497 close
:: Connection
-> IO ()
498 close
(Conn h
) = hPutStrLn h
"close" >> hClose h
500 -- | Retrieve a list of available commands.
501 commands
:: Connection
-> IO [String]
502 commands
= liftM takeValues
. flip getResponse
"commands"
504 -- | Retrieve a list of unavailable commands.
505 notcommands
:: Connection
-> IO [String]
506 notcommands
= liftM takeValues
. flip getResponse
"notcommands"
508 -- | Retrieve a list of available song metadata.
509 tagtypes
:: Connection
-> IO [String]
510 tagtypes
= liftM takeValues
. flip getResponse
"tagtypes"
512 -- | Retrieve a list of supported urlhandlers.
513 urlhandlers
:: Connection
-> IO [String]
514 urlhandlers
= liftM takeValues
. flip getResponse
"urlhandlers"
516 -- XXX should the password be quoted?
517 -- | Send password to server to authenticate session.
518 -- Password is sent as plain text.
519 password
:: Connection
-> String -> IO ()
520 password conn
= getResponse_ conn
. ("password " ++)
522 -- | Check that the server is still responding.
523 ping
:: Connection
-> IO ()
524 ping
= flip getResponse_
"ping"
526 -- | Get server statistics.
527 stats
:: Connection
-> IO Stats
528 stats
= liftM (parseStats
. kvise
) . flip getResponse
"stats"
529 where parseStats xs
=
530 Stats
{ stsArtists
= takeNum
"artists" xs
,
531 stsAlbums
= takeNum
"albums" xs
,
532 stsSongs
= takeNum
"songs" xs
,
533 stsUptime
= takeNum
"uptime" xs
,
534 stsPlaytime
= takeNum
"playtime" xs
,
535 stsDbPlaytime
= takeNum
"db_playtime" xs
,
536 stsDbUpdate
= takeNum
"db_update" xs
}
538 -- | Get the server's status.
539 status
:: Connection
-> IO Status
540 status
= liftM (parseStatus
. kvise
) . flip getResponse
"status"
541 where parseStatus xs
=
542 Status
{ stState
= maybe Stopped parseState
$ lookup "state" xs
,
543 stVolume
= takeNum
"volume" xs
,
544 stRepeat
= takeBool
"repeat" xs
,
545 stRandom
= takeBool
"random" xs
,
546 stPlaylistVersion
= takeNum
"playlist" xs
,
547 stPlaylistLength
= takeNum
"playlistlength" xs
,
548 stXFadeWidth
= takeNum
"xfade" xs
,
550 maybe PLNone
(Pos
. (1+) . read) $ lookup "song" xs
,
551 stSongID
= maybe PLNone
(ID
. read) $ lookup "songid" xs
,
552 stTime
= maybe (0,0) parseTime
$ lookup "time" xs
,
553 stBitrate
= takeNum
"bitrate" xs
,
554 stAudio
= maybe (0,0,0) parseAudio
$ lookup "audio" xs
,
555 stUpdatingDb
= takeNum
"updating_db" xs
,
556 stError
= takeString
"error" xs
558 parseState x
= case x
of "play" -> Playing
561 parseTime x
= let (y
,_
:z
) = break (== ':') x
in (read y
, read z
)
563 let (u
,_
:u
') = break (== ':') x
; (v
,_
:w
) = break (== ':') u
' in
564 (read u
, read v
, read w
)
567 -- Extensions\/shortcuts.
570 -- | Toggles play\/pause. Plays if stopped.
571 toggle
:: Connection
-> IO ()
575 Playing
-> pause conn
True
576 _
-> play conn PLNone
578 -- | Add a list of songs\/folders to a playlist.
579 -- Should be more efficient than running 'add' many times.
580 addMany
:: Connection
-> Maybe String -> [String] -> IO ()
581 addMany _ _
[] = return ()
582 addMany conn plname
[x
] = add_ conn plname x
583 addMany conn plname xs
= getResponses conn
(map (cmd
++) xs
) >> return ()
584 where cmd
= maybe ("add ") (\pl
-> "playlistadd " ++ show pl
++ " ") plname
587 crop
:: Connection
-> PLIndex
-> PLIndex
-> IO ()
588 crop _
(Pos _
) (Pos _
) = undefined
589 crop _ _ _
= return ()
591 -- | Search the database for songs relating to an artist.
592 findArtist
:: Connection
-> String -> IO [Song
]
593 findArtist
= flip find "artist"
595 -- | Search the database for songs relating to an album.
596 findAlbum
:: Connection
-> String -> IO [Song
]
597 findAlbum
= flip find "album"
599 -- | Search the database for songs relating to a song title.
600 findTitle
:: Connection
-> String -> IO [Song
]
601 findTitle
= flip find "title"
603 -- | List the artists in the database.
604 listArtists
:: Connection
-> IO [Artist
]
605 listArtists
= liftM takeValues
. flip getResponse
"list artist"
607 -- | List the albums in the database, optionally matching a given
609 listAlbums
:: Connection
-> Maybe Artist
-> IO [Album
]
610 listAlbums conn artist
=
612 -- XXX according to the spec this shouldn't work (but it does)
613 (getResponse conn
("list album " ++ maybe "" show artist
))
615 -- | List the songs of an album of an artist.
616 listAlbum
:: Connection
-> Artist
-> Album
-> IO [Song
]
617 listAlbum conn artist album
= liftM (filter ((== artist
) . sgArtist
))
618 (findAlbum conn album
)
620 -- | Search the database for songs relating to an artist using 'search'.
621 searchArtist
:: Connection
-> String -> IO [Song
]
622 searchArtist
= flip search
"artist"
624 -- | Search the database for songs relating to an album using 'search'.
625 searchAlbum
:: Connection
-> String -> IO [Song
]
626 searchAlbum
= flip search
"album"
628 -- | Search the database for songs relating to a song title.
629 searchTitle
:: Connection
-> String -> IO [Song
]
630 searchTitle
= flip search
"title"
632 -- | Retrieve the current playlist.
633 -- Equivalent to 'playlistinfo PLNone'.
634 getPlaylist
:: Connection
-> IO [Song
]
635 getPlaylist
= flip playlistinfo PLNone
638 -- Miscellaneous functions.
641 -- | Run getResponse but discard the response.
642 getResponse_
:: Connection
-> String -> IO ()
643 getResponse_ c x
= getResponse c x
>> return ()
645 -- | Get the lines of the daemon's response to a given command.
646 getResponse
:: Connection
-> String -> IO [String]
647 getResponse
(Conn h
) cmd
= hPutStrLn h cmd
>> hFlush h
>> f
[]
652 ('A
':'C
':'K
':_
:e
) -> fail e
655 -- | Get the lines of the daemon's response to a list of commands.
656 getResponses
:: Connection
-> [String] -> IO [String]
657 getResponses conn cmds
= getResponse conn
.
658 unlines $ "command_list_begin" : cmds
++ ["command_list_end"]
660 -- | Break up a list of strings into an assoc list, separating at
662 kvise
:: [String] -> [(String, String)]
664 where f x
= let (k
,v
) = break (== ':') x
in
665 (k
,dropWhile (== ' ') $ drop 1 v
)
667 -- | Takes a assoc list with recurring keys, and groups each cycle of
668 -- keys with their values together. The first key of each cycle needs
669 -- to be present in every cycle for it to work, but the rest don't
672 -- > splitGroups [(1,'a'),(2,'b'),(1,'c'),(2,'d')] ==
673 -- > [[(1,'a'),(2,'b')],[(1,'c'),(2,'d')]]
674 splitGroups
:: Eq a
=> [(a
, b
)] -> [[(a
, b
)]]
676 splitGroups
(x
:xs
) = ((x
:us
):splitGroups vs
)
677 where (us
,vs
) = break (\y
-> fst x
== fst y
) xs
679 -- | Run 'kvise' and return only the values.
680 takeValues
:: [String] -> [String]
681 takeValues
= snd . unzip . kvise
683 takeEntries
:: [String] -> [Either String Song
]
684 takeEntries s
= map Left dirs
++
685 map (Right
. takeSongInfo
) (splitGroups
$ reverse filedata
)
686 where (dirs
, _
, filedata
) = foldl split ([], [], []) $ kvise s
687 split (ds
, pls
, ss
) x
@(k
, v
) | k
== "directory" = (v
:ds
, pls
, ss
)
688 | k
== "playlist" = (ds
, v
:pls
, ss
)
689 |
otherwise = (ds
, pls
, x
:ss
)
691 -- | Build a list of song instances from a response.
692 -- Returns an empty list if input is empty.
693 takeSongs
:: [String] -> [Song
]
694 takeSongs
= map takeSongInfo
. splitGroups
. kvise
696 -- | Builds a song instance from an assoc list.
697 takeSongInfo
:: [(String,String)] -> Song
700 sgArtist
= takeString
"Artist" xs
,
701 sgAlbum
= takeString
"Album" xs
,
702 sgTitle
= takeString
"Title" xs
,
703 sgGenre
= takeString
"Genre" xs
,
704 sgName
= takeString
"Name" xs
,
705 sgComposer
= takeString
"Composer" xs
,
706 sgPerformer
= takeString
"Performer" xs
,
707 sgDate
= takeNum
"Date" xs
,
708 sgTrack
= maybe (0, 0) parseTrack
$ lookup "Track" xs
,
709 sgDisc
= maybe (0, 0) parseTrack
$ lookup "Disc" xs
,
710 sgFilePath
= takeString
"file" xs
,
711 sgLength
= takeNum
"Time" xs
,
712 sgIndex
= maybe PLNone
(ID
. read) $ lookup "Id" xs
714 where parseTrack x
= let (trck
, tot
) = break (== '/') x
715 in (read trck
, parseNum
(drop 1 tot
))
717 -- Helpers for retrieving values from an assoc. list.
718 takeString
:: String -> [(String, String)] -> String
719 takeString v
= fromMaybe "" . lookup v
721 takeNum
:: (Read a
, Num a
) => String -> [(String, String)] -> a
722 takeNum v
= maybe 0 parseNum
. lookup v
724 takeBool
:: String -> [(String, String)] -> Bool
725 takeBool v
= maybe False parseBool
. lookup v
727 -- Parse a numeric value, returning 0 on failure.
728 parseNum
:: (Read a
, Num a
) => String -> a
729 parseNum
= fromMaybe 0 . maybeReads
730 where maybeReads s
= do ; [(x
, "")] <- return (reads s
) ; return x
732 -- Inverts 'parseBool'.
733 showBool
:: Bool -> String
734 showBool x
= if x
then "1" else "0"
736 -- Parse a boolean response value.
737 parseBool
:: String -> Bool
738 parseBool
= (== "1") . take 1