001/* 002 * Copyright (c) 2012, the Last.fm Java Project and Committers 003 * All rights reserved. 004 * 005 * Redistribution and use of this software in source and binary forms, with or without modification, are 006 * permitted provided that the following conditions are met: 007 * 008 * - Redistributions of source code must retain the above 009 * copyright notice, this list of conditions and the 010 * following disclaimer. 011 * 012 * - Redistributions in binary form must reproduce the above 013 * copyright notice, this list of conditions and the 014 * following disclaimer in the documentation and/or other 015 * materials provided with the distribution. 016 * 017 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 018 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 019 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 020 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 021 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 022 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 023 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 024 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 025 */ 026package de.umass.lastfm; 027 028import java.util.ArrayList; 029import java.util.Collection; 030import java.util.Collections; 031import java.util.Date; 032import java.util.HashMap; 033import java.util.List; 034import java.util.Locale; 035import java.util.Map; 036 037import de.umass.lastfm.scrobble.IgnoredMessageCode; 038import de.umass.lastfm.scrobble.ScrobbleData; 039import de.umass.lastfm.scrobble.ScrobbleResult; 040import de.umass.util.MapUtilities; 041import de.umass.util.StringUtilities; 042import de.umass.xml.DomElement; 043 044/** 045 * Bean that contains information related to <code>Track</code>s and provides bindings to methods 046 * in the <code>track.</code> namespace. 047 * 048 * @author Janni Kovacs 049 */ 050public class Track extends MusicEntry { 051 052 private enum ScrobbleResultType { 053 NOW_PLAYING, 054 SINGLE_SCROBBLE, 055 MULTIPLE_SCROBBLES 056 } 057 058 static final ItemFactory<Track> FACTORY = new TrackFactory(); 059 060 public static final String ARTIST_PAGE = "artistpage"; 061 public static final String ALBUM_PAGE = "albumpage"; 062 public static final String TRACK_PAGE = "trackpage"; 063 064 private String artist; 065 private String artistMbid; 066 067 protected String album; // protected for use in Playlist.playlistFromElement 068 private String albumMbid; 069 private int position = -1; 070 071 private boolean fullTrackAvailable; 072 private boolean nowPlaying; 073 074 private Date playedWhen; 075 protected int duration; // protected for use in Playlist.playlistFromElement 076 protected String location; // protected for use in Playlist.playlistFromElement 077 //protected int playcount = 0; 078 079 protected Map<String, String> lastFmExtensionInfos = new HashMap<String, String>(); // protected for use in Playlist.playlistFromElement 080 081 082 protected Track(String name, String url, String artist) { 083 super(name, url); 084 this.artist = artist; 085 } 086 087 protected Track(String name, String url, String mbid, int playcount, int listeners, boolean streamable, 088 String artist, String artistMbid, boolean fullTrackAvailable, boolean nowPlaying) { 089 super(name, url, mbid, playcount, listeners, streamable); 090 this.artist = artist; 091 this.artistMbid = artistMbid; 092 this.fullTrackAvailable = fullTrackAvailable; 093 this.nowPlaying = nowPlaying; 094 } 095 096 /** 097 * Returns the duration of the song, if available, in seconds. The duration attribute is only available 098 * for tracks retrieved by {@link Playlist#fetch(String, String) Playlist.fetch} and 099 * {@link Track#getInfo(String, String, String) Track.getInfo}. 100 * 101 * @return duration in seconds 102 */ 103 public int getDuration() { 104 return duration; 105 } 106 107 public String getArtist() { 108 return artist; 109 } 110 111 public String getArtistMbid() { 112 return artistMbid; 113 } 114 115 public String getAlbum() { 116 return album; 117 } 118 119 public String getAlbumMbid() { 120 return albumMbid; 121 } 122 123 public boolean isFullTrackAvailable() { 124 return fullTrackAvailable; 125 } 126 127 public boolean isNowPlaying() { 128 return nowPlaying; 129 } 130 131 /** 132 * Set Method for class field 'playcount'. 133 * 134 * @param playcount is the value to set this class field to. 135 * 136 **/ 137/* public void setPlaycount(int playcount) 138 { 139 this.playcount = playcount; 140 } */ // setPlaycount Method 141 142 143 /** 144 * Get Method for class field 'playcount'. 145 * 146 * @return int - The value the class field 'playcount'. 147 * 148 **/ 149/* public int getPlaycount() 150 { 151 return playcount; 152 } */ // getPlaycount Method 153 154 155 /** 156 * Returns the location (URL) of this Track. This information is only available with the {@link Radio} services. 157 * 158 * @return the location 159 */ 160 public String getLocation() { 161 return location; 162 } 163 164 /** 165 * Returns last.fm specific information about this Track. Only available in Tracks fetched from 166 * radio playlists. <tt>key</tt> can be one of the following: 167 * <ul> 168 * <li>artistpage</li> 169 * <li>albumpage</li> 170 * <li>trackpage</li> 171 * <li>buyTrackURL</li> 172 * <li>buyAlbumURL</li> 173 * <li>freeTrackURL</li> 174 * </ul> 175 * Or use the available constants in this class.<br/> 176 * Note that the key string is case sensitive. 177 * 178 * @param key A key 179 * @return associated value 180 * @see #ARTIST_PAGE 181 * @see #ALBUM_PAGE 182 * @see #TRACK_PAGE 183 */ 184 public String getLastFmInfo(String key) { 185 return lastFmExtensionInfos.get(key); 186 } 187 188 /** 189 * Returns the time when the track was played, if this data is available (e.g. for recent tracks) or <code>null</code>, 190 * if this data is not available.<br/> 191 * 192 * @return the date when the track was played or <code>null</code> 193 */ 194 public Date getPlayedWhen() { 195 return playedWhen; 196 } 197 198 /** 199 * Returns the position of this track in its associated album, or -1 if not available. 200 * 201 * @return the album position 202 */ 203 public int getPosition() { 204 return position; 205 } 206 207 /** 208 * Searches for a track with the given name and returns a list of possible matches. 209 * 210 * @param track Track name 211 * @param apiKey The API key 212 * @return a list of possible matches 213 * @see #search(String, String, int, String) 214 */ 215 public static Collection<Track> search(String track, String apiKey) { 216 return search(null, track, 30, apiKey); 217 } 218 219 /** 220 * Searches for a track with the given name and returns a list of possible matches. 221 * Specify an artist name or a limit to narrow down search results. 222 * Pass <code>null</code> for the artist parameter if you want to specify a limit but don't want 223 * to define an artist. 224 * 225 * @param artist Artist's name or <code>null</code> 226 * @param track Track name 227 * @param limit Number of maximum results 228 * @param apiKey The API key 229 * @return a list of possible matches 230 */ 231 public static Collection<Track> search(String artist, String track, int limit, String apiKey) { 232 Map<String, String> params = new HashMap<String, String>(); 233 params.put("track", track); 234 params.put("limit", String.valueOf(limit)); 235 MapUtilities.nullSafePut(params, "artist", artist); 236 Result result = Caller.getInstance().call("track.search", apiKey, params); 237 if(!result.isSuccessful()) 238 return Collections.emptyList(); 239 DomElement element = result.getContentElement(); 240 DomElement matches = element.getChild("trackmatches"); 241 return ResponseBuilder.buildCollection(matches, Track.class); 242 } 243 244 /** 245 * Retrieves the top tags for the given track. You either have to specify a track and artist name or 246 * a mbid. If you specify an mbid you may pass <code>null</code> for the first parameter. 247 * 248 * @param artist Artist name or <code>null</code> if an MBID is specified 249 * @param trackOrMbid Track name or MBID 250 * @param apiKey The API key 251 * @return list of tags 252 */ 253 public static Collection<Tag> getTopTags(String artist, String trackOrMbid, String apiKey) { 254 Map<String, String> params = new HashMap<String, String>(); 255 if (StringUtilities.isMbid(trackOrMbid)) { 256 params.put("mbid", trackOrMbid); 257 } else { 258 params.put("artist", artist); 259 params.put("track", trackOrMbid); 260 } 261 Result result = Caller.getInstance().call("track.getTopTags", apiKey, params); 262 return ResponseBuilder.buildCollection(result, Tag.class); 263 } 264 265 /** 266 * Retrieves the top fans for the given track. You either have to specify a track and artist name or 267 * a mbid. If you specify an mbid you may pass <code>null</code> for the first parameter. 268 * 269 * @param artist Artist name or <code>null</code> if an MBID is specified 270 * @param trackOrMbid Track name or MBID 271 * @param apiKey The API key 272 * @return list of fans 273 */ 274 public static Collection<User> getTopFans(String artist, String trackOrMbid, String apiKey) { 275 Map<String, String> params = new HashMap<String, String>(); 276 if (StringUtilities.isMbid(trackOrMbid)) { 277 params.put("mbid", trackOrMbid); 278 } else { 279 params.put("artist", artist); 280 params.put("track", trackOrMbid); 281 } 282 Result result = Caller.getInstance().call("track.getTopFans", apiKey, params); 283 return ResponseBuilder.buildCollection(result, User.class); 284 } 285 286 /** 287 * Tag an album using a list of user supplied tags. 288 * 289 * @param artist The artist name in question 290 * @param track The track name in question 291 * @param tags A comma delimited list of user supplied tags to apply to this track. Accepts a maximum of 10 tags. 292 * @param session A Session instance. 293 * @return the Result of the operation 294 */ 295 public static Result addTags(String artist, String track, String tags, Session session) { 296 return Caller.getInstance().call("track.addTags", session, "artist", artist, "track", track, "tags", tags); 297 } 298 299 /** 300 * Remove a user's tag from a track. 301 * 302 * @param artist The artist name in question 303 * @param track The track name in question 304 * @param tag A single user tag to remove from this track. 305 * @param session A Session instance. 306 * @return the Result of the operation 307 */ 308 public static Result removeTag(String artist, String track, String tag, Session session) { 309 return Caller.getInstance().call("track.removeTag", session, "artist", artist, "track", track, "tag", tag); 310 } 311 312 /** 313 * Share a track twith one or more Last.fm users or other friends. 314 * 315 * @param artist An artist name. 316 * @param track A track name. 317 * @param message A message to send with the recommendation or <code>null</code>. If not supplied a default message will be used. 318 * @param recipient A comma delimited list of email addresses or Last.fm usernames. Maximum is 10. 319 * @param session A Session instance 320 * @return the Result of the operation 321 */ 322 public static Result share(String artist, String track, String message, String recipient, Session session) { 323 Map<String, String> params = StringUtilities.map("artist", artist, "track", track, "recipient", recipient); 324 MapUtilities.nullSafePut(params, "message", message); 325 return Caller.getInstance().call("track.share", session, params); 326 } 327 328 329 /** 330 * Love a track for a user profile. 331 * 332 * @param artist An artist name 333 * @param track A track name 334 * @param session A Session instance 335 * @return the Result of the operation 336 */ 337 public static Result love(String artist, String track, Session session) { 338 return Caller.getInstance().call("track.love", session, "artist", artist, "track", track); 339 } 340 341 342 /** 343 * UnLove a track for a user profile. 344 * 345 * @param artist An artist name 346 * @param track A track name 347 * @param session A Session instance 348 * @return the Result of the operation 349 */ 350 public static Result unlove(String artist, String track, Session session) { 351 return Caller.getInstance().call("track.unlove", session, "artist", artist, "track", track); 352 } 353 354 355 /** 356 * Ban a track for a given user profile. 357 * 358 * @param artist An artist name 359 * @param track A track name 360 * @param session A Session instance 361 * @return the Result of the operation 362 */ 363 public static Result ban(String artist, String track, Session session) { 364 return Caller.getInstance().call("track.ban", session, "artist", artist, "track", track); 365 } 366 367 /** 368 * UnBan a track for a given user profile. 369 * 370 * @param artist An artist name 371 * @param track A track name 372 * @param session A Session instance 373 * @return the Result of the operation 374 */ 375 public static Result unban(String artist, String track, Session session) { 376 return Caller.getInstance().call("track.unban", session, "artist", artist, "track", track); 377 } 378 379 /** 380 * Get the similar tracks for this track on Last.fm, based on listening data.<br/> 381 * You have to provide either an artist and a track name <i>or</i> an mbid. Pass <code>null</code> 382 * for parameters you don't need. 383 * 384 * @param artist The artist name in question 385 * @param trackOrMbid The track name in question or the track's MBID 386 * @param apiKey A Last.fm API key. 387 * @return a list of similar <code>Track</code>s 388 */ 389 public static Collection<Track> getSimilar(String artist, String trackOrMbid, String apiKey) { 390 return getSimilar(artist, trackOrMbid, apiKey, 100); 391 } 392 393 394 /** 395 * Get the similar tracks for this track on Last.fm, based on listening data.<br/> 396 * You have to provide either an artist and a track name <i>or</i> an mbid. Pass <code>null</code> 397 * for parameters you don't need. 398 * 399 * @param artist The artist name in question 400 * @param trackOrMbid The track name in question or the track's MBID 401 * @param apiKey A Last.fm API key. 402 * @param limit number of results to return 403 * @return a list of similar <code>Track</code>s 404 */ 405 public static Collection<Track> getSimilar(String artist, String trackOrMbid, String apiKey, int limit) { 406 Map<String, String> params = new HashMap<String, String>(); 407 if (StringUtilities.isMbid(trackOrMbid)) { 408 params.put("mbid", trackOrMbid); 409 } else { 410 params.put("artist", artist); 411 params.put("track", trackOrMbid); 412 } 413 414 if (limit > 0){ 415 params.put("limit", "" + limit); 416 } 417 Result result = Caller.getInstance().call("track.getSimilar", apiKey, params); 418 return ResponseBuilder.buildCollection(result, Track.class); 419 } 420 421 /** 422 * Get the tags applied by an individual user to an track on Last.fm. 423 * 424 * @param artist The artist name in question 425 * @param track The track name in question 426 * @param session A Session instance 427 * @return a list of tags 428 */ 429 public static Collection<String> getTags(String artist, String track, Session session) { 430 Result result = Caller.getInstance().call("track.getTags", session, "artist", artist, "track", track); 431 DomElement element = result.getContentElement(); 432 Collection<String> tags = new ArrayList<String>(); 433 for (DomElement domElement : element.getChildren("tag")) { 434 tags.add(domElement.getChildText("name")); 435 } 436 return tags; 437 } 438 439 /** 440 * Get the metadata for a track on Last.fm using the artist/track name or a musicbrainz id. 441 * 442 * @param artist The artist name in question or <code>null</code> if an mbid is specified 443 * @param trackOrMbid The track name in question or the musicbrainz id for the track 444 * @param apiKey A Last.fm API key. 445 * @return Track information 446 */ 447 public static Track getInfo(String artist, String trackOrMbid, String apiKey) { 448 return getInfo(artist, trackOrMbid, null, null, apiKey); 449 } 450 451 /** 452 * Get the metadata for a track on Last.fm using the artist/track name or a musicbrainz id. 453 * 454 * @param artist The artist name in question or <code>null</code> if an mbid is specified 455 * @param trackOrMbid The track name in question or the musicbrainz id for the track 456 * @param locale The language to fetch info in, or <code>null</code> 457 * @param username The username for the context of the request, or <code>null</code>. If supplied, the user's playcount for this track and whether they have loved the track is included in the response 458 * @param apiKey A Last.fm API key. 459 * @return Track information 460 */ 461 public static Track getInfo(String artist, String trackOrMbid, Locale locale, String username, String apiKey) { 462 Map<String, String> params = new HashMap<String, String>(); 463 if (StringUtilities.isMbid(trackOrMbid)) { 464 params.put("mbid", trackOrMbid); 465 } else { 466 params.put("artist", artist); 467 params.put("track", trackOrMbid); 468 } 469 if (locale != null && locale.getLanguage().length() != 0) { 470 params.put("lang", locale.getLanguage()); 471 } 472 MapUtilities.nullSafePut(params, "username", username); 473 Result result = Caller.getInstance().call("track.getInfo", apiKey, params); 474 if (!result.isSuccessful()) 475 return null; 476 DomElement content = result.getContentElement(); 477 DomElement album = content.getChild("album"); 478 Track track = FACTORY.createItemFromElement(content); 479 if (album != null) { 480 String pos = album.getAttribute("position"); 481 if ((pos != null) && pos.length() != 0) { 482 track.position = Integer.parseInt(pos); 483 } 484 track.album = album.getChildText("title"); 485 track.albumMbid = album.getChildText("mbid"); 486 ImageHolder.loadImages(track, album); 487 } 488 return track; 489 } 490 491 /** 492 * Get a list of Buy Links for a particular Track. It is required that you supply either the artist and track params or the mbid param. 493 * 494 * @param artist The artist name in question 495 * @param albumOrMbid Track name or MBID 496 * @param country A country name, as defined by the ISO 3166-1 country names standard 497 * @param apiKey A Last.fm API key 498 * @return a Collection of {@link BuyLink}s 499 */ 500 public static Collection<BuyLink> getBuylinks(String artist, String albumOrMbid, String country, String apiKey) { 501 Map<String, String> params = new HashMap<String, String>(); 502 if (StringUtilities.isMbid(albumOrMbid)) { 503 params.put("mbid", albumOrMbid); 504 } else { 505 params.put("artist", artist); 506 params.put("album", albumOrMbid); 507 } 508 params.put("country", country); 509 Result result = Caller.getInstance().call("track.getBuylinks", apiKey, params); 510 if (!result.isSuccessful()) 511 return Collections.emptyList(); 512 DomElement element = result.getContentElement(); 513 DomElement physicals = element.getChild("physicals"); 514 DomElement downloads = element.getChild("downloads"); 515 Collection<BuyLink> links = new ArrayList<BuyLink>(); 516 for (DomElement e : physicals.getChildren("affiliation")) { 517 links.add(BuyLink.linkFromElement(BuyLink.StoreType.PHYSICAl, e)); 518 } 519 for (DomElement e : downloads.getChildren("affiliation")) { 520 links.add(BuyLink.linkFromElement(BuyLink.StoreType.DIGITAL, e)); 521 } 522 return links; 523 } 524 525 /** 526 * Use the last.fm corrections data to check whether the supplied track has a correction to a canonical track. This method returns a new 527 * {@link Track} object containing the corrected data, or <code>null</code> if the supplied Artist/Track combination was not found. 528 * 529 * @param artist The artist name to correct 530 * @param track The track name to correct 531 * @param apiKey A Last.fm API key 532 * @return a new {@link Track}, or <code>null</code> 533 */ 534 public static Track getCorrection(String artist, String track, String apiKey) { 535 Result result = Caller.getInstance().call("track.getCorrection", apiKey, "artist", artist, "track", track); 536 if (!result.isSuccessful()) 537 return null; 538 DomElement correctionElement = result.getContentElement().getChild("correction"); 539 if (correctionElement == null) 540 return new Track(track, null, artist); 541 DomElement trackElem = correctionElement.getChild("track"); 542 return FACTORY.createItemFromElement(trackElem); 543 } 544 545 /** 546 * Get shouts for a track. 547 * 548 * @param artist The artist name 549 * @param trackOrMbid The track name or a mausicbrainz id 550 * @param apiKey A Last.fm API key. 551 * @return a page of <code>Shout</code>s 552 */ 553 public static PaginatedResult<Shout> getShouts(String artist, String trackOrMbid, String apiKey) { 554 return getShouts(artist, trackOrMbid, -1, -1, apiKey); 555 } 556 557 /** 558 * Get shouts for a track. 559 * 560 * @param artist The artist name 561 * @param trackOrMbid The track name or a mausicbrainz id 562 * @param page The page number to fetch 563 * @param apiKey A Last.fm API key. 564 * @return a page of <code>Shout</code>s 565 */ 566 public static PaginatedResult<Shout> getShouts(String artist, String trackOrMbid, int page, String apiKey) { 567 return getShouts(artist, trackOrMbid, page, -1, apiKey); 568 } 569 570 /** 571 * Get shouts for a track. 572 * 573 * @param artist The artist name 574 * @param trackOrMbid The track name or a mausicbrainz id 575 * @param page The page number to fetch 576 * @param limit An integer used to limit the number of shouts returned per page or -1 for default 577 * @param apiKey A Last.fm API key. 578 * @return a page of <code>Shout</code>s 579 */ 580 public static PaginatedResult<Shout> getShouts(String artist, String trackOrMbid, int page, int limit, String apiKey) { 581 Map<String, String> params = new HashMap<String, String>(); 582 if (StringUtilities.isMbid(trackOrMbid)) { 583 params.put("mbid", trackOrMbid); 584 } else { 585 params.put("artist", artist); 586 params.put("track", trackOrMbid); 587 } 588 MapUtilities.nullSafePut(params, "limit", limit); 589 MapUtilities.nullSafePut(params, "page", page); 590 Result result = Caller.getInstance().call("track.getShouts", apiKey, params); 591 return ResponseBuilder.buildPaginatedResult(result, Shout.class); 592 } 593 594 /** 595 * Converts a generic web services Result object into a more specific ScrobbleResult. 596 * 597 * @param result Web services Result. 598 * @param scrobbleResultType The type of scrobble result contained within the Result. 599 * @return A ScrobbleResult containing the original Result information plus extra fields specific to scrobble and now playing results. 600 */ 601 private static List<ScrobbleResult> convertToScrobbleResults(Result result, ScrobbleResultType scrobbleResultType) { 602 List<ScrobbleResult> scrobbleResults = new ArrayList<ScrobbleResult>(); 603 if (!result.isSuccessful()) { 604 // if result failed then we have no extra information 605 ScrobbleResult scrobbleResult = new ScrobbleResult(result); 606 scrobbleResults.add(scrobbleResult); 607 } else { 608 DomElement element = result.getContentElement(); 609 if (scrobbleResultType == ScrobbleResultType.NOW_PLAYING) { 610 ScrobbleResult scrobbleResult = new ScrobbleResult(result); 611 parseIntoScrobbleResult(element, scrobbleResult); 612 scrobbleResults.add(scrobbleResult); 613 } else if (scrobbleResultType == ScrobbleResultType.SINGLE_SCROBBLE) { 614 ScrobbleResult scrobbleResult = new ScrobbleResult(result); 615 parseIntoScrobbleResult(element.getChild("scrobble"), scrobbleResult); 616 scrobbleResults.add(scrobbleResult); 617 } else if (scrobbleResultType == ScrobbleResultType.MULTIPLE_SCROBBLES) { 618 for (DomElement scrobbleElement : element.getChildren("scrobble")) { 619 ScrobbleResult scrobbleResult = new ScrobbleResult(result); 620 parseIntoScrobbleResult(scrobbleElement, scrobbleResult); 621 scrobbleResults.add(scrobbleResult); 622 } 623 } 624 } 625 return scrobbleResults; 626 } 627 628 /** 629 * Parses a DomElement containing scrobble or now playing response data into the passed ScrobbleResult. 630 * 631 * @param scrobbleElement DomElement containing scrobble or now playing response data. 632 * @param scrobbleResult ScrobbleResult to add the response data to. 633 */ 634 private static void parseIntoScrobbleResult(DomElement scrobbleElement, ScrobbleResult scrobbleResult) { 635 DomElement trackElement = scrobbleElement.getChild("track"); 636 scrobbleResult.setTrack(trackElement.getText()); 637 scrobbleResult.setArtistCorrected(StringUtilities.convertToBoolean(trackElement.getAttribute("corrected"))); 638 639 DomElement artistElement = scrobbleElement.getChild("artist"); 640 scrobbleResult.setArtist(artistElement.getText()); 641 scrobbleResult.setArtistCorrected(StringUtilities.convertToBoolean(artistElement.getAttribute("corrected"))); 642 643 DomElement albumElement = scrobbleElement.getChild("album"); 644 scrobbleResult.setAlbum(albumElement.getText()); 645 scrobbleResult.setAlbumCorrected(StringUtilities.convertToBoolean(albumElement.getAttribute("corrected"))); 646 647 DomElement albumArtistElement = scrobbleElement.getChild("albumArtist"); 648 scrobbleResult.setAlbumArtist(albumArtistElement.getText()); 649 scrobbleResult.setAlbumArtistCorrected(StringUtilities.convertToBoolean(albumArtistElement.getAttribute("corrected"))); 650 651 String timeString = scrobbleElement.getChildText("timestamp"); 652 if (timeString != null) { 653 // will be non-null for scrobble results only 654 scrobbleResult.setTimestamp(Integer.parseInt(timeString)); 655 } 656 657 DomElement ignoredMessageElement = scrobbleElement.getChild("ignoredMessage"); 658 int ignoredMessageCode = Integer.parseInt(ignoredMessageElement.getAttribute("code")); 659 if (ignoredMessageCode > 0) { 660 scrobbleResult.setIgnored(true); 661 scrobbleResult.setIgnoredMessageCode(IgnoredMessageCode.valueOfCode(ignoredMessageCode)); 662 scrobbleResult.setIgnoredMessage(ignoredMessageElement.getText()); 663 } 664 } 665 666 public static ScrobbleResult scrobble(ScrobbleData scrobbleData, Session session) { 667 Map<String, String> params = new HashMap<String, String>(); 668 // required params 669 params.put("artist", scrobbleData.getArtist()); 670 params.put("track", scrobbleData.getTrack()); 671 params.put("timestamp", String.valueOf(scrobbleData.getTimestamp())); 672 // optional params 673 MapUtilities.nullSafePut(params, "album", scrobbleData.getAlbum()); 674 MapUtilities.nullSafePut(params, "albumArtist", scrobbleData.getAlbumArtist()); 675 MapUtilities.nullSafePut(params, "duration", scrobbleData.getDuration()); 676 MapUtilities.nullSafePut(params, "mbid", scrobbleData.getMusicBrainzId()); 677 MapUtilities.nullSafePut(params, "trackNumber", scrobbleData.getTrackNumber()); 678 MapUtilities.nullSafePut(params, "streamId", scrobbleData.getStreamId()); 679 params.put("chosenByUser", StringUtilities.convertFromBoolean(scrobbleData.isChosenByUser())); 680 681 Result result = Caller.getInstance().call("track.scrobble", session, params); 682 return convertToScrobbleResults(result, ScrobbleResultType.SINGLE_SCROBBLE).get(0); 683 } 684 685 public static ScrobbleResult scrobble(String artistName, String trackName, int timestamp, Session session) { 686 ScrobbleData scrobbleData = new ScrobbleData(artistName, trackName, timestamp); 687 return scrobble(scrobbleData, session); 688 } 689 690 public static ScrobbleResult scrobble(String artistName, String albumName, String trackName, int timestamp, Session session) { 691 ScrobbleData scrobbleData = new ScrobbleData(artistName, albumName, trackName, timestamp); 692 return scrobble(scrobbleData, session); 693 } 694 695 public static List<ScrobbleResult> scrobble(List<ScrobbleData> scrobbleData, Session session) { 696 Map<String, String> params = new HashMap<String, String>(); 697 for (int i = 0; i < scrobbleData.size(); i++) { 698 ScrobbleData scrobble = scrobbleData.get(i); 699 // required params 700 params.put("artist[" + i + "]", scrobble.getArtist()); 701 params.put("track[" + i + "]", scrobble.getTrack()); 702 params.put("timestamp[" + i + "]", String.valueOf(scrobble.getTimestamp())); 703 // optional params 704 MapUtilities.nullSafePut(params, "album[" + i + "]", scrobble.getAlbum()); 705 MapUtilities.nullSafePut(params, "albumArtist[" + i + "]", scrobble.getAlbumArtist()); 706 MapUtilities.nullSafePut(params, "duration[" + i + "]", scrobble.getDuration()); 707 MapUtilities.nullSafePut(params, "mbid[" + i + "]", scrobble.getMusicBrainzId()); 708 MapUtilities.nullSafePut(params, "trackNumber[" + i + "]", scrobble.getTrackNumber()); 709 MapUtilities.nullSafePut(params, "streamId[" + i + "]", scrobble.getStreamId()); 710 params.put("chosenByUser[" + i + "]", StringUtilities.convertFromBoolean(scrobble.isChosenByUser())); 711 } 712 713 Result result = Caller.getInstance().call("track.scrobble", session, params); 714 return convertToScrobbleResults(result, ScrobbleResultType.MULTIPLE_SCROBBLES); 715 } 716 717 public static ScrobbleResult updateNowPlaying(ScrobbleData scrobbleData, Session session) { 718 Map<String, String> params = new HashMap<String, String>(); 719 // required params 720 params.put("artist", scrobbleData.getArtist()); 721 params.put("track", scrobbleData.getTrack()); 722 // optional params 723 MapUtilities.nullSafePut(params, "album", scrobbleData.getAlbum()); 724 MapUtilities.nullSafePut(params, "albumArtist", scrobbleData.getAlbumArtist()); 725 MapUtilities.nullSafePut(params, "duration", scrobbleData.getDuration()); 726 MapUtilities.nullSafePut(params, "mbid", scrobbleData.getMusicBrainzId()); 727 MapUtilities.nullSafePut(params, "trackNumber", scrobbleData.getTrackNumber()); 728 MapUtilities.nullSafePut(params, "streamId", scrobbleData.getStreamId()); 729 Result result = Caller.getInstance().call("track.updateNowPlaying", session, params); 730 return convertToScrobbleResults(result, ScrobbleResultType.NOW_PLAYING).get(0); 731 } 732 733 public static ScrobbleResult updateNowPlaying(String artistName, String trackName, Session session) { 734 ScrobbleData scrobbleData = new ScrobbleData(); 735 scrobbleData.setArtist(artistName); 736 scrobbleData.setTrack(trackName); 737 return updateNowPlaying(scrobbleData, session); 738 } 739 740 @Override 741 public String toString() { 742 return "Track[name=" + name + ",artist=" + artist + ", album=" + album + ", position=" + position + ", duration=" + duration 743 + ", location=" + location + ", nowPlaying=" + nowPlaying + ", playcount=" + playcount + ", fullTrackAvailable=" + fullTrackAvailable + ", playedWhen=" 744 + playedWhen + ", artistMbId=" + artistMbid + ", albumMbId" + albumMbid + "]"; 745 } 746 747 private static class TrackFactory implements ItemFactory<Track> { 748 public Track createItemFromElement(DomElement element) { 749 Track track = new Track(null, null, null); 750 MusicEntry.loadStandardInfo(track, element); 751 final String nowPlayingAttr = element.getAttribute("nowplaying"); 752 if (nowPlayingAttr != null) 753 track.nowPlaying = Boolean.valueOf(nowPlayingAttr); 754 if (element.hasChild("duration")) { 755 String duration = element.getChildText("duration"); 756 if(duration.length() != 0) { 757 int durationLength = Integer.parseInt(duration); 758 // So it seems last.fm couldn't decide which format to send the duration in. 759 // It's supplied in milliseconds for Playlist.fetch and Track.getInfo but Artist.getTopTracks returns (much saner) seconds 760 // so we're doing a little sanity check for the duration to be over or under 10'000 and decide what to do 761 track.duration = durationLength > 10000 ? durationLength / 1000 : durationLength; 762 } 763 } 764 /* TBG: addition */ 765 /* 766 if (element.hasChild("playcount")) { 767 String playcountStr = element.getChildText("playcount"); 768 if(playcountStr.length() != 0) { 769 track.playcount = Integer.parseInt(playcountStr); 770 } 771 } */ 772 /* TBG: addition */ 773 DomElement album = element.getChild("album"); 774 if (album != null) { 775 track.album = album.getText(); 776 track.albumMbid = album.getAttribute("mbid"); 777 } 778 DomElement artist = element.getChild("artist"); 779 if (artist.getChild("name") != null) { 780 track.artist = artist.getChildText("name"); 781 track.artistMbid = artist.getChildText("mbid"); 782 } else { 783 track.artist = artist.getText(); 784 track.artistMbid = artist.getAttribute("mbid"); 785 } 786 DomElement date = element.getChild("date"); 787 if (date != null) { 788 String uts = date.getAttribute("uts"); 789 long utsTime = Long.parseLong(uts); 790 track.playedWhen = new Date(utsTime * 1000); 791 } 792 DomElement stream = element.getChild("streamable"); 793 if (stream != null) { 794 String s = stream.getAttribute("fulltrack"); 795 track.fullTrackAvailable = s != null && Integer.parseInt(s) == 1; 796 } 797 return track; 798 } 799 } 800}