1 /***
2 * Ambient - A music player for the Android platform
3 Copyright (C) 2007 Martin Vysny
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 package sk.baka.ambient.library;
20
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.net.MalformedURLException;
24 import java.net.URL;
25 import java.util.Set;
26 import java.util.zip.GZIPInputStream;
27
28 import javax.xml.parsers.FactoryConfigurationError;
29
30 import org.xml.sax.Attributes;
31 import org.xml.sax.SAXException;
32 import org.xml.sax.helpers.DefaultHandler;
33
34 import sk.baka.ambient.AmbientApplication;
35 import sk.baka.ambient.NotifyingInputStream;
36 import sk.baka.ambient.R;
37 import sk.baka.ambient.collection.TrackMetadataBean;
38 import sk.baka.ambient.collection.TrackOriginEnum;
39 import sk.baka.ambient.collection.TrackMetadataBean.Builder;
40 import sk.baka.ambient.commons.IOUtils;
41 import sk.baka.ambient.commons.MiscUtils;
42 import android.content.Context;
43 import android.content.SharedPreferences;
44 import android.content.SharedPreferences.Editor;
45 import android.view.Gravity;
46 import android.widget.Toast;
47
48 /***
49 * Scans the Magnatune store.
50 *
51 * @author Martin Vysny
52 */
53 public class MagnatuneScanner implements IFileScanner {
54 private Library library = null;
55 private GenreCache genreCache;
56 /***
57 * The location of a gzipped xml file containing the Magnatune collection.
58 */
59 private final static URL COLLECTION_URL;
60 /***
61 * Timestamp of last collection update.
62 */
63 private final static URL TIMESTAMP_URL;
64 static {
65 try {
66 COLLECTION_URL = new URL(
67 "http://magnatune.com/info/song_info2_xml.gz");
68 TIMESTAMP_URL = new URL(
69 "http://magnatune.com/info/last_update_timestamp");
70 } catch (MalformedURLException e) {
71 throw new RuntimeException(e);
72 }
73 }
74
75 private long getCurrentTimestamp() throws IOException {
76 final InputStream in = TIMESTAMP_URL.openStream();
77 try {
78 final String str = IOUtils.readLine(in);
79 return Long.parseLong(str);
80 } finally {
81 MiscUtils.closeQuietly(in);
82 }
83 }
84
85 public void init(Library library, GenreCache genreCache) {
86 this.library = library;
87 this.genreCache = genreCache;
88 }
89
90 public void run() {
91 try {
92
93 final long timestamp = checkTimestamp();
94 if (timestamp < 0) {
95 return;
96 }
97 checkInterrupted();
98
99 library.backend.clean(getOrigin());
100
101 parseMagnatuneXML();
102
103 if (!Thread.currentThread().isInterrupted()) {
104 final Editor e = getPrefs().edit();
105 e.putLong("timestamp", timestamp);
106 e.commit();
107 }
108 } catch (Exception e) {
109 throw new RuntimeException(e);
110 }
111 }
112
113 private SharedPreferences getPrefs() {
114 return AmbientApplication.getInstance().getSharedPreferences(
115 "magnatune", Context.MODE_PRIVATE);
116 }
117
118 /***
119 * Checks current magnatune timestamp.
120 *
121 * @return positive long when the rescan is needed (the value of the new
122 * timestamp), negative long if rescan is not needed.
123 * @throws IOException
124 */
125 private long checkTimestamp() throws IOException {
126 final long currentTimestamp = getCurrentTimestamp();
127 final SharedPreferences prefs = getPrefs();
128 final long lastTimestamp = prefs.getLong("timestamp", 0);
129 if (lastTimestamp >= currentTimestamp) {
130 AmbientApplication.getHandler().post(new Runnable() {
131 public void run() {
132 final Toast toast = Toast.makeText(AmbientApplication
133 .getInstance(),
134 R.string.magnatuneRescanUnnecessary,
135 Toast.LENGTH_LONG);
136 toast.setGravity(Gravity.CENTER, 0, 0);
137 toast.show();
138 }
139 });
140 userNotified = true;
141 return -1;
142 }
143 return currentTimestamp;
144 }
145
146 private final String caption = AmbientApplication.getInstance().getString(
147 R.string.magnatune_rescanning);
148
149 private void parseMagnatuneXML() throws FactoryConfigurationError,
150 SAXException, IOException {
151 final InputStream in = new GZIPInputStream(NotifyingInputStream
152 .fromURL(COLLECTION_URL, 50, caption));
153 IOUtils.parseXML(in, new Handler());
154 }
155
156 private void checkInterrupted() {
157 if (Thread.currentThread().isInterrupted()) {
158 throw new RuntimeException("Interrupted");
159 }
160 }
161
162 /***
163 * Handles elements from the magnatune collection xml.
164 *
165 * @author Martin Vysny
166 */
167 private final class Handler extends DefaultHandler {
168 private boolean wasAllSongs = false;
169
170 @Override
171 public void endDocument() {
172 wasAllSongs = false;
173 }
174
175 @Override
176 public void startDocument() {
177 checkInterrupted();
178 wasAllSongs = false;
179 }
180
181 @Override
182 public void startElement(String uri, String localName, String name,
183 Attributes attributes) throws SAXException {
184 checkInterrupted();
185 if (localName.equals("AllSongs")) {
186 wasAllSongs = true;
187 } else if (localName.equals("Track")) {
188 if (!wasAllSongs) {
189 throw new SAXException("AllSongs element not found");
190 }
191 handleTrack(attributes);
192 } else {
193 throw new SAXException("Unknown element " + localName);
194 }
195 }
196 }
197
198 public TrackOriginEnum getOrigin() {
199 return TrackOriginEnum.Magnatune;
200 }
201
202 /***
203 * Stores the tag into the db.
204 *
205 * @param tag
206 * the tag to store.
207 */
208 private void storeTag(TrackMetadataBean tag) {
209
210 final Set<Long> genreIds = genreCache.register(tag.getGenre());
211
212 library.backend.registerNewTrack(tag, genreIds);
213 }
214
215 private void handleTrack(Attributes attributes) {
216 final Builder track = TrackMetadataBean.newBuilder();
217 track.artist = MiscUtils.fixArtistAlbumName(attributes
218 .getValue("",
219 "artist"));
220 track.album = MiscUtils
221 .fixArtistAlbumName(attributes.getValue("album"));
222 track.title = attributes.getValue("title");
223 track.trackNumber = attributes.getValue("tracknum");
224 track.yearReleased = attributes.getValue("year");
225 track.genre = attributes.getValue("genre");
226 track.length = Integer.parseInt(attributes.getValue("seconds"));
227 track.location = attributes.getValue("url");
228 track.origin = TrackOriginEnum.Magnatune;
229
230
231
232
233
234 track.buyURL = attributes.getValue("buy");
235 track.artistDesc = attributes.getValue("artistdesc");
236 track.artistURL = attributes.getValue("home");
237 track.license = attributes.getValue("license");
238 final TrackMetadataBean bean = track.build(-1);
239 storeTag(bean);
240 }
241
242 private boolean userNotified = false;
243
244 public boolean isUserNotified() {
245 return userNotified;
246 }
247 }