Merge lp:~thisfred/autoqueue/lp-777354 into lp:autoqueue

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

Subscribers

People subscribed via source and target branches

to all changes: