Clover Coverage Report - Subsonic-Android Coverage Report
Coverage timestamp: ven dic 19 2014 17:57:13 EST
../../../../../img/srcFileCovDistChart5.png 71% of files have more coverage
108   356   57   3,18
26   268   0,53   3,4
34     1,68  
10    
This report was generated with an evaluation server license. Purchase Clover or configure your license.
 
  JukeboxService       Line # 51 78 38 47,5% 0.47457626
  JukeboxService.TaskQueue       Line # 231 10 7 83,3% 0.8333333
  JukeboxService.JukeboxTask       Line # 262 1 1 0% 0.0
  JukeboxService.GetStatus       Line # 272 1 1 0% 0.0
  JukeboxService.SetPlaylist       Line # 279 2 2 100% 1.0
  JukeboxService.Skip       Line # 293 3 2 0% 0.0
  JukeboxService.Stop       Line # 308 1 1 100% 1.0
  JukeboxService.Start       Line # 315 1 1 0% 0.0
  JukeboxService.SetGain       Line # 322 2 2 0% 0.0
  JukeboxService.VolumeToast       Line # 336 9 2 0% 0.0
 
No Tests
 
1    /*
2    This file is part of Subsonic.
3   
4    Subsonic is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8   
9    Subsonic is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12    GNU General Public License for more details.
13   
14    You should have received a copy of the GNU General Public License
15    along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
16   
17    Copyright 2009 (C) Sindre Mehus
18    */
19    package net.sourceforge.subsonic.androidapp.service;
20   
21    import android.content.Context;
22    import android.os.Handler;
23    import android.util.Log;
24    import android.view.Gravity;
25    import android.view.LayoutInflater;
26    import android.view.View;
27    import android.widget.ProgressBar;
28    import android.widget.Toast;
29    import net.sourceforge.subsonic.androidapp.R;
30    import net.sourceforge.subsonic.androidapp.domain.JukeboxStatus;
31    import net.sourceforge.subsonic.androidapp.domain.PlayerState;
32    import net.sourceforge.subsonic.androidapp.service.parser.SubsonicRESTException;
33    import net.sourceforge.subsonic.androidapp.util.Util;
34   
35    import java.util.ArrayList;
36    import java.util.Iterator;
37    import java.util.List;
38    import java.util.concurrent.Executors;
39    import java.util.concurrent.LinkedBlockingQueue;
40    import java.util.concurrent.ScheduledExecutorService;
41    import java.util.concurrent.ScheduledFuture;
42    import java.util.concurrent.TimeUnit;
43    import java.util.concurrent.atomic.AtomicLong;
44   
45    /**
46    * Provides an asynchronous interface to the remote jukebox on the Subsonic server.
47    *
48    * @author Sindre Mehus
49    * @version $Id$
50    */
 
