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