1 package com.melloware.jukes.file;
2
3 import java.beans.PropertyChangeEvent;
4 import java.beans.PropertyChangeListener;
5 import java.io.File;
6 import java.io.FileOutputStream;
7 import java.io.IOException;
8 import java.sql.Timestamp;
9 import java.text.MessageFormat;
10 import java.util.ArrayList;
11 import java.util.Collections;
12 import java.util.Iterator;
13 import java.util.List;
14 import java.util.Random;
15
16 import org.apache.commons.io.FileUtils;
17 import org.apache.commons.io.FilenameUtils;
18 import org.apache.commons.lang.StringUtils;
19 import org.apache.commons.logging.Log;
20 import org.apache.commons.logging.LogFactory;
21 import org.dom4j.Document;
22 import org.dom4j.DocumentHelper;
23 import org.dom4j.io.OutputFormat;
24 import org.dom4j.io.XMLWriter;
25
26 import com.jgoodies.binding.beans.Model;
27 import com.jgoodies.uif.action.ActionManager;
28 import com.jgoodies.uif.util.ResourceUtils;
29 import com.melloware.jspiff.jaxp.XspfPlaylist;
30 import com.melloware.jspiff.jaxp.XspfPlaylistTrackList;
31 import com.melloware.jspiff.jaxp.XspfTrack;
32 import com.melloware.jukes.db.HibernateDao;
33 import com.melloware.jukes.db.orm.Artist;
34 import com.melloware.jukes.db.orm.Catalog;
35 import com.melloware.jukes.db.orm.Disc;
36 import com.melloware.jukes.db.orm.Track;
37 import com.melloware.jukes.file.filter.M3uFilter;
38 import com.melloware.jukes.file.filter.XspfFilter;
39 import com.melloware.jukes.gui.tool.Actions;
40 import com.melloware.jukes.gui.tool.MainModule;
41 import com.melloware.jukes.util.TimeSpan;
42
43
44
45
46
47
48
49
50
51
52
53 @SuppressWarnings("unchecked")
54 public final class Playlist extends Model implements PropertyChangeListener {
55
56 private static final Log LOG = LogFactory.getLog(Playlist.class);
57 public static final String PROPERTYNAME_CURRENT_LIST = "currentList";
58 public static final String PROPERTYNAME_HISTORY_LIST = "historyList";
59 private boolean current = true;
60 private boolean shuffleCatalog = false;
61 private boolean shufflePlaylist = false;
62 private Catalog catalog = null;
63 private final List currentList;
64 private final List historyList;
65 private Track currentTrack;
66
67
68
69
70 public Playlist() {
71 super();
72 LOG.debug("Playlist created.");
73 this.historyList = new ArrayList();
74 this.currentList = new ArrayList();
75 }
76
77
78
79
80
81 public Iterator getBackIterator() {
82 return historyList.iterator();
83 }
84
85
86
87
88
89
90 public String getCurrentDuration() {
91 long duration = 0;
92 final Iterator iter = getNextIterator();
93 while (iter.hasNext()) {
94 final Track track = (Track) iter.next();
95 duration += track.getDuration() * 1000;
96 }
97 return new TimeSpan(duration).getMusicDuration();
98 }
99
100
101
102
103
104
105 public List getCurrentList() {
106 return this.currentList;
107 }
108
109
110
111
112
113
114 public Track getCurrentTrack() {
115 synchronized (this) {
116 return this.currentTrack;
117 }
118 }
119
120
121
122
123
124 public Track removeCurrentTrack() {
125 synchronized (this) {
126 this.currentTrack = null;
127 return this.currentTrack;
128 }
129 }
130
131
132
133
134
135 public String getHistoryDuration() {
136 long duration = 0;
137 final Iterator iter = getBackIterator();
138 while (iter.hasNext()) {
139 final Track track = (Track) iter.next();
140 duration += track.getDuration() * 1000;
141 }
142 return new TimeSpan(duration).getMusicDuration();
143 }
144
145
146
147
148
149
150 public List getHistoryList() {
151 return this.historyList;
152 }
153
154
155
156
157
158
159 public List getList() {
160 if (isCurrent()) {
161 return this.currentList;
162 } else {
163 return this.historyList;
164 }
165 }
166
167
168
169
170
171 public Object getNextImmediate() {
172 Object next = null;
173 next = ((hasNext()) ? currentList.get(0) : null);
174 if (next != null) {
175 synchronized (this) {
176 currentList.remove(next);
177 historyList.add(next);
178 currentTrack = (Track) next;
179 }
180
181 }
182 updateState();
183 return next;
184
185 }
186
187
188
189
190
191 public Object getNext() {
192 Object next = null;
193 if (isShufflePlaylist()) {
194 final Random random = new Random();
195 next = ((hasNext()) ? currentList.get(random.nextInt(currentList.size())) : null);
196 } else if (isShuffleCatalog()) {
197 final Random random = new Random();
198
199
200 final List artists = catalog.getArtists();
201 if (artists == null) {
202 return null;
203 }
204 final Artist artist = (Artist) artists.get(random.nextInt(artists.size()));
205
206
207 Object[] discs = null;
208 final String filter = MainModule.SETTINGS.getFilter();
209
210
211 if (StringUtils.isNotBlank(filter)) {
212 final String resource = ResourceUtils.getString("hql.filter.disc");
213 final String hql = MessageFormat.format(resource, new Object[] { artist.getId(), filter });
214 discs = HibernateDao.findByQuery(hql).toArray();
215 } else {
216
217 discs = artist.getDiscs().toArray();
218 }
219
220 final Disc disc = (Disc) discs[(random.nextInt(discs.length))];
221
222
223 final Object[] tracks = disc.getTracks().toArray();
224 next = (Track) tracks[(random.nextInt(tracks.length))];
225 } else {
226 next = ((hasNext()) ? currentList.get(0) : null);
227 }
228
229 if (next != null) {
230 synchronized (this) {
231 currentList.remove(next);
232 historyList.add(next);
233 currentTrack = (Track) next;
234 }
235
236 }
237 updateState();
238 return next;
239 }
240
241
242
243
244
245 public Iterator getNextIterator() {
246 return currentList.iterator();
247 }
248
249
250
251
252
253 public Object getPrevious() {
254 final Object prev = ((hasPrevious()) ? historyList.get(historyList.size() - 1) : null);
255 if (prev != null) {
256 this.addNext(prev);
257 synchronized (this) {
258 historyList.remove(prev);
259 currentTrack = (Track) prev;
260 }
261 }
262 updateState();
263 return prev;
264 }
265
266
267
268
269
270
271 public void setCurrent(final boolean aCurrent) {
272 this.current = aCurrent;
273 updateState();
274 }
275
276
277
278
279
280
281 public void setShuffleCatalog(final boolean aShuffleCatalog) {
282 this.shuffleCatalog = aShuffleCatalog;
283 }
284
285
286
287
288
289
290 public void setShufflePlaylist(final boolean aShufflePlaylist) {
291 this.shufflePlaylist = aShufflePlaylist;
292 }
293
294
295
296
297
298
299 public boolean isCurrent() {
300 return this.current;
301 }
302
303
304
305
306
307
308 public boolean isShuffleCatalog() {
309 return this.shuffleCatalog;
310 }
311
312
313
314
315
316
317 public boolean isShufflePlaylist() {
318 return this.shufflePlaylist;
319 }
320
321
322
323
324
325
326 public void add(final Object o) {
327 if (LOG.isDebugEnabled()) {
328 LOG.debug("Adding to playlist bottom: " + o);
329 }
330
331 synchronized (this) {
332 final ArrayList tracks = new ArrayList();
333 if (o instanceof Artist) {
334 final Artist artist = (Artist) o;
335 final ArrayList discs = new ArrayList();
336 discs.addAll(artist.getDiscs());
337 Collections.sort(discs);
338 for (final Iterator iter = discs.iterator(); iter.hasNext();) {
339 final Disc disc = (Disc) iter.next();
340 tracks.clear();
341 tracks.addAll(disc.getTracks());
342 Collections.sort(tracks);
343 for (final Iterator iterator = tracks.iterator(); iterator.hasNext();) {
344 final Track track = (Track) iterator.next();
345 currentList.add(track);
346 }
347 }
348 } else if (o instanceof Disc) {
349 final Disc disc = (Disc) o;
350 tracks.clear();
351 tracks.addAll(disc.getTracks());
352 Collections.sort(tracks);
353 for (final Iterator iterator = tracks.iterator(); iterator.hasNext();) {
354 final Track track = (Track) iterator.next();
355 currentList.add(track);
356 }
357
358 } else if (o instanceof Track) {
359 currentList.add(o);
360 }
361 if (currentTrack == null) {
362 currentTrack = (Track) currentList.get(0);
363 ActionManager.get(Actions.PLAYER_PLAY_ID).setEnabled(true);
364 }
365 updateState();
366 }
367 }
368
369
370
371
372
373
374 public void addNext(final Object o) {
375 if (LOG.isDebugEnabled()) {
376 LOG.debug("Adding to playlist top: " + o);
377 }
378 synchronized (this) {
379 final ArrayList tracks = new ArrayList();
380 if (o instanceof Artist) {
381 final Artist artist = (Artist) o;
382 final ArrayList discs = new ArrayList();
383 discs.addAll(artist.getDiscs());
384 Collections.reverse(discs);
385 for (final Iterator iter = discs.iterator(); iter.hasNext();) {
386 final Disc disc = (Disc) iter.next();
387 tracks.clear();
388 tracks.addAll(disc.getTracks());
389 Collections.reverse(tracks);
390 for (final Iterator iterator = tracks.iterator(); iterator.hasNext();) {
391 final Track track = (Track) iterator.next();
392 currentList.add(0, track);
393 }
394 }
395 } else if (o instanceof Disc) {
396 final Disc disc = (Disc) o;
397 tracks.clear();
398 tracks.addAll(disc.getTracks());
399 Collections.reverse(tracks);
400 for (final Iterator iterator = tracks.iterator(); iterator.hasNext();) {
401 final Track track = (Track) iterator.next();
402 currentList.add(0, track);
403 }
404
405 } else if (o instanceof Track) {
406 currentList.add(0, o);
407 }
408
409 if (currentTrack == null) {
410 currentTrack = (Track) currentList.get(0);
411 }
412 updateState();
413 }
414 }
415
416
417
418
419
420 public boolean contains(final Object o) {
421 return (containsNext(o) || containsPrevious(o));
422 }
423
424
425
426
427
428 public boolean containsNext(final Object o) {
429 return (hasNext()) ? currentList.contains(o) : false;
430 }
431
432
433
434
435
436 public boolean containsPrevious(final Object o) {
437 return (hasPrevious()) ? historyList.contains(o) : false;
438 }
439
440
441
442
443
444 public boolean hasNext() {
445 synchronized (this) {
446 return ((!currentList.isEmpty()) || this.shuffleCatalog);
447 }
448 }
449
450
451
452
453
454 public boolean hasPrevious() {
455 synchronized (this) {
456 return (!historyList.isEmpty());
457 }
458 }
459
460
461
462
463
464
465 public void moveDown(final int index) {
466 synchronized (this) {
467 final Object temp = getList().remove(index);
468 getList().add(index + 1, temp);
469 }
470 }
471
472
473
474
475
476
477 public void moveOver(final int index) {
478 synchronized (this) {
479 final Object temp = getList().get(index);
480 if (isCurrent()) {
481 historyList.add(temp);
482 } else {
483 currentList.add(temp);
484 }
485 }
486 }
487
488
489
490
491
492
493 public void moveUp(final int index) {
494 synchronized (this) {
495 final Object temp = getList().remove(index);
496 getList().add(index - 1, temp);
497 }
498 }
499
500
501
502
503
504 public void playImmediate(final Track track) {
505 synchronized (this) {
506 getList().remove(track);
507 historyList.add(track);
508 }
509 updateState();
510 }
511
512
513
514
515
516 public void propertyChange(final PropertyChangeEvent evt) {
517 final String propertyName = evt.getPropertyName();
518 if (MainModule.PROPERTYNAME_CATALOG.equals(propertyName)) {
519 catalog = ((Catalog) evt.getNewValue());
520 }
521 }
522
523
524
525
526
527
528 public void remove(final int index) {
529 synchronized (this) {
530 if (index < getList().size()) {
531 getList().remove(index);
532 }
533 }
534 }
535
536
537
538
539
540
541
542 public void save(final File aFile) throws Exception {
543
544 if (FilenameUtils.isExtension(aFile.getName(), M3uFilter.M3U)) {
545 saveM3U(aFile);
546 } else if (FilenameUtils.isExtension(aFile.getName(), XspfFilter.XSPF)) {
547 saveXSPF(aFile);
548 }
549
550 }
551
552
553
554
555
556 public int size() {
557 if (isCurrent()) {
558 return sizeNext();
559 } else {
560 return sizePrevious();
561 }
562 }
563
564
565
566
567
568 public int sizeNext() {
569 return currentList.size();
570 }
571
572
573
574
575
576 public int sizePrevious() {
577 return historyList.size();
578 }
579
580 public String toString() {
581 return "(Current: " + currentList.size() + "; History: " + historyList.size() + ")";
582 }
583
584
585
586
587 public void updateState() {
588 firePropertyChange(PROPERTYNAME_CURRENT_LIST, null, currentList);
589 firePropertyChange(PROPERTYNAME_HISTORY_LIST, null, historyList);
590 ActionManager.get(Actions.PLAYER_NEXT_ID).setEnabled(hasNext());
591 ActionManager.get(Actions.PLAYER_PREVIOUS_ID).setEnabled(hasPrevious());
592 }
593
594
595
596
597
598
599
600 private void saveM3U(final File aFile) throws IOException {
601 final ArrayList results = new ArrayList();
602
603 results.add("#EXTM3U");
604
605 final StringBuffer sb = new StringBuffer();
606
607
608 if (this.getCurrentTrack() != null) {
609 final Track track = this.getCurrentTrack();
610 sb.delete(0, sb.length());
611 sb.append("#EXTINF:");
612 sb.append(track.getDuration());
613 sb.append(',');
614 sb.append(track.getDisc().getArtist().getName());
615 sb.append(" - ");
616 sb.append(track.getDisc().getName());
617 sb.append(" - ");
618 sb.append(track.getName());
619 results.add(sb.toString());
620 results.add(track.getTrackUrl());
621 }
622
623
624 for (final Iterator iter = getList().iterator(); iter.hasNext();) {
625 final Track track = (Track) iter.next();
626 sb.delete(0, sb.length());
627 sb.append("#EXTINF:");
628 sb.append(track.getDuration());
629 sb.append(',');
630 sb.append(track.getDisc().getArtist().getName());
631 sb.append(" - ");
632 sb.append(track.getDisc().getName());
633 sb.append(" - ");
634 sb.append(track.getName());
635 results.add(sb.toString());
636 results.add(track.getTrackUrl());
637 }
638
639 FileUtils.writeLines(aFile, null, results);
640 }
641
642
643
644
645
646
647
648 private void saveXSPF(final File aFile) throws Exception {
649
650 final XspfPlaylist playlist = new XspfPlaylist();
651 playlist.setTitle(System.getProperty("application.name") + " Playlist");
652 playlist.setCreator(System.getProperty("user.name"));
653 playlist.setDate(new Timestamp(System.currentTimeMillis()));
654 playlist.setInfo("http://melloware.com/");
655 playlist.setVersion("1");
656 final XspfPlaylistTrackList tracks = new XspfPlaylistTrackList();
657 XspfTrack out = null;
658
659
660 if (this.getCurrentTrack() != null) {
661 final Track track = this.getCurrentTrack();
662 out = new XspfTrack();
663 out.setLocation(new File(track.getTrackUrl()).toURI().toASCIIString());
664 out.setCreator(track.getDisc().getArtist().getName());
665 out.setAlbum(track.getDisc().getName());
666 out.setTitle(track.getName());
667 out.setDurationByString(String.valueOf(track.getDuration() * 1000));
668 out.setTrackNumByString(track.getTrackNumber());
669 tracks.addTrack(out);
670 }
671
672
673 for (final Iterator iter = getList().iterator(); iter.hasNext();) {
674 final Track track = (Track) iter.next();
675
676 out = new XspfTrack();
677 out.setLocation(new File(track.getTrackUrl()).toURI().toASCIIString());
678 out.setCreator(track.getDisc().getArtist().getName());
679 out.setAlbum(track.getDisc().getName());
680 out.setTitle(track.getName());
681 out.setTrackNumByString(track.getTrackNumber());
682 tracks.addTrack(out);
683 }
684 playlist.setPlaylistTrackList(tracks);
685 final OutputFormat format = OutputFormat.createPrettyPrint();
686 format.setEncoding("UTF-8");
687 final XMLWriter writer = new XMLWriter(new FileOutputStream(aFile), format);
688 final String xml = new String(playlist.makeTextDocument().getBytes("UTF-8"), "UTF-8");
689 final Document doc = DocumentHelper.parseText(xml);
690 writer.write(doc);
691 writer.close();
692 }
693
694 }