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
55
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
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
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 }