51    public class JukeboxService {
52   
53    private static final String TAG = JukeboxService.class.getSimpleName();
54    private static final long STATUS_UPDATE_INTERVAL_SECONDS = 5L;
55   
56    private final Handler handler = new Handler();
57    private final TaskQueue tasks = new TaskQueue();
58    private final DownloadServiceImpl downloadService;
59    private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
60    private ScheduledFuture<?> statusUpdateFuture;
61    private final AtomicLong timeOfLastUpdate = new AtomicLong();
62    private JukeboxStatus jukeboxStatus;
63    private float gain = 0.5f;
64    private VolumeToast volumeToast;
65   
66    // TODO: Report warning if queue fills up.
67    // TODO: Create shutdown method?
68    // TODO: Disable repeat.
69    // TODO: Persist RC state?
70    // TODO: Minimize status updates.
71   
 
72  1 toggle public JukeboxService(DownloadServiceImpl downloadService) {
73  1 this.downloadService = downloadService;
74  1 new Thread() {
 
75  1 toggle @Override
76    public void run() {
77  1 processTasks();
78    }
79    }.start();
80    }
81   
 
82  0 toggle private synchronized void startStatusUpdate() {
83  0 stopStatusUpdate();
84  0 Runnable updateTask = new Runnable() {
 
85  0 toggle @Override
86    public void run() {
87  0 tasks.remove(GetStatus.class);
88  0 tasks.add(new GetStatus());
89    }
90    };
91  0 statusUpdateFuture = executorService.scheduleWithFixedDelay(updateTask, STATUS_UPDATE_INTERVAL_SECONDS,
92    STATUS_UPDATE_INTERVAL_SECONDS, TimeUnit.SECONDS);
93    }
94   
 
95  6 toggle private synchronized void stopStatusUpdate() {
96  6 if (statusUpdateFuture != null) {
97  0 statusUpdateFuture.cancel(false);
98  0 statusUpdateFuture = null;
99    }
100    }
101   
 
102  1 toggle private void processTasks() {
103  1 while (true) {
104  7 JukeboxTask task = null;
105  7 try {
106  7 task = tasks.take();
107  6 JukeboxStatus status = task.execute();
108  0 onStatusUpdate(status);
109    } catch (Throwable x) {
110  6 onError(task, x);
111    }
112    }
113    }
114   
 
115  0 toggle private void onStatusUpdate(JukeboxStatus jukeboxStatus) {
116  0 timeOfLastUpdate.set(System.currentTimeMillis());
117  0 this.jukeboxStatus = jukeboxStatus;
118   
119    // Track change?
120  0 Integer index = jukeboxStatus.getCurrentPlayingIndex();
121  0 if (index != null && index != -1 && index != downloadService.getCurrentPlayingIndex()) {
122  0 downloadService.setCurrentPlaying(index, true);
123    }
124    }
125   
 
126  6 toggle private void onError(JukeboxTask task, Throwable x) {
127  6 if (x instanceof ServerTooOldException && !(task instanceof Stop)) {
128  0 disableJukeboxOnError(x, R.string.download_jukebox_server_too_old);
129  6 } else if (x instanceof OfflineException && !(task instanceof Stop)) {
130  1 disableJukeboxOnError(x, R.string.download_jukebox_offline);
131  5 } else if (x instanceof SubsonicRESTException && ((SubsonicRESTException) x).getCode() == 50 && !(task instanceof Stop)) {
132  2 disableJukeboxOnError(x, R.string.download_jukebox_not_authorized);
133    } else {
134  3 Log.e(TAG, "Failed to process jukebox task: " + x, x);
135    }
136    }
137   
 
138  3 toggle private void disableJukeboxOnError(Throwable x, final int resourceId) {
139  3 Log.w(TAG, x.toString());
140  3 handler.post(new Runnable() {
 
141  3 toggle @Override
142    public void run() {
143  3 Util.toast(downloadService, resourceId, false);
144    }
145    });
146  3 downloadService.setJukeboxEnabled(false);
147    }
148   
 
149  3 toggle public void updatePlaylist() {
150  3 tasks.remove(Skip.class);
151  3 tasks.remove(Stop.class);
152  3 tasks.remove(Start.class);
153   
154  3 List<String> ids = new ArrayList<String>();
155  3 for (DownloadFile file : downloadService.getDownloads()) {
156  60 ids.add(file.getSong().getId());
157    }
158  3 tasks.add(new SetPlaylist(ids));
159    }
160   
 
161  0 toggle public void skip(final int index, final int offsetSeconds) {
162  0 tasks.remove(Skip.class);
163  0 tasks.remove(Stop.class);
164  0 tasks.remove(Start.class);
165   
166  0 startStatusUpdate();
167  0 if (jukeboxStatus != null) {
168  0 jukeboxStatus.setPositionSeconds(offsetSeconds);
169    }
170  0 tasks.add(new Skip(index, offsetSeconds));
171  0 downloadService.setPlayerState(PlayerState.STARTED);
172    }
173   
 
174  6 toggle public void stop() {
175  6 tasks.remove(Stop.class);
176  6 tasks.remove(Start.class);
177   
178  6 stopStatusUpdate();
179  6 tasks.add(new Stop());
180    }
181   
 
182  0 toggle public void start() {
183  0 tasks.remove(Stop.class);
184  0 tasks.remove(Start.class);
185   
186  0 startStatusUpdate();
187  0 tasks.add(new Start());
188    }
189   
 
190  0 toggle public synchronized void adjustVolume(boolean up) {
191  0 float delta = up ? 0.1f : -0.1f;
192  0 gain += delta;
193  0 gain = Math.max(gain, 0.0f);
194  0 gain = Math.min(gain, 1.0f);
195   
196  0 tasks.remove(SetGain.class);
197  0 tasks.add(new SetGain(gain));
198   
199  0 if (volumeToast == null) {
200  0 volumeToast = new VolumeToast(downloadService);
201    }
202  0 volumeToast.setVolume(gain);
203    }
204   
 
205  6 toggle private MusicService getMusicService() {
206  6 return MusicServiceFactory.getMusicService(downloadService);
207    }
208   
 
209  0 toggle public int getPositionSeconds() {
210  0 if (jukeboxStatus == null || jukeboxStatus.getPositionSeconds() == null || timeOfLastUpdate.get() == 0) {
211  0 return 0;
212    }
213   
214  0 if (jukeboxStatus.isPlaying()) {
215  0 int secondsSinceLastUpdate = (int) ((System.currentTimeMillis() - timeOfLastUpdate.get()) / 1000L);
216  0 return jukeboxStatus.getPositionSeconds() + secondsSinceLastUpdate;
217    }
218   
219  0 return jukeboxStatus.getPositionSeconds();
220    }
221   
 
222  6 toggle public void setEnabled(boolean enabled) {
223  6 tasks.clear();
224  6 if (enabled) {
225  3 updatePlaylist();
226    }
227  6 stop();
228  6 downloadService.setPlayerState(PlayerState.IDLE);
229    }
230   
 
231    private static class TaskQueue {
232   
233    private final LinkedBlockingQueue<JukeboxTask> queue = new LinkedBlockingQueue<JukeboxTask>();
234   
 
235  9 toggle void add(JukeboxTask jukeboxTask) {
236  9 queue.add(jukeboxTask);
237    }
238   
 
239  7 toggle JukeboxTask take() throws InterruptedException {
240  7 return queue.take();
241    }
242   
 
243  21 toggle void remove(Class<? extends JukeboxTask> clazz) {
244  21 try {
245  21 Iterator<JukeboxTask> iterator = queue.iterator();
246  22 while (iterator.hasNext()) {
247  1 JukeboxTask task = iterator.next();
248  1 if (clazz.equals(task.getClass())) {
249  0 iterator.remove();
250    }
251    }
252    } catch (Throwable x) {
253  0 Log.w(TAG, "Failed to clean-up task queue.", x);
254    }
255    }
256   
 
257  6 toggle void clear() {
258  6 queue.clear();
259    }
260    }
261   
 
262    private abstract class JukeboxTask {
263   
264    abstract JukeboxStatus execute() throws Exception;
265   
 
266  0 toggle @Override
267    public String toString() {
268  0 return getClass().getSimpleName();
269    }
270    }
271   
 
272    private class GetStatus extends JukeboxTask {
 
273  0 toggle @Override
274    JukeboxStatus execute() throws Exception {
275  0 return getMusicService().getJukeboxStatus(downloadService, null);
276    }
277    }
278   
 
279    private class SetPlaylist extends JukeboxTask {
280   
281    private final List<String> ids;
282   
 
283  3 toggle SetPlaylist(List<String> ids) {
284  3 this.ids = ids;
285    }
286   
 
287  3 toggle @Override
288    JukeboxStatus execute() throws Exception {
289  3 return getMusicService().updateJukeboxPlaylist(ids, downloadService, null);
290    }
291    }
292   
 
293    private class Skip extends JukeboxTask {
294    private final int index;
295    private final int offsetSeconds;
296   
 
297  0 toggle Skip(int index, int offsetSeconds) {
298  0 this.index = index;
299  0 this.offsetSeconds = offsetSeconds;
300    }
301   
 
302  0 toggle @Override
303    JukeboxStatus execute() throws Exception {
304  0 return getMusicService().skipJukebox(index, offsetSeconds, downloadService, null);
305    }
306    }
307   
 
308    private class Stop extends JukeboxTask {
 
309  3 toggle @Override
310    JukeboxStatus execute() throws Exception {
311  3 return getMusicService().stopJukebox(downloadService, null);
312    }
313    }
314   
 
315    private class Start extends JukeboxTask {
 
316  0 toggle @Override
317    JukeboxStatus execute() throws Exception {
318  0 return getMusicService().startJukebox(downloadService, null);
319    }
320    }
321   
 
322    private class SetGain extends JukeboxTask {
323   
324    private final float gain;
325   
 
326  0 toggle private SetGain(float gain) {
327  0 this.gain = gain;
328    }
329   
 
330  0 toggle @Override
331    JukeboxStatus execute() throws Exception {
332  0 return getMusicService().setJukeboxGain(gain, downloadService, null);
333    }
334    }
335   
 
336    private static class VolumeToast extends Toast {
337   
338    private final ProgressBar progressBar;
339   
 
340  0 toggle public VolumeToast(Context context) {
341  0 super(context);
342  0 setDuration(Toast.LENGTH_SHORT);
343  0 LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
344  0 View view = inflater.inflate(R.layout.jukebox_volume, null);
345  0 progressBar = (ProgressBar) view.findViewById(R.id.jukebox_volume_progress_bar);
346   
347  0 setView(view);
348  0 setGravity(Gravity.TOP, 0, 0);
349    }
350   
 
351  0 toggle public void setVolume(float volume) {
352  0 progressBar.setProgress(Math.round(100 * volume));
353  0 show();
354    }
355    }
356    }