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