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 call() throws IOException, SAXException {
91
92 final long timestamp = checkTimestamp();
93 if (timestamp < 0) {
94 return null;
95 }
96 checkInterrupted();
97
98 library.backend.clean(getOrigin());
99
100 parseMagnatuneXML();
101
102 if (!Thread.currentThread().isInterrupted()) {
103 final Editor e = getPrefs().edit();
104 e.putLong("timestamp", timestamp);
105 e.commit();
106 }
107 return null;
108 }
109
110 private SharedPreferences getPrefs() {
111 return AmbientApplication.getInstance().getSharedPreferences(
112 "magnatune", Context.MODE_PRIVATE);
113 }
114
115 /***
116 * Checks current magnatune timestamp.
117 *
118 * @return positive long when the rescan is needed (the value of the new
119 * timestamp), negative long if rescan is not needed.
120 * @throws IOException
121 */
122 private long checkTimestamp() throws IOException {
123 final long currentTimestamp = getCurrentTimestamp();
124 final SharedPreferences prefs = getPrefs();
125 final long lastTimestamp = prefs.getLong("timestamp", 0);
126 if (lastTimestamp >= currentTimestamp) {
127 AmbientApplication.getHandler().post(new Runnable() {
128 public void run() {
129 final Toast toast = Toast.makeText(AmbientApplication
130 .getInstance(),
131 R.string.magnatuneRescanUnnecessary,
132 Toast.LENGTH_LONG);
133 toast.setGravity(Gravity.CENTER, 0, 0);
134 toast.show();
135 }
136 });
137 userNotified = true;
138 return -1;
139 }
140 return currentTimestamp;
141 }
142
143 private final String caption = AmbientApplication.getInstance().getString(
144 R.string.magnatune_rescanning);
145
146 private void parseMagnatuneXML() throws FactoryConfigurationError,
147 SAXException, IOException {
148 final InputStream in = new GZIPInputStream(NotifyingInputStream
149 .fromURL(COLLECTION_URL, 50, caption));
150 IOUtils.parseXML(in, new Handler());
151 }
152
153 private void checkInterrupted() {
154 if (Thread.currentThread().isInterrupted()) {
155 throw new RuntimeException("Interrupted");
156 }
157 }
158
159 /***
160 * Handles elements from the magnatune collection xml.
161 *
162 * @author Martin Vysny
163 */
164 private final class Handler extends DefaultHandler {
165 private boolean wasAllSongs = false;
166
167 @Override
168 public void endDocument() {
169 wasAllSongs = false;
170 }
171
172 @Override
173 public void startDocument() {
174 checkInterrupted();
175 wasAllSongs = false;
176 }
177
178 @Override
179 public void startElement(String uri, String localName, String name,
180 Attributes attributes) throws SAXException {
181 checkInterrupted();
182 if (localName.equals("AllSongs")) {
183 wasAllSongs = true;
184 } else if (localName.equals("Track")) {
185 if (!wasAllSongs) {
186 throw new SAXException("AllSongs element not found");
187 }
188 handleTrack(attributes);
189 } else {
190 throw new SAXException("Unknown element " + localName);
191 }
192 }
193 }
194
195 public TrackOriginEnum getOrigin() {
196 return TrackOriginEnum.Magnatune;
197 }
198
199 /***
200 * Stores the tag into the db.
201 *
202 * @param tag
203 * the tag to store.
204 */
205 private void storeTag(TrackMetadataBean tag) {
206
207 final Set<Long> genreIds = genreCache.register(tag.getGenre());
208
209 library.backend.registerNewTrack(tag, genreIds);
210 }
211
212 private void handleTrack(Attributes attributes) {
213 final Builder track = TrackMetadataBean.newBuilder();
214 track.artist = MiscUtils.fixArtistAlbumName(attributes.getValue("",
215 "artist"));
216 track.album = MiscUtils
217 .fixArtistAlbumName(attributes.getValue("album"));
218 track.title = attributes.getValue("title");
219 track.trackNumber = attributes.getValue("tracknum");
220 track.yearReleased = attributes.getValue("year");
221 track.genre = attributes.getValue("genre");
222 track.length = Integer.parseInt(attributes.getValue("seconds"));
223 track.location = attributes.getValue("url");
224 track.origin = TrackOriginEnum.Magnatune;
225
226
227
228
229
230 track.buyURL = attributes.getValue("buy");
231 track.artistDesc = attributes.getValue("artistdesc");
232 track.artistURL = attributes.getValue("home");
233 track.license = attributes.getValue("license");
234 final TrackMetadataBean bean = track.build(-1);
235 storeTag(bean);
236 }
237
238 private boolean userNotified = false;
239
240 public boolean isUserNotified() {
241 return userNotified;
242 }
243 }