Merge lp:~thisfred/autoqueue/lp-777354 into lp:autoqueue
- lp-777354
- Merge into trunk
Proposed by
Eric Casteleijn
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Eric Casteleijn | ||||
Approved revision: | 323 | ||||
Merged at revision: | 322 | ||||
Proposed branch: | lp:~thisfred/autoqueue/lp-777354 | ||||
Merge into: | lp:autoqueue | ||||
Diff against target: |
647 lines (+152/-158) 2 files modified
autoqueue/__init__.py (+3/-7) mpd_autoqueue.py (+149/-151) |
||||
To merge this branch: | bzr merge lp:~thisfred/autoqueue/lp-777354 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Eric Casteleijn | Pending | ||
Review via email: mp+60223@code.launchpad.net |
Commit message
Should fix mpd support
Description of the change
Should fix mpd support
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'autoqueue/__init__.py' | |||
2 | --- autoqueue/__init__.py 2011-05-06 15:30:42 +0000 | |||
3 | +++ autoqueue/__init__.py 2011-05-06 17:48:32 +0000 | |||
4 | @@ -75,10 +75,6 @@ | |||
5 | 75 | """Return filename for the song.""" | 75 | """Return filename for the song.""" |
6 | 76 | 76 | ||
7 | 77 | @abstractmethod | 77 | @abstractmethod |
8 | 78 | def get_length(self): | ||
9 | 79 | """Return length in seconds.""" | ||
10 | 80 | |||
11 | 81 | @abstractmethod | ||
12 | 82 | def get_last_started(self): | 78 | def get_last_started(self): |
13 | 83 | """Return the datetime the song was last played.""" | 79 | """Return the datetime the song was last played.""" |
14 | 84 | 80 | ||
15 | @@ -162,7 +158,7 @@ | |||
16 | 162 | self.running = False | 158 | self.running = False |
17 | 163 | self.log('Error handler received: %r, %r' % (args, kwargs)) | 159 | self.log('Error handler received: %r, %r' % (args, kwargs)) |
18 | 164 | 160 | ||
20 | 165 | def player_get_cache_dir(self): | 161 | def get_cache_dir(self): |
21 | 166 | """Get the directory to store temporary data. | 162 | """Get the directory to store temporary data. |
22 | 167 | 163 | ||
23 | 168 | Defaults to $XDG_CACHE_HOME/autoqueue on Gnome. | 164 | Defaults to $XDG_CACHE_HOME/autoqueue on Gnome. |
24 | @@ -180,7 +176,7 @@ | |||
25 | 180 | def get_blocked_artists_pickle(self): | 176 | def get_blocked_artists_pickle(self): |
26 | 181 | """Read the list of blocked artists from disk.""" | 177 | """Read the list of blocked artists from disk.""" |
27 | 182 | dump = os.path.join( | 178 | dump = os.path.join( |
29 | 183 | self.player_get_cache_dir(), "autoqueue_block_cache") | 179 | self.get_cache_dir(), "autoqueue_block_cache") |
30 | 184 | try: | 180 | try: |
31 | 185 | pickle = open(dump, 'r') | 181 | pickle = open(dump, 'r') |
32 | 186 | try: | 182 | try: |
33 | @@ -206,7 +202,7 @@ | |||
34 | 206 | artist_name, | 202 | artist_name, |
35 | 207 | len(self._blocked_artists))) | 203 | len(self._blocked_artists))) |
36 | 208 | dump = os.path.join( | 204 | dump = os.path.join( |
38 | 209 | self.player_get_cache_dir(), "autoqueue_block_cache") | 205 | self.get_cache_dir(), "autoqueue_block_cache") |
39 | 210 | try: | 206 | try: |
40 | 211 | os.remove(dump) | 207 | os.remove(dump) |
41 | 212 | except OSError: | 208 | except OSError: |
42 | 213 | 209 | ||
43 | === modified file 'mpd_autoqueue.py' | |||
44 | --- mpd_autoqueue.py 2010-02-28 02:04:38 +0000 | |||
45 | +++ mpd_autoqueue.py 2011-05-06 17:48:32 +0000 | |||
46 | @@ -1,161 +1,136 @@ | |||
47 | 1 | #!/usr/bin/env python | 1 | #!/usr/bin/env python |
80 | 2 | ''' | 2 | """mpd implementation of autoqueue. |
81 | 3 | Copyright (c) 2008, Rick van Hattem | 3 | |
82 | 4 | All rights reserved. | 4 | Copyright (c) 2008-2011 Rick van Hattem, Eric Casteleijn |
83 | 5 | 5 | """ | |
52 | 6 | Redistribution and use in source and binary forms, with or without | ||
53 | 7 | modification, are permitted provided that the following conditions are | ||
54 | 8 | met: | ||
55 | 9 | |||
56 | 10 | * Redistributions of source code must retain the above copyright | ||
57 | 11 | notice, this list of conditions and the following disclaimer. | ||
58 | 12 | |||
59 | 13 | * Redistributions in binary form must reproduce the above | ||
60 | 14 | copyright notice, this list of conditions and the following | ||
61 | 15 | disclaimer in the documentation and/or other materials provided | ||
62 | 16 | with the distribution. | ||
63 | 17 | |||
64 | 18 | * The names of the contributors may not be used to endorse or | ||
65 | 19 | promote products derived from this software without specific | ||
66 | 20 | prior written permission. | ||
67 | 21 | |||
68 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||
69 | 23 | 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||
70 | 24 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||
71 | 25 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||
72 | 26 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||
73 | 27 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||
74 | 28 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
75 | 29 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
76 | 30 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
77 | 31 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
78 | 32 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
79 | 33 | ''' | ||
84 | 34 | 6 | ||
85 | 35 | import os | 7 | import os |
86 | 36 | import sys | 8 | import sys |
87 | 37 | import mpd | 9 | import mpd |
88 | 38 | import time | 10 | import time |
89 | 39 | import errno | 11 | import errno |
90 | 40 | import atexit | ||
91 | 41 | import socket | 12 | import socket |
92 | 42 | import signal | 13 | import signal |
93 | 43 | import optparse | 14 | import optparse |
94 | 44 | import autoqueue | 15 | import autoqueue |
95 | 45 | 16 | ||
96 | 17 | |||
97 | 46 | def expand_path(path): | 18 | def expand_path(path): |
98 | 47 | path = os.path.expandvars(os.path.expanduser(path)) | 19 | path = os.path.expandvars(os.path.expanduser(path)) |
99 | 48 | if not os.path.isdir(path): | 20 | if not os.path.isdir(path): |
100 | 49 | os.mkdir(path) | 21 | os.mkdir(path) |
101 | 50 | return path | 22 | return path |
102 | 51 | 23 | ||
103 | 24 | |||
104 | 52 | def expand_file(path): | 25 | def expand_file(path): |
105 | 53 | path, file_ = os.path.split(path) | 26 | path, file_ = os.path.split(path) |
106 | 54 | return os.path.join(expand_path(path), file_) | 27 | return os.path.join(expand_path(path), file_) |
107 | 55 | 28 | ||
113 | 56 | '''The settings path automatically expands ~ to the user home directory | 29 | # The settings path automatically expands ~ to the user home directory |
114 | 57 | and $VAR to environment variables | 30 | # and $VAR to environment variables On most *n?x systems ~/.autoqueue |
115 | 58 | On most *n?x systems ~/.autoqueue will be expanded to /home/username/.autoqueue/ | 31 | # will be expanded to /home/username/.autoqueue/ Setting the path to |
116 | 59 | Setting the path to $HOME/.autoqueue/ will usually yield the same result | 32 | # $HOME/.autoqueue/ will usually yield the same result |
117 | 60 | ''' | 33 | |
118 | 61 | SETTINGS_PATH = '~/.autoqueue/' | 34 | SETTINGS_PATH = '~/.autoqueue/' |
119 | 62 | 35 | ||
122 | 63 | '''These settings will be overwritten by the MPD_HOST/MPD_PORT environment | 36 | # These settings will be overwritten by the MPD_HOST/MPD_PORT |
123 | 64 | variables and/or by the commandline arguments''' | 37 | # environment variables and/or by the commandline arguments |
124 | 38 | |||
125 | 65 | MPD_PORT = 6600 | 39 | MPD_PORT = 6600 |
126 | 66 | MPD_HOST = 'localhost' | 40 | MPD_HOST = 'localhost' |
127 | 67 | 41 | ||
128 | 68 | # The PID file to see if there are no other instances running | 42 | # The PID file to see if there are no other instances running |
129 | 43 | |||
130 | 69 | PID_FILE = os.path.join(expand_path(SETTINGS_PATH), 'mpd.pid') | 44 | PID_FILE = os.path.join(expand_path(SETTINGS_PATH), 'mpd.pid') |
131 | 70 | 45 | ||
132 | 71 | # Send a KILL signal if the process didn't end KILL_TIMEOUT seconds | 46 | # Send a KILL signal if the process didn't end KILL_TIMEOUT seconds |
133 | 72 | # after the TERM signal, check every KILL_CHECK_DELAY seconds if it's | 47 | # after the TERM signal, check every KILL_CHECK_DELAY seconds if it's |
134 | 73 | # still alive after sending the TERM signal | 48 | # still alive after sending the TERM signal |
135 | 49 | |||
136 | 74 | KILL_TIMEOUT = 10 | 50 | KILL_TIMEOUT = 10 |
137 | 75 | KILL_CHECK_DELAY = 0.1 | 51 | KILL_CHECK_DELAY = 0.1 |
138 | 76 | 52 | ||
140 | 77 | '''The targets for stdin, stdout and stderr when daemonizing''' | 53 | # The targets for stdin, stdout and stderr when daemonizing |
141 | 54 | |||
142 | 78 | STDIN = '/dev/null' | 55 | STDIN = '/dev/null' |
143 | 79 | STDOUT = SETTINGS_PATH + 'stdout' | 56 | STDOUT = SETTINGS_PATH + 'stdout' |
144 | 80 | STDERR = SETTINGS_PATH + 'stderr' | 57 | STDERR = SETTINGS_PATH + 'stderr' |
145 | 81 | 58 | ||
148 | 82 | '''The interval to refresh the MPD status, the faster this is set, the faster | 59 | # The interval to refresh the MPD status, the faster this is set, the |
149 | 83 | the MPD server will be polled to see if the queue is empty''' | 60 | # faster the MPD server will be polled to see if the queue is empty |
150 | 61 | |||
151 | 84 | REFRESH_INTERVAL = 10 | 62 | REFRESH_INTERVAL = 10 |
152 | 85 | 63 | ||
156 | 86 | '''The desired length for the queue, set this to 0 to add a song for every | 64 | # The desired length for the queue, set this to 0 to add a song for |
157 | 87 | finished song or to any other number for the number of seconds to keep the | 65 | # every finished song or to any other number for the number of seconds |
158 | 88 | queue filled''' | 66 | # to keep the queue filled |
159 | 67 | |||
160 | 89 | DESIRED_QUEUE_LENGTH = 0 | 68 | DESIRED_QUEUE_LENGTH = 0 |
161 | 90 | 69 | ||
166 | 91 | '''Make sure we have a little margin before changing the song so the queue | 70 | # Make sure we have a little margin before changing the song so the |
167 | 92 | won't run empty, keeping this at 15 seconds should be safe | 71 | # queue won't run empty, keeping this at 15 seconds should be safe Do |
168 | 93 | Do note that when DESIRED_QUEUE_LENGTH = 0 than this would probably work | 72 | # note that when DESIRED_QUEUE_LENGTH = 0 than this would probably |
169 | 94 | better with a value of 0''' | 73 | # work better with a value of 0 |
170 | 74 | |||
171 | 95 | QUEUE_MARGIN = 0 | 75 | QUEUE_MARGIN = 0 |
172 | 96 | 76 | ||
175 | 97 | '''When MPD is not running, should we launch? | 77 | # When MPD is not running, should we launch? And if MPD exits, should |
176 | 98 | And if MPD exits, should we exit?''' | 78 | # we exit? |
177 | 79 | |||
178 | 99 | EXIT_WITH_MPD = False | 80 | EXIT_WITH_MPD = False |
179 | 100 | 81 | ||
180 | 82 | |||
181 | 101 | class Song(autoqueue.SongBase): | 83 | class Song(autoqueue.SongBase): |
184 | 102 | '''A MPD song object''' | 84 | """An MPD song object.""" |
185 | 103 | def __init__(self, file=None, time=0, **kwargs): | 85 | |
186 | 86 | # pylint: disable=W0231 | ||
187 | 87 | def __init__(self, song_file=None, song_length=0, **kwargs): | ||
188 | 104 | self.title = self.artist = self.album = '' | 88 | self.title = self.artist = self.album = '' |
191 | 105 | self._file = file | 89 | self.file = song_file |
192 | 106 | self.time = time | 90 | self.time = song_length |
193 | 107 | self.__dict__.update(**kwargs) | 91 | self.__dict__.update(**kwargs) |
194 | 92 | # pylint: enable=W0231 | ||
195 | 108 | 93 | ||
196 | 109 | def get_artist(self): | 94 | def get_artist(self): |
198 | 110 | '''return lowercase UNICODE name of artist''' | 95 | """Return lowercase UNICODE name of artist.""" |
199 | 111 | return unicode(self.artist.lower(), 'utf-8') | 96 | return unicode(self.artist.lower(), 'utf-8') |
200 | 112 | 97 | ||
201 | 98 | def get_artists(self): | ||
202 | 99 | """Get list of artists and performers for the song.""" | ||
203 | 100 | return [self.get_artist()] | ||
204 | 101 | |||
205 | 113 | def get_title(self): | 102 | def get_title(self): |
207 | 114 | '''return lowercase UNICODE title of song''' | 103 | """Return lowercase UNICODE title of song.""" |
208 | 115 | return unicode(self.title.lower(), 'utf-8') | 104 | return unicode(self.title.lower(), 'utf-8') |
209 | 116 | 105 | ||
210 | 117 | def get_album(self): | ||
211 | 118 | '''return lowercase UNICODE album of song''' | ||
212 | 119 | return unicode(self.album.lower(), 'utf-8') | ||
213 | 120 | |||
214 | 121 | def get_tags(self): | 106 | def get_tags(self): |
216 | 122 | '''return a list of tags for the songs''' | 107 | """Return a list of tags for the song.""" |
217 | 123 | return [] | 108 | return [] |
218 | 124 | 109 | ||
219 | 125 | def get_filename(self): | 110 | def get_filename(self): |
220 | 111 | """Return filename for the song.""" | ||
221 | 126 | return '/var/lib/mpd/music/' + self.file | 112 | return '/var/lib/mpd/music/' + self.file |
222 | 127 | 113 | ||
227 | 128 | @property | 114 | def get_last_started(self): |
228 | 129 | def file(self): | 115 | """Return the datetime the song was last played.""" |
229 | 130 | '''file is an immutable attribute that's used for the hash method''' | 116 | return 0 |
230 | 131 | return self._file | 117 | |
231 | 118 | def get_rating(self): | ||
232 | 119 | """Return the rating of the song.""" | ||
233 | 120 | return 0 | ||
234 | 121 | |||
235 | 122 | def get_playcount(self): | ||
236 | 123 | """Return the playcount of the song.""" | ||
237 | 124 | return 0 | ||
238 | 132 | 125 | ||
239 | 133 | def __repr__(self): | 126 | def __repr__(self): |
241 | 134 | return '<%s: %s - %s - %s (%s)>' % ( | 127 | return '<%s: %s - %s - %s>' % ( |
242 | 135 | self.__class__.__name__, | 128 | self.__class__.__name__, |
243 | 136 | self.album, | 129 | self.album, |
244 | 137 | self.artist, | 130 | self.artist, |
245 | 138 | self.title, | 131 | self.title, |
246 | 139 | self.duration, | ||
247 | 140 | ) | 132 | ) |
248 | 141 | 133 | ||
249 | 142 | def get_time(self): | ||
250 | 143 | return self._time | ||
251 | 144 | |||
252 | 145 | def set_time(self, value): | ||
253 | 146 | self._time = int(value) | ||
254 | 147 | |||
255 | 148 | time = property(get_time, set_time, doc='song duration in seconds') | ||
256 | 149 | |||
257 | 150 | @property | ||
258 | 151 | def duration(self): | ||
259 | 152 | t = time.gmtime(int(self.time)) | ||
260 | 153 | if t.tm_hour: | ||
261 | 154 | fmt = '%H:%M:%S' | ||
262 | 155 | else: | ||
263 | 156 | fmt = '%M:%S' | ||
264 | 157 | return time.strftime(fmt, t) | ||
265 | 158 | |||
266 | 159 | def __int__(self): | 134 | def __int__(self): |
267 | 160 | return self.time | 135 | return self.time |
268 | 161 | 136 | ||
269 | @@ -177,9 +152,10 @@ | |||
270 | 177 | def __ne__(self, other): | 152 | def __ne__(self, other): |
271 | 178 | return hash(self) != hash(other) | 153 | return hash(self) != hash(other) |
272 | 179 | 154 | ||
273 | 155 | |||
274 | 180 | class Search(object): | 156 | class Search(object): |
277 | 181 | ''' | 157 | """ |
278 | 182 | Search object which keeps track of all search parameters | 158 | Search object which keeps track of all search parameters. |
279 | 183 | 159 | ||
280 | 184 | ALLOWED_FIELDS - list of allowed search fields | 160 | ALLOWED_FIELDS - list of allowed search fields |
281 | 185 | 161 | ||
282 | @@ -192,13 +168,13 @@ | |||
283 | 192 | >>> search.add_parameters(any='test3', album='test4') | 168 | >>> search.add_parameters(any='test3', album='test4') |
284 | 193 | >>> search.get_parameters() | 169 | >>> search.get_parameters() |
285 | 194 | ['album', 'test4', 'title', 'test2', 'any', 'test3', 'artist', 'test'] | 170 | ['album', 'test4', 'title', 'test2', 'any', 'test3', 'artist', 'test'] |
287 | 195 | ''' | 171 | """ |
288 | 196 | ALLOWED_FIELDS = ('artist', 'album', 'title', 'track', 'name', 'genre', | 172 | ALLOWED_FIELDS = ('artist', 'album', 'title', 'track', 'name', 'genre', |
289 | 197 | 'date', 'composer', 'performer', 'comment', 'disc', 'filename', 'any') | 173 | 'date', 'composer', 'performer', 'comment', 'disc', 'filename', 'any') |
290 | 198 | 174 | ||
291 | 199 | def __init__(self, field=None, value=None, **parameters): | 175 | def __init__(self, field=None, value=None, **parameters): |
294 | 200 | ''' | 176 | """ |
295 | 201 | Create a search object | 177 | Create a search object. |
296 | 202 | 178 | ||
297 | 203 | >>> search = Search(artist='test') | 179 | >>> search = Search(artist='test') |
298 | 204 | >>> search.parameters | 180 | >>> search.parameters |
299 | @@ -211,15 +187,15 @@ | |||
300 | 211 | >>> search = Search('artist', 'test') | 187 | >>> search = Search('artist', 'test') |
301 | 212 | >>> search.parameters | 188 | >>> search.parameters |
302 | 213 | {'artist': set(['test'])} | 189 | {'artist': set(['test'])} |
304 | 214 | ''' | 190 | """ |
305 | 215 | self.parameters = {} | 191 | self.parameters = {} |
306 | 216 | if field and value: | 192 | if field and value: |
307 | 217 | self.add_parameter(field, value) | 193 | self.add_parameter(field, value) |
308 | 218 | self.add_parameters(**parameters) | 194 | self.add_parameters(**parameters) |
309 | 219 | 195 | ||
310 | 220 | def add_parameters(self, **parameters): | 196 | def add_parameters(self, **parameters): |
313 | 221 | ''' | 197 | """ |
314 | 222 | Add one or more parameters to the search query | 198 | Add one or more parameters to the search query. |
315 | 223 | 199 | ||
316 | 224 | Use with named arguments, the key must be in ALLOWED_FIELDS | 200 | Use with named arguments, the key must be in ALLOWED_FIELDS |
317 | 225 | 201 | ||
318 | @@ -227,12 +203,12 @@ | |||
319 | 227 | >>> search.add_parameters(artist='test1', title='test2') | 203 | >>> search.add_parameters(artist='test1', title='test2') |
320 | 228 | >>> search.parameters | 204 | >>> search.parameters |
321 | 229 | {'artist': set(['test1']), 'title': set(['test2'])} | 205 | {'artist': set(['test1']), 'title': set(['test2'])} |
323 | 230 | ''' | 206 | """ |
324 | 231 | [self.add_parameter(k, v) for k, v in parameters.iteritems()] | 207 | [self.add_parameter(k, v) for k, v in parameters.iteritems()] |
325 | 232 | 208 | ||
326 | 233 | def add_parameter(self, field, value): | 209 | def add_parameter(self, field, value): |
329 | 234 | ''' | 210 | """ |
330 | 235 | Add a parameter to the search query | 211 | Add a parameter to the search query. |
331 | 236 | 212 | ||
332 | 237 | field - must be in ALLOWED_FIELDS | 213 | field - must be in ALLOWED_FIELDS |
333 | 238 | value - a literal string to be searched for | 214 | value - a literal string to be searched for |
334 | @@ -247,7 +223,7 @@ | |||
335 | 247 | Traceback (most recent call last): | 223 | Traceback (most recent call last): |
336 | 248 | ... | 224 | ... |
337 | 249 | TypeError: "spam" is not a valid field, please choose on from ALLOWED_FIELDS | 225 | TypeError: "spam" is not a valid field, please choose on from ALLOWED_FIELDS |
339 | 250 | ''' | 226 | """ |
340 | 251 | if field in self.ALLOWED_FIELDS: | 227 | if field in self.ALLOWED_FIELDS: |
341 | 252 | self.parameters.setdefault(field, set()).add(value.lower().strip()) | 228 | self.parameters.setdefault(field, set()).add(value.lower().strip()) |
342 | 253 | else: | 229 | else: |
343 | @@ -255,8 +231,8 @@ | |||
344 | 255 | 'on from ALLOWED_FIELDS' % field | 231 | 'on from ALLOWED_FIELDS' % field |
345 | 256 | 232 | ||
346 | 257 | def get_parameters(self): | 233 | def get_parameters(self): |
349 | 258 | ''' | 234 | """ |
350 | 259 | Return a list of parameters for the MPDClient.search method | 235 | Return a list of parameters for the MPDClient.search method. |
351 | 260 | 236 | ||
352 | 261 | >>> search = Search(artist='test1', title='test2') | 237 | >>> search = Search(artist='test1', title='test2') |
353 | 262 | >>> search.get_parameters() | 238 | >>> search.get_parameters() |
354 | @@ -268,7 +244,7 @@ | |||
355 | 268 | Traceback (most recent call last): | 244 | Traceback (most recent call last): |
356 | 269 | ... | 245 | ... |
357 | 270 | ValueError: Empty search queries are not allowed | 246 | ValueError: Empty search queries are not allowed |
359 | 271 | ''' | 247 | """ |
360 | 272 | ret = [] | 248 | ret = [] |
361 | 273 | for k, vs in self.parameters.iteritems(): | 249 | for k, vs in self.parameters.iteritems(): |
362 | 274 | ret += [[k, v.encode('utf-8')] for v in vs] | 250 | ret += [[k, v.encode('utf-8')] for v in vs] |
363 | @@ -277,47 +253,56 @@ | |||
364 | 277 | raise ValueError, 'Empty search queries are not allowed' | 253 | raise ValueError, 'Empty search queries are not allowed' |
365 | 278 | return sum(ret, []) | 254 | return sum(ret, []) |
366 | 279 | 255 | ||
367 | 256 | |||
368 | 280 | class Daemon(object): | 257 | class Daemon(object): |
371 | 281 | '''Class to easily create a daemon which transparently handles the saving | 258 | """ |
372 | 282 | and removing of the PID file''' | 259 | Class to easily create a daemon which transparently handles the |
373 | 260 | saving and removing of the PID file. | ||
374 | 261 | |||
375 | 262 | """ | ||
376 | 283 | 263 | ||
377 | 284 | def __init__(self, pid_file): | 264 | def __init__(self, pid_file): |
379 | 285 | '''Create a new Daemon | 265 | """Create a new Daemon. |
380 | 286 | 266 | ||
381 | 287 | pid_file -- The file to save the PID in | 267 | pid_file -- The file to save the PID in |
383 | 288 | ''' | 268 | """ |
384 | 269 | self._pid_file = None | ||
385 | 289 | self.pid_file = pid_file | 270 | self.pid_file = pid_file |
386 | 290 | signal.signal(signal.SIGTERM, lambda *args: self.exit()) | 271 | signal.signal(signal.SIGTERM, lambda *args: self.exit()) |
387 | 291 | signal.signal(signal.SIGINT, lambda *args: self.exit()) | 272 | signal.signal(signal.SIGINT, lambda *args: self.exit()) |
388 | 292 | signal.signal(signal.SIGQUIT, lambda *args: self.exit()) | 273 | signal.signal(signal.SIGQUIT, lambda *args: self.exit()) |
389 | 293 | 274 | ||
390 | 294 | def set_pid_file(self, pid_file): | 275 | def set_pid_file(self, pid_file): |
391 | 276 | """Set pid file.""" | ||
392 | 295 | self._pid_file = pid_file | 277 | self._pid_file = pid_file |
393 | 296 | open(pid_file, 'w').write('%d' % os.getpid()) | 278 | open(pid_file, 'w').write('%d' % os.getpid()) |
394 | 297 | 279 | ||
395 | 298 | def get_pid_file(self): | 280 | def get_pid_file(self): |
396 | 281 | """Get pid file.""" | ||
397 | 299 | return self._pid_file | 282 | return self._pid_file |
398 | 300 | 283 | ||
399 | 301 | def del_pid_file(self): | 284 | def del_pid_file(self): |
400 | 285 | """Delete pid file.""" | ||
401 | 302 | try: | 286 | try: |
402 | 303 | os.unlink(self._pid_file) | 287 | os.unlink(self._pid_file) |
404 | 304 | print >>sys.stderr, 'Removed PID file' | 288 | print >> sys.stderr, 'Removed PID file' |
405 | 305 | except OSError: | 289 | except OSError: |
407 | 306 | print >>sys.stderr, 'Trying to remove non-existing PID file' | 290 | print >> sys.stderr, 'Trying to remove non-existing PID file' |
408 | 307 | 291 | ||
409 | 308 | pid_file = property(get_pid_file, set_pid_file, del_pid_file, | 292 | pid_file = property(get_pid_file, set_pid_file, del_pid_file, |
410 | 309 | 'The PID file will be written when set and deleted when unset') | 293 | 'The PID file will be written when set and deleted when unset') |
411 | 310 | 294 | ||
412 | 311 | def exit(self): | 295 | def exit(self): |
414 | 312 | '''Kill the daemon and remove the PID file | 296 | """Kill the daemon and remove the PID file |
415 | 313 | 297 | ||
416 | 314 | This method will be called automatically when the process is | 298 | This method will be called automatically when the process is |
418 | 315 | terminated''' | 299 | terminated""" |
419 | 316 | del self.pid_file | 300 | del self.pid_file |
420 | 317 | raise SystemExit | 301 | raise SystemExit |
421 | 318 | 302 | ||
422 | 319 | @classmethod | 303 | @classmethod |
423 | 320 | def is_running(cls, pid): | 304 | def is_running(cls, pid): |
424 | 305 | """Check whether we're already running.'""" | ||
425 | 321 | if not pid: | 306 | if not pid: |
426 | 322 | return False | 307 | return False |
427 | 323 | try: | 308 | try: |
428 | @@ -328,13 +313,14 @@ | |||
429 | 328 | 313 | ||
430 | 329 | @classmethod | 314 | @classmethod |
431 | 330 | def kill(cls, pid, pid_file=None): | 315 | def kill(cls, pid, pid_file=None): |
432 | 316 | """Kill process.""" | ||
433 | 331 | try: | 317 | try: |
434 | 332 | pid = int(pid) | 318 | pid = int(pid) |
435 | 333 | except (TypeError, ValueError): | 319 | except (TypeError, ValueError): |
436 | 334 | pid = None | 320 | pid = None |
437 | 335 | 321 | ||
438 | 336 | if cls.is_running(pid): | 322 | if cls.is_running(pid): |
440 | 337 | print >>sys.stderr, 'Sending TERM signal to process %d' % pid | 323 | print >> sys.stderr, 'Sending TERM signal to process %d' % pid |
441 | 338 | os.kill(pid, signal.SIGTERM) | 324 | os.kill(pid, signal.SIGTERM) |
442 | 339 | i = KILL_TIMEOUT | 325 | i = KILL_TIMEOUT |
443 | 340 | while i > 0 and cls.is_running(pid): | 326 | while i > 0 and cls.is_running(pid): |
444 | @@ -342,23 +328,26 @@ | |||
445 | 342 | time.sleep(KILL_CHECK_DELAY) | 328 | time.sleep(KILL_CHECK_DELAY) |
446 | 343 | 329 | ||
447 | 344 | if cls.is_running(pid): | 330 | if cls.is_running(pid): |
449 | 345 | print >>sys.stderr, 'Sending KILL signal to process %d' % pid | 331 | print >> sys.stderr, 'Sending KILL signal to process %d' % pid |
450 | 346 | os.kill(pid, signal.SIGKILL) | 332 | os.kill(pid, signal.SIGKILL) |
451 | 347 | 333 | ||
452 | 348 | time.sleep(1) | 334 | time.sleep(1) |
453 | 349 | if cls.is_running(pid): | 335 | if cls.is_running(pid): |
455 | 350 | print >>sys.stderr, 'Unable to kill process %d, still running' | 336 | print >> sys.stderr, 'Unable to kill process %d, still running' |
456 | 351 | else: | 337 | else: |
457 | 352 | if isinstance(pid, int): | 338 | if isinstance(pid, int): |
459 | 353 | print >>sys.stderr, 'Process %d not running' % pid | 339 | print >> sys.stderr, 'Process %d not running' % pid |
460 | 354 | if pid_file: | 340 | if pid_file: |
462 | 355 | print >>sys.stderr, 'Removing stale PID file' | 341 | print >> sys.stderr, 'Removing stale PID file' |
463 | 356 | os.unlink(pid_file) | 342 | os.unlink(pid_file) |
464 | 357 | 343 | ||
465 | 358 | @classmethod | 344 | @classmethod |
466 | 359 | def daemonize(cls): | 345 | def daemonize(cls): |
469 | 360 | '''Daemonize using the double fork method so the process keeps running | 346 | """ |
470 | 361 | Even after the original shell exits''' | 347 | Daemonize using the double fork method so the process keeps |
471 | 348 | running Even after the original shell exits. | ||
472 | 349 | |||
473 | 350 | """ | ||
474 | 362 | stdin_file = expand_file(STDIN) | 351 | stdin_file = expand_file(STDIN) |
475 | 363 | stdout_file = expand_file(STDOUT) | 352 | stdout_file = expand_file(STDOUT) |
476 | 364 | stderr_file = expand_file(STDERR) | 353 | stderr_file = expand_file(STDERR) |
477 | @@ -367,7 +356,7 @@ | |||
478 | 367 | if pid > 0: | 356 | if pid > 0: |
479 | 368 | sys.exit(0) | 357 | sys.exit(0) |
480 | 369 | except OSError, e: | 358 | except OSError, e: |
482 | 370 | print >>sys.stderr, 'Unable to fork: %s' % e | 359 | print >> sys.stderr, 'Unable to fork: %s' % e |
483 | 371 | sys.exit(1) | 360 | sys.exit(1) |
484 | 372 | 361 | ||
485 | 373 | os.chdir('/') | 362 | os.chdir('/') |
486 | @@ -379,10 +368,10 @@ | |||
487 | 379 | if pid > 0: | 368 | if pid > 0: |
488 | 380 | sys.exit(0) | 369 | sys.exit(0) |
489 | 381 | except OSError, e: | 370 | except OSError, e: |
491 | 382 | print >>sys.stderr, 'Unable to fork: %s' % e | 371 | print >> sys.stderr, 'Unable to fork: %s' % e |
492 | 383 | sys.exit(1) | 372 | sys.exit(1) |
493 | 384 | 373 | ||
495 | 385 | '''Redirect stdout, stderr and stdin''' | 374 | # Redirect stdout, stderr and stdin |
496 | 386 | stdin = file(stdin_file, 'r') | 375 | stdin = file(stdin_file, 'r') |
497 | 387 | stdout = file(stdout_file, 'a+') | 376 | stdout = file(stdout_file, 'a+') |
498 | 388 | stderr = file(stderr_file, 'a+', 0) | 377 | stderr = file(stderr_file, 'a+', 0) |
499 | @@ -390,20 +379,23 @@ | |||
500 | 390 | os.dup2(stdout.fileno(), sys.stdout.fileno()) | 379 | os.dup2(stdout.fileno(), sys.stdout.fileno()) |
501 | 391 | os.dup2(stderr.fileno(), sys.stderr.fileno()) | 380 | os.dup2(stderr.fileno(), sys.stderr.fileno()) |
502 | 392 | 381 | ||
503 | 382 | |||
504 | 393 | class AutoQueuePlugin(autoqueue.AutoQueueBase, Daemon): | 383 | class AutoQueuePlugin(autoqueue.AutoQueueBase, Daemon): |
505 | 384 | """Mpd implementation of autoqueue.""" | ||
506 | 385 | |||
507 | 394 | def __init__(self, host, port, pid_file): | 386 | def __init__(self, host, port, pid_file): |
508 | 395 | self.host, self.port = host, port | 387 | self.host, self.port = host, port |
509 | 396 | self.client = mpd.MPDClient() | 388 | self.client = mpd.MPDClient() |
510 | 397 | self.connect() | 389 | self.connect() |
511 | 398 | autoqueue.AutoQueueBase.__init__(self) | 390 | autoqueue.AutoQueueBase.__init__(self) |
512 | 399 | self.random = True | ||
513 | 400 | self.verbose = True | 391 | self.verbose = True |
514 | 401 | self.by_mirage = True | ||
515 | 402 | self.desired_queue_length = DESIRED_QUEUE_LENGTH | 392 | self.desired_queue_length = DESIRED_QUEUE_LENGTH |
516 | 403 | Daemon.__init__(self, pid_file) | 393 | Daemon.__init__(self, pid_file) |
517 | 404 | self._current_song = None | 394 | self._current_song = None |
518 | 395 | self._host = None | ||
519 | 405 | 396 | ||
520 | 406 | def run(self): | 397 | def run(self): |
521 | 398 | """Run.""" | ||
522 | 407 | running = True | 399 | running = True |
523 | 408 | while running or not EXIT_WITH_MPD: | 400 | while running or not EXIT_WITH_MPD: |
524 | 409 | interval = REFRESH_INTERVAL | 401 | interval = REFRESH_INTERVAL |
525 | @@ -416,8 +408,7 @@ | |||
526 | 416 | 408 | ||
527 | 417 | interval = min( | 409 | interval = min( |
528 | 418 | REFRESH_INTERVAL, | 410 | REFRESH_INTERVAL, |
531 | 419 | int(self.player_get_queue_length()) - QUEUE_MARGIN | 411 | int(self.player_get_queue_length()) - QUEUE_MARGIN) |
530 | 420 | ) | ||
532 | 421 | running = True | 412 | running = True |
533 | 422 | except mpd.ConnectionError: | 413 | except mpd.ConnectionError: |
534 | 423 | print "disconnecting" | 414 | print "disconnecting" |
535 | @@ -432,6 +423,7 @@ | |||
536 | 432 | self.exit() | 423 | self.exit() |
537 | 433 | 424 | ||
538 | 434 | def connect(self): | 425 | def connect(self): |
539 | 426 | """Connect.""" | ||
540 | 435 | try: | 427 | try: |
541 | 436 | self.client.connect(self.host, self.port) | 428 | self.client.connect(self.host, self.port) |
542 | 437 | return True | 429 | return True |
543 | @@ -439,6 +431,7 @@ | |||
544 | 439 | return False | 431 | return False |
545 | 440 | 432 | ||
546 | 441 | def disconnect(self): | 433 | def disconnect(self): |
547 | 434 | """Disconnect.""" | ||
548 | 442 | try: | 435 | try: |
549 | 443 | self.client.disconnect() | 436 | self.client.disconnect() |
550 | 444 | return True | 437 | return True |
551 | @@ -446,56 +439,61 @@ | |||
552 | 446 | return False | 439 | return False |
553 | 447 | 440 | ||
554 | 448 | def get_host(self): | 441 | def get_host(self): |
555 | 442 | """Get host.""" | ||
556 | 449 | return self._host | 443 | return self._host |
557 | 450 | 444 | ||
558 | 451 | def set_host(self, host): | 445 | def set_host(self, host): |
559 | 446 | """Set host.""" | ||
560 | 452 | self._host = socket.gethostbyname(host) | 447 | self._host = socket.gethostbyname(host) |
561 | 453 | 448 | ||
563 | 454 | host = property(get_host, set_host, doc='MPD ip (can be set by ip and hostname)') | 449 | host = property( |
564 | 450 | get_host, set_host, doc='MPD ip (can be set by ip and hostname)') | ||
565 | 455 | 451 | ||
569 | 456 | def player_construct_track_search(self, artist, title, restrictions): | 452 | def player_construct_track_search(self, artist, title, restrictions=None): |
570 | 457 | '''construct a search that looks for songs with this artist | 453 | """Construct a search that looks for songs with this artist |
571 | 458 | and title''' | 454 | and title.""" |
572 | 459 | return Search(artist=artist, title=title) | 455 | return Search(artist=artist, title=title) |
573 | 460 | 456 | ||
576 | 461 | def player_construct_tag_search(self, tags, exclude_artists, restrictions): | 457 | def player_construct_tag_search(self, tags, exclude_artists, |
577 | 462 | '''construct a search that looks for songs with these tags''' | 458 | restrictions=None): |
578 | 459 | """Construct a search that looks for songs with these tags.""" | ||
579 | 463 | return None | 460 | return None |
580 | 464 | 461 | ||
583 | 465 | def player_construct_artist_search(self, artist, restrictions): | 462 | def player_construct_artist_search(self, artist, restrictions=None): |
584 | 466 | '''construct a search that looks for songs with this artist''' | 463 | """Construct a search that looks for songs with this artist.""" |
585 | 467 | return Search(artist=artist) | 464 | return Search(artist=artist) |
586 | 468 | 465 | ||
591 | 469 | def player_construct_restrictions( | 466 | def player_set_variables_from_config(self): |
592 | 470 | self, track_block_time, relaxors, restrictors): | 467 | """Initialize user settings from the configuration storage.""" |
593 | 471 | '''construct a search to further modify the searches''' | 468 | pass |
590 | 472 | return None | ||
594 | 473 | 469 | ||
595 | 474 | def player_search(self, search): | 470 | def player_search(self, search): |
597 | 475 | '''perform a player search''' | 471 | """perform a player search""" |
598 | 476 | 472 | ||
599 | 477 | results = self.client.search(*search.get_parameters()) | 473 | results = self.client.search(*search.get_parameters()) |
600 | 478 | 474 | ||
613 | 479 | '''Make all search results lowercase and strip whitespace''' | 475 | # Make all search results lowercase and strip whitespace |
614 | 480 | for r in results: | 476 | for result in results: |
615 | 481 | for k, v in r.items(): | 477 | for key, value in result.items(): |
616 | 482 | if isinstance(v, basestring): | 478 | if isinstance(value, basestring): |
617 | 483 | r['%s_search' % k] = unicode(v, 'utf-8').strip().lower() | 479 | result['%s_search' % key] = unicode( |
618 | 484 | 480 | value, 'utf-8').strip().lower() | |
619 | 485 | '''Filter all non-exact matches''' | 481 | |
620 | 486 | for k, vs in search.parameters.iteritems(): | 482 | # Filter all non-exact matches |
621 | 487 | for v in vs: | 483 | for key, values in search.parameters.iteritems(): |
622 | 488 | results = [r for r in results if r.get('%s_search' % k) == v] | 484 | for value in values: |
623 | 489 | 485 | results = [ | |
624 | 490 | '''Convert all rows to song objects''' | 486 | r for r in results if r.get('%s_search' % key) == value] |
625 | 487 | |||
626 | 488 | # Convert all rows to song objects | ||
627 | 491 | return [Song(**x) for x in results] | 489 | return [Song(**x) for x in results] |
628 | 492 | 490 | ||
629 | 493 | def player_enqueue(self, song): | 491 | def player_enqueue(self, song): |
631 | 494 | '''Put the song at the end of the queue''' | 492 | """Put the song at the end of the queue""" |
632 | 495 | self.client.add(song.file) | 493 | self.client.add(song.file) |
633 | 496 | 494 | ||
634 | 497 | def player_get_userdir(self): | 495 | def player_get_userdir(self): |
636 | 498 | '''get the application user directory to store files''' | 496 | """get the application user directory to store files""" |
637 | 499 | return expand_path(SETTINGS_PATH) | 497 | return expand_path(SETTINGS_PATH) |
638 | 500 | 498 | ||
639 | 501 | def player_current_song(self): | 499 | def player_current_song(self): |
640 | @@ -511,7 +509,7 @@ | |||
641 | 511 | return (Song(**x) for x in self.client.playlistid()) | 509 | return (Song(**x) for x in self.client.playlistid()) |
642 | 512 | 510 | ||
643 | 513 | def player_get_songs_in_queue(self): | 511 | def player_get_songs_in_queue(self): |
645 | 514 | '''return (wrapped) song objects for the songs in the queue''' | 512 | """return (wrapped) song objects for the songs in the queue""" |
646 | 515 | id = self.player_current_song_id() | 513 | id = self.player_current_song_id() |
647 | 516 | return [s for i, s in enumerate(self.player_playlist()) if i >= id] | 514 | return [s for i, s in enumerate(self.player_playlist()) if i >= id] |
648 | 517 | 515 |