1 package org.musicontroller.rss;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.io.OutputStream;
7 import java.io.OutputStreamWriter;
8 import java.io.UnsupportedEncodingException;
9 import java.io.Writer;
10 import java.net.MalformedURLException;
11 import java.net.URI;
12 import java.net.URISyntaxException;
13 import java.net.URLConnection;
14 import java.util.HashMap;
15 import java.util.Iterator;
16 import java.util.Map;
17
18 import javax.xml.transform.OutputKeys;
19 import javax.xml.transform.Transformer;
20 import javax.xml.transform.TransformerConfigurationException;
21 import javax.xml.transform.sax.SAXTransformerFactory;
22 import javax.xml.transform.sax.TransformerHandler;
23 import javax.xml.transform.stream.StreamResult;
24
25 import org.apache.log4j.Logger;
26 import org.musicontroller.core.Band;
27 import org.musicontroller.core.Contract_PS;
28 import org.musicontroller.core.Keywordbag;
29 import org.musicontroller.core.Link;
30 import org.musicontroller.core.Playlist;
31 import org.musicontroller.core.Song;
32 import org.musicontroller.dao.BagAndKeywordUtils;
33 import org.musicontroller.dao.Dao;
34 import org.varienaja.util.DateTools;
35 import org.xml.sax.Attributes;
36 import org.xml.sax.InputSource;
37 import org.xml.sax.SAXException;
38 import org.xml.sax.XMLReader;
39 import org.xml.sax.helpers.AttributesImpl;
40 import org.xml.sax.helpers.DefaultHandler;
41 import org.xml.sax.helpers.XMLReaderFactory;
42
43
44
45
46
47 public class RssDAO {
48 private static final Logger LOG = Logger.getLogger(RssDAO.class);
49 private static Dao DAO;
50 private static final String ENCLOSURE = "enclosure";
51 private static final String AUTHOR = "itunes:author";
52 private static final String TITLE = "itunes:title";
53 private static final String ITEM = "item";
54 private static final String DURATION = "itunes:duration";
55 private static final String URL = "url";
56 private static final String KEYWORDS = "itunes:keywords";
57 private static final String PLTITLE = "title";
58
59
60
61
62
63 class PodCastRSSHandler extends DefaultHandler {
64 private Playlist _playlist;
65 private StringBuilder _content;
66 private Map<String,String> _contentMap;
67 private boolean _playlistnameset;
68 private boolean _playlistkeywordsset;
69 private String _playlistKeywords;
70 private String _podcastAuthor;
71 private int _songidx;
72
73
74
75
76 public PodCastRSSHandler() {
77 _playlist = null;
78 _content = new StringBuilder();
79 _contentMap = new HashMap<String,String>();
80 _playlistnameset = false;
81 _playlistkeywordsset = false;
82 _playlistKeywords = null;
83 _podcastAuthor = null;
84 _songidx = 0;
85 }
86
87
88
89
90
91
92 protected Playlist getPlaylist() {
93 return _playlist;
94 }
95
96
97
98
99
100 public void startElement(String uri, String name, String qName, Attributes atts) {
101 if (ITEM.equals(qName)) {
102 _contentMap = new HashMap<String,String>();
103 }
104 if (ENCLOSURE.equals(qName)) {
105 _contentMap.put(URL, atts.getValue(URL));
106 }
107 _content = new StringBuilder();
108 }
109
110
111
112
113
114 public void endElement(String uri, String name, String qName) {
115
116 _contentMap.put(qName, _content.toString());
117
118 if (PLTITLE.equals(qName) && !_playlistnameset) {
119 String playlistname = _contentMap.get(PLTITLE);
120 _playlist = DAO.getPlaylistByName(playlistname);
121 if (_playlist==null) {
122 _playlist = new Playlist();
123 _playlist.setName(playlistname);
124 }
125 _playlistnameset = true;
126 }
127 _content = new StringBuilder();
128
129 if (AUTHOR.equals(qName)) {
130 if (_podcastAuthor==null) {
131 _podcastAuthor = _contentMap.get(AUTHOR);
132 }
133 }
134
135 if (KEYWORDS.equals(qName)) {
136 if (!_playlistkeywordsset) {
137 _playlistKeywords = _contentMap.get(KEYWORDS);
138 _playlistkeywordsset = true;
139 }
140 }
141
142 if (ITEM.equals(qName)) {
143 _songidx++;
144 String bandname = _contentMap.get(AUTHOR);
145 if (bandname==null) {
146
147 bandname = _podcastAuthor;
148 }
149 Band band = null;
150 if (bandname!=null) {
151 band = DAO.getBandByName(bandname);
152 }
153 if (band==null) {
154
155 for (Contract_PS contract : _playlist.getSongs()) {
156 Band candidate = contract.getSong().getBand();
157 if (candidate.getName().equals(bandname)) {
158 band = candidate;
159 }
160 }
161 if (band==null) {
162 band = new Band();
163 band.setName(bandname);
164 }
165 }
166
167 String songname = _contentMap.get(TITLE);
168 if (songname == null) {
169
170 songname = _contentMap.get(PLTITLE);
171 }
172
173 Song song = band.getId()==-1L ? null : DAO.getSong(band, songname);
174 if (song==null) {
175 song = new Song();
176 song.setName(songname);
177 song.setLength(_contentMap.get(DURATION));
178 song.setBand(band);
179
180 Link link = new Link();
181 link.setUrl(_contentMap.get(URL));
182 song.setLink(link);
183
184 String allKeywords = _contentMap.get(KEYWORDS);
185 if (allKeywords==null || "".equals(allKeywords)) {
186
187
188 allKeywords = _playlistKeywords;
189 }
190 if (allKeywords!=null) {
191 Keywordbag bag = cachedGetKeywordbag(allKeywords);
192 song.setKeywordbag(bag);
193 }
194 }
195
196 _playlist.addSongIfNew(song, _songidx);
197 }
198 }
199
200
201
202
203
204
205 public void characters(char ch[], int start, int length) {
206 _content.append(ch, start, length);
207 }
208 }
209
210 private HashMap<String, Keywordbag> _cache;
211
212
213
214
215
216
217
218
219 public Playlist readPlaylistFromRss(InputStream in) {
220 _cache = new HashMap<String, Keywordbag>();
221 try {
222 XMLReader xr = XMLReaderFactory.createXMLReader();
223 PodCastRSSHandler handler = new PodCastRSSHandler();
224 xr.setContentHandler(handler);
225 xr.setErrorHandler(handler);
226 xr.parse(new InputSource(in));
227 Playlist playlist = handler.getPlaylist();
228 LOG.debug("Read "+playlist.getSongs().size()+" Songs from RSS-feed.");
229 return playlist;
230 } catch (SAXException e) {
231 LOG.error("Error parsing RSS: "+e);
232 } catch (IOException e) {
233 LOG.error("I/O Exception reading RSS: "+e);
234 } finally {
235 _cache = null;
236 }
237 return null;
238 }
239
240
241
242
243
244
245 public Playlist readPlaylistFromRss(String url) {
246 Playlist result = null;
247 InputStream in = null;
248 try {
249 URLConnection urlc = new URI(url).toURL().openConnection();
250 urlc.setRequestProperty("user-agent","Mozilla/5.0");
251
252 in = urlc.getInputStream();
253 result = readPlaylistFromRss(in);
254 if (result.getLink()==null) {
255 Link link = new Link();
256 link.setUrl(url);
257 result.setLink(link);
258 }
259 } catch (URISyntaxException e) {
260 LOG.error(e);
261 } catch (MalformedURLException e) {
262 LOG.error(e);
263 } catch (IOException e) {
264 LOG.error(e);
265 } finally {
266 if (in!=null) {
267 try {
268 in.close();
269 } catch (IOException e) {
270 LOG.error(e);
271 }
272 }
273 }
274 return result;
275 }
276
277
278
279
280
281
282
283
284 public void writePlaylistRSS(Playlist playlist, OutputStream out, MetadataProvider provider) {
285 Writer writer = null;
286 try {
287 try {
288 writer = new OutputStreamWriter(out, "UTF-8");
289 } catch (UnsupportedEncodingException e) {
290 LOG.error("Error setting output-charset to UTF-8! " + e);
291 }
292 StreamResult streamResult = new StreamResult(writer);
293 SAXTransformerFactory tf = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
294 TransformerHandler hd = tf.newTransformerHandler();
295 Transformer t = hd.getTransformer();
296 t.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
297 t.setOutputProperty(OutputKeys.INDENT, "yes");
298 hd.setResult(streamResult);
299 hd.startDocument();
300
301 AttributesImpl atts = new AttributesImpl();
302 atts.addAttribute("", "", "version", "CDATA", "2.0");
303 atts.addAttribute("", "", "xmlns:itunes", "CDATA", "http://www.itunes.com/dtds/podcast-1.0.dtd");
304 hd.startElement("", "", "rss", atts);
305
306 atts.clear();
307 hd.startElement("", "", "channel", atts);
308 writeElement(PLTITLE, playlist.getName(), hd);
309 writeElement("link", provider.getUrl(playlist), hd);
310
311 writeElement("language", "en-us", hd);
312 writeElement("copyright", "todo", hd);
313 writeElement("itunes:subtitle", "The subtitle of " + playlist.getName(), hd);
314 writeElement(AUTHOR, playlist.getBandname(), hd);
315 writeElement("itunes:summary", "The summary of " + playlist.getName(), hd);
316 writeElement("description", "The description of " + playlist.getName(), hd);
317
318 atts.clear();
319 atts.addAttribute("", "", "href", "CDATA", provider.getCoverUrl(playlist));
320 hd.startElement("", "", "itunes:image", atts);
321 hd.endElement("", "", "itunes:image");
322
323 atts.clear();
324 hd.startElement("", "", "itunes:owner", atts);
325 writeElement("itunes:name", "Someone Else", hd);
326 writeElement("itunes:email", "someone@example.com", hd);
327 hd.endElement("", "", "itunes:owner");
328
329
330 atts.clear();
331 atts.addAttribute("", "", "text", "CDATA", "Music");
332 hd.startElement("", "", "itunes:category", atts);
333 hd.endElement("", "", "itunes:category");
334
335
336 writeElement("itunes:explicit", "no", hd);
337
338
339 atts.clear();
340 atts.addAttribute("", "", "rel", "CDATA", "self");
341 atts.addAttribute("", "", "type", "CDATA", "application/rss+xml");
342 atts.addAttribute("", "", "href", "CDATA", provider.getRssUrl(playlist));
343
344 for (Contract_PS cps : playlist.getSongs()) {
345 atts.clear();
346 hd.startElement("", "", "item", atts);
347 writeElement(PLTITLE, cps.getSong().getBand().getName() + " - " +
348 cps.getSong().getName(), hd);
349 writeElement("description", "A " + cps.getSong().getKeywordbag() + "song.", hd);
350 writeElement("link", provider.getUrl(cps.getSong()), hd);
351
352 writeElement(TITLE, cps.getSong().getName(), hd);
353 writeElement(AUTHOR, cps.getSong().getBand().getName(), hd);
354 writeElement("itunes:album", playlist.getName(), hd);
355 writeElement("itunes:subtitle", "Subtitle of this song", hd);
356 writeElement("itunes:summary", "Summary of this song", hd);
357
358 atts.clear();
359 File file = cps.getSong().getLink().getFile();
360 atts.addAttribute("", "", "length", "CDATA",Long.toString(file.length()));
361
362 String downloadurl = provider.getDownloadUrl(cps.getSong());
363 atts.addAttribute("", "", URL, "CDATA", downloadurl);
364 atts.addAttribute("", "", "type", "CDATA", "audio/mpeg");
365 hd.startElement("", "", ENCLOSURE, atts);
366 hd.endElement("", "", ENCLOSURE);
367
368 writeElement("guid", downloadurl, hd);
369
370 writeElement("pubDate", DateTools.formatDate(cps.getSong().getInserted(), "EEE, d MMM yyyy HH:mm:ss")+" +0000", hd);
371 writeElement(DURATION, cps.getSong().getFormattedLength(), hd);
372 writeElement(KEYWORDS, ""+cps.getSong().getKeywordbag(), hd);
373 hd.endElement("", "", "item");
374 }
375
376 hd.endElement("", "", "channel");
377 hd.endElement("", "", "rss");
378 hd.endDocument();
379 } catch (TransformerConfigurationException e) {
380 LOG.error("TransformerConfigurationException during podcast creation: " + e);
381 } catch (SAXException e) {
382 LOG.error("SAXException during podcast creation: " + e);
383 } finally {
384 try {
385 if (writer!=null) writer.close();
386 } catch (IOException e) {
387 LOG.error("Error closing writer "+e);
388 }
389 }
390 }
391
392
393
394
395
396
397
398
399 private void writeElement(String eltName, String content, TransformerHandler hd) throws SAXException {
400 AttributesImpl atts = new AttributesImpl();
401 hd.startElement("", "", eltName, atts);
402 hd.characters(content.toCharArray(), 0, content.length());
403 hd.endElement("", "", eltName);
404 }
405
406
407
408
409
410 public void setDao(Dao dao) {
411 DAO = dao;
412 }
413
414
415
416
417
418
419
420
421
422
423
424 public void merge(Playlist original, Playlist fromrss) {
425
426
427 Iterator<Contract_PS> it = original.getSongs().iterator();
428 for (Contract_PS contract : fromrss.getSongs()) {
429 Contract_PS orig = it.hasNext() ? it.next() : null;
430 if (orig==null) {
431 original.addSong(contract.getSong(), contract.getRowno());
432 } else {
433 orig.setSong(contract.getSong());
434 orig.setRowno(contract.getRowno());
435 }
436 }
437
438 while (it.hasNext()) {
439 it.next();
440 it.remove();
441 }
442 }
443
444
445
446
447
448
449
450 public void updatePlaylist(long playlistid) {
451 Playlist playlist = DAO.getPlaylistById(playlistid, null);
452 if (playlist.getLink()==null) {
453 LOG.debug("Playlist: " + playlist.getName() + " cannot be updated because it does not point to a URL.");
454 return;
455 }
456 String url = playlist.getLink().getUrl();
457 LOG.debug("Updating podcast: " + playlist.getName() + " from: " + url);
458 Playlist fromrss = readPlaylistFromRss(url);
459 if (fromrss!=null) {
460 merge(playlist, fromrss);
461 DAO.save(playlist);
462 } else {
463 LOG.debug("Could not read Playlist from: " + url);
464 }
465 LOG.debug("Done updating podcast.");
466 }
467
468
469
470
471
472
473
474 private Keywordbag cachedGetKeywordbag(String allKeywords) {
475 Keywordbag result = _cache.get(allKeywords);
476 if (result==null) {
477 result = BagAndKeywordUtils.getKeywordBag(allKeywords);
478 _cache.put(allKeywords, result);
479 }
480 return result;
481 }
482
483 }