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