]> kaliko git repositories - python-musicpd.git/blob - doc/source/use.rst
Better socket timeout documentation (closes #14)
[python-musicpd.git] / doc / source / use.rst
1 Using the client library
2 =========================
3
4 Introduction
5 ------------
6
7 The client library can be used as follows:
8
9 .. code-block:: python
10
11     client = musicpd.MPDClient()       # create client object
12     client.connect()                   # use MPD_HOST/MPD_PORT if set else
13                                        #   test ${XDG_RUNTIME_DIR}/mpd/socket for existence
14                                        #   fallback to localhost:6600
15                                        # connect support host/port argument as well
16     print(client.mpd_version)          # print the mpd protocol version
17     print(client.cmd('foo', 42))       # print result of the request "cmd foo 42"
18                                        # (nb. for actual command, see link to the protocol below)
19     client.disconnect()                # disconnect from the server
20
21 In the example above `cmd` in not an actual MPD command, for a list of
22 supported commands, their arguments (as MPD currently understands
23 them), and the functions used to parse their responses see :ref:`commands`.
24
25 See the `MPD protocol documentation`_ for more details.
26
27 Environment variables
28 ---------------------
29
30 The client honors the following environment variables:
31
32   * ``MPD_HOST`` MPD host (:abbr:`FQDN (fully qualified domain name)`, socket path or abstract socket) and password.
33
34     | To define a password set MPD_HOST to "`password@host`" (password only "`password@`")
35     | For abstract socket use "@" as prefix : "`@socket`" and then with a password  "`pass@@socket`"
36     | Regular unix socket are set with an absolute path: "`/run/mpd/socket`"
37   * ``MPD_PORT`` MPD port, relevant for TCP socket only, ie with :abbr:`FQDN (fully qualified domain name)` defined host
38   * ``MPD_TIMEOUT`` timeout for connecting to MPD and waiting for MPD’s response in seconds
39   * ``XDG_RUNTIME_DIR`` path to look for potential socket: ``${XDG_RUNTIME_DIR}/mpd/socket``
40
41 Defaults settings
42 -----------------
43
44   * If ``MPD_HOST`` is not set, then look for a socket in ``${XDG_RUNTIME_DIR}/mpd/socket``
45   * If there is no socket use ``localhost``
46   * If ``MPD_PORT`` is not set, then use ``6600``
47   * If ``MPD_TIMEOUT`` is not set, then uses :py:obj:`musicpd.CONNECTION_TIMEOUT`
48
49 Command lists
50 -------------
51
52 Command lists are also supported using `command_list_ok_begin()` and
53 `command_list_end()` :
54
55 .. code-block:: python
56
57     client.command_list_ok_begin()       # start a command list
58     client.update()                      # insert the update command into the list
59     client.status()                      # insert the status command into the list
60     results = client.command_list_end()  # results will be a list with the results
61
62 Ranges
63 ------
64
65 Provide a 2-tuple as argument for command supporting ranges (cf. `MPD protocol documentation`_ for more details).
66 Possible ranges are: "START:END", "START:" and ":" :
67
68 .. code-block:: python
69
70     # An intelligent clear
71     # clears played track in the queue, currentsong included
72     pos = client.currentsong().get('pos', 0)
73     # the 2-tuple range object accepts str, no need to convert to int
74     client.delete((0, pos))
75     # missing end interpreted as highest value possible, pay attention still need a tuple.
76     client.delete((pos,))  # purge queue from current to the end
77
78 A notable case is the `rangeid` command allowing an empty range specified
79 as a single colon as argument (i.e. sending just ":"):
80
81 .. code-block:: python
82
83     # sending "rangeid :" to clear the range, play everything
84     client.rangeid(())  # send an empty tuple
85
86 Empty start in range (i.e. ":END") are not possible and will raise a CommandError.
87
88 Iterators
89 ----------
90
91 Commands may also return iterators instead of lists if `iterate` is set to
92 `True`:
93
94 .. code-block:: python
95
96     client.iterate = True
97     for song in client.playlistinfo():
98         print song['file']
99
100 Idle prefixed commands
101 ----------------------
102
103 Each command have a *send\_<CMD>* and a *fetch\_<CMD>* variant, which allows to
104 send a MPD command and then fetch the result later.
105 This is useful for the idle command:
106
107 .. code-block:: python
108
109     >>> client.send_idle()
110     # do something else or use function like select()
111     # http://docs.python.org/howto/sockets.html#non-blocking-sockets
112     # ex. select([client], [], [])
113     >>> events = client.fetch_idle()
114
115     # more complex use for example, with glib/gobject:
116     >>> def callback(source, condition):
117     >>>    changes = client.fetch_idle()
118     >>>    print changes
119     >>>    return False  # removes the IO watcher
120
121     >>> client.send_idle()
122     >>> gobject.io_add_watch(client, gobject.IO_IN, callback)
123     >>> gobject.MainLoop().run()
124
125 Fetching binary content (cover art)
126 -----------------------------------
127
128 Fetching album covers is possible with albumart, here is an example:
129
130 .. code-block:: python
131
132     >>> cli = musicpd.MPDClient()
133     >>> cli.connect()
134     >>> track = "Steve Reich/1978-Music for 18 Musicians"
135     >>> aart = cli.albumart(track, 0)
136     >>> received = int(aart.get('binary'))
137     >>> size = int(aart.get('size'))
138     >>> with open('/tmp/cover', 'wb') as cover:
139     >>>     # aart = {'size': 42, 'binary': 2051, data: bytes(...)}
140     >>>     cover.write(aart.get('data'))
141     >>>     while received < size:
142     >>>         aart = cli.albumart(track, received)
143     >>>         cover.write(aart.get('data'))
144     >>>         received += int(aart.get('binary'))
145     >>>     if received != size:
146     >>>         print('something went wrong', file=sys.stderr)
147     >>> cli.disconnect()
148
149 A `CommandError` is raised if the album does not expose a cover.
150
151 You can also use `readpicture` command to fetch embedded picture:
152
153 .. code-block:: python
154
155     >>> cli = musicpd.MPDClient()
156     >>> cli.connect()
157     >>> track = 'muse/Amon Tobin/2011-ISAM/01-Amon Tobin - Journeyman.mp3'
158     >>> rpict = cli.readpicture(track, 0)
159     >>> if not rpict:
160     >>>     print('No embedded picture found', file=sys.stderr)
161     >>>     sys.exit(1)
162     >>> size = int(rpict['size'])
163     >>> done = int(rpict['binary'])
164     >>> with open('/tmp/cover', 'wb') as cover:
165     >>>     cover.write(rpict['data'])
166     >>>     while size > done:
167     >>>         rpict = cli.readpicture(track, done)
168     >>>         done += int(rpict['binary'])
169     >>>         print(f'writing {rpict["binary"]}, done {100*done/size:03.0f}%')
170     >>>         cover.write(rpict['data'])
171     >>> cli.disconnect()
172
173 Refer to `MPD protocol documentation`_ for the meaning of `binary`, `size` and `data`.
174
175 Socket timeout
176 --------------
177
178 .. note::
179   When the timeout is reached it raises a :py:obj:`socket.timeout` exception. An :py:obj:`OSError` subclass.
180
181 A timeout is used for the initial MPD connection (``connect`` command), then
182 the socket is put in blocking mode with no timeout. Its value is set in
183 :py:obj:`musicpd.CONNECTION_TIMEOUT` at module level and
184 :py:obj:`musicpd.MPDClient.mpd_timeout` in MPDClient instances . However it
185 is possible to set socket timeout for all command setting
186 :py:obj:`musicpd.MPDClient.socket_timeout` attribute to a value in second.
187
188 Having ``socket_timeout`` enabled can help to detect "half-open connection".
189 For instance loosing connectivity without the server explicitly closing the
190 connection (switching network interface ethernet/wifi, router down, etc…).
191
192 **Nota bene**: with ``socket_timeout`` enabled each command sent to MPD might
193 timeout. A couple of seconds should be enough for commands to complete except
194 for the special case of ``idle`` command which by definition *“ waits until
195 there is a noteworthy change in one or more of MPD’s subsystems.”* (cf. `MPD
196 protocol documentation`_).
197
198 Here is a solution to use ``idle`` command with ``socket_timeout``:
199
200 .. code-block:: python
201
202     import musicpd
203     import select
204     import socket
205
206     cli = musicpd.MPDClient()
207     try:
208         cli.socket_timeout = 10  # seconds
209         select_timeout = 5 # second
210         cli.connect()
211         while True:
212             cli.send_idle()  # use send_ API to avoid blocking on read
213             _read, _, _ = select.select([cli], [], [], select_timeout)
214             if _read:  # tries to read response
215                 ret = cli.fetch_idle()
216                 print(', '.join(ret))  # Do something
217             else: # cancels idle
218                 cli.noidle()
219     except socket.timeout as err:
220         print(f'{err} (timeout {cli.socket_timeout})')
221     except (OSError, musicpd.MPDError) as err:
222         print(f'{err!r}')
223         if cli._sock is not None:
224             cli.disconnect()
225     except KeyboardInterrupt:
226         pass
227
228 Some explanations:
229
230   * First launch a non blocking ``idle`` command. This call do not wait for a
231     response to avoid socket timeout waiting for an MPD event.
232   * ``select`` waits for something to read on the socket (the idle response
233     in this case), returns after ``select_timeout`` seconds anyway.
234   * In case there is something to read read it using ``fetch_idle``
235   * Nothing to read, cancel idle with ``noidle``
236
237 All three commands in the while loop (send_idle, fetch_idle, noidle) are not
238 triggering a socket timeout unless the connection is actually lost (actually it
239 could also be that MPD took to much time to answer, but MPD taking more than a
240 couple of seconds for these commands should never occur).
241
242
243 .. _MPD protocol documentation: http://www.musicpd.org/doc/protocol/
244 .. vim: spell spelllang=en