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
=== modified file 'autoqueue/__init__.py'
--- autoqueue/__init__.py 2011-05-06 15:30:42 +0000
+++ autoqueue/__init__.py 2011-05-06 17:48:32 +0000
@@ -75,10 +75,6 @@
75 """Return filename for the song."""75 """Return filename for the song."""
7676
77 @abstractmethod77 @abstractmethod
78 def get_length(self):
79 """Return length in seconds."""
80
81 @abstractmethod
82 def get_last_started(self):78 def get_last_started(self):
83 """Return the datetime the song was last played."""79 """Return the datetime the song was last played."""
8480
@@ -162,7 +158,7 @@
162 self.running = False158 self.running = False
163 self.log('Error handler received: %r, %r' % (args, kwargs))159 self.log('Error handler received: %r, %r' % (args, kwargs))
164160
165 def player_get_cache_dir(self):161 def get_cache_dir(self):
166 """Get the directory to store temporary data.162 """Get the directory to store temporary data.
167163
168 Defaults to $XDG_CACHE_HOME/autoqueue on Gnome.164 Defaults to $XDG_CACHE_HOME/autoqueue on Gnome.
@@ -180,7 +176,7 @@
180 def get_blocked_artists_pickle(self):176 def get_blocked_artists_pickle(self):
181 """Read the list of blocked artists from disk."""177 """Read the list of blocked artists from disk."""
182 dump = os.path.join(178 dump = os.path.join(
183 self.player_get_cache_dir(), "autoqueue_block_cache")179 self.get_cache_dir(), "autoqueue_block_cache")
184 try:180 try:
185 pickle = open(dump, 'r')181 pickle = open(dump, 'r')
186 try:182 try:
@@ -206,7 +202,7 @@
206 artist_name,202 artist_name,
207 len(self._blocked_artists)))203 len(self._blocked_artists)))
208 dump = os.path.join(204 dump = os.path.join(
209 self.player_get_cache_dir(), "autoqueue_block_cache")205 self.get_cache_dir(), "autoqueue_block_cache")
210 try:206 try:
211 os.remove(dump)207 os.remove(dump)
212 except OSError:208 except OSError:
213209
=== modified file 'mpd_autoqueue.py'
--- mpd_autoqueue.py 2010-02-28 02:04:38 +0000
+++ mpd_autoqueue.py 2011-05-06 17:48:32 +0000
@@ -1,161 +1,136 @@
1#!/usr/bin/env python1#!/usr/bin/env python
2'''2"""mpd implementation of autoqueue.
3Copyright (c) 2008, Rick van Hattem3
4All rights reserved.4Copyright (c) 2008-2011 Rick van Hattem, Eric Casteleijn
55"""
6Redistribution and use in source and binary forms, with or without
7modification, are permitted provided that the following conditions are
8met:
9
10 * Redistributions of source code must retain the above copyright
11 notice, this list of conditions and the following disclaimer.
12
13 * Redistributions in binary form must reproduce the above
14 copyright notice, this list of conditions and the following
15 disclaimer in the documentation and/or other materials provided
16 with the distribution.
17
18 * The names of the contributors may not be used to endorse or
19 promote products derived from this software without specific
20 prior written permission.
21
22THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33'''
346
35import os7import os
36import sys8import sys
37import mpd9import mpd
38import time10import time
39import errno11import errno
40import atexit
41import socket12import socket
42import signal13import signal
43import optparse14import optparse
44import autoqueue15import autoqueue
4516
17
46def expand_path(path):18def expand_path(path):
47 path = os.path.expandvars(os.path.expanduser(path))19 path = os.path.expandvars(os.path.expanduser(path))
48 if not os.path.isdir(path):20 if not os.path.isdir(path):
49 os.mkdir(path)21 os.mkdir(path)
50 return path22 return path
5123
24
52def expand_file(path):25def expand_file(path):
53 path, file_ = os.path.split(path)26 path, file_ = os.path.split(path)
54 return os.path.join(expand_path(path), file_)27 return os.path.join(expand_path(path), file_)
5528
56'''The settings path automatically expands ~ to the user home directory29# The settings path automatically expands ~ to the user home directory
57and $VAR to environment variables30# and $VAR to environment variables On most *n?x systems ~/.autoqueue
58On most *n?x systems ~/.autoqueue will be expanded to /home/username/.autoqueue/31# will be expanded to /home/username/.autoqueue/ Setting the path to
59Setting the path to $HOME/.autoqueue/ will usually yield the same result32# $HOME/.autoqueue/ will usually yield the same result
60'''33
61SETTINGS_PATH = '~/.autoqueue/'34SETTINGS_PATH = '~/.autoqueue/'
6235
63'''These settings will be overwritten by the MPD_HOST/MPD_PORT environment36# These settings will be overwritten by the MPD_HOST/MPD_PORT
64variables and/or by the commandline arguments'''37# environment variables and/or by the commandline arguments
38
65MPD_PORT = 660039MPD_PORT = 6600
66MPD_HOST = 'localhost'40MPD_HOST = 'localhost'
6741
68# The PID file to see if there are no other instances running42# The PID file to see if there are no other instances running
43
69PID_FILE = os.path.join(expand_path(SETTINGS_PATH), 'mpd.pid')44PID_FILE = os.path.join(expand_path(SETTINGS_PATH), 'mpd.pid')
7045
71# Send a KILL signal if the process didn't end KILL_TIMEOUT seconds46# Send a KILL signal if the process didn't end KILL_TIMEOUT seconds
72# after the TERM signal, check every KILL_CHECK_DELAY seconds if it's47# after the TERM signal, check every KILL_CHECK_DELAY seconds if it's
73# still alive after sending the TERM signal48# still alive after sending the TERM signal
49
74KILL_TIMEOUT = 1050KILL_TIMEOUT = 10
75KILL_CHECK_DELAY = 0.151KILL_CHECK_DELAY = 0.1
7652
77'''The targets for stdin, stdout and stderr when daemonizing'''53# The targets for stdin, stdout and stderr when daemonizing
54
78STDIN = '/dev/null'55STDIN = '/dev/null'
79STDOUT = SETTINGS_PATH + 'stdout'56STDOUT = SETTINGS_PATH + 'stdout'
80STDERR = SETTINGS_PATH + 'stderr'57STDERR = SETTINGS_PATH + 'stderr'
8158
82'''The interval to refresh the MPD status, the faster this is set, the faster59# The interval to refresh the MPD status, the faster this is set, the
83the 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
61
84REFRESH_INTERVAL = 1062REFRESH_INTERVAL = 10
8563
86'''The desired length for the queue, set this to 0 to add a song for every64# The desired length for the queue, set this to 0 to add a song for
87finished song or to any other number for the number of seconds to keep the65# every finished song or to any other number for the number of seconds
88queue filled'''66# to keep the queue filled
67
89DESIRED_QUEUE_LENGTH = 068DESIRED_QUEUE_LENGTH = 0
9069
91'''Make sure we have a little margin before changing the song so the queue70# Make sure we have a little margin before changing the song so the
92won't run empty, keeping this at 15 seconds should be safe71# queue won't run empty, keeping this at 15 seconds should be safe Do
93Do note that when DESIRED_QUEUE_LENGTH = 0 than this would probably work72# note that when DESIRED_QUEUE_LENGTH = 0 than this would probably
94better with a value of 0'''73# work better with a value of 0
74
95QUEUE_MARGIN = 075QUEUE_MARGIN = 0
9676
97'''When MPD is not running, should we launch?77# When MPD is not running, should we launch? And if MPD exits, should
98And if MPD exits, should we exit?'''78# we exit?
79
99EXIT_WITH_MPD = False80EXIT_WITH_MPD = False
10081
82
101class Song(autoqueue.SongBase):83class Song(autoqueue.SongBase):
102 '''A MPD song object'''84 """An MPD song object."""
103 def __init__(self, file=None, time=0, **kwargs):85
86 # pylint: disable=W0231
87 def __init__(self, song_file=None, song_length=0, **kwargs):
104 self.title = self.artist = self.album = ''88 self.title = self.artist = self.album = ''
105 self._file = file89 self.file = song_file
106 self.time = time90 self.time = song_length
107 self.__dict__.update(**kwargs)91 self.__dict__.update(**kwargs)
92 # pylint: enable=W0231
10893
109 def get_artist(self):94 def get_artist(self):
110 '''return lowercase UNICODE name of artist'''95 """Return lowercase UNICODE name of artist."""
111 return unicode(self.artist.lower(), 'utf-8')96 return unicode(self.artist.lower(), 'utf-8')
11297
98 def get_artists(self):
99 """Get list of artists and performers for the song."""
100 return [self.get_artist()]
101
113 def get_title(self):102 def get_title(self):
114 '''return lowercase UNICODE title of song'''103 """Return lowercase UNICODE title of song."""
115 return unicode(self.title.lower(), 'utf-8')104 return unicode(self.title.lower(), 'utf-8')
116105
117 def get_album(self):
118 '''return lowercase UNICODE album of song'''
119 return unicode(self.album.lower(), 'utf-8')
120
121 def get_tags(self):106 def get_tags(self):
122 '''return a list of tags for the songs'''107 """Return a list of tags for the song."""
123 return []108 return []
124109
125 def get_filename(self):110 def get_filename(self):
111 """Return filename for the song."""
126 return '/var/lib/mpd/music/' + self.file112 return '/var/lib/mpd/music/' + self.file
127113
128 @property114 def get_last_started(self):
129 def file(self):115 """Return the datetime the song was last played."""
130 '''file is an immutable attribute that's used for the hash method'''116 return 0
131 return self._file117
118 def get_rating(self):
119 """Return the rating of the song."""
120 return 0
121
122 def get_playcount(self):
123 """Return the playcount of the song."""
124 return 0
132125
133 def __repr__(self):126 def __repr__(self):
134 return '<%s: %s - %s - %s (%s)>' % (127 return '<%s: %s - %s - %s>' % (
135 self.__class__.__name__,128 self.__class__.__name__,
136 self.album,129 self.album,
137 self.artist,130 self.artist,
138 self.title,131 self.title,
139 self.duration,
140 )132 )
141133
142 def get_time(self):
143 return self._time
144
145 def set_time(self, value):
146 self._time = int(value)
147
148 time = property(get_time, set_time, doc='song duration in seconds')
149
150 @property
151 def duration(self):
152 t = time.gmtime(int(self.time))
153 if t.tm_hour:
154 fmt = '%H:%M:%S'
155 else:
156 fmt = '%M:%S'
157 return time.strftime(fmt, t)
158
159 def __int__(self):134 def __int__(self):
160 return self.time135 return self.time
161136
@@ -177,9 +152,10 @@
177 def __ne__(self, other):152 def __ne__(self, other):
178 return hash(self) != hash(other)153 return hash(self) != hash(other)
179154
155
180class Search(object):156class Search(object):
181 '''157 """
182 Search object which keeps track of all search parameters158 Search object which keeps track of all search parameters.
183159
184 ALLOWED_FIELDS - list of allowed search fields160 ALLOWED_FIELDS - list of allowed search fields
185161
@@ -192,13 +168,13 @@
192 >>> search.add_parameters(any='test3', album='test4')168 >>> search.add_parameters(any='test3', album='test4')
193 >>> search.get_parameters()169 >>> search.get_parameters()
194 ['album', 'test4', 'title', 'test2', 'any', 'test3', 'artist', 'test']170 ['album', 'test4', 'title', 'test2', 'any', 'test3', 'artist', 'test']
195 '''171 """
196 ALLOWED_FIELDS = ('artist', 'album', 'title', 'track', 'name', 'genre',172 ALLOWED_FIELDS = ('artist', 'album', 'title', 'track', 'name', 'genre',
197 'date', 'composer', 'performer', 'comment', 'disc', 'filename', 'any')173 'date', 'composer', 'performer', 'comment', 'disc', 'filename', 'any')
198174
199 def __init__(self, field=None, value=None, **parameters):175 def __init__(self, field=None, value=None, **parameters):
200 '''176 """
201 Create a search object177 Create a search object.
202178
203 >>> search = Search(artist='test')179 >>> search = Search(artist='test')
204 >>> search.parameters180 >>> search.parameters
@@ -211,15 +187,15 @@
211 >>> search = Search('artist', 'test')187 >>> search = Search('artist', 'test')
212 >>> search.parameters188 >>> search.parameters
213 {'artist': set(['test'])}189 {'artist': set(['test'])}
214 '''190 """
215 self.parameters = {}191 self.parameters = {}
216 if field and value:192 if field and value:
217 self.add_parameter(field, value)193 self.add_parameter(field, value)
218 self.add_parameters(**parameters)194 self.add_parameters(**parameters)
219195
220 def add_parameters(self, **parameters):196 def add_parameters(self, **parameters):
221 '''197 """
222 Add one or more parameters to the search query198 Add one or more parameters to the search query.
223199
224 Use with named arguments, the key must be in ALLOWED_FIELDS200 Use with named arguments, the key must be in ALLOWED_FIELDS
225201
@@ -227,12 +203,12 @@
227 >>> search.add_parameters(artist='test1', title='test2')203 >>> search.add_parameters(artist='test1', title='test2')
228 >>> search.parameters204 >>> search.parameters
229 {'artist': set(['test1']), 'title': set(['test2'])}205 {'artist': set(['test1']), 'title': set(['test2'])}
230 '''206 """
231 [self.add_parameter(k, v) for k, v in parameters.iteritems()]207 [self.add_parameter(k, v) for k, v in parameters.iteritems()]
232208
233 def add_parameter(self, field, value):209 def add_parameter(self, field, value):
234 '''210 """
235 Add a parameter to the search query211 Add a parameter to the search query.
236212
237 field - must be in ALLOWED_FIELDS213 field - must be in ALLOWED_FIELDS
238 value - a literal string to be searched for214 value - a literal string to be searched for
@@ -247,7 +223,7 @@
247 Traceback (most recent call last):223 Traceback (most recent call last):
248 ...224 ...
249 TypeError: "spam" is not a valid field, please choose on from ALLOWED_FIELDS225 TypeError: "spam" is not a valid field, please choose on from ALLOWED_FIELDS
250 '''226 """
251 if field in self.ALLOWED_FIELDS:227 if field in self.ALLOWED_FIELDS:
252 self.parameters.setdefault(field, set()).add(value.lower().strip())228 self.parameters.setdefault(field, set()).add(value.lower().strip())
253 else:229 else:
@@ -255,8 +231,8 @@
255 'on from ALLOWED_FIELDS' % field231 'on from ALLOWED_FIELDS' % field
256232
257 def get_parameters(self):233 def get_parameters(self):
258 '''234 """
259 Return a list of parameters for the MPDClient.search method235 Return a list of parameters for the MPDClient.search method.
260236
261 >>> search = Search(artist='test1', title='test2')237 >>> search = Search(artist='test1', title='test2')
262 >>> search.get_parameters()238 >>> search.get_parameters()
@@ -268,7 +244,7 @@
268 Traceback (most recent call last):244 Traceback (most recent call last):
269 ...245 ...
270 ValueError: Empty search queries are not allowed246 ValueError: Empty search queries are not allowed
271 '''247 """
272 ret = []248 ret = []
273 for k, vs in self.parameters.iteritems():249 for k, vs in self.parameters.iteritems():
274 ret += [[k, v.encode('utf-8')] for v in vs]250 ret += [[k, v.encode('utf-8')] for v in vs]
@@ -277,47 +253,56 @@
277 raise ValueError, 'Empty search queries are not allowed'253 raise ValueError, 'Empty search queries are not allowed'
278 return sum(ret, [])254 return sum(ret, [])
279255
256
280class Daemon(object):257class Daemon(object):
281 '''Class to easily create a daemon which transparently handles the saving258 """
282 and removing of the PID file'''259 Class to easily create a daemon which transparently handles the
260 saving and removing of the PID file.
261
262 """
283263
284 def __init__(self, pid_file):264 def __init__(self, pid_file):
285 '''Create a new Daemon265 """Create a new Daemon.
286266
287 pid_file -- The file to save the PID in267 pid_file -- The file to save the PID in
288 '''268 """
269 self._pid_file = None
289 self.pid_file = pid_file270 self.pid_file = pid_file
290 signal.signal(signal.SIGTERM, lambda *args: self.exit())271 signal.signal(signal.SIGTERM, lambda *args: self.exit())
291 signal.signal(signal.SIGINT, lambda *args: self.exit())272 signal.signal(signal.SIGINT, lambda *args: self.exit())
292 signal.signal(signal.SIGQUIT, lambda *args: self.exit())273 signal.signal(signal.SIGQUIT, lambda *args: self.exit())
293274
294 def set_pid_file(self, pid_file):275 def set_pid_file(self, pid_file):
276 """Set pid file."""
295 self._pid_file = pid_file277 self._pid_file = pid_file
296 open(pid_file, 'w').write('%d' % os.getpid())278 open(pid_file, 'w').write('%d' % os.getpid())
297279
298 def get_pid_file(self):280 def get_pid_file(self):
281 """Get pid file."""
299 return self._pid_file282 return self._pid_file
300283
301 def del_pid_file(self):284 def del_pid_file(self):
285 """Delete pid file."""
302 try:286 try:
303 os.unlink(self._pid_file)287 os.unlink(self._pid_file)
304 print >>sys.stderr, 'Removed PID file'288 print >> sys.stderr, 'Removed PID file'
305 except OSError:289 except OSError:
306 print >>sys.stderr, 'Trying to remove non-existing PID file'290 print >> sys.stderr, 'Trying to remove non-existing PID file'
307291
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,
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')
310294
311 def exit(self):295 def exit(self):
312 '''Kill the daemon and remove the PID file296 """Kill the daemon and remove the PID file
313297
314 This method will be called automatically when the process is298 This method will be called automatically when the process is
315 terminated'''299 terminated"""
316 del self.pid_file300 del self.pid_file
317 raise SystemExit301 raise SystemExit
318302
319 @classmethod303 @classmethod
320 def is_running(cls, pid):304 def is_running(cls, pid):
305 """Check whether we're already running.'"""
321 if not pid:306 if not pid:
322 return False307 return False
323 try:308 try:
@@ -328,13 +313,14 @@
328313
329 @classmethod314 @classmethod
330 def kill(cls, pid, pid_file=None):315 def kill(cls, pid, pid_file=None):
316 """Kill process."""
331 try:317 try:
332 pid = int(pid)318 pid = int(pid)
333 except (TypeError, ValueError):319 except (TypeError, ValueError):
334 pid = None320 pid = None
335321
336 if cls.is_running(pid):322 if cls.is_running(pid):
337 print >>sys.stderr, 'Sending TERM signal to process %d' % pid323 print >> sys.stderr, 'Sending TERM signal to process %d' % pid
338 os.kill(pid, signal.SIGTERM)324 os.kill(pid, signal.SIGTERM)
339 i = KILL_TIMEOUT325 i = KILL_TIMEOUT
340 while i > 0 and cls.is_running(pid):326 while i > 0 and cls.is_running(pid):
@@ -342,23 +328,26 @@
342 time.sleep(KILL_CHECK_DELAY)328 time.sleep(KILL_CHECK_DELAY)
343329
344 if cls.is_running(pid):330 if cls.is_running(pid):
345 print >>sys.stderr, 'Sending KILL signal to process %d' % pid331 print >> sys.stderr, 'Sending KILL signal to process %d' % pid
346 os.kill(pid, signal.SIGKILL)332 os.kill(pid, signal.SIGKILL)
347333
348 time.sleep(1)334 time.sleep(1)
349 if cls.is_running(pid):335 if cls.is_running(pid):
350 print >>sys.stderr, 'Unable to kill process %d, still running'336 print >> sys.stderr, 'Unable to kill process %d, still running'
351 else:337 else:
352 if isinstance(pid, int):338 if isinstance(pid, int):
353 print >>sys.stderr, 'Process %d not running' % pid339 print >> sys.stderr, 'Process %d not running' % pid
354 if pid_file:340 if pid_file:
355 print >>sys.stderr, 'Removing stale PID file'341 print >> sys.stderr, 'Removing stale PID file'
356 os.unlink(pid_file)342 os.unlink(pid_file)
357343
358 @classmethod344 @classmethod
359 def daemonize(cls):345 def daemonize(cls):
360 '''Daemonize using the double fork method so the process keeps running346 """
361 Even after the original shell exits'''347 Daemonize using the double fork method so the process keeps
348 running Even after the original shell exits.
349
350 """
362 stdin_file = expand_file(STDIN)351 stdin_file = expand_file(STDIN)
363 stdout_file = expand_file(STDOUT)352 stdout_file = expand_file(STDOUT)
364 stderr_file = expand_file(STDERR)353 stderr_file = expand_file(STDERR)
@@ -367,7 +356,7 @@
367 if pid > 0:356 if pid > 0:
368 sys.exit(0)357 sys.exit(0)
369 except OSError, e:358 except OSError, e:
370 print >>sys.stderr, 'Unable to fork: %s' % e359 print >> sys.stderr, 'Unable to fork: %s' % e
371 sys.exit(1)360 sys.exit(1)
372361
373 os.chdir('/')362 os.chdir('/')
@@ -379,10 +368,10 @@
379 if pid > 0:368 if pid > 0:
380 sys.exit(0)369 sys.exit(0)
381 except OSError, e:370 except OSError, e:
382 print >>sys.stderr, 'Unable to fork: %s' % e371 print >> sys.stderr, 'Unable to fork: %s' % e
383 sys.exit(1)372 sys.exit(1)
384373
385 '''Redirect stdout, stderr and stdin'''374 # Redirect stdout, stderr and stdin
386 stdin = file(stdin_file, 'r')375 stdin = file(stdin_file, 'r')
387 stdout = file(stdout_file, 'a+')376 stdout = file(stdout_file, 'a+')
388 stderr = file(stderr_file, 'a+', 0)377 stderr = file(stderr_file, 'a+', 0)
@@ -390,20 +379,23 @@
390 os.dup2(stdout.fileno(), sys.stdout.fileno())379 os.dup2(stdout.fileno(), sys.stdout.fileno())
391 os.dup2(stderr.fileno(), sys.stderr.fileno())380 os.dup2(stderr.fileno(), sys.stderr.fileno())
392381
382
393class AutoQueuePlugin(autoqueue.AutoQueueBase, Daemon):383class AutoQueuePlugin(autoqueue.AutoQueueBase, Daemon):
384 """Mpd implementation of autoqueue."""
385
394 def __init__(self, host, port, pid_file):386 def __init__(self, host, port, pid_file):
395 self.host, self.port = host, port387 self.host, self.port = host, port
396 self.client = mpd.MPDClient()388 self.client = mpd.MPDClient()
397 self.connect()389 self.connect()
398 autoqueue.AutoQueueBase.__init__(self)390 autoqueue.AutoQueueBase.__init__(self)
399 self.random = True
400 self.verbose = True391 self.verbose = True
401 self.by_mirage = True
402 self.desired_queue_length = DESIRED_QUEUE_LENGTH392 self.desired_queue_length = DESIRED_QUEUE_LENGTH
403 Daemon.__init__(self, pid_file)393 Daemon.__init__(self, pid_file)
404 self._current_song = None394 self._current_song = None
395 self._host = None
405396
406 def run(self):397 def run(self):
398 """Run."""
407 running = True399 running = True
408 while running or not EXIT_WITH_MPD:400 while running or not EXIT_WITH_MPD:
409 interval = REFRESH_INTERVAL401 interval = REFRESH_INTERVAL
@@ -416,8 +408,7 @@
416408
417 interval = min(409 interval = min(
418 REFRESH_INTERVAL,410 REFRESH_INTERVAL,
419 int(self.player_get_queue_length()) - QUEUE_MARGIN411 int(self.player_get_queue_length()) - QUEUE_MARGIN)
420 )
421 running = True412 running = True
422 except mpd.ConnectionError:413 except mpd.ConnectionError:
423 print "disconnecting"414 print "disconnecting"
@@ -432,6 +423,7 @@
432 self.exit()423 self.exit()
433424
434 def connect(self):425 def connect(self):
426 """Connect."""
435 try:427 try:
436 self.client.connect(self.host, self.port)428 self.client.connect(self.host, self.port)
437 return True429 return True
@@ -439,6 +431,7 @@
439 return False431 return False
440432
441 def disconnect(self):433 def disconnect(self):
434 """Disconnect."""
442 try:435 try:
443 self.client.disconnect()436 self.client.disconnect()
444 return True437 return True
@@ -446,56 +439,61 @@
446 return False439 return False
447440
448 def get_host(self):441 def get_host(self):
442 """Get host."""
449 return self._host443 return self._host
450444
451 def set_host(self, host):445 def set_host(self, host):
446 """Set host."""
452 self._host = socket.gethostbyname(host)447 self._host = socket.gethostbyname(host)
453448
454 host = property(get_host, set_host, doc='MPD ip (can be set by ip and hostname)')449 host = property(
450 get_host, set_host, doc='MPD ip (can be set by ip and hostname)')
455451
456 def player_construct_track_search(self, artist, title, restrictions):452 def player_construct_track_search(self, artist, title, restrictions=None):
457 '''construct a search that looks for songs with this artist453 """Construct a search that looks for songs with this artist
458 and title'''454 and title."""
459 return Search(artist=artist, title=title)455 return Search(artist=artist, title=title)
460456
461 def player_construct_tag_search(self, tags, exclude_artists, restrictions):457 def player_construct_tag_search(self, tags, exclude_artists,
462 '''construct a search that looks for songs with these tags'''458 restrictions=None):
459 """Construct a search that looks for songs with these tags."""
463 return None460 return None
464461
465 def player_construct_artist_search(self, artist, restrictions):462 def player_construct_artist_search(self, artist, restrictions=None):
466 '''construct a search that looks for songs with this artist'''463 """Construct a search that looks for songs with this artist."""
467 return Search(artist=artist)464 return Search(artist=artist)
468465
469 def player_construct_restrictions(466 def player_set_variables_from_config(self):
470 self, track_block_time, relaxors, restrictors):467 """Initialize user settings from the configuration storage."""
471 '''construct a search to further modify the searches'''468 pass
472 return None
473469
474 def player_search(self, search):470 def player_search(self, search):
475 '''perform a player search'''471 """perform a player search"""
476472
477 results = self.client.search(*search.get_parameters())473 results = self.client.search(*search.get_parameters())
478474
479 '''Make all search results lowercase and strip whitespace'''475 # Make all search results lowercase and strip whitespace
480 for r in results:476 for result in results:
481 for k, v in r.items():477 for key, value in result.items():
482 if isinstance(v, basestring):478 if isinstance(value, basestring):
483 r['%s_search' % k] = unicode(v, 'utf-8').strip().lower()479 result['%s_search' % key] = unicode(
484480 value, 'utf-8').strip().lower()
485 '''Filter all non-exact matches'''481
486 for k, vs in search.parameters.iteritems():482 # Filter all non-exact matches
487 for v in vs:483 for key, values in search.parameters.iteritems():
488 results = [r for r in results if r.get('%s_search' % k) == v]484 for value in values:
489485 results = [
490 '''Convert all rows to song objects'''486 r for r in results if r.get('%s_search' % key) == value]
487
488 # Convert all rows to song objects
491 return [Song(**x) for x in results]489 return [Song(**x) for x in results]
492490
493 def player_enqueue(self, song):491 def player_enqueue(self, song):
494 '''Put the song at the end of the queue'''492 """Put the song at the end of the queue"""
495 self.client.add(song.file)493 self.client.add(song.file)
496494
497 def player_get_userdir(self):495 def player_get_userdir(self):
498 '''get the application user directory to store files'''496 """get the application user directory to store files"""
499 return expand_path(SETTINGS_PATH)497 return expand_path(SETTINGS_PATH)
500498
501 def player_current_song(self):499 def player_current_song(self):
@@ -511,7 +509,7 @@
511 return (Song(**x) for x in self.client.playlistid())509 return (Song(**x) for x in self.client.playlistid())
512510
513 def player_get_songs_in_queue(self):511 def player_get_songs_in_queue(self):
514 '''return (wrapped) song objects for the songs in the queue'''512 """return (wrapped) song objects for the songs in the queue"""
515 id = self.player_current_song_id()513 id = self.player_current_song_id()
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]
517515

Subscribers

People subscribed via source and target branches

to all changes: