View Javadoc

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.collection.ampache;
20  
21  import java.io.IOException;
22  import java.util.Collection;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  
27  import org.xml.sax.SAXException;
28  
29  import sk.baka.ambient.activity.AmpacheClientBean;
30  import sk.baka.ambient.collection.CategoryEnum;
31  import sk.baka.ambient.collection.CategoryItem;
32  import sk.baka.ambient.collection.CollectionException;
33  import sk.baka.ambient.collection.CollectionUtils;
34  import sk.baka.ambient.collection.ICollection;
35  import sk.baka.ambient.collection.IDynamicPlaylistTrackProvider;
36  import sk.baka.ambient.collection.Statistics;
37  import sk.baka.ambient.collection.TrackMetadataBean;
38  import sk.baka.ambient.commons.MiscUtils;
39  import sk.baka.ambient.playlist.Random;
40  
41  /***
42   * Wraps Ampache as a collection.
43   * 
44   * @author Martin Vysny
45   */
46  public final class AmpacheCollection implements ICollection {
47  	public List<TrackMetadataBean> findTracks(Map<CategoryEnum, String> criteria) throws CollectionException {
48  		if (criteria.isEmpty()) {
49  			throw new IllegalArgumentException("Empty criteria");
50  		}
51  		final String firstValue = criteria.values().iterator().next();
52  		try {
53  			connect();
54  			// temporary implementation until the new Ampache API is
55  			// implemented.
56  			return client.searchTracks(firstValue);
57  		} catch (Exception e) {
58  			throw new CollectionException(e);
59  		}
60  	}
61  
62  	private volatile AmpacheClient client;
63  	/***
64  	 * The user, may be <code>null</code>
65  	 */
66  	private volatile String user;
67  	/***
68  	 * Password.
69  	 */
70  	private volatile String password;
71  
72  	/***
73  	 * Creates new wrapper.
74  	 * 
75  	 * @param client
76  	 *            client instance, may not be connected.
77  	 * @param user
78  	 *            The user, may be <code>null</code>
79  	 * @param password
80  	 *            password.
81  	 */
82  	public AmpacheCollection(final AmpacheClient client, final String user,
83  			final String password) {
84  		this.client = client;
85  		this.user = user;
86  		this.password = password;
87  	}
88  
89  	/***
90  	 * Reinitializes this object with new username and password. Connection is
91  	 * performed lazily. Reset is performed only when host, user or password was
92  	 * changed.
93  	 * 
94  	 * @param bean
95  	 *            the client data.
96  	 */
97  	public void reset(final AmpacheClientBean bean) {
98  		final String url = bean.getURL();
99  		if ((client == null) || !MiscUtils.nullEquals(url, client.serverURL)) {
100 			client = new AmpacheClient(url);
101 			connected = false;
102 		}
103 		if (!MiscUtils.nullEquals(this.password, bean.password)
104 				|| !MiscUtils.nullEquals(this.user, bean.username)) {
105 			this.user = bean.username;
106 			this.password = bean.password;
107 			connected = false;
108 		}
109 	}
110 
111 	/***
112 	 * If <code>true</code> then the client is connected.
113 	 */
114 	private volatile boolean connected = false;
115 
116 	private AmpacheInfo connect() throws AmpacheException, IOException,
117 			SAXException {
118 		if (!connected) {
119 			final AmpacheInfo result = client.connect(user, password);
120 			connected = true;
121 			return result;
122 		}
123 		return client.getInfo();
124 	}
125 
126 	@SuppressWarnings("incomplete-switch")
127 	public List<CategoryItem> getCategoryList(final CategoryEnum request,
128 			final CategoryItem context, final String substring,
129 			final boolean sortByYear) throws CollectionException {
130 		try {
131 			connect();
132 			final String id = context == null ? null : (String) context.id;
133 			List<CategoryItem> result = null;
134 			switch (request) {
135 			case Album: {
136 				if (context == null) {
137 					result = client.getAlbums(substring);
138 				} else if (context.category == CategoryEnum.Genre) {
139 					result = client.getGenreAlbums(id, substring);
140 				} else if (context.category == CategoryEnum.Artist) {
141 					result = client.getArtistAlbums(id, substring);
142 				}
143 			}
144 				break;
145 			case Artist: {
146 				if (context == null) {
147 					result = client.getArtists(substring);
148 				} else if (context.category == CategoryEnum.Genre) {
149 					result = client.getGenreArtists(id, substring);
150 				}
151 			}
152 				break;
153 			case Genre: {
154 				if (context == null) {
155 					result = client.getGenres(substring);
156 				}
157 			}
158 				break;
159 			}
160 			if (result == null) {
161 				throw new IllegalArgumentException(
162 						"Unsupported category search from "
163 								+ (context == null ? "null" : context.category)
164 								+ " to " + request);
165 			}
166 			if (sortByYear) {
167 				CollectionUtils.sortByYearKey(result);
168 			} else {
169 				CollectionUtils.sortByKey(result);
170 			}
171 			return result;
172 		} catch (Exception ex) {
173 			throw new CollectionException(ex);
174 		}
175 	}
176 
177 	public boolean isLocal() {
178 		return false;
179 	}
180 
181 	public List<TrackMetadataBean> getTracks(CategoryItem context)
182 			throws CollectionException {
183 		final List<TrackMetadataBean> result;
184 		try {
185 			connect();
186 			switch (context.category) {
187 			case Genre:
188 				result = client.getGenreSongs((String) context.id);
189 				break;
190 			case Album:
191 				result = client.getAlbumSongs((String) context.id);
192 				break;
193 			case Artist:
194 				result = client.getArtistSongs((String) context.id);
195 				break;
196 			default:
197 				throw new IllegalArgumentException("Cannot return tracks for "
198 						+ context.category);
199 			}
200 		} catch (Exception ex) {
201 			throw new CollectionException(ex);
202 		}
203 		return result;
204 	}
205 
206 	public List<TrackMetadataBean> findTracks(String substring)
207 			throws CollectionException {
208 		try {
209 			connect();
210 			return client.searchTracks(substring);
211 		} catch (Exception ex) {
212 			throw new CollectionException(ex);
213 		}
214 	}
215 
216 	public String getName() {
217 		return "Ampache";
218 	}
219 
220 	public Statistics getStatistics() throws CollectionException {
221 		try {
222 			connect();
223 		} catch (Exception e) {
224 			throw new CollectionException(e);
225 		}
226 		final Statistics result = new Statistics();
227 		result.albums = client.getInfo().albums;
228 		result.artists = client.getInfo().artists;
229 		result.tracks = client.getInfo().songs;
230 		return result;
231 	}
232 
233 	public CategoryItem deserializeItem(String serializedItem) {
234 		return new CategoryItem(serializedItem, null, null,
235 				CategoryEnum.Origin, -1, -1);
236 	}
237 
238 	public String serializeItem(CategoryItem item) {
239 		return (String) item.id;
240 	}
241 
242 	public IDynamicPlaylistTrackProvider newTrackProvider(final Random random) {
243 		return null;
244 	}
245 
246 	public Map<String, String> fixLocations(Collection<String> locations)
247 			throws CollectionException {
248 		if (locations.isEmpty()) {
249 			throw new IllegalArgumentException("locations is empty");
250 		}
251 		// try to reconnect and obtain a new token
252 		final String token;
253 		try {
254 			connected = false;
255 			token = connect().token;
256 		} catch (Exception e) {
257 			throw new CollectionException("Failed to connect: "
258 					+ e.getMessage(), e);
259 		}
260 		// okay. replace all ?sid= (or ?auth= in case of our embedded server)
261 		final Map<String, String> result = new HashMap<String, String>();
262 		for (final String loc : locations) {
263 			final String fixedLoc = fixLoc(loc, token);
264 			result.put(loc, fixedLoc);
265 		}
266 		return result;
267 	}
268 
269 	private String fixLoc(String loc, String token) {
270 		int oldTokenStart = loc.indexOf("?sid=");
271 		if (oldTokenStart >= 0) {
272 			oldTokenStart += "?sid=".length();
273 		} else {
274 			oldTokenStart = loc.indexOf("?auth=");
275 			if (oldTokenStart >= 0) {
276 				oldTokenStart += "?auth=".length();
277 			}
278 		}
279 		if (oldTokenStart < 0) {
280 			return loc;
281 		}
282 		int oldTokenEnd = oldTokenStart;
283 		for (; oldTokenEnd < loc.length(); oldTokenEnd++) {
284 			if (loc.charAt(oldTokenEnd) == '&') {
285 				break;
286 			}
287 		}
288 		if (oldTokenEnd == oldTokenStart) {
289 			return loc;
290 		}
291 		return loc.substring(0, oldTokenStart) + token
292 				+ loc.substring(oldTokenEnd);
293 	}
294 
295 	public boolean supportsLocationFix() {
296 		return true;
297 	}
298 }