pax_global_header00006660000000000000000000000064120013773110014505gustar00rootroot0000000000000052 comment=a0049caf0294815685a27a40864d4d43692dbd82 lastfm-java-lastfm-java-0.1.2/000077500000000000000000000000001200137731100161155ustar00rootroot00000000000000lastfm-java-lastfm-java-0.1.2/.gitignore000066400000000000000000000000531200137731100201030ustar00rootroot00000000000000# intellij files *.iml *.iws *.ipr targetlastfm-java-lastfm-java-0.1.2/changes.txt000066400000000000000000000211371200137731100202720ustar00rootroot00000000000000change-log for last.fm bindings. Please read with each new release. =================================================================== 4.5.2012 - added: User.getPersonalTags (issue 45) - changed: PaginatedResult is now Iterable 24.4.2012 - added: Album#getTracks() 19.4.2012 - added: Event#getAttendanceStatus() 1.4.2012 - added: Radio.search, Radio.getPlaylist parameters (issue 41) 12.2.2012 - changed: some #getEvents methods now return a PaginatedResult instead of a Collection, accept festivalsOnly parameter 25.1.2012 - changed: Playlist#getTracks() now returns a List instead of a Collection 21.12.2011 - changed: made ResponseBuilder public (issue 29) - fixed: DatabaseCache now works with MySQL 5, fixed text encoding bug (issue 33) 11.12.2011 - added Library.removeXXX methods 04.11.2011 - added support for "chosenByUser" parameter to track.scrobble. 12.10.2011 - added: Geo.getEvents with lat/long and distance (issue 32) - fixed: Events without a venue specified (issue 35) 5.7.2011 - changed: Geo.getEvents now accepts limit parameter (issue 26) 31.5.2011 - changed: User.getFriends now returns PaginatedResult instead of a Collection (issue 24) 2.5.2011 - fixed: Tracks returned from getSimilarTracks do not include "match" attribute (issue 20) 22.2.2011 - fixed issue with empty duration elements and also different units of measure (issue 17) 27.1.2011 - added XXX.getShouts methods 2.1.2011 - documentation and code cleanup 6.12.2010 - added: Chart.getHypedXXX and Chart.getTopXXX methods - changed: moved Artist.getPercentageChange to MusicEntry.getPercentageChange - added: PaginatedResult.isEmpty() 14.11.2010 - added: Geo.getMetroXXXChart methods (issue 8) - Refactorings, code cleanup, documentation 06.11.2010 - changed: Track.getTopTags() now returns a more general Collection instead of a List - Refactorings 05.11.2010 - added: Artist.getCorrection - fixed: Track.getCorrection failed if supplied artist/track are already correct or Last.fm can't supply a correction - added: Track.unlove() and Track.unban() - added: Tag.getInfo() - changed: Track.getInfo() now takes a Locale parameter - changed: Artist.getInfo(), Track.getInfo(), Tag.getInfo() now don't pass a language parameter to the webservice if no Locale was specified, instead of the default Locale - added: Geo.getMetros() and Geo.Metro class (issue 8) - added: ItemFactory, ItemFactoryBuilder, ResponseBuilder as well as several implementations of ItemFactory to help remove duplicate code - changed: Tag.getSimilar() now returns a Collection of Tags instead of Strings - changed: Tag.getTopTags() now returns a more general Collection instead of a List - changed: Tag.search() now returns a Collection of Tags instead of Strings - Refactorings in various classes to make use of the new ResponseBuilder - updated ant build script 04.11.2010 - Added trackNumber and streamId params to scrobbling classes. 31.10.2010 - Added license info to all files, updated license.txt - Some documentation changes - fixed: StringUtilities.isMD5() where a string was recognized as possible md5 encoded - added: Scrobbler.setHandshakeURL() (issue 13) - added: Track.getCorrection() (issue 14) - added: Group.getHype() and Artist.getPercentageChange() (issue 9) - added: Album.getTopTags() - added: User.getNewReleases() and User.getBannedTracks() (issue 15) - fixed: Result.getContentElement() returned the wrong element on Android (see issue 12) 26.10.2010 - Added support for scrobbling and now playing requests using the new Last.fm Scrobble 2.0 API (Adrian Woodhead). - Deprecated 1.2.x scrobble protocol classes. 22.4: - fixed: Event.getStartDate() (issue 7) - added: Event.getEndDate() 17.3: - changed: User.getRecentTracks returns PaginatedResult (issue 6) - added: MusicEntry.getUserPlaycount(): returns user's playcount for Album/Artist/Tracks, when a username was supplied on the respective new getInfo() calls. - changed: Session now has private default constructor to precent accidentally instantiation. Use the static methods provided in the Authenticator class. - changed: User.getInfo() changed to an unauthenticated call, now takes username parameter - added: Album.getBuylinks(), Track.getBuylinks() and the BuyLink class - added: Artist.getPastEvents() - added: User.getArtistTracks() - added: Venue.getPhonenumber(), Venue.getWebsite() - added: Venue.getImageUrl() 10.8: - fixed: Explicitly using UTF-8 encoding now 31.7: - changed: User.getLovedTracks now returns a PaginatedResult and accepts a page parameter 15.6: - fixed: User.getNeighbours(user, key) was not working properly 12.6: - added: User.getRealname() method (thanks Marko Luther) 26.6: - added: MEGA size to ImageSize enum - R.I.P. Michael Jackson 19.6: - added: Event.getWebsite(), Event.getTicketSuppliers() 18.6: - fixed: NPE in Track.search() w/o artist parameter 21.5: - fixed: Library.add methods 14.5: - changed: some methods in User changed to public 23.4: - added: Caller.getLastResult 13.4: - changed: User() constructor now private, was public - added: Track.getLastFmInfo 11.4: - added: Track.getPosition (thanks again Marko Luther) 9.4: - changed: implemented Radio 2.0 API 27.3: - fixed: User.getInfo 20.3: - added: Event.getAttendees - added: Artist.getImages - added: Image class, ImageHolder.availableSizes() - added: User.shout, Artist.shout 10.3: - added: ScrobbleCache.clearScrobbleCache - fixed: new SubmissionData(String) ArrayIndexOutOfBoundsException 28.2: - added: ScrobbleCache - added: SubmissionData.toString() SubmissionData(String) - added: FileSystemCache now supports caching scrobbles through the ScrobbleCache interface 9.2: - added: Radio.skipsLeft 5.2: - added: Session.createSession to restore sessions - changed: Radio.handshake and Authenticator.getMobileSession now accept 32-character MD5 string as password parameter 5.1: - added: DatabaseCache - Cache implementation that stores into any jdbc database - added: DomElement.getTagName method 1.1: - changed: Cache.createCacheEntryName now returns md5 hashes by default. 30.12: - fixed: StringUtilities.cleanUp not replacing * lead to caching error (thanks Andrew Collins) 20.12: - Venue promoted to top level class - added: Venue.search, Venue.getEvents, Venue.getPastEvents - added: Venue.getId 11.12: - added: Track.getInfo recognizes duration if available - fixed: Track.getInfo for tracks with no album (thanks Robin Fernandes) - ant build now includes debug information 10.12: - changed: getTopTags methods now return list of Tag (thanks Rudolf Mayer) - fixed: FileSystemCache directory is created lazily - happy birthday yet another K. ;-) 6.12: - changed license to 2-clause BSD - fixed: User.getInfo tests if age element is present 5.12: - added: User.getRecommendedArtists - added: Group.getMembers 1.12: - fixed: now playing notification UTF-8 encoding 25.11: - fixed: caching may fail due to invalid filename characters, added StringUtilities.cleanUp (thanks Jakob Frank) 12.11: - added: Basic cache implementation in net.roarsoftware.lastfm.cache and Caller.setCache (thanks Martin Chorley for his cache implementation I built upon) - added: Rating ability to Scrobbler/SubmissionData (thanks Lukasz Wisniewski) 10.11: - added: Artist.getEvents (thanks Idan Zohar) - added: Artist.share - fixed: Event.getStartDate now contains event's start time if available (thanks Idan Zohar) 5.11: - added: User.getRecommendedEvents - added: Library.addArtist, Library.addAlbum, Library.addTrack 24.10: - added: localised Artist.getInfo method - fixed: Playlist.create - warning: Artist.getInfo => getWikiLastChanged may return null if locale neither english nor default. 18.10: - added: Playlist.create 6.10: - added: Album.search method - added: Tag.getWeeklyArtistChart method - added: internal Chart.getChart, Chart.getWeeklyChartList, Chart.getWeeklyChartListAsCharts methods - changed: refactored all Chart methods to use the new internal Chart.getXXX methods 28.9: - fixed: Authenticator.getSession() now sends api_sig (thanks Alex Aranda) 25.9: - fixed: MusicEntry.getPlaycount() (thanks Henrique Pinto) 17.9: - changed: ImageSize.EXTRALARGE constant added. 8.9: - changed: for consistency reasons order of parameters in Track are now always: artist, track/mbid - changed: biography loading and accessor methods now renamed to getWikiXXX() and made available in MusicEntry - added: Track.getInfo - fixed: StringUtilities.isMbid - added: Event.share - ps: happy birthday, K. :-)lastfm-java-lastfm-java-0.1.2/license.txt000066400000000000000000000024761200137731100203110ustar00rootroot00000000000000Copyright (c) 2012, the Last.fm Java Project and Committers All rights reserved. Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. lastfm-java-lastfm-java-0.1.2/pom.xml000066400000000000000000000023031200137731100174300ustar00rootroot00000000000000 4.0.0 org.sonatype.oss oss-parent 7 de.u-mass lastfm-java 0.1.2 jar Last.fm bindings Java bindings for the Last.fm webservice API http://code.google.com/p/lastfm-java/ The BSD 2-Clause License http://opensource.org/licenses/bsd-license.php repo scm:git:https://code.google.com/p/lastfm-java scm:git:https://code.google.com/p/lastfm-java http://code.google.com/p/lastfm-java/ Janni Kovacs jannikovacs@gmail.com lastfm-java-lastfm-java-0.1.2/src/000077500000000000000000000000001200137731100167045ustar00rootroot00000000000000lastfm-java-lastfm-java-0.1.2/src/main/000077500000000000000000000000001200137731100176305ustar00rootroot00000000000000lastfm-java-lastfm-java-0.1.2/src/main/java/000077500000000000000000000000001200137731100205515ustar00rootroot00000000000000lastfm-java-lastfm-java-0.1.2/src/main/java/de/000077500000000000000000000000001200137731100211415ustar00rootroot00000000000000lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/000077500000000000000000000000001200137731100222715ustar00rootroot00000000000000lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/000077500000000000000000000000001200137731100235575ustar00rootroot00000000000000lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/Album.java000066400000000000000000000277521200137731100254770ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; import de.umass.util.MapUtilities; import de.umass.util.StringUtilities; import de.umass.xml.DomElement; /** * Wrapper class for Album related API calls and Album Bean. * * @author Janni Kovacs */ public class Album extends MusicEntry { static final ItemFactory FACTORY = new AlbumFactory(); private static final DateFormat RELEASE_DATE_FORMAT = new SimpleDateFormat("d MMM yyyy, HH:mm", Locale.ENGLISH); private static final DateFormat RELEASE_DATE_FORMAT_2 = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss Z", Locale.ENGLISH); /* only used in User.getNewReleases() */ private String artist; private Date releaseDate; private Collection tracks; private Album(String name, String url, String artist) { super(name, url); this.artist = artist; } private Album(String name, String url, String mbid, int playcount, int listeners, boolean streamable, String artist) { super(name, url, mbid, playcount, listeners, streamable); this.artist = artist; } public String getArtist() { return artist; } public Date getReleaseDate() { return releaseDate; } /** * Returns the list of {@link Track}s on this album. This information is only available in * {@link Album#getInfo(String, String, String)} responses. * * @return the list of tracks * @see Album#getInfo(String, String, String) */ public Collection getTracks() { return tracks; } /** * Get the metadata for an album on Last.fm using the album name or a musicbrainz id. * See playlist.fetch on how to get the album playlist. * * @param artist Artist's name * @param albumOrMbid Album name or MBID * @param apiKey The API key * @return Album metadata */ public static Album getInfo(String artist, String albumOrMbid, String apiKey) { return getInfo(artist, albumOrMbid, null, apiKey); } /** * Get the metadata for an album on Last.fm using the album name or a musicbrainz id. * See playlist.fetch on how to get the album playlist. * * @param artist Artist's name * @param albumOrMbid Album name or MBID * @param username The username for the context of the request. If supplied, the user's playcount for this album is included in the response. * @param apiKey The API key * @return Album metadata */ public static Album getInfo(String artist, String albumOrMbid, String username, String apiKey) { Map params = new HashMap(); if (StringUtilities.isMbid(albumOrMbid)) { params.put("mbid", albumOrMbid); } else { params.put("artist", artist); params.put("album", albumOrMbid); } MapUtilities.nullSafePut(params, "username", username); Result result = Caller.getInstance().call("album.getInfo", apiKey, params); return ResponseBuilder.buildItem(result, Album.class); } /** * Tag an album using a list of user supplied tags.
* * @param artist The artist name in question * @param album The album name in question * @param tags A comma delimited list of user supplied tags to apply to this album. Accepts a maximum of 10 tags. * @param session The Session instance * @return the Result of the operation * @see Authenticator */ public static Result addTags(String artist, String album, String tags, Session session) { return Caller.getInstance().call("album.addTags", session, "artist", artist, "album", album, "tags", tags); } /** * Remove a user's tag from an album. * * @param artist The artist name in question * @param album The album name in question * @param tag A single user tag to remove from this album. * @param session The Session instance * @return the Result of the operation * @see Authenticator */ public static Result removeTag(String artist, String album, String tag, Session session) { return Caller.getInstance().call("album.removeTag", session, "artist", artist, "album", album, "tag", tag); } /** * Get the tags applied by an individual user to an album on Last.fm. * * @param artist The artist name in question * @param album The album name in question * @param session A Session instance * @return a list of tags */ public static Collection getTags(String artist, String album, Session session) { Result result = Caller.getInstance().call("album.getTags", session, "artist", artist, "album", album); if (!result.isSuccessful()) return Collections.emptyList(); DomElement element = result.getContentElement(); Collection tags = new ArrayList(); for (DomElement domElement : element.getChildren("tag")) { tags.add(domElement.getChildText("name")); } return tags; } /** * Search for an album by name. Returns album matches sorted by relevance. * * @param album The album name in question. * @param apiKey A Last.fm API key. * @return a Collection of matches */ public static Collection search(String album, String apiKey) { Result result = Caller.getInstance().call("album.search", apiKey, "album", album); DomElement matches = result.getContentElement().getChild("albummatches"); Collection children = matches.getChildren("album"); Collection albums = new ArrayList(children.size()); for (DomElement element : children) { albums.add(FACTORY.createItemFromElement(element)); } return albums; } /** * Get a list of Buy Links for a particular Album. It is required that you supply either the artist and track params or the mbid param. * * @param artist The artist name in question * @param albumOrMbid Album name or MBID * @param country A country name, as defined by the ISO 3166-1 country names standard * @param apiKey A Last.fm API key * @return a Collection of {@link BuyLink}s */ public static Collection getBuylinks(String artist, String albumOrMbid, String country, String apiKey) { Map params = new HashMap(); if (StringUtilities.isMbid(albumOrMbid)) { params.put("mbid", albumOrMbid); } else { params.put("artist", artist); params.put("album", albumOrMbid); } params.put("country", country); Result result = Caller.getInstance().call("album.getBuylinks", apiKey, params); if (!result.isSuccessful()) return Collections.emptyList(); DomElement element = result.getContentElement(); DomElement physicals = element.getChild("physicals"); DomElement downloads = element.getChild("downloads"); Collection links = new ArrayList(); for (DomElement e : physicals.getChildren("affiliation")) { links.add(BuyLink.linkFromElement(BuyLink.StoreType.PHYSICAl, e)); } for (DomElement e : downloads.getChildren("affiliation")) { links.add(BuyLink.linkFromElement(BuyLink.StoreType.DIGITAL, e)); } return links; } /** * Get the top tags for an album on Last.fm, ordered by popularity. You either have to specify an album and artist name or * an mbid. If you specify an mbid you may pass null for the first parameter. * * @param artist The artist name * @param albumOrMbid Album name or MBID * @param apiKey A Last.fm API key * @return list of top tags */ public static Collection getTopTags(String artist, String albumOrMbid, String apiKey) { Map params = new HashMap(); if (StringUtilities.isMbid(albumOrMbid)) { params.put("mbid", albumOrMbid); } else { params.put("artist", artist); params.put("album", albumOrMbid); } Result result = Caller.getInstance().call("album.getTopTags", apiKey, params); return ResponseBuilder.buildCollection(result, Tag.class); } /** * Get shouts for an album. * * @param artist The artist name * @param albumOrMbid The album name or a mausicbrainz id * @param apiKey A Last.fm API key. * @return a page of Shouts */ public static PaginatedResult getShouts(String artist, String albumOrMbid, String apiKey) { return getShouts(artist, albumOrMbid, -1, -1, apiKey); } /** * Get shouts for an album. * * @param artist The artist name * @param albumOrMbid The album name or a mausicbrainz id * @param page The page number to fetch * @param apiKey A Last.fm API key. * @return a page of Shouts */ public static PaginatedResult getShouts(String artist, String albumOrMbid, int page, String apiKey) { return getShouts(artist, albumOrMbid, page, -1, apiKey); } /** * Get shouts for an album. * * @param artist The artist name * @param albumOrMbid The album name or a mausicbrainz id * @param page The page number to fetch * @param limit An integer used to limit the number of shouts returned per page or -1 for default * @param apiKey A Last.fm API key. * @return a page of Shouts */ public static PaginatedResult getShouts(String artist, String albumOrMbid, int page, int limit, String apiKey) { Map params = new HashMap(); if (StringUtilities.isMbid(albumOrMbid)) { params.put("mbid", albumOrMbid); } else { params.put("artist", artist); params.put("album", albumOrMbid); } MapUtilities.nullSafePut(params, "limit", limit); MapUtilities.nullSafePut(params, "page", page); Result result = Caller.getInstance().call("album.getShouts", apiKey, params); return ResponseBuilder.buildPaginatedResult(result, Shout.class); } private static class AlbumFactory implements ItemFactory { public Album createItemFromElement(DomElement element) { Album album = new Album(null, null, null); MusicEntry.loadStandardInfo(album, element); if (element.hasChild("artist")) { album.artist = element.getChild("artist").getChildText("name"); if (album.artist == null) album.artist = element.getChildText("artist"); } if (element.hasChild("tracks")) { album.tracks = ResponseBuilder.buildCollection(element.getChild("tracks"), Track.class); } if (element.hasChild("releasedate")) { try { album.releaseDate = RELEASE_DATE_FORMAT.parse(element.getChildText("releasedate")); } catch (ParseException e) { // uh oh } } String releaseDateAttribute = element.getAttribute("releasedate"); if (releaseDateAttribute != null) { try { album.releaseDate = RELEASE_DATE_FORMAT_2.parse(releaseDateAttribute); } catch (ParseException e) { // uh oh } } return album; } } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/Artist.java000066400000000000000000000431141200137731100256730ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import de.umass.util.MapUtilities; import de.umass.util.StringUtilities; import de.umass.xml.DomElement; /** * Bean that contains artist information.
This class contains static methods that executes API methods relating to artists.
Method * names are equivalent to the last.fm API method names. * * @author Janni Kovacs */ public class Artist extends MusicEntry { static final ItemFactory FACTORY = new ArtistFactory(); private Collection similar = new ArrayList(); protected Artist(String name, String url) { super(name, url); } protected Artist(String name, String url, String mbid, int playcount, int listeners, boolean streamable) { super(name, url, mbid, playcount, listeners, streamable); } /** * Returns a list of similar Artists. Note that this method does not retrieve this list from the server but instead returns * the result of an artist.getInfo call.
If you need to retrieve similar artists to a specified artist use the {@link * #getSimilar(String, String)} method. * * @return list of similar artists * @see #getSimilar(String, String) * @see #getSimilar(String, int, String) */ public Collection getSimilar() { return similar; } /** * Retrieves detailed artist info for the given artist or mbid entry. * * @param artistOrMbid Name of the artist or an mbid * @param apiKey The API key * @return detailed artist info */ public static Artist getInfo(String artistOrMbid, String apiKey) { return getInfo(artistOrMbid, null, null, apiKey); } /** * Retrieves detailed artist info for the given artist or mbid entry. * * @param artistOrMbid Name of the artist or an mbid * @param username The username for the context of the request, or null. If supplied, the user's playcount for this artist is * included in the response * @param apiKey The API key * @return detailed artist info */ public static Artist getInfo(String artistOrMbid, String username, String apiKey) { return getInfo(artistOrMbid, null, username, apiKey); } /** * Retrieves detailed artist info for the given artist or mbid entry. * * @param artistOrMbid Name of the artist or an mbid * @param locale The language to fetch info in, or null * @param username The username for the context of the request, or null. If supplied, the user's playcount for this artist is * included in the response * @param apiKey The API key * @return detailed artist info */ public static Artist getInfo(String artistOrMbid, Locale locale, String username, String apiKey) { Map params = new HashMap(); if (StringUtilities.isMbid(artistOrMbid)) { params.put("mbid", artistOrMbid); } else { params.put("artist", artistOrMbid); } if (locale != null && locale.getLanguage().length() != 0) { params.put("lang", locale.getLanguage()); } MapUtilities.nullSafePut(params, "username", username); Result result = Caller.getInstance().call("artist.getInfo", apiKey, params); return ResponseBuilder.buildItem(result, Artist.class); } /** * Calls {@link #getSimilar(String, int, String)} with the default limit of 100. * * @param artist Artist's name * @param apiKey The API key * @return similar artists * @see #getSimilar(String, int, String) */ public static Collection getSimilar(String artist, String apiKey) { return getSimilar(artist, 100, apiKey); } /** * Returns limit similar artists to the given one. * * @param artist Artist's name * @param limit Number of maximum results * @param apiKey The API key * @return similar artists */ public static Collection getSimilar(String artist, int limit, String apiKey) { Result result = Caller.getInstance().call("artist.getSimilar", apiKey, "artist", artist, "limit", String.valueOf(limit)); return ResponseBuilder.buildCollection(result, Artist.class); } /** * Searches for an artist and returns a Collection of possible matches. * * @param name The artist name to look up * @param apiKey The API key * @return a list of possible matches */ public static Collection search(String name, String apiKey) { Result result = Caller.getInstance().call("artist.search", apiKey, "artist", name); Collection children = result.getContentElement().getChild("artistmatches").getChildren("artist"); List list = new ArrayList(children.size()); for (DomElement c : children) { list.add(FACTORY.createItemFromElement(c)); } return list; } /** * Returns a list of the given artist's top albums. * * @param artist Artist's name * @param apiKey The API key * @return list of top albums */ public static Collection getTopAlbums(String artist, String apiKey) { Result result = Caller.getInstance().call("artist.getTopAlbums", apiKey, "artist", artist); return ResponseBuilder.buildCollection(result, Album.class); } /** * Retrieves a list of the top fans of the given artist. * * @param artist Artist's name * @param apiKey The API key * @return list of top fans */ public static Collection getTopFans(String artist, String apiKey) { Result result = Caller.getInstance().call("artist.getTopFans", apiKey, "artist", artist); return ResponseBuilder.buildCollection(result, User.class); } /** * Retrieves the top tags for the given artist. * * @param artist Artist's name * @param apiKey The API key * @return list of top tags */ public static Collection getTopTags(String artist, String apiKey) { Result result = Caller.getInstance().call("artist.getTopTags", apiKey, "artist", artist); return ResponseBuilder.buildCollection(result, Tag.class); } /** * Get the top tracks by an artist on Last.fm, ordered by popularity * * @param artist The artist name in question * @param apiKey A Last.fm API key. * @return list of top tracks */ public static Collection getTopTracks(String artist, String apiKey) { Result result = Caller.getInstance().call("artist.getTopTracks", apiKey, "artist", artist); return ResponseBuilder.buildCollection(result, Track.class); } /** * Tag an artist with one or more user supplied tags. * * @param artist The artist name in question. * @param tags A comma delimited list of user supplied tags to apply to this artist. Accepts a maximum of 10 tags. * @param session A Session instance * @return the result of the operation */ public static Result addTags(String artist, String tags, Session session) { return Caller.getInstance().call("artist.addTags", session, "artist", artist, "tags", tags); } /** * Remove a user's tag from an artist. * * @param artist The artist name in question. * @param tag A single user tag to remove from this artist. * @param session A Session instance * @return the result of the operation */ public static Result removeTag(String artist, String tag, Session session) { return Caller.getInstance().call("artist.removeTag", session, "artist", artist, "tag", tag); } /** * Share an artist with one or more Last.fm users or other friends. * * @param artist The artist to share. * @param recipients A comma delimited list of email addresses or Last.fm usernames. Maximum is 10. * @param message An optional message to send with the recommendation. * @param session A Session instance * @return the Result of the operation */ public static Result share(String artist, String recipients, String message, Session session) { return Caller.getInstance().call("artist.share", session, "artist", artist, "recipient", recipients, "message", message); } /** * Get the tags applied by an individual user to an artist on Last.fm. * * @param artist The artist name in question * @param session A Session instance * @return a list of tags */ public static Collection getTags(String artist, Session session) { Result result = Caller.getInstance().call("artist.getTags", session, "artist", artist); if (!result.isSuccessful()) return Collections.emptyList(); DomElement element = result.getContentElement(); Collection tags = new ArrayList(); for (DomElement domElement : element.getChildren("tag")) { tags.add(domElement.getChildText("name")); } return tags; } /** * Returns a list of upcoming events for an artist. * * @param artistOrMbid The artist name in question * @param apiKey A Last.fm API key * @return a list of events */ public static PaginatedResult getEvents(String artistOrMbid, String apiKey) { return getEvents(artistOrMbid, false, -1, -1, apiKey); } /** * Returns a list of upcoming events for an artist. * * @param artistOrMbid The artist name in question * @param festivalsOnly Whether only festivals should be returned, or all events * @param page The page number to fetch * @param limit The number of results to fetch per page * @param apiKey A Last.fm API key * @return a list of events */ public static PaginatedResult getEvents(String artistOrMbid, boolean festivalsOnly, int page, int limit, String apiKey) { Map params = new HashMap(); if (StringUtilities.isMbid(artistOrMbid)) { params.put("mbid", artistOrMbid); } else { params.put("artist", artistOrMbid); } MapUtilities.nullSafePut(params, "page", page); MapUtilities.nullSafePut(params, "limit", limit); if(festivalsOnly) params.put("festivalsonly", "1"); Result result = Caller.getInstance().call("artist.getEvents", apiKey, params); return ResponseBuilder.buildPaginatedResult(result, Event.class); } /** * Get a paginated list of all the events this artist has played at in the past. * * @param artistOrMbid The name of the artist you would like to fetch event listings for * @param apiKey A Last.fm API key * @return a list of past events */ public static PaginatedResult getPastEvents(String artistOrMbid, String apiKey) { return getPastEvents(artistOrMbid, false, -1, -1, apiKey); } /** * Get a paginated list of all the events this artist has played at in the past. * * @param artistOrMbid The name of the artist you would like to fetch event listings for * @param festivalsOnly Whether only festivals should be returned, or all events * @param page The page of results to return * @param limit The maximum number of results to return per page * @param apiKey A Last.fm API key * @return a list of past events */ public static PaginatedResult getPastEvents(String artistOrMbid, boolean festivalsOnly, int page, int limit, String apiKey) { Map params = new HashMap(); if (StringUtilities.isMbid(artistOrMbid)) { params.put("mbid", artistOrMbid); } else { params.put("artist", artistOrMbid); } MapUtilities.nullSafePut(params, "page", page); MapUtilities.nullSafePut(params, "limit", limit); if(festivalsOnly) params.put("festivalsonly", "1"); Result result = Caller.getInstance().call("artist.getPastEvents", apiKey, params); return ResponseBuilder.buildPaginatedResult(result, Event.class); } /** * Get {@link Image}s for this artist in a variety of sizes. * * @param artistOrMbid The artist name in question * @param apiKey A Last.fm API key * @return a list of {@link Image}s */ public static PaginatedResult getImages(String artistOrMbid, String apiKey) { return getImages(artistOrMbid, -1, -1, apiKey); } /** * Get {@link Image}s for this artist in a variety of sizes. * * @param artistOrMbid The artist name in question * @param page Which page of limit amount to display * @param limit How many to return. Defaults and maxes out at 50 * @param apiKey A Last.fm API key * @return a list of {@link Image}s */ public static PaginatedResult getImages(String artistOrMbid, int page, int limit, String apiKey) { Map params = new HashMap(); if (StringUtilities.isMbid(artistOrMbid)) { params.put("mbid", artistOrMbid); } else { params.put("artist", artistOrMbid); } MapUtilities.nullSafePut(params, "page", page); MapUtilities.nullSafePut(params, "limit", limit); Result result = Caller.getInstance().call("artist.getImages", apiKey, params); return ResponseBuilder.buildPaginatedResult(result, Image.class); } /** * Shout on this artist's shoutbox * * @param artist The name of the artist to shout on * @param message The message to post to the shoutbox * @param session A Session instance * @return the result of the operation */ public static Result shout(String artist, String message, Session session) { return Caller.getInstance().call("artist.shout", session, "artist", artist, "message", message); } /** * Use the last.fm corrections data to check whether the supplied artist has a correction to a canonical artist. This method returns a new * {@link Artist} object containing the corrected data, or null if the supplied Artist was not found. * * @param artist The artist name to correct * @param apiKey A Last.fm API key * @return a new {@link Artist}, or null */ public static Artist getCorrection(String artist, String apiKey) { Result result = Caller.getInstance().call("artist.getCorrection", apiKey, "artist", artist); if (!result.isSuccessful()) return null; DomElement correctionElement = result.getContentElement().getChild("correction"); if (correctionElement == null) return new Artist(artist, null); DomElement artistElem = correctionElement.getChild("artist"); return FACTORY.createItemFromElement(artistElem); } /** * Get shouts for an artist. * * @param artistOrMbid The artist name or a musicbrainz id * @param apiKey A Last.fm API key. * @return a page of Shouts */ public static PaginatedResult getShouts(String artistOrMbid, String apiKey) { return getShouts(artistOrMbid, -1, -1, apiKey); } /** * Get shouts for an artist. * * @param artistOrMbid The artist name or a musicbrainz id * @param page The page number to fetch * @param apiKey A Last.fm API key. * @return a page of Shouts */ public static PaginatedResult getShouts(String artistOrMbid, int page, String apiKey) { return getShouts(artistOrMbid, page, -1, apiKey); } /** * Get shouts for an artist. * * @param artistOrMbid The artist name or a musicbrainz id * @param page The page number to fetch * @param limit An integer used to limit the number of shouts returned per page or -1 for default * @param apiKey A Last.fm API key. * @return a page of Shouts */ public static PaginatedResult getShouts(String artistOrMbid, int page, int limit, String apiKey) { Map params = new HashMap(); if (StringUtilities.isMbid(artistOrMbid)) { params.put("mbid", artistOrMbid); } else { params.put("artist", artistOrMbid); } MapUtilities.nullSafePut(params, "limit", limit); MapUtilities.nullSafePut(params, "page", page); Result result = Caller.getInstance().call("artist.getShouts", apiKey, params); return ResponseBuilder.buildPaginatedResult(result, Shout.class); } private static class ArtistFactory implements ItemFactory { public Artist createItemFromElement(DomElement element) { Artist artist = new Artist(null, null); MusicEntry.loadStandardInfo(artist, element); // similar artists DomElement similar = element.getChild("similar"); if (similar != null) { Collection children = similar.getChildren("artist"); for (DomElement child : children) { artist.similar.add(createItemFromElement(child)); } } return artist; } } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/Authenticator.java000066400000000000000000000106721200137731100272420ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import de.umass.xml.DomElement; import static de.umass.util.StringUtilities.isMD5; import static de.umass.util.StringUtilities.map; import static de.umass.util.StringUtilities.md5; /** * Provides bindings for the authentication methods of the last.fm API. * See http://www.last.fm/api/authentication for * authentication methods. * * @author Janni Kovacs * @see Session */ public class Authenticator { private Authenticator() { } /** * Create a web service session for a user. Used for authenticating a user when the password can be inputted by the user. * * @param username last.fm username * @param password last.fm password in cleartext or 32-char md5 string * @param apiKey The API key * @param secret Your last.fm API secret * @return a Session instance * @see Session */ public static Session getMobileSession(String username, String password, String apiKey, String secret) { if (!isMD5(password)) password = md5(password); String authToken = md5(username + password); Map params = map("api_key", apiKey, "username", username, "authToken", authToken); String sig = createSignature("auth.getMobileSession", params, secret); Result result = Caller.getInstance() .call("auth.getMobileSession", apiKey, "username", username, "authToken", authToken, "api_sig", sig); DomElement element = result.getContentElement(); return Session.sessionFromElement(element, apiKey, secret); } /** * Fetch an unathorized request token for an API account. * * @param apiKey A last.fm API key. * @return a token */ public static String getToken(String apiKey) { Result result = Caller.getInstance().call("auth.getToken", apiKey); return result.getContentElement().getText(); } /** * Fetch a session key for a user. * * @param token A token returned by {@link #getToken(String)} * @param apiKey A last.fm API key * @param secret Your last.fm API secret * @return a Session instance * @see Session */ public static Session getSession(String token, String apiKey, String secret) { String m = "auth.getSession"; Map params = new HashMap(); params.put("api_key", apiKey); params.put("token", token); params.put("api_sig", createSignature(m, params, secret)); Result result = Caller.getInstance().call(m, apiKey, params); return Session.sessionFromElement(result.getContentElement(), apiKey, secret); } static String createSignature(String method, Map params, String secret) { params = new TreeMap(params); params.put("method", method); StringBuilder b = new StringBuilder(100); for (Entry entry : params.entrySet()) { b.append(entry.getKey()); b.append(entry.getValue()); } b.append(secret); return md5(b.toString()); } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/BuyLink.java000066400000000000000000000102611200137731100257770ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import de.umass.xml.DomElement; /** * A BuyLink contains information about places to buy an Album or Track. BuyLinks can point to physical * and digital music stores. Some suppliers have icons, some do have price information, others don't (eBay for example). * Common suppliers you will receive via the getBuylinks() methods are Amazon, Amazon MP3, iTunes and * 7digital. All stores but eBay do supply icons at the time of writing. * * @author Janni Kovacs * @see Album#getBuylinks(String, String, String, String) * @see Track#getBuylinks(String, String, String, String) */ public class BuyLink { public static enum StoreType { PHYSICAl, DIGITAL } private StoreType type; private String name; private String link; private String icon; private boolean search; private String currency; private double price; private BuyLink(String name, StoreType type, String link) { this.name = name; this.type = type; this.link = link; } public String getName() { return name; } public String getLink() { return link; } public StoreType getType() { return type; } /** * Returns a url to a 16x16 pixel icon for the store, or null if no icon url was supplied. * * @return Icon URL or null */ public String getIcon() { return icon; } /** * Returns true if this link points to a search page instead of an actual product page. Note that * for search links there is no price information available. * * @return if this is a search link */ public boolean isSearch() { return search; } /** * Returns the currency of the price of the item. Check if this is null to double-check if there is * price information available * * @return currency */ public String getCurrency() { return currency; } /** * Returns the price for the item, or 0.0 if no price information is available. Use {@link #getCurrency()} and * {@link #isSearch()} to check if price information is available. * * @return price, if available */ public double getPrice() { return price; } static BuyLink linkFromElement(StoreType type, DomElement element) { BuyLink link = new BuyLink(element.getChildText("supplierName"), type, element.getChildText("buyLink")); link.search = "1".equals(element.getChildText("isSearch")); link.icon = element.getChildText("supplierIcon"); if (link.icon != null && link.icon.length() == 0) link.icon = null; if (element.hasChild("price")) { DomElement child = element.getChild("price"); link.currency = child.getChildText("currency"); link.price = Double.parseDouble(child.getChildText("amount")); } return link; } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/CallException.java000066400000000000000000000034301200137731100271540ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; /** * @author Janni Kovacs */ public class CallException extends RuntimeException { public CallException() { } public CallException(Throwable cause) { super(cause); } public CallException(String message) { super(message); } public CallException(String message, Throwable cause) { super(message, cause); } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/Caller.java000066400000000000000000000317631200137731100256360ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.io.*; import java.net.HttpURLConnection; import java.net.Proxy; import java.net.URL; import java.util.*; import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; import de.umass.lastfm.Result.Status; import de.umass.lastfm.cache.Cache; import de.umass.lastfm.cache.FileSystemCache; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import static de.umass.util.StringUtilities.*; /** * The Caller class handles the low-level communication between the client and last.fm.
* Direct usage of this class should be unnecessary since all method calls are available via the methods in * the Artist, Album, User, etc. classes. * If specialized calls which are not covered by the Java API are necessary this class may be used directly.
* Supports the setting of a custom {@link Proxy} and a custom User-Agent HTTP header. * * @author Janni Kovacs */ public class Caller { private static final String PARAM_API_KEY = "api_key"; private static final String PARAM_METHOD = "method"; private static final String DEFAULT_API_ROOT = "http://ws.audioscrobbler.com/2.0/"; private static final Caller instance = new Caller(); private final Logger log = Logger.getLogger("de.umass.lastfm.Caller"); private String apiRootUrl = DEFAULT_API_ROOT; private Proxy proxy; private String userAgent = "tst"; private boolean debugMode = false; private Cache cache; private Result lastResult; private Caller() { cache = new FileSystemCache(); } /** * Returns the single instance of the Caller class. * * @return a Caller */ public static Caller getInstance() { return instance; } /** * Set api root url. * * @param apiRootUrl new api root url */ public void setApiRootUrl(String apiRootUrl) { this.apiRootUrl = apiRootUrl; } /** * Sets a {@link Proxy} instance this Caller will use for all upcoming HTTP requests. May be null. * * @param proxy A Proxy or null. */ public void setProxy(Proxy proxy) { this.proxy = proxy; } public Proxy getProxy() { return proxy; } /** * Sets a User Agent this Caller will use for all upcoming HTTP requests. For testing purposes use "tst". * If you distribute your application use an identifiable User-Agent. * * @param userAgent a User-Agent string */ public void setUserAgent(String userAgent) { this.userAgent = userAgent; } public String getUserAgent() { return userAgent; } /** * Returns the current {@link Cache}. * * @return the Cache */ public Cache getCache() { return cache; } /** * Sets the active {@link Cache}. May be null to disable caching. * * @param cache the new Cache or null */ public void setCache(Cache cache) { this.cache = cache; } /** * Sets the debugMode property. If debugMode is true all call() methods * will print debug information and error messages on failure to stdout and stderr respectively.
* Default is false. Set this to true while in development and for troubleshooting. * * @see de.umass.lastfm.Caller#getLogger() * @param debugMode true to enable debug mode * @deprecated Use the Logger instead */ public void setDebugMode(boolean debugMode) { this.debugMode = debugMode; log.setLevel(debugMode ? Level.ALL : Level.OFF); } /** * @see de.umass.lastfm.Caller#getLogger() * @return the debugMode property * @deprecated Use the Logger instead */ public boolean isDebugMode() { return debugMode; } public Logger getLogger() { return log; } /** * Returns the {@link Result} of the last operation, or null if no call operation has been * performed yet. * * @return the last Result object */ public Result getLastResult() { return lastResult; } public Result call(String method, String apiKey, String... params) throws CallException { return call(method, apiKey, map(params)); } public Result call(String method, String apiKey, Map params) throws CallException { return call(method, apiKey, params, null); } public Result call(String method, Session session, String... params) { return call(method, session.getApiKey(), map(params), session); } public Result call(String method, Session session, Map params) { return call(method, session.getApiKey(), params, session); } /** * Performs the web-service call. If the session parameter is non-null then an * authenticated call is made. If it's null then an unauthenticated call is made.
* The apiKey parameter is always required, even when a valid session is passed to this method. * * @param method The method to call * @param apiKey A Last.fm API key * @param params Parameters * @param session A Session instance or null * @return the result of the operation */ private Result call(String method, String apiKey, Map params, Session session) { params = new HashMap(params); // create new Map in case params is an immutable Map InputStream inputStream = null; // try to load from cache String cacheEntryName = Cache.createCacheEntryName(method, params); if (session == null && cache != null) { inputStream = getStreamFromCache(cacheEntryName); } // no entry in cache, load from web if (inputStream == null) { // fill parameter map with apiKey and session info params.put(PARAM_API_KEY, apiKey); if (session != null) { params.put("sk", session.getKey()); params.put("api_sig", Authenticator.createSignature(method, params, session.getSecret())); } try { HttpURLConnection urlConnection = openPostConnection(method, params); inputStream = getInputStreamFromConnection(urlConnection); if (inputStream == null) { this.lastResult = Result.createHttpErrorResult(urlConnection.getResponseCode(), urlConnection.getResponseMessage()); return lastResult; } else { if (cache != null) { long expires = urlConnection.getHeaderFieldDate("Expires", -1); if (expires == -1) { expires = cache.findExpirationDate(method, params); } if (expires != -1) { cache.store(cacheEntryName, inputStream, expires); // if data wasn't cached store new result inputStream = cache.load(cacheEntryName); if (inputStream == null) throw new CallException("Caching/Reloading failed"); } } } } catch (IOException e) { throw new CallException(e); } } try { Result result = createResultFromInputStream(inputStream); if (!result.isSuccessful()) { log.warning(String.format("API call failed with result: %s%n", result)); if (cache != null) { cache.remove(cacheEntryName); } } this.lastResult = result; return result; } catch (IOException e) { throw new CallException(e); } catch (SAXException e) { throw new CallException(e); } } private InputStream getStreamFromCache(String cacheEntryName) { if (cache != null && cache.contains(cacheEntryName) && !cache.isExpired(cacheEntryName)) { return cache.load(cacheEntryName); } return null; } /** * Creates a new {@link HttpURLConnection}, sets the proxy, if available, and sets the User-Agent property. * * @param url URL to connect to * @return a new connection. * @throws IOException if an I/O exception occurs. */ public HttpURLConnection openConnection(String url) throws IOException { log.info("Open connection: " + url); URL u = new URL(url); HttpURLConnection urlConnection; if (proxy != null) urlConnection = (HttpURLConnection) u.openConnection(proxy); else urlConnection = (HttpURLConnection) u.openConnection(); urlConnection.setRequestProperty("User-Agent", userAgent); return urlConnection; } private HttpURLConnection openPostConnection(String method, Map params) throws IOException { HttpURLConnection urlConnection = openConnection(apiRootUrl); urlConnection.setRequestMethod("POST"); urlConnection.setDoOutput(true); OutputStream outputStream = urlConnection.getOutputStream(); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream)); String post = buildPostBody(method, params); log.info("Post body: " + post); writer.write(post); writer.close(); return urlConnection; } private InputStream getInputStreamFromConnection(HttpURLConnection connection) throws IOException { int responseCode = connection.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_FORBIDDEN || responseCode == HttpURLConnection.HTTP_BAD_REQUEST) { return connection.getErrorStream(); } else if (responseCode == HttpURLConnection.HTTP_OK) { return connection.getInputStream(); } return null; } private Result createResultFromInputStream(InputStream inputStream) throws SAXException, IOException { Document document = newDocumentBuilder().parse(new InputSource(new InputStreamReader(inputStream, "UTF-8"))); Element root = document.getDocumentElement(); // lfm element String statusString = root.getAttribute("status"); Status status = "ok".equals(statusString) ? Status.OK : Status.FAILED; if (status == Status.FAILED) { Element errorElement = (Element) root.getElementsByTagName("error").item(0); int errorCode = Integer.parseInt(errorElement.getAttribute("code")); String message = errorElement.getTextContent(); return Result.createRestErrorResult(errorCode, message); } else { return Result.createOkResult(document); } } private DocumentBuilder newDocumentBuilder() { try { DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); return builderFactory.newDocumentBuilder(); } catch (ParserConfigurationException e) { // better never happens throw new RuntimeException(e); } } private String buildPostBody(String method, Map params, String... strings) { StringBuilder builder = new StringBuilder(100); builder.append("method="); builder.append(method); builder.append('&'); for (Iterator> it = params.entrySet().iterator(); it.hasNext();) { Entry entry = it.next(); builder.append(entry.getKey()); builder.append('='); builder.append(encode(entry.getValue())); if (it.hasNext() || strings.length > 0) builder.append('&'); } int count = 0; for (String string : strings) { builder.append(count % 2 == 0 ? string : encode(string)); count++; if (count != strings.length) { if (count % 2 == 0) { builder.append('&'); } else { builder.append('='); } } } return builder.toString(); } private String createSignature(Map params, String secret) { Set sorted = new TreeSet(params.keySet()); StringBuilder builder = new StringBuilder(50); for (String s : sorted) { builder.append(s); builder.append(encode(params.get(s))); } builder.append(secret); return md5(builder.toString()); } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/Chart.java000066400000000000000000000257501200137731100254740ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import java.util.*; import de.umass.util.MapUtilities; import de.umass.xml.DomElement; /** * Bean for Chart information. Contains a start date, an end date and a list of entries. * * @author Janni Kovacs */ public class Chart { private Date from, to; private Collection entries; public Chart(Date from, Date to, Collection entries) { this.from = from; this.to = to; this.entries = entries; } public Collection getEntries() { return entries; } public Date getFrom() { return from; } public Date getTo() { return to; } /** * This is an internal method to retrieve Chart data. * * @param method The method to call, must be one of the getWeeklyXXXChart methods * @param sourceType The name of the parameter to get the charts for, either "user", "tag" or "group" * @param source The username, tag or group to get charts from * @param target The expected chart type, either "album", "artist" or "track" * @param from Start date or null * @param to End date or null * @param limit The number of chart items to return or -1 * @param apiKey A Last.fm API key. * @return a Chart */ static Chart getChart(String method, String sourceType, String source, String target, String from, String to, int limit, String apiKey) { Map params = new HashMap(); params.put(sourceType, source); return getChart(method, target, params, from, to, limit, apiKey); } /** * This is an internal method to retrieve Chart data. * * @param method The method to call, must be one of the getWeeklyXXXChart methods * @param params Extra parameters that will be passed to the webservice, e.g. containing user or tag name * @param target The expected chart type, either "album", "artist" or "track" * @param from Start date or null * @param to End date or null * @param limit The number of chart items to return or -1 * @param apiKey A Last.fm API key. * @return a Chart */ @SuppressWarnings("unchecked") static Chart getChart(String method, String target, Map params, String from, String to, int limit, String apiKey) { if (from != null && to != null) { params.put("from", from); params.put("to", to); } MapUtilities.nullSafePut(params, "limit", limit); Result result = Caller.getInstance().call(method, apiKey, params); if (!result.isSuccessful()) return null; DomElement element = result.getContentElement(); Collection children = element.getChildren(target); Collection collection = new ArrayList(children.size()); boolean targetArtist = "artist".equals(target); boolean targetTrack = "track".equals(target); boolean targetAlbum = "album".equals(target); for (DomElement domElement : children) { if (targetArtist) collection.add(ResponseBuilder.buildItem(domElement, Artist.class)); if (targetTrack) collection.add(ResponseBuilder.buildItem(domElement, Track.class)); if (targetAlbum) collection.add(ResponseBuilder.buildItem(domElement, Album.class)); } long fromTime = 0; long toTime = 0; // workaround for geo.getMetroXXX methods, since they don't have from & to attributes if no dates were given upon calling if (element.hasAttribute("from")) { fromTime = 1000 * Long.parseLong(element.getAttribute("from")); toTime = 1000 * Long.parseLong(element.getAttribute("to")); } return new Chart(new Date(fromTime), new Date(toTime), collection); } /** * This is an internal method to get a list of available charts. * * @param methodName The name of the method to be called, e.g. user.getWeeklyChartList * @param paramName The name of the parameter which is passed to the specified method, e.g. user * @param paramValue The value of the parameter which is passed to the specified method, e.g. the user name * @param apiKey A Last.fm API key. * @return a list of available charts as a Map */ static LinkedHashMap getWeeklyChartList(String methodName, String paramName, String paramValue, String apiKey) { Result result = Caller.getInstance().call(methodName, apiKey, paramName, paramValue); if (!result.isSuccessful()) return new LinkedHashMap(0); DomElement element = result.getContentElement(); LinkedHashMap list = new LinkedHashMap(); for (DomElement domElement : element.getChildren("chart")) { list.put(domElement.getAttribute("from"), domElement.getAttribute("to")); } return list; } /** * This is an internal method to get a list of available charts. * * @param sourceType The name of the parameter to get the charts for, either "user", "tag" or "group" * @param source The username, tag or group to get charts from * @param apiKey A Last.fm API key. * @return a list of available charts as a Collection of Charts */ @SuppressWarnings("unchecked") static Collection getWeeklyChartListAsCharts(String sourceType, String source, String apiKey) { Result result = Caller.getInstance().call(sourceType + ".getWeeklyChartList", apiKey, sourceType, source); if (!result.isSuccessful()) return Collections.emptyList(); DomElement element = result.getContentElement(); List list = new ArrayList(); for (DomElement domElement : element.getChildren("chart")) { long fromTime = 1000 * Long.parseLong(domElement.getAttribute("from")); long toTime = 1000 * Long.parseLong(domElement.getAttribute("to")); list.add(new Chart(new Date(fromTime), new Date(toTime), null)); } return list; } /** * Get the top artists chart. * * @param apiKey A Last.fm API key * @return Top artists chart */ public static PaginatedResult getTopArtists(String apiKey) { return getTopArtists(1, apiKey); } /** * Get the top artists chart. * * @param page The page to fetch * @param apiKey A Last.fm API key * @return Top artists chart */ public static PaginatedResult getTopArtists(int page, String apiKey) { Result result = Caller.getInstance().call("chart.getTopArtists", apiKey, "page", String.valueOf(page)); return ResponseBuilder.buildPaginatedResult(result, Artist.class); } /** * Get the top tags chart. * * @param apiKey A Last.fm API key * @return Top tags chart */ public static PaginatedResult getTopTags(String apiKey) { return getTopTags(1, apiKey); } /** * Get the top tags chart. * * @param page The page to fetch * @param apiKey A Last.fm API key * @return Top tags chart */ public static PaginatedResult getTopTags(int page, String apiKey) { Result result = Caller.getInstance().call("chart.getTopTags", apiKey, "page", String.valueOf(page)); return ResponseBuilder.buildPaginatedResult(result, Tag.class); } /** * Get the top tracks chart. * * @param apiKey A Last.fm API key * @return Top tracks chart */ public static PaginatedResult getTopTracks(String apiKey) { return getTopTracks(1, apiKey); } /** * Get the top tracks chart. * * @param page The page to fetch * @param apiKey A Last.fm API key * @return Top tracks chart */ public static PaginatedResult getTopTracks(int page, String apiKey) { Result result = Caller.getInstance().call("chart.getTopTracks", apiKey, "page", String.valueOf(page)); return ResponseBuilder.buildPaginatedResult(result, Track.class); } /** * Get the most loved tracks chart. * * @param apiKey A Last.fm API key * @return Most loved tracks chart */ public static PaginatedResult getLovedTracks(String apiKey) { return getLovedTracks(1, apiKey); } /** * Get the most loved tracks chart. * * @param page The page to fetch * @param apiKey A Last.fm API key * @return Most loved tracks chart */ public static PaginatedResult getLovedTracks(int page, String apiKey) { Result result = Caller.getInstance().call("chart.getLovedTracks", apiKey, "page", String.valueOf(page)); return ResponseBuilder.buildPaginatedResult(result, Track.class); } /** * Get the hyped tracks chart. * * @param apiKey A Last.fm API key * @return Hyped tracks chart */ public static PaginatedResult getHypedTracks(String apiKey) { return getHypedTracks(1, apiKey); } /** * Get the hyped tracks chart. * * @param page The page to fetch * @param apiKey A Last.fm API key * @return Hyped tracks chart */ public static PaginatedResult getHypedTracks(int page, String apiKey) { Result result = Caller.getInstance().call("chart.getHypedTracks", apiKey, "page", String.valueOf(page)); return ResponseBuilder.buildPaginatedResult(result, Track.class); } /** * Get the hyped artists chart. * * @param apiKey A Last.fm API key * @return Hyped artists chart */ public static PaginatedResult getHypedArtists(String apiKey) { return getHypedArtists(1, apiKey); } /** * Get the hyped artists chart. * * @param page The page to fetch * @param apiKey A Last.fm API key * @return Hyped artists chart */ public static PaginatedResult getHypedArtists(int page, String apiKey) { Result result = Caller.getInstance().call("chart.getHypedArtists", apiKey, "page", String.valueOf(page)); return ResponseBuilder.buildPaginatedResult(result, Artist.class); } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/Event.java000066400000000000000000000236121200137731100255070ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; import de.umass.util.MapUtilities; import de.umass.xml.DomElement; /** * Bean for Events. * * @author Janni Kovacs */ public class Event extends ImageHolder { static final ItemFactory FACTORY = new EventFactory(); private static final DateFormat DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss", Locale.ENGLISH); private int id; private String title; private Collection artists; private String headliner; private Collection tickets; private Date startDate; private Date endDate; private String description; private String url; private String website; private int attendance; private int reviews; private Venue venue; private AttendanceStatus userAttendanceStatus; private Event() { } public Collection getArtists() { return artists; } public int getAttendance() { return attendance; } public String getDescription() { return description; } public String getHeadliner() { return headliner; } public int getId() { return id; } public int getReviews() { return reviews; } /** * Returns the start date and time of this event. Note that the time might not be correct, but instead a random time, if not set to a * proper value on last.fm (happens often). * * @return start date */ public Date getStartDate() { return startDate; } /** * Returns the event's end date, or null if not available. End dates are only supplied for events such as festivals, which * last longer than one day. * * @return end date */ public Date getEndDate() { return endDate; } public String getTitle() { return title; } /** * Returns the last.fm event url, i.e. http://www.last.fm/event/event-id * * @return last.fm url */ public String getUrl() { return url; } /** * Returns the event website url, if available. * * @return event website url */ public String getWebsite() { return website; } public Collection getTicketSuppliers() { return tickets; } public Venue getVenue() { return venue; } public AttendanceStatus getAttendanceStatus() { return this.userAttendanceStatus; } /** * Get the metadata for an event on Last.fm. Includes attendance and lineup information. * * @param eventId The numeric last.fm event id * @param apiKey A Last.fm API key. * @return Event metadata */ public static Event getInfo(String eventId, String apiKey) { Result result = Caller.getInstance().call("event.getInfo", apiKey, "event", eventId); return ResponseBuilder.buildItem(result, Event.class); } /** * Set a user's attendance status for an event. * * @param eventId The numeric last.fm event id * @param status The attendance status * @param session A Session instance * @return the Result of the operation. * @see de.umass.lastfm.Event.AttendanceStatus * @see de.umass.lastfm.Authenticator */ public static Result attend(String eventId, AttendanceStatus status, Session session) { return Caller.getInstance().call("event.attend", session, "event", eventId, "status", String.valueOf(status.getId())); } /** * Share an event with one or more Last.fm users or other friends. * * @param eventId An event ID * @param recipients A comma delimited list of email addresses or Last.fm usernames. Maximum is 10. * @param message An optional message to send with the recommendation. * @param session A Session instance * @return the Result of the operation */ public static Result share(String eventId, String recipients, String message, Session session) { return Caller.getInstance().call("event.share", session, "event", eventId, "recipient", recipients, "message", message); } /** * Get a list of attendees for an event. * * @param eventId The numeric last.fm event id * @param apiKey A Last.fm API key * @return a list of users who attended the given event */ public static Collection getAttendees(String eventId, String apiKey) { Result result = Caller.getInstance().call("event.getAttendees", apiKey, "event", eventId); return ResponseBuilder.buildCollection(result, User.class); } /** * Get shouts for an event. * * @param eventId The numeric last.fm event id * @param apiKey A Last.fm API key. * @return a page of Shouts */ public static PaginatedResult getShouts(String eventId, String apiKey) { return getShouts(eventId, -1, -1, apiKey); } /** * Get shouts for an event. * * @param eventId The numeric last.fm event id * @param page The page number to fetch * @param apiKey A Last.fm API key. * @return a page of Shouts */ public static PaginatedResult getShouts(String eventId, int page, String apiKey) { return getShouts(eventId, page, -1, apiKey); } /** * Get shouts for an event. * * @param eventId The numeric last.fm event id * @param page The page number to fetch * @param limit An integer used to limit the number of shouts returned per page or -1 for default * @param apiKey A Last.fm API key. * @return a page of Shouts */ public static PaginatedResult getShouts(String eventId, int page, int limit, String apiKey) { Map params = new HashMap(); params.put("event", eventId); MapUtilities.nullSafePut(params, "limit", limit); MapUtilities.nullSafePut(params, "page", page); Result result = Caller.getInstance().call("event.getShouts", apiKey, params); return ResponseBuilder.buildPaginatedResult(result, Shout.class); } /** * Enumeration for the attendance status parameter of the attend operation. */ public static enum AttendanceStatus { ATTENDING(0), MAYBE_ATTENDING(1), NOT_ATTENDING(2); private int id; private AttendanceStatus(int id) { this.id = id; } public int getId() { return id; } public static AttendanceStatus getByID(int statusId) { for (AttendanceStatus status : AttendanceStatus.values()) { if(status.id == statusId) return status; } return null; } } public static class TicketSupplier { private String name; private String website; public TicketSupplier(String name, String website) { this.name = name; this.website = website; } public String getName() { return name; } public String getWebsite() { return website; } } private static class EventFactory implements ItemFactory { public Event createItemFromElement(DomElement element) { // if (element == null) // return null; Event event = new Event(); ImageHolder.loadImages(event, element); event.id = Integer.parseInt(element.getChildText("id")); event.title = element.getChildText("title"); event.description = element.getChildText("description"); event.url = element.getChildText("url"); if (element.hasChild("attendance")) event.attendance = Integer.parseInt(element.getChildText("attendance")); if (element.hasChild("reviews")) event.reviews = Integer.parseInt(element.getChildText("reviews")); try { event.startDate = DATE_FORMAT.parse(element.getChildText("startDate")); if (element.hasChild("endDate")) { event.endDate = DATE_FORMAT.parse(element.getChildText("endDate")); } } catch (ParseException e1) { // Date format not valid !?, should definitely not happen. } event.headliner = element.getChild("artists").getChildText("headliner"); event.artists = new ArrayList(); for (DomElement artist : element.getChild("artists").getChildren("artist")) { event.artists.add(artist.getText()); } event.website = element.getChildText("website"); event.tickets = new ArrayList(); if (element.hasChild("tickets")) { for (DomElement ticket : element.getChild("tickets").getChildren("ticket")) { event.tickets.add(new TicketSupplier(ticket.getAttribute("supplier"), ticket.getText())); } } if(element.hasAttribute("status")) event.userAttendanceStatus = AttendanceStatus.getByID(Integer.parseInt(element.getAttribute("status"))); if(element.hasChild("venue")) event.venue = ResponseBuilder.buildItem(element.getChild("venue"), Venue.class); return event; } } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/Geo.java000066400000000000000000000343621200137731100251440ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import java.util.*; import de.umass.util.MapUtilities; import de.umass.util.StringUtilities; import de.umass.xml.DomElement; /** * Provides nothing more than a namespace for the API methods starting with geo. * * @author Janni Kovacs */ public class Geo { /** * This inner class represents a Metro, which is composed of its name and the name of its country. * * @see Geo#getMetros(String, String) */ public static class Metro { private String name; private String country; public Metro(String name, String country) { this.name = name; this.country = country; } public String getName() { return name; } public String getCountry() { return country; } } private Geo() { } /** * Get all events in a specific location by country or city name.
This method returns all events by subsequently calling * {@link #getEvents(String, String, int, String)} and concatenating the single results into one list.
Pay attention if you use this * method as it may produce a lot of network traffic and therefore may consume a long time. * * @param location Specifies a location to retrieve events for * @param distance Find events within a specified radius (in kilometres) * @param apiKey A Last.fm API key. * @return a list containing all events */ public static Collection getAllEvents(String location, String distance, String apiKey) { Collection events = null; int page = 1, total; do { PaginatedResult result = getEvents(location, distance, page, apiKey); total = result.getTotalPages(); Collection pageResults = result.getPageResults(); if (events == null) { // events is initialized here to initialize it with the right size and avoid array copying later on events = new ArrayList(total * pageResults.size()); } for (Event artist : pageResults) { events.add(artist); } page++; } while (page <= total); return events; } /** * Get all events in a specific location by country or city name.
This method only returns the first page of a possibly paginated * result. To retrieve all pages get the total number of pages via {@link de.umass.lastfm.PaginatedResult#getTotalPages()} and subsequently * call {@link #getEvents(String, String, int, String)} with the successive page numbers. * * @param location Specifies a location to retrieve events for * @param distance Find events within a specified radius (in kilometres) * @param apiKey A Last.fm API key. * @return a {@link PaginatedResult} containing a list of events */ public static PaginatedResult getEvents(String location, String distance, String apiKey) { return getEvents(location, distance, 1, apiKey); } /** * Get all events in a specific location by country or city name.
This method only returns the specified page of a paginated result. * * @param location Specifies a location to retrieve events for * @param distance Find events within a specified radius (in kilometres) * @param page A page number for pagination * @param apiKey A Last.fm API key. * @return a {@link PaginatedResult} containing a list of events */ public static PaginatedResult getEvents(String location, String distance, int page, String apiKey) { return getEvents(location, distance, page, -1, apiKey); } public static PaginatedResult getEvents(String location, String distance, int page, int limit, String apiKey) { Map params = new HashMap(); params.put("page", String.valueOf(page)); MapUtilities.nullSafePut(params, "location", location); MapUtilities.nullSafePut(params, "distance", distance); MapUtilities.nullSafePut(params, "limit", limit); Result result = Caller.getInstance().call("geo.getEvents", apiKey, params); return ResponseBuilder.buildPaginatedResult(result, Event.class); } /** * Get all events in a specific location by latitude/longitude.
This method only returns the specified page of a paginated result. * * @param latitude Latitude * @param longitude Longitude * @param page A page number for pagination * @param apiKey A Last.fm API key. * @return a {@link PaginatedResult} containing a list of events */ public static PaginatedResult getEvents(double latitude, double longitude, int page, String apiKey) { return getEvents(latitude, longitude, page, -1, apiKey); } public static PaginatedResult getEvents(double latitude, double longitude, int page, int limit, String apiKey) { Map params = new HashMap(); params.put("page", String.valueOf(page)); params.put("lat", String.valueOf(latitude)); params.put("long", String.valueOf(longitude)); MapUtilities.nullSafePut(params, "limit", limit); Result result = Caller.getInstance().call("geo.getEvents", apiKey, params); return ResponseBuilder.buildPaginatedResult(result, Event.class); } public static PaginatedResult getEvents(double latitude, double longitude, String distance, String apiKey) { return getEvents(latitude, longitude, distance, -1, -1, apiKey); } /** * Get all events within the specified distance of the location specified by latitude/longitude.
* This method only returns the specified page of a paginated result. * * @param latitude Latitude * @param longitude Longitude * @param distance Find events within a specified radius (in kilometres) * @param page A page number for pagination * @param limit The maximum number of items returned per page * @param apiKey A Last.fm API key. * @return a {@link PaginatedResult} containing a list of events */ public static PaginatedResult getEvents(double latitude, double longitude, String distance, int page, int limit, String apiKey) { Map params = new HashMap(); params.put("lat", String.valueOf(latitude)); params.put("long", String.valueOf(longitude)); params.put("distance", distance); MapUtilities.nullSafePut(params, "page", page); MapUtilities.nullSafePut(params, "limit", limit); Result result = Caller.getInstance().call("geo.getEvents", apiKey, params); return ResponseBuilder.buildPaginatedResult(result, Event.class); } /** * Get the most popular artists on Last.fm by country * * @param country A country name, as defined by the ISO 3166-1 country names standard * @param apiKey A Last.fm API key. * @return list of Artists */ public static Collection getTopArtists(String country, String apiKey) { Result result = Caller.getInstance().call("geo.getTopArtists", apiKey, "country", country); return ResponseBuilder.buildCollection(result, Artist.class); } /** * Get the most popular tracks on Last.fm by country * * @param country A country name, as defined by the ISO 3166-1 country names standard * @param apiKey A Last.fm API key. * @return a list of Tracks */ public static Collection getTopTracks(String country, String apiKey) { Result result = Caller.getInstance().call("geo.getTopTracks", apiKey, "country", country); return ResponseBuilder.buildCollection(result, Track.class); } /** * Get a list of valid countries and {@link Metro}s for use in the other webservices. * * @param apiKey A Last.fm API key * @return a List of {@link Metro}s */ public static Collection getMetros(String apiKey) { return getMetros(null, apiKey); } /** * Get a list of valid countries and {@link Metro}s for use in the other webservices. * * @param country Optionally restrict the results to those Metros from a particular country, as defined by the ISO 3166-1 country names * standard * @param apiKey A Last.fm API key * @return a List of {@link Metro}s */ public static Collection getMetros(String country, String apiKey) { Map params = new HashMap(); MapUtilities.nullSafePut(params, "country", country); Result result = Caller.getInstance().call("geo.getMetros", apiKey, params); if (!result.isSuccessful()) return Collections.emptyList(); Collection children = result.getContentElement().getChildren("metro"); Collection metros = new ArrayList(children.size()); for (DomElement child : children) { metros.add(new Metro(child.getChildText("name"), child.getChildText("country"))); } return metros; } /** * Get a list of available chart periods for this metro, expressed as date ranges which can be sent to the chart services. * * @param metro The name of the metro, or null * @param apiKey A Last.fm API key * @return a list of available charts as a Map */ public static LinkedHashMap getMetroWeeklyChartList(String metro, String apiKey) { return Chart.getWeeklyChartList("geo.getMetroWeeklyChartList", "metro", metro, apiKey); } public static Chart getMetroArtistChart(String country, String metro, String apiKey) { return getMetroArtistChart(country, metro, null, null, apiKey); } public static Chart getMetroArtistChart(Metro metro, String start, String end, String apiKey) { return getMetroArtistChart(metro.getCountry(), metro.getName(), start, end, apiKey); } public static Chart getMetroArtistChart(String country, String metro, String start, String end, String apiKey) { return Chart.getChart("geo.getMetroArtistChart", "artist", StringUtilities.map("country", country, "metro", metro), start, end, -1, apiKey); } public static Chart getMetroTrackChart(String country, String metro, String apiKey) { return getMetroTrackChart(country, metro, null, null, apiKey); } public static Chart getMetroTrackChart(Metro metro, String start, String end, String apiKey) { return getMetroTrackChart(metro.getCountry(), metro.getName(), start, end, apiKey); } public static Chart getMetroTrackChart(String country, String metro, String start, String end, String apiKey) { return Chart.getChart("geo.getMetroTrackChart", "track", StringUtilities.map("country", country, "metro", metro), start, end, -1, apiKey); } public static Chart getMetroHypeArtistChart(String country, String metro, String apiKey) { return getMetroHypeArtistChart(country, metro, null, null, apiKey); } public static Chart getMetroHypeArtistChart(Metro metro, String start, String end, String apiKey) { return getMetroHypeArtistChart(metro.getCountry(), metro.getName(), start, end, apiKey); } public static Chart getMetroHypeArtistChart(String country, String metro, String start, String end, String apiKey) { return Chart.getChart("geo.getMetroHypeArtistChart", "artist", StringUtilities.map("country", country, "metro", metro), start, end, -1, apiKey); } public static Chart getMetroHypeTrackChart(String country, String metro, String apiKey) { return getMetroHypeTrackChart(country, metro, null, null, apiKey); } public static Chart getMetroHypeTrackChart(Metro metro, String start, String end, String apiKey) { return getMetroHypeTrackChart(metro.getCountry(), metro.getName(), start, end, apiKey); } public static Chart getMetroHypeTrackChart(String country, String metro, String start, String end, String apiKey) { return Chart.getChart("geo.getMetroHypeTrackChart", "track", StringUtilities.map("country", country, "metro", metro), start, end, -1, apiKey); } public static Chart getMetroUniqueArtistChart(String country, String metro, String apiKey) { return getMetroUniqueArtistChart(country, metro, null, null, apiKey); } public static Chart getMetroUniqueArtistChart(Metro metro, String start, String end, String apiKey) { return getMetroUniqueArtistChart(metro.getCountry(), metro.getName(), start, end, apiKey); } public static Chart getMetroUniqueArtistChart(String country, String metro, String start, String end, String apiKey) { return Chart.getChart("geo.getMetroUniqueArtistChart", "artist", StringUtilities.map("country", country, "metro", metro), start, end, -1, apiKey); } public static Chart getMetroUniqueTrackChart(String country, String metro, String apiKey) { return getMetroUniqueTrackChart(country, metro, null, null, apiKey); } public static Chart getMetroUniqueTrackChart(Metro metro, String start, String end, String apiKey) { return getMetroUniqueTrackChart(metro.getCountry(), metro.getName(), start, end, apiKey); } public static Chart getMetroUniqueTrackChart(String country, String metro, String start, String end, String apiKey) { return Chart.getChart("geo.getMetroUniqueTrackChart", "track", StringUtilities.map("country", country, "metro", metro), start, end, -1, apiKey); } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/Group.java000066400000000000000000000115201200137731100255150ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import java.util.Collection; import java.util.LinkedHashMap; /** * Provides nothing more than a namespace for the API methods starting with group. * * @author Janni Kovacs */ public class Group { private Group() { } public static Chart getWeeklyAlbumChart(String group, String apiKey) { return getWeeklyAlbumChart(group, null, null, -1, apiKey); } public static Chart getWeeklyAlbumChart(String group, int limit, String apiKey) { return getWeeklyAlbumChart(group, null, null, limit, apiKey); } public static Chart getWeeklyAlbumChart(String group, String from, String to, int limit, String apiKey) { return Chart.getChart("group.getWeeklyAlbumChart", "group", group, "album", from, to, limit, apiKey); } public static Chart getWeeklyArtistChart(String group, String apiKey) { return getWeeklyArtistChart(group, null, null, -1, apiKey); } public static Chart getWeeklyArtistChart(String group, int limit, String apiKey) { return getWeeklyArtistChart(group, null, null, limit, apiKey); } public static Chart getWeeklyArtistChart(String group, String from, String to, int limit, String apiKey) { return Chart.getChart("group.getWeeklyArtistChart", "group", group, "artist", from, to, limit, apiKey); } public static Chart getWeeklyTrackChart(String group, String apiKey) { return getWeeklyTrackChart(group, null, null, -1, apiKey); } public static Chart getWeeklyTrackChart(String group, int limit, String apiKey) { return getWeeklyTrackChart(group, null, null, limit, apiKey); } public static Chart getWeeklyTrackChart(String group, String from, String to, int limit, String apiKey) { return Chart.getChart("group.getWeeklyTrackChart", "group", group, "track", from, to, limit, apiKey); } public static LinkedHashMap getWeeklyChartList(String group, String apiKey) { return Chart.getWeeklyChartList("group.getWeeklyChartList", "group", group, apiKey); } public static Collection getWeeklyChartListAsCharts(String group, String apiKey) { return Chart.getWeeklyChartListAsCharts("group", group, apiKey); } /** * Get a list of members for this group. * * @param group The group name to fetch the members of * @param apiKey A Last.fm API key * @return the list of {@link User}s */ public static PaginatedResult getMembers(String group, String apiKey) { return getMembers(group, 1, apiKey); } /** * Get a list of members for this group. * * @param group The group name to fetch the members of * @param page The results page you would like to fetch * @param apiKey A Last.fm API key * @return the list of {@link User}s */ public static PaginatedResult getMembers(String group, int page, String apiKey) { Result result = Caller.getInstance().call("group.getMembers", apiKey, "group", group, "page", String.valueOf(page)); return ResponseBuilder.buildPaginatedResult(result, User.class); } /** * Get the hype list for a group. * * @param group The last.fm group name * @param apiKey A Last.fm API key * @return a Collection of {@link Artist}s */ public static Collection getHype(String group, String apiKey) { Result result = Caller.getInstance().call("group.getHype", apiKey, "group", group); return ResponseBuilder.buildCollection(result, Artist.class); } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/Image.java000066400000000000000000000101641200137731100254460ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import de.umass.xml.DomElement; /** * An Image contains metadata and URLs for an artist's image. Metadata contains title, votes, format and other. * Images are available in various sizes, see {@link ImageSize} for all sizes. * * @author Janni Kovacs * @see ImageSize * @see Artist#getImages(String, String) */ public class Image extends ImageHolder { static final ItemFactory FACTORY = new ImageFactory(); private static final DateFormat DATE_ADDED_FORMAT = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss", Locale.ENGLISH); private String title; private String url; private Date dateAdded; private String format; private String owner; private int thumbsUp, thumbsDown; private Image() { } public String getTitle() { return title; } public String getUrl() { return url; } public Date getDateAdded() { return dateAdded; } public String getFormat() { return format; } public String getOwner() { return owner; } public int getThumbsUp() { return thumbsUp; } public int getThumbsDown() { return thumbsDown; } private static class ImageFactory implements ItemFactory { public Image createItemFromElement(DomElement element) { Image i = new Image(); i.title = element.getChildText("title"); i.url = element.getChildText("url"); i.format = element.getChildText("format"); try { i.dateAdded = DATE_ADDED_FORMAT.parse(element.getChildText("dateadded")); } catch (ParseException e1) { e1.printStackTrace(); } DomElement owner = element.getChild("owner"); if (owner != null) i.owner = owner.getChildText("name"); DomElement votes = element.getChild("votes"); if (votes != null) { i.thumbsUp = Integer.parseInt(votes.getChildText("thumbsup")); i.thumbsDown = Integer.parseInt(votes.getChildText("thumbsdown")); } DomElement sizes = element.getChild("sizes"); for (DomElement image : sizes.getChildren("size")) { // code copied from ImageHolder.loadImages String attribute = image.getAttribute("name"); ImageSize size = null; if (attribute == null) { size = ImageSize.MEDIUM; // workaround for image responses without size attr. } else { try { size = ImageSize.valueOf(attribute.toUpperCase(Locale.ENGLISH)); } catch (IllegalArgumentException e) { // if they suddenly again introduce a new image size } } if (size != null) i.imageUrls.put(size, image.getText()); } return i; } } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/ImageHolder.java000066400000000000000000000055341200137731100266110ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import java.util.*; import de.umass.xml.DomElement; /** * Abstract superclass for all items that may contain images (such as {@link Artist}s, {@link Album}s or {@link Track}s). * * @author Janni Kovacs */ public abstract class ImageHolder { protected Map imageUrls = new HashMap(); /** * Returns a Set of all {@link ImageSize}s available. * * @return all sizes */ public Set availableSizes() { return imageUrls.keySet(); } /** * Returns the URL of the image in the specified size, or null if not available. * * @param size The preferred size * @return an image URL */ public String getImageURL(ImageSize size) { return imageUrls.get(size); } protected static void loadImages(ImageHolder holder, DomElement element) { Collection images = element.getChildren("image"); for (DomElement image : images) { String attribute = image.getAttribute("size"); ImageSize size = null; if (attribute == null) { size = ImageSize.MEDIUM; // workaround for image responses without size attr. } else { try { size = ImageSize.valueOf(attribute.toUpperCase(Locale.ENGLISH)); } catch (IllegalArgumentException e) { // if they suddenly again introduce a new image size } } if (size != null) holder.imageUrls.put(size, image.getText()); } } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/ImageSize.java000066400000000000000000000031241200137731100262770ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; /** * @author Janni Kovacs */ public enum ImageSize { SMALL, MEDIUM, LARGE, LARGESQUARE, HUGE, EXTRALARGE, MEGA, ORIGINAL } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/ItemFactory.java000066400000000000000000000040551200137731100266540ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import de.umass.xml.DomElement; /** * An ItemFactory can be used to instantiate a value object - such as Artist, Album, Track, Tag - from an XML element. Use the * {@link ItemFactoryBuilder} to obtain item factories for a specific type. * * @author Janni Kovacs * @see de.umass.lastfm.ItemFactoryBuilder * @see ResponseBuilder */ interface ItemFactory { /** * Create a new instance of the type T, based on the passed {@link DomElement}. * * @param element the XML element * @return a new object */ public T createItemFromElement(DomElement element); } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/ItemFactoryBuilder.java000066400000000000000000000061011200137731100301550ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import java.util.HashMap; import java.util.Map; /** * The ItemFactoryBuilder can be used to obtain {@link ItemFactory ItemFactories} for a specific type. * * @author Janni Kovacs * @see ItemFactory */ final class ItemFactoryBuilder { private static final ItemFactoryBuilder INSTANCE = new ItemFactoryBuilder(); private Map factories = new HashMap(); private ItemFactoryBuilder() { // register default factories addItemFactory(Album.class, Album.FACTORY); addItemFactory(Track.class, Track.FACTORY); addItemFactory(Artist.class, Artist.FACTORY); addItemFactory(Tag.class, Tag.FACTORY); addItemFactory(Image.class, Image.FACTORY); addItemFactory(User.class, User.FACTORY); addItemFactory(Event.class, Event.FACTORY); addItemFactory(Venue.class, Venue.FACTORY); addItemFactory(Shout.class, Shout.FACTORY); addItemFactory(Playlist.class, Playlist.FACTORY); } /** * Retrieve the instance of the ItemFactoryBuilder. * * @return the instance */ public static ItemFactoryBuilder getFactoryBuilder() { return INSTANCE; } public void addItemFactory(Class itemClass, ItemFactory factory) { factories.put(itemClass, factory); } /** * Retrieves an {@link ItemFactory} for the given type, or null if no such factory was registered. * * @param itemClass the type's Class object * @return the ItemFactory or null */ @SuppressWarnings("unchecked") public ItemFactory getItemFactory(Class itemClass) { return factories.get(itemClass); } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/Library.java000066400000000000000000000303371200137731100260340ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; /** * Contains bindings for all methods in the "library" namespace. * * @author Martin Chorley * @author Janni Kovacs */ public class Library { private Library() { } /** * Retrieves a paginated list of all the artists in a user's library. * * @param user The user whose library you want to fetch. * @param apiKey A Last.fm API key. * @return a {@link PaginatedResult} of the artists */ public static PaginatedResult getArtists(String user, String apiKey) { return getArtists(user, 1, 0, apiKey); } /** * Retrieves a paginated list of all the artists in a user's library. * * @param user The user whose library you want to fetch. * @param page The page number you wish to scan to. * @param apiKey A Last.fm API key. * @return a {@link PaginatedResult} of the artists */ public static PaginatedResult getArtists(String user, int page, String apiKey) { return getArtists(user, page, 0, apiKey); } /** * Retrieves a paginated list of all the artists in a user's library. * * @param user The user whose library you want to fetch. * @param page The page number you wish to scan to. * @param limit Limit the amount of artists returned (maximum/default is 50). * @param apiKey A Last.fm API key. * @return a {@link PaginatedResult} of the artists */ public static PaginatedResult getArtists(String user, int page, int limit, String apiKey) { Map params = new HashMap(); params.put("user", user); params.put("page", String.valueOf(page)); params.put("limit", String.valueOf(limit)); Result result = Caller.getInstance().call("library.getArtists", apiKey, params); return ResponseBuilder.buildPaginatedResult(result, Artist.class); } /** * Retrieves all artists in a user's library. Pay attention if you use this method as it may produce * a lot of network traffic and therefore may consume a long time. * * @param user The user whose library you want to fetch. * @param apiKey A Last.fm API key. * @return all artists in a user's library */ public static Collection getAllArtists(String user, String apiKey) { Collection artists = null; int page = 1, total; do { PaginatedResult result = getArtists(user, page, apiKey); total = result.getTotalPages(); Collection pageResults = result.getPageResults(); if (artists == null) { // artists is initialized here to initialize it with the right size and avoid array copying later on artists = new ArrayList(total * pageResults.size()); } artists.addAll(pageResults); page++; } while (page <= total); return artists; } /** * Retrieves a paginated list of all the albums in a user's library. * * @param user The user whose library you want to fetch. * @param apiKey A Last.fm API key. * @return a {@link PaginatedResult} of the albums */ public static PaginatedResult getAlbums(String user, String apiKey) { return getAlbums(user, 1, 0, apiKey); } /** * Retrieves a paginated list of all the albums in a user's library. * * @param user The user whose library you want to fetch. * @param page The page number you wish to scan to. * @param apiKey A Last.fm API key. * @return a {@link PaginatedResult} of the albums */ public static PaginatedResult getAlbums(String user, int page, String apiKey) { return getAlbums(user, page, 0, apiKey); } /** * Retrieves a paginated list of all the albums in a user's library. * * @param user The user whose library you want to fetch. * @param page The page number you wish to scan to. * @param limit Limit the amount of albumss returned (maximum/default is 50). * @param apiKey A Last.fm API key. * @return a {@link PaginatedResult} of the albums */ public static PaginatedResult getAlbums(String user, int page, int limit, String apiKey) { Map params = new HashMap(); params.put("user", user); params.put("page", String.valueOf(page)); params.put("limit", String.valueOf(limit)); Result result = Caller.getInstance().call("library.getAlbums", apiKey, params); return ResponseBuilder.buildPaginatedResult(result, Album.class); } /** * Retrieves all albums in a user's library. Pay attention if you use this method as it may produce * a lot of network traffic and therefore may consume a long time. * * @param user The user whose library you want to fetch. * @param apiKey A Last.fm API key. * @return all albums in a user's library */ public static Collection getAllAlbums(String user, String apiKey) { Collection albums = null; int page = 1, total; do { PaginatedResult result = getAlbums(user, page, apiKey); total = result.getTotalPages(); Collection pageResults = result.getPageResults(); if (albums == null) { // albums is initialized here to initialize it with the right size and avoid array copying later on albums = new ArrayList(total * pageResults.size()); } albums.addAll(pageResults); page++; } while (page <= total); return albums; } /** * Retrieves a paginated list of all the tracks in a user's library. * * @param user The user whose library you want to fetch. * @param apiKey A Last.fm API key. * @return a {@link PaginatedResult} of the tracks */ public static PaginatedResult getTracks(String user, String apiKey) { return getTracks(user, 1, 0, apiKey); } /** * Retrieves a paginated list of all the tracks in a user's library. * * @param user The user whose library you want to fetch. * @param page The page number you wish to scan to. * @param apiKey A Last.fm API key. * @return a {@link PaginatedResult} of the tracks */ public static PaginatedResult getTracks(String user, int page, String apiKey) { return getTracks(user, page, 0, apiKey); } /** * Retrieves a paginated list of all the tracks in a user's library. * * @param user The user whose library you want to fetch. * @param page The page number you wish to scan to. * @param limit Limit the amount of albumss returned (maximum/default is 50). * @param apiKey A Last.fm API key. * @return a {@link PaginatedResult} of the tracks */ public static PaginatedResult getTracks(String user, int page, int limit, String apiKey) { Map params = new HashMap(); params.put("user", user); params.put("page", String.valueOf(page)); params.put("limit", String.valueOf(limit)); Result result = Caller.getInstance().call("library.getTracks", apiKey, params); return ResponseBuilder.buildPaginatedResult(result, Track.class); } /** * Retrieves all tracks in a user's library. Pay attention if you use this method as it may produce * a lot of network traffic and therefore may consume a long time. * * @param user The user whose library you want to fetch. * @param apiKey A Last.fm API key. * @return all tracks in a user's library */ public static Collection getAllTracks(String user, String apiKey) { Collection tracks = null; int page = 1, total; do { PaginatedResult result = getTracks(user, page, apiKey); total = result.getTotalPages(); Collection pageResults = result.getPageResults(); if (tracks == null) { // tracks is initialized here to initialize it with the right size and avoid array copying later on tracks = new ArrayList(total * pageResults.size()); } tracks.addAll(pageResults); page++; } while (page <= total); return tracks; } /** * Add an artist to a user's Last.fm library * * @param artist The artist name you wish to add * @param session A Session instance * @return the result of the operation */ public static Result addArtist(String artist, Session session) { return Caller.getInstance().call("library.addArtist", session, "artist", artist); } /** * Add an album to a user's Last.fm library * * @param artist The artist that composed the track * @param album The album name you wish to add * @param session A Session instance * @return the result of the operation */ public static Result addAlbum(String artist, String album, Session session) { return Caller.getInstance().call("library.addAlbum", session, "artist", artist, "album", album); } /** * Add a track to a user's Last.fm library * * @param artist The artist that composed the track * @param track The track name you wish to add * @param session A Session instance * @return the result of the operation */ public static Result addTrack(String artist, String track, Session session) { return Caller.getInstance().call("library.addTrack", session, "artist", artist, "track", track); } /** * Remove an artist from a user's Last.fm library * * @param artist The artist name you wish to remove * @param session A Session instance * @return the result of the operation */ public static Result removeArtist(String artist, Session session) { return Caller.getInstance().call("library.removeArtist", session, "artist", artist); } /** * Remove an album from a user's Last.fm library * * @param artist The artist that composed the album * @param album The name of the album you wish to remove * @param session A Session instance * @return the result of the operation */ public static Result removeAlbum(String artist, String album, Session session) { return Caller.getInstance().call("library.removeAlbum", session, "artist", artist, "album", album); } /** * Remove a track from a user's Last.fm library * * @param artist The artist that composed the track * @param track The name of the track that you wish to remove * @param session A Session instance * @return the result of the operation */ public static Result removeTrack(String artist, String track, Session session) { return Caller.getInstance().call("library.removeTrack", session, "artist", artist, "track", track); } /** * Remove a scrobble from a user's Last.fm library * * @param artist The artist that composed the track * @param track The name of the track * @param timestamp The unix timestamp of the scrobble that you wish to remove * @param session A Session instance * @return the result of the operation */ public static Result removeScrobble(String artist, String track, long timestamp, Session session) { return Caller.getInstance().call("library.removeScrobble", session, "artist", artist, "track", track, "timestamp", String.valueOf(timestamp)); } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/MusicEntry.java000066400000000000000000000176071200137731100265370ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.Locale; import de.umass.xml.DomElement; /** * MusicEntry is the abstract superclass for {@link Track}, {@link Artist} and {@link Album}. It encapsulates data and provides * methods used in all subclasses, for example: name, playcount, images and more. * * @author Janni Kovacs */ public abstract class MusicEntry extends ImageHolder { private static final DateFormat DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss ZZZZ", Locale.ENGLISH); protected String name; protected String url; protected String mbid; protected int playcount; protected int userPlaycount; protected int listeners; protected boolean streamable; protected String id; /** * This property is only available on hype charts, like {@link Chart#getHypedArtists(String)} or {@link * de.umass.lastfm.Group#getHype(String, String)} */ protected int percentageChange; protected Collection tags = new ArrayList(); private Date wikiLastChanged; private String wikiSummary; private String wikiText; private float similarityMatch; protected MusicEntry(String name, String url) { this(name, url, null, -1, -1, false); } protected MusicEntry(String name, String url, String mbid, int playcount, int listeners, boolean streamable) { this.name = name; this.url = url; this.mbid = mbid; this.playcount = playcount; this.listeners = listeners; this.streamable = streamable; } public int getListeners() { return listeners; } public String getMbid() { return mbid; } public String getName() { return name; } public String getId() { return id; } public int getPlaycount() { return playcount; } public int getUserPlaycount() { return userPlaycount; } public boolean isStreamable() { return streamable; } public String getUrl() { return url; } public Collection getTags() { return tags; } /** * Returns the value of the "percentage change" fields in weekly hype charts responses, such as in {@link Group#getHype(String, String)} * or {@link Chart#getHypedArtists(String)}. * * @return Weekly percentage change */ public int getPercentageChange() { return percentageChange; } public Date getWikiLastChanged() { return wikiLastChanged; } public String getWikiSummary() { return wikiSummary; } public String getWikiText() { return wikiText; } /** * Returns the "similarity" property, which is included in Artist.getSimilar and Track.getSimilar responses * * @return similarity */ public float getSimilarityMatch() { return similarityMatch; } @Override public String toString() { return this.getClass().getSimpleName() + "[" + "name='" + name + '\'' + ", id='" + id + '\'' + ", url='" + url + '\'' + ", mbid='" + mbid + '\'' + ", playcount=" + playcount + ", listeners=" + listeners + ", streamable=" + streamable + ']'; } /** * Loads all generic information from an XML DomElement into the given MusicEntry instance, i.e. the following * tags:
  • playcount/plays
  • listeners
  • streamable
  • name
  • url
  • mbid
  • image
  • *
  • tags
* * @param entry An entry * @param element XML source element */ protected static void loadStandardInfo(MusicEntry entry, DomElement element) { // playcount & listeners DomElement statsChild = element.getChild("stats"); String playcountString; String userPlaycountString; String listenersString; if (statsChild != null) { playcountString = statsChild.getChildText("playcount"); userPlaycountString = statsChild.getChildText("userplaycount"); listenersString = statsChild.getChildText("listeners"); } else { playcountString = element.getChildText("playcount"); userPlaycountString = element.getChildText("userplaycount"); listenersString = element.getChildText("listeners"); } if (element.hasChild("id")) { entry.id = element.getChildText("id"); } // match for similar artists/tracks response if (element.hasChild("match")) { entry.similarityMatch = Float.parseFloat(element.getChildText("match")); } // percentagechange in getHype() responses if (element.hasChild("percentagechange")) { entry.percentageChange = Integer.parseInt(element.getChildText("percentagechange")); } int playcount = playcountString == null || playcountString.length() == 0 ? -1 : Integer .parseInt(playcountString); int userPlaycount = userPlaycountString == null || userPlaycountString.length() == 0 ? -1 : Integer .parseInt(userPlaycountString); int listeners = listenersString == null || listenersString.length() == 0 ? -1 : Integer .parseInt(listenersString); // streamable String s = element.getChildText("streamable"); boolean streamable = s != null && s.length() != 0 && Integer.parseInt(s) == 1; // copy entry.name = element.getChildText("name"); entry.url = element.getChildText("url"); entry.mbid = element.getChildText("mbid"); entry.playcount = playcount; entry.userPlaycount = userPlaycount; entry.listeners = listeners; entry.streamable = streamable; // tags DomElement tags = element.getChild("tags"); if (tags == null) tags = element.getChild("toptags"); if (tags != null) { for (DomElement tage : tags.getChildren("tag")) { entry.tags.add(tage.getChildText("name")); } } // wiki DomElement wiki = element.getChild("bio"); if (wiki == null) wiki = element.getChild("wiki"); if (wiki != null) { String publishedText = wiki.getChildText("published"); try { entry.wikiLastChanged = DATE_FORMAT.parse(publishedText); } catch (ParseException e) { // try parsing it with current locale try { DateFormat clFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss ZZZZ", Locale.getDefault()); entry.wikiLastChanged = clFormat.parse(publishedText); } catch (ParseException e2) { // cannot parse date, wrong locale. wait for last.fm to fix. } } entry.wikiSummary = wiki.getChildText("summary"); entry.wikiText = wiki.getChildText("content"); } // images ImageHolder.loadImages(entry, element); } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/PaginatedResult.java000066400000000000000000000060001200137731100275110ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import java.util.Collection; import java.util.Iterator; /** * A PaginatedResult is returned by methods which result set might be so large that it needs * to be paginated. Each PaginatedResult contains the total number of result pages, the current * page and a Collection of entries for the current page. * * @author Janni Kovacs */ public class PaginatedResult implements Iterable { private int page; private int totalPages; private Collection pageResults; PaginatedResult(int page, int totalPages, Collection pageResults) { this.page = page; this.totalPages = totalPages; this.pageResults = pageResults; } /** * Returns the page number of this result. * * @return page number */ public int getPage() { return page; } /** * Returns a list of entries of the type T for this page. * * @return page results */ public Collection getPageResults() { return pageResults; } /** * Returns the total number of pages available. * * @return total pages */ public int getTotalPages() { return totalPages; } /** * Returns true if this Result contains no elements, which is the case for service calls that would have returned a * PaginatedResult but fail. * * @return true if this result contains no elements */ public boolean isEmpty() { return pageResults == null || pageResults.isEmpty(); } public Iterator iterator() { return getPageResults().iterator(); } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/Period.java000066400000000000000000000034201200137731100256430ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; /** * @author Janni Kovacs */ public enum Period { OVERALL("overall"), WEEK("7day"), ONE_MONTH("1month"), THREE_MONTHS("3month"), SIX_MONTHS("6month"), TWELVE_MONTHS("12month"); private String string; Period(String string) { this.string = string; } public String getString() { return string; } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/Playlist.java000066400000000000000000000157511200137731100262340ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import de.umass.xml.DomElement; import java.util.ArrayList; import java.util.List; /** * Bean for music playlists. Contains the {@link #fetch(String, String) fetch} method and various fetchXXX * methods to retrieve playlists from the server. Playlists are identified by lastfm:// playlist urls. Valid urls * include: *
    *
  • Album Playlists: lastfm://playlist/album/{@literal }
  • *
  • User Playlists: lastfm://playlist/{@literal }
  • *
  • Tag Playlists: lastfm://playlist/tag/{@literal }/freetracks
  • *
* See http://www.last.fm/api/playlists for more information about playlists. * * @author Janni Kovacs */ public class Playlist { static final ItemFactory FACTORY = new PlaylistFactory(); private int id; private String title; private String annotation; private int size; private String creator; private List tracks = new ArrayList(); private Playlist() { } public String getCreator() { return creator; } public int getId() { return id; } public int getSize() { return size; } public String getTitle() { return title; } public String getAnnotation() { return annotation; } public List getTracks() { return tracks; } /** * Fetches an album playlist, which contains the tracks of the specified album. * * @param albumId The album id as returned in {@link Album#getInfo(String, String, String) Album.getInfo}. * @param apiKey A Last.fm API key. * @return a playlist */ public static Playlist fetchAlbumPlaylist(String albumId, String apiKey) { return fetch("lastfm://playlist/album/" + albumId, apiKey); } /** * Fetches a user-created playlist. * * @param playlistId A playlist id. * @param apiKey A Last.fm API key. * @return a playlist */ public static Playlist fetchUserPlaylist(int playlistId, String apiKey) { return fetch("lastfm://playlist/" + playlistId, apiKey); } /** * Fetches a playlist of freetracks for a given tag name. * * @param tag A tag name. * @param apiKey A Last.fm API key. * @return a playlist */ public static Playlist fetchTagPlaylist(String tag, String apiKey) { return fetch("lastfm://playlist/tag/" + tag + "/freetracks", apiKey); } /** * Fetches a playlist using a lastfm playlist url. See the class description for a list of valid * playlist urls. * * @param playlistUrl A valid playlist url. * @param apiKey A Last.fm API key. * @return a playlist */ public static Playlist fetch(String playlistUrl, String apiKey) { Result result = Caller.getInstance().call("playlist.fetch", apiKey, "playlistURL", playlistUrl); return ResponseBuilder.buildItem(result, Playlist.class); } /** * Add a track to a Last.fm user's playlist. * * @param playlistId The ID of the playlist - this is available in user.getPlaylists * @param artist The artist name that corresponds to the track to be added. * @param track The track name to add to the playlist. * @param session A Session instance. * @return the result of the operation */ public static Result addTrack(int playlistId, String artist, String track, Session session) { return Caller.getInstance() .call("playlist.addTrack", session, "playlistID", String.valueOf(playlistId), "artist", artist, "track", track); } /** * Creates a Last.fm playlist. * * @param title A title for the playlist * @param description A description for the playlist * @param session A Session instance * @return the result of the operation */ public static Playlist create(String title, String description, Session session) { Result result = Caller.getInstance().call("playlist.create", session, "title", title, "description", description); if (!result.isSuccessful()) return null; return ResponseBuilder.buildItem(result.getContentElement().getChild("playlist"), Playlist.class); } private static class PlaylistFactory implements ItemFactory { public Playlist createItemFromElement(DomElement element) { Playlist playlist = new Playlist(); if (element.hasChild("id")) playlist.id = Integer.parseInt(element.getChildText("id")); playlist.title = element.getChildText("title"); if (element.hasChild("size")) playlist.size = Integer.parseInt(element.getChildText("size")); playlist.creator = element.getChildText("creator"); playlist.annotation = element.getChildText("annotation"); DomElement trackList = element.getChild("trackList"); if (trackList != null) { for (DomElement te : trackList.getChildren("track")) { Track t = new Track(te.getChildText("title"), te.getChildText("identifier"), te.getChildText("creator")); t.album = te.getChildText("album"); t.duration = Integer.parseInt(te.getChildText("duration")) / 1000; t.imageUrls.put(ImageSize.LARGE, te.getChildText("image")); t.imageUrls.put(ImageSize.ORIGINAL, te.getChildText("image")); t.location = te.getChildText("location"); for (DomElement ext : te.getChildren("extension")) { if ("http://www.last.fm".equals(ext.getAttribute("application"))) { for (DomElement child : ext.getChildren()) { t.lastFmExtensionInfos.put(child.getTagName(), child.getText()); } } } playlist.tracks.add(t); } if (playlist.size == 0) playlist.size = playlist.tracks.size(); } return playlist; } } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/Radio.java000066400000000000000000000220021200137731100254540ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import java.util.HashMap; import java.util.Locale; import java.util.Map; import de.umass.util.MapUtilities; import de.umass.xml.DomElement; /** * Provides access to the Last.fm radio streaming service.
* Note that you have to be a subscriber (or have a special API key) to use this API. * Official documentation can be found here: http://www.last.fm/api/radio * * @author Janni Kovacs */ public class Radio { private String type; private String stationName; private String stationUrl; private boolean supportsDiscovery; private Session session; private int expiry = -1; private Radio(Session session) { this.session = session; } public String getType() { return type; } public String getStationName() { return stationName; } public String getStationUrl() { return stationUrl; } public boolean supportsDiscovery() { return supportsDiscovery; } /** * Returns the playlist expiration value for the last playlist fetchet, or -1 if no playlist has been fetched yet. * * @return playlist expiration in seconds */ public int playlistExpiresIn() { return expiry; } /** * Resolve the name of a resource into a station depending on which resource it is most likely to represent * * @param name The tag or artist to resolve * @param apiKey A Last.fm API key. * @return A {@link RadioStation} or null */ public static RadioStation search(String name, String apiKey) { Result result = Caller.getInstance().call("radio.search", apiKey, "name", name); if(!result.isSuccessful()) return null; DomElement stationElement = result.getContentElement().getChild("station"); if(stationElement == null) return null; return new RadioStation(stationElement.getChildText("url"), stationElement.getChildText("name")); } /** * Tune in to a Last.fm radio station. * * @param station An instance of {@link RadioStation} * @param session A Session instance * @return a Radio instance */ public static Radio tune(RadioStation station, Session session) { return tune(station, Locale.getDefault(), session); } /** * Tune in to a Last.fm radio station. * * @param station An instance of {@link RadioStation} * @param locale The language you want the radio's name in * @param session A Session instance * @return a Radio instance */ public static Radio tune(RadioStation station, Locale locale, Session session) { return tune(station.getUrl(), locale, session); } /** * Tune in to a Last.fm radio station. * * @param station A lastfm radio URL * @param locale The language you want the radio's name in * @param session A Session instance * @return a Radio instance */ public static Radio tune(String station, Locale locale, Session session) { Map params = new HashMap(); params.put("station", station); if (locale != null && locale.getLanguage().length() != 0) { params.put("lang", locale.getLanguage()); } Result result = Caller.getInstance().call("radio.tune", session, params); if (!result.isSuccessful()) return null; DomElement root = result.getContentElement(); Radio radio = new Radio(session); radio.type = root.getChildText("type"); radio.stationName = root.getChildText("name"); radio.stationUrl = root.getChildText("url"); radio.supportsDiscovery = "1".equals(root.getChildText("type")); return radio; } /** * Fetches a new radio playlist or null if an error occured, such as when the user is not allowed to stream radio * (no subscriber). * * @return a new {@link Playlist} or null */ public Playlist getPlaylist() { return getPlaylist(false, false); } /** * Fetches a new radio playlist. * * @param discovery Whether to request last.fm content with discovery mode switched on * @param rtp Whether the user is scrobbling or not during this radio session (helps content generation) * @return a new Playlist */ public Playlist getPlaylist(boolean discovery, boolean rtp) { return getPlaylist(discovery, rtp, false, -1, -1); } /** * Fetches a new radio playlist. * * @param discovery Whether to request last.fm content with discovery mode switched on * @param rtp Whether the user is scrobbling or not during this radio session (helps content generation) * @param buyLinks Whether the response should contain links for purchase/download, if available * @param speedMultiplier The rate at which to provide the stream (supported multipliers are 1.0 and 2.0) * @param bitrate What bitrate to stream content at, in kbps (supported bitrates are 64 and 128) * @return a new Playlist */ public Playlist getPlaylist(boolean discovery, boolean rtp, boolean buyLinks, double speedMultiplier, int bitrate) { Map params = new HashMap(); params.put("discovery", String.valueOf(discovery)); params.put("rtp", String.valueOf(rtp)); params.put("buylinks", String.valueOf(buyLinks)); MapUtilities.nullSafePut(params, "speed_multiplier", speedMultiplier); MapUtilities.nullSafePut(params, "bitrate", bitrate); Result result = Caller.getInstance().call("radio.getPlaylist", session, params); if (!result.isSuccessful()) return null; DomElement root = result.getContentElement(); for (DomElement e : root.getChildren("link")) { if ("http://www.last.fm/expiry".equals(e.getAttribute("rel"))) { this.expiry = Integer.parseInt(e.getText()); break; } } return ResponseBuilder.buildItem(root, Playlist.class); } public static class RadioStation { private String name; private String url; public RadioStation(String url) { this(url, null); } public RadioStation(String url, String name) { this.url = url; this.name = name; } public String getUrl() { return url; } public String getName() { return name; } public static RadioStation similarArtists(String artist) { return new RadioStation("lastfm://artist/" + artist + "/similarartists"); } public static RadioStation artistFans(String artist) { return new RadioStation("lastfm://artist/" + artist + "/fans"); } public static RadioStation library(String user) { return new RadioStation("lastfm://user/" + user + "/library"); } public static RadioStation neighbours(String user) { return new RadioStation("lastfm://user/" + user + "/neighbours"); } /** * @deprecated This station has been deprected as of nov. 2010, see here */ public static RadioStation lovedTracks(String user) { return new RadioStation("lastfm://user/" + user + "/loved"); } public static RadioStation recommended(String user) { return new RadioStation("lastfm://user/" + user + "/recommended"); } public static RadioStation tagged(String tag) { return new RadioStation("lastfm://globaltags/" + tag); } /** * @deprecated This station has been deprected as of nov. 2010, see here */ public static RadioStation playlist(String playlistId) { return new RadioStation("lastfm://playlist/" + playlistId); } /** * @deprecated This station has been deprected as of nov. 2010, see here */ public static RadioStation personalTag(String user, String tag) { return new RadioStation("lastfm://usertags/" + user + "/" + tag); } } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/ResponseBuilder.java000066400000000000000000000114541200137731100275340ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import de.umass.xml.DomElement; /** * This utility class can be used to generically generate Result objects (usually Lists or {@link PaginatedResult}s) from an XML response * using {@link ItemFactory ItemFactories}. * * @author Janni Kovacs */ public final class ResponseBuilder { private ResponseBuilder() { } private static ItemFactory getItemFactory(Class itemClass) { return ItemFactoryBuilder.getFactoryBuilder().getItemFactory(itemClass); } public static Collection buildCollection(Result result, Class itemClass) { return buildCollection(result, getItemFactory(itemClass)); } public static Collection buildCollection(Result result, ItemFactory factory) { if (!result.isSuccessful()) return Collections.emptyList(); return buildCollection(result.getContentElement(), factory); } public static Collection buildCollection(DomElement element, Class itemClass) { return buildCollection(element, getItemFactory(itemClass)); } public static Collection buildCollection(DomElement element, ItemFactory factory) { if (element == null) return Collections.emptyList(); Collection children = element.getChildren(); Collection items = new ArrayList(children.size()); for (DomElement child : children) { items.add(factory.createItemFromElement(child)); } return items; } public static PaginatedResult buildPaginatedResult(Result result, Class itemClass) { return buildPaginatedResult(result, getItemFactory(itemClass)); } public static PaginatedResult buildPaginatedResult(Result result, ItemFactory factory) { if (!result.isSuccessful()) { return new PaginatedResult(0, 0, Collections.emptyList()); } DomElement contentElement = result.getContentElement(); return buildPaginatedResult(contentElement, contentElement, factory); } public static PaginatedResult buildPaginatedResult(DomElement contentElement, DomElement childElement, Class itemClass) { return buildPaginatedResult(contentElement, childElement, getItemFactory(itemClass)); } public static PaginatedResult buildPaginatedResult(DomElement contentElement, DomElement childElement, ItemFactory factory) { Collection items = buildCollection(childElement, factory); String totalPagesAttribute = contentElement.getAttribute("totalPages"); if (totalPagesAttribute == null) totalPagesAttribute = contentElement.getAttribute("totalpages"); int page = Integer.parseInt(contentElement.getAttribute("page")); int totalPages = Integer.parseInt(totalPagesAttribute); return new PaginatedResult(page, totalPages, items); } public static T buildItem(Result result, Class itemClass) { return buildItem(result, getItemFactory(itemClass)); } public static T buildItem(Result result, ItemFactory factory) { if (!result.isSuccessful()) return null; return buildItem(result.getContentElement(), factory); } public static T buildItem(DomElement element, Class itemClass) { return buildItem(element, getItemFactory(itemClass)); } private static T buildItem(DomElement element, ItemFactory factory) { return factory.createItemFromElement(element); } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/Result.java000066400000000000000000000071021200137731100257000ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import org.w3c.dom.Document; import de.umass.xml.DomElement; /** * The Result class contains the response sent by the server, i.e. the status (either ok or failed), * an error code and message if failed and the xml response sent by the server. * * @author Janni Kovacs */ public class Result { public enum Status { OK, FAILED } protected Status status; protected String errorMessage = null; protected int errorCode = -1; protected int httpErrorCode = -1; protected Document resultDocument; protected Result(Document resultDocument) { this.status = Status.OK; this.resultDocument = resultDocument; } protected Result(String errorMessage) { this.status = Status.FAILED; this.errorMessage = errorMessage; } static Result createOkResult(Document resultDocument) { return new Result(resultDocument); } static Result createHttpErrorResult(int httpErrorCode, String errorMessage) { Result r = new Result(errorMessage); r.httpErrorCode = httpErrorCode; return r; } static Result createRestErrorResult(int errorCode, String errorMessage) { Result r = new Result(errorMessage); r.errorCode = errorCode; return r; } /** * Returns if the operation was successful. Same as getStatus() == Status.OK. * * @return true if the operation was successful */ public boolean isSuccessful() { return status == Status.OK; } public int getErrorCode() { return errorCode; } public int getHttpErrorCode() { return httpErrorCode; } public Status getStatus() { return status; } public Document getResultDocument() { return resultDocument; } public String getErrorMessage() { return errorMessage; } public DomElement getContentElement() { if (!isSuccessful()) return null; return new DomElement(resultDocument.getDocumentElement()).getChild("*"); } @Override public String toString() { return "Result[isSuccessful=" + isSuccessful() + ", errorCode=" + errorCode + ", httpErrorCode=" + httpErrorCode + ", errorMessage=" + errorMessage + ", status=" + status+"]"; } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/Session.java000066400000000000000000000073401200137731100260510ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import de.umass.xml.DomElement; /** * Contains Session data relevant for making API calls which require authentication. * A Session instance is passed to all methods requiring previous authentication. * * @author Janni Kovacs * @see de.umass.lastfm.Authenticator */ public class Session { private String apiKey; private String secret; private String username; private String key; private boolean subscriber; private Session() { } /** * Restores a Session instance with the given session key. * * @param apiKey An api key * @param secret A secret * @param sessionKey The previously obtained session key * @return a Session instance */ public static Session createSession(String apiKey, String secret, String sessionKey) { return createSession(apiKey, secret, sessionKey, null, false); } /** * Restores a Session instance with the given session key. * * @param apiKey An api key * @param secret A secret * @param sessionKey The previously obtained session key * @param username A Last.fm username * @param subscriber Subscriber status * @return a Session instance */ public static Session createSession(String apiKey, String secret, String sessionKey, String username, boolean subscriber) { Session s = new Session(); s.apiKey = apiKey; s.secret = secret; s.key = sessionKey; s.username = username; s.subscriber = subscriber; return s; } public String getSecret() { return secret; } public String getApiKey() { return apiKey; } public String getKey() { return key; } public boolean isSubscriber() { return subscriber; } public String getUsername() { return username; } @Override public String toString() { return "Session[" + "apiKey=" + apiKey + ", secret=" + secret + ", username=" + username + ", key=" + key + ", subscriber=" + subscriber + ']'; } static Session sessionFromElement(DomElement element, String apiKey, String secret) { if (element == null) return null; String user = element.getChildText("name"); String key = element.getChildText("key"); boolean subsc = element.getChildText("subscriber").equals("1"); return createSession(apiKey, secret, key, user, subsc); } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/Shout.java000066400000000000000000000050411200137731100255240ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import de.umass.xml.DomElement; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; public class Shout { private static final DateFormat DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss", Locale.ENGLISH); static final ItemFactory FACTORY = new ShoutFactory(); private String body; private String author; private Date date; public Shout(String body, String author, Date date) { this.body = body; this.author = author; this.date = date; } public String getBody() { return body; } public String getAuthor() { return author; } public Date getDate() { return date; } private static class ShoutFactory implements ItemFactory { public Shout createItemFromElement(DomElement element) { Date date = null; try { date = DATE_FORMAT.parse(element.getChildText("date")); } catch (ParseException e) { date = null; } return new Shout(element.getChildText("body"), element.getChildText("author"), date); } } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/Tag.java000066400000000000000000000222271200137731100251420ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; import de.umass.util.StringUtilities; import de.umass.xml.DomElement; /** * Bean for Tag data and provides methods for global tags. * * @author Janni Kovacs */ public class Tag implements Comparable { /** * Implementation of {@link ItemFactory} for this class */ static final ItemFactory FACTORY = new TagFactory(); private static final DateFormat DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss ZZZZ", Locale.ENGLISH); private String name; private String url; private int count; private boolean streamable; private int reach; private Date wikiLastChanged; private String wikiSummary; private String wikiText; private Tag(String name) { this.name = name; } public int getCount() { return count; } /** * Returns the number of taggings of this specific tag. Alias for {@link #getCount()}. * * @return Number of Taggings * @see Tag#getInfo(String, String) */ public int getTaggings() { return count; } public String getName() { return name; } public String getUrl() { return url; } public boolean isStreamable() { return streamable; } public int getReach() { return reach; } public Date getWikiLastChanged() { return wikiLastChanged; } public String getWikiSummary() { return wikiSummary; } public String getWikiText() { return wikiText; } /** * Returns the sum of all count elements in the results. * * @param tags a list of tags * @return the total count of all tags */ public static long getTagCountSum(Collection tags) { long total = 0; for (Tag topTag : tags) { total += topTag.count; } return total; } /** * Filters tags from the given list; retains only those tags with a count * higher than the given percentage of the total sum as from * {@link #getTagCountSum(Collection)}. * * @param tags list of tags * @param percentage cut off percentage * @return the filtered list of tags */ public static List filter(Collection tags, double percentage) { ArrayList tops = new ArrayList(); long total = getTagCountSum(tags); double cutOff = total / 100.0 * percentage; for (Tag tag : tags) { if (tag.count > cutOff) { tops.add(tag); } } return tops; } /** * Search for tags similar to this one. Returns tags ranked by similarity, based on listening data. * * @param tag The tag name * @param apiKey A Last.fm API key * @return a List of Tags */ public static Collection getSimilar(String tag, String apiKey) { Result result = Caller.getInstance().call("tag.getSimilar", apiKey, "tag", tag); return ResponseBuilder.buildCollection(result, Tag.class); } public static Collection getTopTags(String apiKey) { Result result = Caller.getInstance().call("tag.getTopTags", apiKey); return ResponseBuilder.buildCollection(result, Tag.class); } public static Collection getTopAlbums(String tag, String apiKey) { Result result = Caller.getInstance().call("tag.getTopAlbums", apiKey, "tag", tag); return ResponseBuilder.buildCollection(result, Album.class); } public static Collection getTopTracks(String tag, String apiKey) { Result result = Caller.getInstance().call("tag.getTopTracks", apiKey, "tag", tag); return ResponseBuilder.buildCollection(result, Track.class); } public static Collection getTopArtists(String tag, String apiKey) { Result result = Caller.getInstance().call("tag.getTopArtists", apiKey, "tag", tag); return ResponseBuilder.buildCollection(result, Artist.class); } public static Collection search(String tag, String apiKey) { return search(tag, 30, apiKey); } public static Collection search(String tag, int limit, String apiKey) { Result result = Caller.getInstance().call("tag.search", apiKey, "tag", tag, "limit", String.valueOf(limit)); Collection children = result.getContentElement().getChild("tagmatches").getChildren("tag"); List tags = new ArrayList(children.size()); for (DomElement s : children) { tags.add(FACTORY.createItemFromElement(s)); } return tags; } public static Chart getWeeklyArtistChart(String tag, String apiKey) { return getWeeklyArtistChart(tag, null, null, -1, apiKey); } public static Chart getWeeklyArtistChart(String tag, int limit, String apiKey) { return getWeeklyArtistChart(tag, null, null, limit, apiKey); } public static Chart getWeeklyArtistChart(String tag, String from, String to, int limit, String apiKey) { return Chart.getChart("tag.getWeeklyArtistChart", "tag", tag, "artist", from, to, limit, apiKey); } public static LinkedHashMap getWeeklyChartList(String tag, String apiKey) { return Chart.getWeeklyChartList("tag.getWeeklyChartList", "tag", tag, apiKey); } public static Collection getWeeklyChartListAsCharts(String tag, String apiKey) { return Chart.getWeeklyChartListAsCharts("tag", tag, apiKey); } /** * Gets the metadata for a tag. * * @param tag The tag name * @param apiKey A Last.fm API key * @return Tag metdata such as Wiki Text, reach and tag count */ public static Tag getInfo(String tag, String apiKey) { return getInfo(tag, null, apiKey); } /** * Gets the metadata for a tag. * * @param tag The tag name * @param locale The language to fetch info in, or null * @param apiKey A Last.fm API key * @return Tag metdata such as Wiki Text, reach and tag count */ public static Tag getInfo(String tag, Locale locale, String apiKey) { Map params = new HashMap(); params.put("tag", tag); if (locale != null && locale.getLanguage().length() != 0) { params.put("lang", locale.getLanguage()); } Result result = Caller.getInstance().call("tag.getInfo", apiKey, params); return ResponseBuilder.buildItem(result, Tag.class); } public int compareTo(Tag o) { // descending order return Double.compare(o.getCount(), this.getCount()); } /** * This implementation of {@link ItemFactory} creates {@link Tag} objects based on the passed xml element. * * @see Tag * @see Tag#FACTORY */ private static class TagFactory implements ItemFactory { public Tag createItemFromElement(DomElement element) { Tag t = new Tag(element.getChildText("name")); t.url = element.getChildText("url"); if (element.hasChild("count")) t.count = Integer.parseInt(element.getChildText("count")); else if (element.hasChild("taggings")) t.count = Integer.parseInt(element.getChildText("taggings")); if (element.hasChild("reach")) t.reach = Integer.parseInt(element.getChildText("reach")); if (element.hasChild("streamable")) t.streamable = StringUtilities.convertToBoolean(element.getChildText("streamable")); // wiki DomElement wiki = element.getChild("wiki"); if (wiki != null) { String publishedText = wiki.getChildText("published"); try { t.wikiLastChanged = DATE_FORMAT.parse(publishedText); } catch (ParseException e) { // try parsing it with current locale try { DateFormat clFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss ZZZZ", Locale.getDefault()); t.wikiLastChanged = clFormat.parse(publishedText); } catch (ParseException e2) { // cannot parse date, wrong locale. wait for last.fm to fix. } } t.wikiSummary = wiki.getChildText("summary"); t.wikiText = wiki.getChildText("content"); } return t; } } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/Tasteometer.java000066400000000000000000000073601200137731100267240ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import java.util.ArrayList; import java.util.Collection; import java.util.List; import de.umass.xml.DomElement; /** * Provides the binding for the "tasteometer.compare" method. * * @author Janni Kovacs */ public class Tasteometer { private Tasteometer() { } /** * Get a Tasteometer score from two inputs, along with a list of shared artists. * * @param type1 Type of the first input * @param value1 First input value * @param type2 Type of the second input * @param value2 Second input value * @param apiKey The Last.fm API key * @return result of Tasteometer comparison */ public static ComparisonResult compare(InputType type1, String value1, InputType type2, String value2, String apiKey) { Result result = Caller.getInstance().call("tasteometer.compare", apiKey, "type1", type1.name().toLowerCase(), "type2", type2.name().toLowerCase(), "value1", value1, "value2", value2); if (!result.isSuccessful()) return null; DomElement element = result.getContentElement(); DomElement re = element.getChild("result"); float score = Float.parseFloat(re.getChildText("score")); List artists = new ArrayList(); for (DomElement domElement : re.getChild("artists").getChildren("artist")) { artists.add(ResponseBuilder.buildItem(domElement, Artist.class)); } return new ComparisonResult(score, artists); } /** * Contains the result of a tasteometer comparison, i.e. the score (0.0-1.0) and a list of * shared artists. */ public static class ComparisonResult { private float score; private Collection matches; ComparisonResult(float score, Collection matches) { this.score = score; this.matches = matches; } /** * Returns a list of artist matches, i.e. artists both partys listen to. * * @return artist matches */ public Collection getMatches() { return matches; } /** * Returns the compatability score between 0.0 (no compatability) and 1.0 (highest compatability). * * @return the score */ public float getScore() { return score; } } /** * Specifies the type of the input for the {@linkplain Tasteometer#compare Tasteometer.compare} operation. */ public enum InputType { USER, ARTISTS, MYSPACE } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/Track.java000066400000000000000000000733201200137731100254730ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import de.umass.lastfm.scrobble.IgnoredMessageCode; import de.umass.lastfm.scrobble.ScrobbleData; import de.umass.lastfm.scrobble.ScrobbleResult; import de.umass.util.MapUtilities; import de.umass.util.StringUtilities; import de.umass.xml.DomElement; /** * Bean that contains information related to Tracks and provides bindings to methods * in the track. namespace. * * @author Janni Kovacs */ public class Track extends MusicEntry { private enum ScrobbleResultType { NOW_PLAYING, SINGLE_SCROBBLE, MULTIPLE_SCROBBLES } static final ItemFactory FACTORY = new TrackFactory(); public static final String ARTIST_PAGE = "artistpage"; public static final String ALBUM_PAGE = "albumpage"; public static final String TRACK_PAGE = "trackpage"; private String artist; private String artistMbid; protected String album; // protected for use in Playlist.playlistFromElement private String albumMbid; private int position = -1; private boolean fullTrackAvailable; private boolean nowPlaying; private Date playedWhen; protected int duration; // protected for use in Playlist.playlistFromElement protected String location; // protected for use in Playlist.playlistFromElement protected Map lastFmExtensionInfos = new HashMap(); // protected for use in Playlist.playlistFromElement protected Track(String name, String url, String artist) { super(name, url); this.artist = artist; } protected Track(String name, String url, String mbid, int playcount, int listeners, boolean streamable, String artist, String artistMbid, boolean fullTrackAvailable, boolean nowPlaying) { super(name, url, mbid, playcount, listeners, streamable); this.artist = artist; this.artistMbid = artistMbid; this.fullTrackAvailable = fullTrackAvailable; this.nowPlaying = nowPlaying; } /** * Returns the duration of the song, if available, in seconds. The duration attribute is only available * for tracks retrieved by {@link Playlist#fetch(String, String) Playlist.fetch} and * {@link Track#getInfo(String, String, String) Track.getInfo}. * * @return duration in seconds */ public int getDuration() { return duration; } public String getArtist() { return artist; } public String getArtistMbid() { return artistMbid; } public String getAlbum() { return album; } public String getAlbumMbid() { return albumMbid; } public boolean isFullTrackAvailable() { return fullTrackAvailable; } public boolean isNowPlaying() { return nowPlaying; } /** * Returns the location (URL) of this Track. This information is only available with the {@link Radio} services. * * @return the location */ public String getLocation() { return location; } /** * Returns last.fm specific information about this Track. Only available in Tracks fetched from * radio playlists. key can be one of the following: *
    *
  • artistpage
  • *
  • albumpage
  • *
  • trackpage
  • *
  • buyTrackURL
  • *
  • buyAlbumURL
  • *
  • freeTrackURL
  • *
* Or use the available constants in this class.
* Note that the key string is case sensitive. * * @param key A key * @return associated value * @see #ARTIST_PAGE * @see #ALBUM_PAGE * @see #TRACK_PAGE */ public String getLastFmInfo(String key) { return lastFmExtensionInfos.get(key); } /** * Returns the time when the track was played, if this data is available (e.g. for recent tracks) or null, * if this data is not available.
* * @return the date when the track was played or null */ public Date getPlayedWhen() { return playedWhen; } /** * Returns the position of this track in its associated album, or -1 if not available. * * @return the album position */ public int getPosition() { return position; } /** * Searches for a track with the given name and returns a list of possible matches. * * @param track Track name * @param apiKey The API key * @return a list of possible matches * @see #search(String, String, int, String) */ public static Collection search(String track, String apiKey) { return search(null, track, 30, apiKey); } /** * Searches for a track with the given name and returns a list of possible matches. * Specify an artist name or a limit to narrow down search results. * Pass null for the artist parameter if you want to specify a limit but don't want * to define an artist. * * @param artist Artist's name or null * @param track Track name * @param limit Number of maximum results * @param apiKey The API key * @return a list of possible matches */ public static Collection search(String artist, String track, int limit, String apiKey) { Map params = new HashMap(); params.put("track", track); params.put("limit", String.valueOf(limit)); MapUtilities.nullSafePut(params, "artist", artist); Result result = Caller.getInstance().call("track.search", apiKey, params); if(!result.isSuccessful()) return Collections.emptyList(); DomElement element = result.getContentElement(); DomElement matches = element.getChild("trackmatches"); return ResponseBuilder.buildCollection(matches, Track.class); } /** * Retrieves the top tags for the given track. You either have to specify a track and artist name or * a mbid. If you specify an mbid you may pass null for the first parameter. * * @param artist Artist name or null if an MBID is specified * @param trackOrMbid Track name or MBID * @param apiKey The API key * @return list of tags */ public static Collection getTopTags(String artist, String trackOrMbid, String apiKey) { Map params = new HashMap(); if (StringUtilities.isMbid(trackOrMbid)) { params.put("mbid", trackOrMbid); } else { params.put("artist", artist); params.put("track", trackOrMbid); } Result result = Caller.getInstance().call("track.getTopTags", apiKey, params); return ResponseBuilder.buildCollection(result, Tag.class); } /** * Retrieves the top fans for the given track. You either have to specify a track and artist name or * a mbid. If you specify an mbid you may pass null for the first parameter. * * @param artist Artist name or null if an MBID is specified * @param trackOrMbid Track name or MBID * @param apiKey The API key * @return list of fans */ public static Collection getTopFans(String artist, String trackOrMbid, String apiKey) { Map params = new HashMap(); if (StringUtilities.isMbid(trackOrMbid)) { params.put("mbid", trackOrMbid); } else { params.put("artist", artist); params.put("track", trackOrMbid); } Result result = Caller.getInstance().call("track.getTopFans", apiKey, params); return ResponseBuilder.buildCollection(result, User.class); } /** * Tag an album using a list of user supplied tags. * * @param artist The artist name in question * @param track The track name in question * @param tags A comma delimited list of user supplied tags to apply to this track. Accepts a maximum of 10 tags. * @param session A Session instance. * @return the Result of the operation */ public static Result addTags(String artist, String track, String tags, Session session) { return Caller.getInstance().call("track.addTags", session, "artist", artist, "track", track, "tags", tags); } /** * Remove a user's tag from a track. * * @param artist The artist name in question * @param track The track name in question * @param tag A single user tag to remove from this track. * @param session A Session instance. * @return the Result of the operation */ public static Result removeTag(String artist, String track, String tag, Session session) { return Caller.getInstance().call("track.removeTag", session, "artist", artist, "track", track, "tag", tag); } /** * Share a track twith one or more Last.fm users or other friends. * * @param artist An artist name. * @param track A track name. * @param message A message to send with the recommendation or null. If not supplied a default message will be used. * @param recipient A comma delimited list of email addresses or Last.fm usernames. Maximum is 10. * @param session A Session instance * @return the Result of the operation */ public static Result share(String artist, String track, String message, String recipient, Session session) { Map params = StringUtilities.map("artist", artist, "track", track, "recipient", recipient); MapUtilities.nullSafePut(params, "message", message); return Caller.getInstance().call("track.share", session, params); } /** * Love a track for a user profile. * * @param artist An artist name * @param track A track name * @param session A Session instance * @return the Result of the operation */ public static Result love(String artist, String track, Session session) { return Caller.getInstance().call("track.love", session, "artist", artist, "track", track); } /** * UnLove a track for a user profile. * * @param artist An artist name * @param track A track name * @param session A Session instance * @return the Result of the operation */ public static Result unlove(String artist, String track, Session session) { return Caller.getInstance().call("track.unlove", session, "artist", artist, "track", track); } /** * Ban a track for a given user profile. * * @param artist An artist name * @param track A track name * @param session A Session instance * @return the Result of the operation */ public static Result ban(String artist, String track, Session session) { return Caller.getInstance().call("track.ban", session, "artist", artist, "track", track); } /** * UnBan a track for a given user profile. * * @param artist An artist name * @param track A track name * @param session A Session instance * @return the Result of the operation */ public static Result unban(String artist, String track, Session session) { return Caller.getInstance().call("track.unban", session, "artist", artist, "track", track); } /** * Get the similar tracks for this track on Last.fm, based on listening data.
* You have to provide either an artist and a track name or an mbid. Pass null * for parameters you don't need. * * @param artist The artist name in question * @param trackOrMbid The track name in question or the track's MBID * @param apiKey A Last.fm API key. * @return a list of similar Tracks */ public static Collection getSimilar(String artist, String trackOrMbid, String apiKey) { Map params = new HashMap(); if (StringUtilities.isMbid(trackOrMbid)) { params.put("mbid", trackOrMbid); } else { params.put("artist", artist); params.put("track", trackOrMbid); } Result result = Caller.getInstance().call("track.getSimilar", apiKey, params); return ResponseBuilder.buildCollection(result, Track.class); } /** * Get the tags applied by an individual user to an track on Last.fm. * * @param artist The artist name in question * @param track The track name in question * @param session A Session instance * @return a list of tags */ public static Collection getTags(String artist, String track, Session session) { Result result = Caller.getInstance().call("track.getTags", session, "artist", artist, "track", track); DomElement element = result.getContentElement(); Collection tags = new ArrayList(); for (DomElement domElement : element.getChildren("tag")) { tags.add(domElement.getChildText("name")); } return tags; } /** * Get the metadata for a track on Last.fm using the artist/track name or a musicbrainz id. * * @param artist The artist name in question or null if an mbid is specified * @param trackOrMbid The track name in question or the musicbrainz id for the track * @param apiKey A Last.fm API key. * @return Track information */ public static Track getInfo(String artist, String trackOrMbid, String apiKey) { return getInfo(artist, trackOrMbid, null, null, apiKey); } /** * Get the metadata for a track on Last.fm using the artist/track name or a musicbrainz id. * * @param artist The artist name in question or null if an mbid is specified * @param trackOrMbid The track name in question or the musicbrainz id for the track * @param locale The language to fetch info in, or null * @param username The username for the context of the request, or null. If supplied, the user's playcount for this track and whether they have loved the track is included in the response * @param apiKey A Last.fm API key. * @return Track information */ public static Track getInfo(String artist, String trackOrMbid, Locale locale, String username, String apiKey) { Map params = new HashMap(); if (StringUtilities.isMbid(trackOrMbid)) { params.put("mbid", trackOrMbid); } else { params.put("artist", artist); params.put("track", trackOrMbid); } if (locale != null && locale.getLanguage().length() != 0) { params.put("lang", locale.getLanguage()); } MapUtilities.nullSafePut(params, "username", username); Result result = Caller.getInstance().call("track.getInfo", apiKey, params); if (!result.isSuccessful()) return null; DomElement content = result.getContentElement(); DomElement album = content.getChild("album"); Track track = FACTORY.createItemFromElement(content); if (album != null) { String pos = album.getAttribute("position"); if ((pos != null) && pos.length() != 0) { track.position = Integer.parseInt(pos); } track.album = album.getChildText("title"); track.albumMbid = album.getChildText("mbid"); ImageHolder.loadImages(track, album); } return track; } /** * 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. * * @param artist The artist name in question * @param albumOrMbid Track name or MBID * @param country A country name, as defined by the ISO 3166-1 country names standard * @param apiKey A Last.fm API key * @return a Collection of {@link BuyLink}s */ public static Collection getBuylinks(String artist, String albumOrMbid, String country, String apiKey) { Map params = new HashMap(); if (StringUtilities.isMbid(albumOrMbid)) { params.put("mbid", albumOrMbid); } else { params.put("artist", artist); params.put("album", albumOrMbid); } params.put("country", country); Result result = Caller.getInstance().call("track.getBuylinks", apiKey, params); if (!result.isSuccessful()) return Collections.emptyList(); DomElement element = result.getContentElement(); DomElement physicals = element.getChild("physicals"); DomElement downloads = element.getChild("downloads"); Collection links = new ArrayList(); for (DomElement e : physicals.getChildren("affiliation")) { links.add(BuyLink.linkFromElement(BuyLink.StoreType.PHYSICAl, e)); } for (DomElement e : downloads.getChildren("affiliation")) { links.add(BuyLink.linkFromElement(BuyLink.StoreType.DIGITAL, e)); } return links; } /** * Use the last.fm corrections data to check whether the supplied track has a correction to a canonical track. This method returns a new * {@link Track} object containing the corrected data, or null if the supplied Artist/Track combination was not found. * * @param artist The artist name to correct * @param track The track name to correct * @param apiKey A Last.fm API key * @return a new {@link Track}, or null */ public static Track getCorrection(String artist, String track, String apiKey) { Result result = Caller.getInstance().call("track.getCorrection", apiKey, "artist", artist, "track", track); if (!result.isSuccessful()) return null; DomElement correctionElement = result.getContentElement().getChild("correction"); if (correctionElement == null) return new Track(track, null, artist); DomElement trackElem = correctionElement.getChild("track"); return FACTORY.createItemFromElement(trackElem); } /** * Get shouts for a track. * * @param artist The artist name * @param trackOrMbid The track name or a mausicbrainz id * @param apiKey A Last.fm API key. * @return a page of Shouts */ public static PaginatedResult getShouts(String artist, String trackOrMbid, String apiKey) { return getShouts(artist, trackOrMbid, -1, -1, apiKey); } /** * Get shouts for a track. * * @param artist The artist name * @param trackOrMbid The track name or a mausicbrainz id * @param page The page number to fetch * @param apiKey A Last.fm API key. * @return a page of Shouts */ public static PaginatedResult getShouts(String artist, String trackOrMbid, int page, String apiKey) { return getShouts(artist, trackOrMbid, page, -1, apiKey); } /** * Get shouts for a track. * * @param artist The artist name * @param trackOrMbid The track name or a mausicbrainz id * @param page The page number to fetch * @param limit An integer used to limit the number of shouts returned per page or -1 for default * @param apiKey A Last.fm API key. * @return a page of Shouts */ public static PaginatedResult getShouts(String artist, String trackOrMbid, int page, int limit, String apiKey) { Map params = new HashMap(); if (StringUtilities.isMbid(trackOrMbid)) { params.put("mbid", trackOrMbid); } else { params.put("artist", artist); params.put("track", trackOrMbid); } MapUtilities.nullSafePut(params, "limit", limit); MapUtilities.nullSafePut(params, "page", page); Result result = Caller.getInstance().call("track.getShouts", apiKey, params); return ResponseBuilder.buildPaginatedResult(result, Shout.class); } /** * Converts a generic web services Result object into a more specific ScrobbleResult. * * @param result Web services Result. * @param scrobbleResultType The type of scrobble result contained within the Result. * @return A ScrobbleResult containing the original Result information plus extra fields specific to scrobble and now playing results. */ private static List convertToScrobbleResults(Result result, ScrobbleResultType scrobbleResultType) { List scrobbleResults = new ArrayList(); if (!result.isSuccessful()) { // if result failed then we have no extra information ScrobbleResult scrobbleResult = new ScrobbleResult(result); scrobbleResults.add(scrobbleResult); } else { DomElement element = result.getContentElement(); if (scrobbleResultType == ScrobbleResultType.NOW_PLAYING) { ScrobbleResult scrobbleResult = new ScrobbleResult(result); parseIntoScrobbleResult(element, scrobbleResult); scrobbleResults.add(scrobbleResult); } else if (scrobbleResultType == ScrobbleResultType.SINGLE_SCROBBLE) { ScrobbleResult scrobbleResult = new ScrobbleResult(result); parseIntoScrobbleResult(element.getChild("scrobble"), scrobbleResult); scrobbleResults.add(scrobbleResult); } else if (scrobbleResultType == ScrobbleResultType.MULTIPLE_SCROBBLES) { for (DomElement scrobbleElement : element.getChildren("scrobble")) { ScrobbleResult scrobbleResult = new ScrobbleResult(result); parseIntoScrobbleResult(scrobbleElement, scrobbleResult); scrobbleResults.add(scrobbleResult); } } } return scrobbleResults; } /** * Parses a DomElement containing scrobble or now playing response data into the passed ScrobbleResult. * * @param scrobbleElement DomElement containing scrobble or now playing response data. * @param scrobbleResult ScrobbleResult to add the response data to. */ private static void parseIntoScrobbleResult(DomElement scrobbleElement, ScrobbleResult scrobbleResult) { DomElement trackElement = scrobbleElement.getChild("track"); scrobbleResult.setTrack(trackElement.getText()); scrobbleResult.setArtistCorrected(StringUtilities.convertToBoolean(trackElement.getAttribute("corrected"))); DomElement artistElement = scrobbleElement.getChild("artist"); scrobbleResult.setArtist(artistElement.getText()); scrobbleResult.setArtistCorrected(StringUtilities.convertToBoolean(artistElement.getAttribute("corrected"))); DomElement albumElement = scrobbleElement.getChild("album"); scrobbleResult.setAlbum(albumElement.getText()); scrobbleResult.setAlbumCorrected(StringUtilities.convertToBoolean(albumElement.getAttribute("corrected"))); DomElement albumArtistElement = scrobbleElement.getChild("albumArtist"); scrobbleResult.setAlbumArtist(albumArtistElement.getText()); scrobbleResult.setAlbumArtistCorrected(StringUtilities.convertToBoolean(albumArtistElement.getAttribute("corrected"))); String timeString = scrobbleElement.getChildText("timestamp"); if (timeString != null) { // will be non-null for scrobble results only scrobbleResult.setTimestamp(Integer.parseInt(timeString)); } DomElement ignoredMessageElement = scrobbleElement.getChild("ignoredMessage"); int ignoredMessageCode = Integer.parseInt(ignoredMessageElement.getAttribute("code")); if (ignoredMessageCode > 0) { scrobbleResult.setIgnored(true); scrobbleResult.setIgnoredMessageCode(IgnoredMessageCode.valueOfCode(ignoredMessageCode)); scrobbleResult.setIgnoredMessage(ignoredMessageElement.getText()); } } public static ScrobbleResult scrobble(ScrobbleData scrobbleData, Session session) { Map params = new HashMap(); // required params params.put("artist", scrobbleData.getArtist()); params.put("track", scrobbleData.getTrack()); params.put("timestamp", String.valueOf(scrobbleData.getTimestamp())); // optional params MapUtilities.nullSafePut(params, "album", scrobbleData.getAlbum()); MapUtilities.nullSafePut(params, "albumArtist", scrobbleData.getAlbumArtist()); MapUtilities.nullSafePut(params, "duration", scrobbleData.getDuration()); MapUtilities.nullSafePut(params, "mbid", scrobbleData.getMusicBrainzId()); MapUtilities.nullSafePut(params, "trackNumber", scrobbleData.getTrackNumber()); MapUtilities.nullSafePut(params, "streamId", scrobbleData.getStreamId()); params.put("chosenByUser", StringUtilities.convertFromBoolean(scrobbleData.isChosenByUser())); Result result = Caller.getInstance().call("track.scrobble", session, params); return convertToScrobbleResults(result, ScrobbleResultType.SINGLE_SCROBBLE).get(0); } public static ScrobbleResult scrobble(String artistName, String trackName, int timestamp, Session session) { ScrobbleData scrobbleData = new ScrobbleData(artistName, trackName, timestamp); return scrobble(scrobbleData, session); } public static List scrobble(List scrobbleData, Session session) { Map params = new HashMap(); for (int i = 0; i < scrobbleData.size(); i++) { ScrobbleData scrobble = scrobbleData.get(i); // required params params.put("artist[" + i + "]", scrobble.getArtist()); params.put("track[" + i + "]", scrobble.getTrack()); params.put("timestamp[" + i + "]", String.valueOf(scrobble.getTimestamp())); // optional params MapUtilities.nullSafePut(params, "album[" + i + "]", scrobble.getAlbum()); MapUtilities.nullSafePut(params, "albumArtist[" + i + "]", scrobble.getAlbumArtist()); MapUtilities.nullSafePut(params, "duration[" + i + "]", scrobble.getDuration()); MapUtilities.nullSafePut(params, "mbid[" + i + "]", scrobble.getMusicBrainzId()); MapUtilities.nullSafePut(params, "trackNumber[" + i + "]", scrobble.getTrackNumber()); MapUtilities.nullSafePut(params, "streamId[" + i + "]", scrobble.getStreamId()); params.put("chosenByUser[" + i + "]", StringUtilities.convertFromBoolean(scrobble.isChosenByUser())); } Result result = Caller.getInstance().call("track.scrobble", session, params); return convertToScrobbleResults(result, ScrobbleResultType.MULTIPLE_SCROBBLES); } public static ScrobbleResult updateNowPlaying(ScrobbleData scrobbleData, Session session) { Map params = new HashMap(); // required params params.put("artist", scrobbleData.getArtist()); params.put("track", scrobbleData.getTrack()); // optional params MapUtilities.nullSafePut(params, "album", scrobbleData.getAlbum()); MapUtilities.nullSafePut(params, "albumArtist", scrobbleData.getAlbumArtist()); MapUtilities.nullSafePut(params, "duration", scrobbleData.getDuration()); MapUtilities.nullSafePut(params, "mbid", scrobbleData.getMusicBrainzId()); MapUtilities.nullSafePut(params, "trackNumber", scrobbleData.getTrackNumber()); MapUtilities.nullSafePut(params, "streamId", scrobbleData.getStreamId()); Result result = Caller.getInstance().call("track.updateNowPlaying", session, params); return convertToScrobbleResults(result, ScrobbleResultType.NOW_PLAYING).get(0); } public static ScrobbleResult updateNowPlaying(String artistName, String trackName, Session session) { ScrobbleData scrobbleData = new ScrobbleData(); scrobbleData.setArtist(artistName); scrobbleData.setTrack(trackName); return updateNowPlaying(scrobbleData, session); } @Override public String toString() { return "Track[name=" + name + ",artist=" + artist + ", album=" + album + ", position=" + position + ", duration=" + duration + ", location=" + location + ", nowPlaying=" + nowPlaying + ", fullTrackAvailable=" + fullTrackAvailable + ", playedWhen=" + playedWhen + ", artistMbId=" + artistMbid + ", albumMbId" + albumMbid + "]"; } private static class TrackFactory implements ItemFactory { public Track createItemFromElement(DomElement element) { Track track = new Track(null, null, null); MusicEntry.loadStandardInfo(track, element); final String nowPlayingAttr = element.getAttribute("nowplaying"); if (nowPlayingAttr != null) track.nowPlaying = Boolean.valueOf(nowPlayingAttr); if (element.hasChild("duration")) { String duration = element.getChildText("duration"); if(duration.length() != 0) { int durationLength = Integer.parseInt(duration); // So it seems last.fm couldn't decide which format to send the duration in. // It's supplied in milliseconds for Playlist.fetch and Track.getInfo but Artist.getTopTracks returns (much saner) seconds // so we're doing a little sanity check for the duration to be over or under 10'000 and decide what to do track.duration = durationLength > 10000 ? durationLength / 1000 : durationLength; } } DomElement album = element.getChild("album"); if (album != null) { track.album = album.getText(); track.albumMbid = album.getAttribute("mbid"); } DomElement artist = element.getChild("artist"); if (artist.getChild("name") != null) { track.artist = artist.getChildText("name"); track.artistMbid = artist.getChildText("mbid"); } else { track.artist = artist.getText(); track.artistMbid = artist.getAttribute("mbid"); } DomElement date = element.getChild("date"); if (date != null) { String uts = date.getAttribute("uts"); long utsTime = Long.parseLong(uts); track.playedWhen = new Date(utsTime * 1000); } DomElement stream = element.getChild("streamable"); if (stream != null) { String s = stream.getAttribute("fulltrack"); track.fullTrackAvailable = s != null && Integer.parseInt(s) == 1; } return track; } } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/User.java000066400000000000000000000605001200137731100253410ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import java.util.*; import de.umass.util.MapUtilities; import de.umass.util.StringUtilities; import de.umass.xml.DomElement; /** * Contains user information and provides bindings to the methods in the user. namespace. * * @author Janni Kovacs */ public class User extends ImageHolder { static final ItemFactory FACTORY = new UserFactory(); private String id; private String name; private String url; private String realname; private String language; private String country; private int age = -1; private String gender; private boolean subscriber; private int numPlaylists; private int playcount; private Date registeredDate; private User(String name, String url) { this.name = name; this.url = url; } public String getName() { return name; } public String getRealname() { return realname; } public String getUrl() { return url; } public int getAge() { return age; } public String getCountry() { return country; } public String getGender() { return gender; } public String getLanguage() { return language; } public int getNumPlaylists() { return numPlaylists; } public int getPlaycount() { return playcount; } public boolean isSubscriber() { return subscriber; } public String getImageURL() { return getImageURL(ImageSize.MEDIUM); } public String getId() { return id; } public Date getRegisteredDate() { return registeredDate; } /** * Get a list of tracks by a given artist scrobbled by this user, including scrobble time. Can be limited to specific timeranges, defaults * to all time. * * @param user The last.fm username to fetch the recent tracks of * @param artist The artist name you are interested in * @param apiKey A Last.fm API key * @return a list of Tracks */ public static PaginatedResult getArtistTracks(String user, String artist, String apiKey) { return getArtistTracks(user, artist, 1, 0, 0, apiKey); } /** * Get a list of tracks by a given artist scrobbled by this user, including scrobble time. Can be limited to specific timeranges, defaults * to all time. * * @param user The last.fm username to fetch the recent tracks of * @param artist The artist name you are interested in * @param page An integer used to fetch a specific page of tracks * @param startTimestamp An unix timestamp to start at * @param endTimestamp An unix timestamp to end at * @param apiKey A Last.fm API key * @return a list of Tracks */ public static PaginatedResult getArtistTracks(String user, String artist, int page, long startTimestamp, long endTimestamp, String apiKey) { Map params = new HashMap(); params.put("user", user); params.put("artist", artist); params.put("page", String.valueOf(page)); params.put("startTimestamp", String.valueOf(startTimestamp)); params.put("endTimestamp", String.valueOf(endTimestamp)); Result result = Caller.getInstance().call("user.getArtistTracks", apiKey, params); return ResponseBuilder.buildPaginatedResult(result, Track.class); } public static PaginatedResult getFriends(String user, String apiKey) { return getFriends(user, false, 1, 50, apiKey); } public static PaginatedResult getFriends(String user, boolean recenttracks, int page, int limit, String apiKey) { Result result = Caller.getInstance().call("user.getFriends", apiKey, "user", user, "recenttracks", String.valueOf(recenttracks ? 1 : 0), "limit", String.valueOf(limit), "page", String.valueOf(page)); return ResponseBuilder.buildPaginatedResult(result, User.class); } public static Collection getNeighbours(String user, String apiKey) { return getNeighbours(user, 100, apiKey); } public static Collection getNeighbours(String user, int limit, String apiKey) { Result result = Caller.getInstance().call("user.getNeighbours", apiKey, "user", user, "limit", String.valueOf(limit)); return ResponseBuilder.buildCollection(result, User.class); } public static PaginatedResult getRecentTracks(String user, String apiKey) { return getRecentTracks(user, 1, 10, apiKey); } public static PaginatedResult getRecentTracks(String user, int page, int limit, String apiKey) { Map params = new HashMap(); params.put("user", user); params.put("limit", String.valueOf(limit)); params.put("page", String.valueOf(page)); Result result = Caller.getInstance().call("user.getRecentTracks", apiKey, params); return ResponseBuilder.buildPaginatedResult(result, Track.class); } public static Collection getTopAlbums(String user, String apiKey) { return getTopAlbums(user, Period.OVERALL, apiKey); } public static Collection getTopAlbums(String user, Period period, String apiKey) { Result result = Caller.getInstance().call("user.getTopAlbums", apiKey, "user", user, "period", period.getString()); return ResponseBuilder.buildCollection(result, Album.class); } public static Collection getTopArtists(String user, String apiKey) { return getTopArtists(user, Period.OVERALL, apiKey); } public static Collection getTopArtists(String user, Period period, String apiKey) { Result result = Caller.getInstance().call("user.getTopArtists", apiKey, "user", user, "period", period.getString()); return ResponseBuilder.buildCollection(result, Artist.class); } public static Collection getTopTracks(String user, String apiKey) { return getTopTracks(user, Period.OVERALL, apiKey); } public static Collection getTopTracks(String user, Period period, String apiKey) { Result result = Caller.getInstance().call("user.getTopTracks", apiKey, "user", user, "period", period.getString()); return ResponseBuilder.buildCollection(result, Track.class); } public static Collection getTopTags(String user, String apiKey) { return getTopTags(user, -1, apiKey); } public static Collection getTopTags(String user, int limit, String apiKey) { Map params = new HashMap(); params.put("user", user); MapUtilities.nullSafePut(params, "limit", limit); Result result = Caller.getInstance().call("user.getTopTags", apiKey, params); return ResponseBuilder.buildCollection(result, Tag.class); } public static Chart getWeeklyAlbumChart(String user, String apiKey) { return getWeeklyAlbumChart(user, null, null, -1, apiKey); } public static Chart getWeeklyAlbumChart(String user, int limit, String apiKey) { return getWeeklyAlbumChart(user, null, null, limit, apiKey); } public static Chart getWeeklyAlbumChart(String user, String from, String to, int limit, String apiKey) { return Chart.getChart("user.getWeeklyAlbumChart", "user", user, "album", from, to, limit, apiKey); } public static Chart getWeeklyArtistChart(String user, String apiKey) { return getWeeklyArtistChart(user, null, null, -1, apiKey); } public static Chart getWeeklyArtistChart(String user, int limit, String apiKey) { return getWeeklyArtistChart(user, null, null, limit, apiKey); } public static Chart getWeeklyArtistChart(String user, String from, String to, int limit, String apiKey) { return Chart.getChart("user.getWeeklyArtistChart", "user", user, "artist", from, to, limit, apiKey); } public static Chart getWeeklyTrackChart(String user, String apiKey) { return getWeeklyTrackChart(user, null, null, -1, apiKey); } public static Chart getWeeklyTrackChart(String user, int limit, String apiKey) { return getWeeklyTrackChart(user, null, null, limit, apiKey); } public static Chart getWeeklyTrackChart(String user, String from, String to, int limit, String apiKey) { return Chart.getChart("user.getWeeklyTrackChart", "user", user, "track", from, to, limit, apiKey); } public static LinkedHashMap getWeeklyChartList(String user, String apiKey) { return Chart.getWeeklyChartList("user.getWeeklyChartList", "user", user, apiKey); } public static Collection getWeeklyChartListAsCharts(String user, String apiKey) { return Chart.getWeeklyChartListAsCharts("user", user, apiKey); } /** * GetS a list of upcoming events that this user is attending. * * @param user The user to fetch the events for. * @param apiKey A Last.fm API key. * @return a list of upcoming events */ public static PaginatedResult getEvents(String user, String apiKey) { return getEvents(user, -1, apiKey); } /** * GetS a list of upcoming events that this user is attending. * * @param user The user to fetch the events for. * @param page The page number to fetch. Defaults to first page. * @param apiKey A Last.fm API key. * @return a list of upcoming events */ public static PaginatedResult getEvents(String user, int page, String apiKey) { return getEvents(user, false, page, -1, apiKey); } /** * GetS a list of upcoming events that this user is attending. * * @param user The user to fetch the events for. * @param page The page number to fetch. Defaults to first page. * @param limit The number of results to fetch per page. Defaults to 50. * @param festivalsOnly Whether only festivals should be returned, or all events. * @param apiKey A Last.fm API key. * @return a list of upcoming events */ public static PaginatedResult getEvents(String user, boolean festivalsOnly, int page, int limit, String apiKey) { Map params = new HashMap(); MapUtilities.nullSafePut(params, "user", user); MapUtilities.nullSafePut(params, "page", page); MapUtilities.nullSafePut(params, "limit", limit); if (festivalsOnly) { params.put("festivalsonly", "1"); } Result result = Caller.getInstance().call("user.getEvents", apiKey, params); return ResponseBuilder.buildPaginatedResult(result, Event.class); } /** * Get the first page of a paginated result of all events a user has attended in the past. * * @param user The username to fetch the events for. * @param apiKey A Last.fm API key. * @return a list of past {@link Event}s */ public static PaginatedResult getPastEvents(String user, String apiKey) { return getPastEvents(user, -1, apiKey); } /** * Gets a paginated list of all events a user has attended in the past. * * @param user The username to fetch the events for. * @param page The page number to scan to. * @param apiKey A Last.fm API key. * @return a list of past {@link Event}s */ public static PaginatedResult getPastEvents(String user, int page, String apiKey) { Map params = new HashMap(); params.put("user", user); MapUtilities.nullSafePut(params, "page", page); Result result = Caller.getInstance().call("user.getPastEvents", apiKey, params); return ResponseBuilder.buildPaginatedResult(result, Event.class); } public static PaginatedResult getRecommendedEvents(Session session) { return getRecommendedEvents(1, session); } public static PaginatedResult getRecommendedEvents(int page, Session session) { Result result = Caller.getInstance().call("user.getRecommendedEvents", session, "page", String.valueOf(page), "user", session.getUsername()); return ResponseBuilder.buildPaginatedResult(result, Event.class); } /** * Gets a list of a user's playlists on Last.fm. Note that this method only fetches metadata regarding the user's playlists. If you want to * retrieve the list of tracks in a playlist use {@link Playlist#fetch(String, String) Playlist.fetch()}. * * @param user The last.fm username to fetch the playlists of. * @param apiKey A Last.fm API key. * @return a list of Playlists */ public static Collection getPlaylists(String user, String apiKey) { Result result = Caller.getInstance().call("user.getPlaylists", apiKey, "user", user); if (!result.isSuccessful()) return Collections.emptyList(); Collection playlists = new ArrayList(); for (DomElement element : result.getContentElement().getChildren("playlist")) { playlists.add(ResponseBuilder.buildItem(element, Playlist.class)); } return playlists; } /** * Retrieves the loved tracks by a user. * * @param user The user name to fetch the loved tracks for. * @param apiKey A Last.fm API key. * @return the loved tracks */ public static PaginatedResult getLovedTracks(String user, String apiKey) { return getLovedTracks(user, 1, apiKey); } /** * Retrieves the loved tracks by a user. * * @param user The user name to fetch the loved tracks for. * @param page The page number to scan to * @param apiKey A Last.fm API key. * @return the loved tracks */ public static PaginatedResult getLovedTracks(String user, int page, String apiKey) { Result result = Caller.getInstance().call("user.getLovedTracks", apiKey, "user", user, "page", String.valueOf(page)); return ResponseBuilder.buildPaginatedResult(result, Track.class); } /** * Retrieves profile information about the specified user. * * @param user A username * @param apiKey A Last.fm API key. * @return User info */ public static User getInfo(String user, String apiKey) { Result result = Caller.getInstance().call("user.getInfo", apiKey, "user", user); return ResponseBuilder.buildItem(result, User.class); } /** * Retrieves profile information about the authenticated user. * * @param session A session for the user, for whom to get the profile for * @return User info */ public static User getInfo(Session session) { Result result = Caller.getInstance().call("user.getInfo", session); return ResponseBuilder.buildItem(result, User.class); } /** * Get Last.fm artist recommendations for a user. * * @param session A Session instance * @return a list of {@link Artist}s */ public static PaginatedResult getRecommendedArtists(Session session) { return getRecommendedArtists(1, session); } /** * Get Last.fm artist recommendations for a user. * * @param page The page to fetch * @param session A Session instance * @return a list of {@link Artist}s */ public static PaginatedResult getRecommendedArtists(int page, Session session) { Result result = Caller.getInstance().call("user.getRecommendedArtists", session, "page", String.valueOf(page)); return ResponseBuilder.buildPaginatedResult(result, Artist.class); } /** * Shout on this user's shoutbox * * @param user The name of the user to shout on * @param message The message to post to the shoutbox * @param session A Session instance * @return the result of the operation */ public static Result shout(String user, String message, Session session) { return Caller.getInstance().call("user.shout", session, "user", user, "message", message); } /** * Gets a list of forthcoming releases based on a user's musical taste. * * @param user The Last.fm username * @param apiKey A Last.fm API key * @return a Collection of new {@link Album} releases */ public static Collection getNewReleases(String user, String apiKey) { return getNewReleases(user, false, apiKey); } /** * Gets a list of forthcoming releases based on a user's musical taste. * * @param user The Last.fm username * @param useRecommendations If true, the feed contains new releases based on Last.fm's artist recommendations for this user. * Otherwise, it is based on their library (the default) * @param apiKey A Last.fm API key * @return a Collection of new {@link Album} releases */ public static Collection getNewReleases(String user, boolean useRecommendations, String apiKey) { Result result = Caller.getInstance().call("user.getNewReleases", apiKey, "user", user, "userecs", useRecommendations ? "1" : "0"); return ResponseBuilder.buildCollection(result, Album.class); } /** * Returns the tracks banned by the user. * * @param user The user name * @param apiKey A Last.fm API key * @return the banned tracks */ public static PaginatedResult getBannedTracks(String user, String apiKey) { return getBannedTracks(user, 1, apiKey); } /** * Returns the tracks banned by the user. * * @param user The user name * @param page The page number to fetch * @param apiKey A Last.fm API key * @return the banned tracks */ public static PaginatedResult getBannedTracks(String user, int page, String apiKey) { Result result = Caller.getInstance().call("user.getBannedTracks", apiKey, "user", user, "page", String.valueOf(page)); return ResponseBuilder.buildPaginatedResult(result, Track.class); } /** * Get shouts for a user. * * @param user The username to fetch shouts for * @param apiKey A Last.fm API key. * @return a page of Shouts */ public static PaginatedResult getShouts(String user, String apiKey) { return getShouts(user, -1, -1, apiKey); } /** * Get shouts for a user. * * @param user The username to fetch shouts for * @param page The page number to fetch * @param apiKey A Last.fm API key. * @return a page of Shouts */ public static PaginatedResult getShouts(String user, int page, String apiKey) { return getShouts(user, page, -1, apiKey); } /** * Get shouts for a user. * * @param user The username to fetch shouts for * @param page The page number to fetch * @param limit An integer used to limit the number of shouts returned per page or -1 for default * @param apiKey A Last.fm API key. * @return a page of Shouts */ public static PaginatedResult getShouts(String user, int page, int limit, String apiKey) { Map params = new HashMap(); params.put("user", user); MapUtilities.nullSafePut(params, "limit", limit); MapUtilities.nullSafePut(params, "page", page); Result result = Caller.getInstance().call("user.getShouts", apiKey, params); return ResponseBuilder.buildPaginatedResult(result, Shout.class); } /** * Get the user's personal tags. * * @param user The user who performed the taggings * @param tag The tag you're interested in * @param taggingType Either Artist.class, Album.class or Track.class * @param apiKey A Last.fm API key * @return the items the user has tagged with the specified tag * @throws IllegalArgumentException if taggingType is null or not one of the above mentioned classes */ public static PaginatedResult getPersonalTags(String user, String tag, Class taggingType, String apiKey) { return getPersonalTags(user, tag, taggingType, -1, -1, apiKey); } /** * Get the user's personal tags. * * @param user The user who performed the taggings * @param tag The tag you're interested in * @param taggingType Either Artist.class, Album.class or Track.class * @param page The page number to fetch * @param apiKey A Last.fm API key * @return the items the user has tagged with the specified tag * @throws IllegalArgumentException if taggingType is null or not one of the above mentioned classes */ public static PaginatedResult getPersonalTags(String user, String tag, Class taggingType, int page, String apiKey) { return getPersonalTags(user, tag, taggingType, page, -1, apiKey); } /** * Get the user's personal tags. * * @param user The user who performed the taggings * @param tag The tag you're interested in * @param taggingType Either Artist.class, Album.class or Track.class * @param page The page number to fetch * @param limit The number of results to fetch per page. Defaults to 50 * @param apiKey A Last.fm API key * @return the items the user has tagged with the specified tag * @throws IllegalArgumentException if taggingType is null or not one of the above mentioned classes */ public static PaginatedResult getPersonalTags(String user, String tag, Class taggingType, int page, int limit, String apiKey) { Map params = StringUtilities.map("user", user, "tag", tag); MapUtilities.nullSafePut(params, "page", page); MapUtilities.nullSafePut(params, "limit", limit); String taggingTypeParam = "taggingtype"; if (taggingType == Track.class) params.put(taggingTypeParam, "track"); else if (taggingType == Artist.class) params.put(taggingTypeParam, "artist"); else if (taggingType == Album.class) params.put(taggingTypeParam, "album"); else throw new IllegalArgumentException("Parameter taggingType has to be one of Artist.class, Album.class or Track.class."); Result result = Caller.getInstance().call("user.getPersonalTags", apiKey, params); if (!result.isSuccessful()) return new PaginatedResult(0, 0, Collections.emptyList()); String childElementName = params.get(taggingTypeParam) + "s"; DomElement contentElement = result.getContentElement(); DomElement childElement = contentElement.getChild(childElementName); return ResponseBuilder.buildPaginatedResult(contentElement, childElement, taggingType); } private static class UserFactory implements ItemFactory { public User createItemFromElement(DomElement element) { User user = new User(element.getChildText("name"), element.getChildText("url")); user.id = element.getChildText("id"); if (element.hasChild("realname")) user.realname = element.getChildText("realname"); ImageHolder.loadImages(user, element); user.language = element.getChildText("lang"); user.country = element.getChildText("country"); if (element.hasChild("age")) { try { user.age = Integer.parseInt(element.getChildText("age")); } catch (NumberFormatException e) { // no age } } user.gender = element.getChildText("gender"); user.subscriber = "1".equals(element.getChildText("subscriber")); if (element.hasChild("playcount")) { // extended user information try { user.playcount = Integer.parseInt(element.getChildText("playcount")); } catch (NumberFormatException e) { // no playcount } } if (element.hasChild("playlists")) { // extended user information try { user.numPlaylists = Integer.parseInt(element.getChildText("playlists")); } catch (NumberFormatException e) { // no playlists } } if (element.hasChild("registered")) { String unixtime = element.getChild("registered").getAttribute("unixtime"); user.registeredDate = new Date(Long.parseLong(unixtime) * 1000); } return user; } } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/Venue.java000066400000000000000000000164551200137731100255170ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import de.umass.util.MapUtilities; import de.umass.xml.DomElement; /** * Venue information bean. * * @author Janni Kovacs */ public class Venue extends ImageHolder { static final ItemFactory FACTORY = new VenueFactory(); private String name; private String url, website; private String city, country, street, postal, phonenumber; private float latitude, longitude; private String timezone; private String id; private Venue() { } public String getId() { return id; } /** * Returns a last.fm URL to this venue, e.g.: http://www.last.fm/venue/<id>-<venue name> * * @return last.fm url * @see #getWebsite() */ public String getUrl() { return url; } /** * Returns an URL to the actual venue's website. * * @return website url */ public String getWebsite() { return website; } public String getCity() { return city; } public String getCountry() { return country; } public float getLatitude() { return latitude; } public float getLongitude() { return longitude; } public String getName() { return name; } public String getPostal() { return postal; } public String getStreet() { return street; } public String getTimezone() { return timezone; } public String getPhonenumber() { return phonenumber; } /** * Search for a venue by venue name. * * @param venue The venue name you would like to search for * @param apiKey A Last.fm API key * @return a list of venues */ public static Collection search(String venue, String apiKey) { return search(venue, null, apiKey); } /** * Search for a venue by venue name. * * @param venue The venue name you would like to search for * @param country Filter your results by country. Expressed as an ISO 3166-2 code * @param apiKey A Last.fm API key * @return a list of venues */ public static Collection search(String venue, String country, String apiKey) { Map params = new HashMap(); params.put("venue", venue); MapUtilities.nullSafePut(params, "country", country); Result result = Caller.getInstance().call("venue.search", apiKey, params); if (!result.isSuccessful()) return Collections.emptyList(); DomElement child = result.getContentElement().getChild("venuematches"); return ResponseBuilder.buildCollection(child, Venue.class); } /** * Get a list of upcoming events at this venue. * * @param venueId The venue id to fetch the events for * @param apiKey A Last.fm API key * @return a list of events * @see #getPastEvents */ public static Collection getEvents(String venueId, String apiKey) { return getEvents(venueId, false, apiKey); } /** * Get a list of upcoming events at this venue. * * @param venueId The venue id to fetch the events for * @param festivalsOnly Whether only festivals should be returned, or all events * @param apiKey A Last.fm API key * @return a list of events * @see #getPastEvents */ public static Collection getEvents(String venueId, boolean festivalsOnly, String apiKey) { Result result = Caller.getInstance().call("venue.getEvents", apiKey, "venue", venueId, "festivalsonly", festivalsOnly ? "1" : "0" ); return ResponseBuilder.buildCollection(result, Event.class); } /** * Get a paginated list of all the events held at this venue in the past. * * @param venueId The id for the venue you would like to fetch event listings for * @param apiKey A Last.fm API key * @return a paginated list of events */ public static PaginatedResult getPastEvents(String venueId, String apiKey) { return getPastEvents(venueId, false, -1, -1, apiKey); } /** * Get a paginated list of all the events held at this venue in the past. * * @param venueId The id for the venue you would like to fetch event listings for * @param festivalsOnly Whether only festivals should be returned, or all events. * @param page The page of results to return * @param limit The number of results to fetch per page. * @param apiKey A Last.fm API key * @return a paginated list of events */ public static PaginatedResult getPastEvents(String venueId, boolean festivalsOnly, int page, int limit, String apiKey) { Map params = new HashMap(); params.put("venue", venueId); params.put("festivalsonly", festivalsOnly ? "1" : "0"); MapUtilities.nullSafePut(params, "page", page); MapUtilities.nullSafePut(params, "limit", limit); Result result = Caller.getInstance().call("venue.getPastEvents", apiKey, params); return ResponseBuilder.buildPaginatedResult(result, Event.class); } private static class VenueFactory implements ItemFactory { public Venue createItemFromElement(DomElement element) { Venue venue = new Venue(); venue.id = element.getChildText("id"); venue.name = element.getChildText("name"); venue.url = element.getChildText("url"); venue.phonenumber = element.getChildText("phonenumber"); venue.website = element.getChildText("website"); ImageHolder.loadImages(venue, element); DomElement l = element.getChild("location"); venue.city = l.getChildText("city"); venue.country = l.getChildText("country"); venue.street = l.getChildText("street"); venue.postal = l.getChildText("postalcode"); venue.timezone = l.getChildText("timezone"); DomElement p = l.getChild("geo:point"); if (p.getChildText("geo:lat").length() != 0) { // some venues don't have geo information applied venue.latitude = Float.parseFloat(p.getChildText("geo:lat")); venue.longitude = Float.parseFloat(p.getChildText("geo:long")); } return venue; } } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/cache/000077500000000000000000000000001200137731100246225ustar00rootroot00000000000000lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/cache/Cache.java000066400000000000000000000150151200137731100264720ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm.cache; import java.io.InputStream; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import de.umass.util.StringUtilities; /** * The Cache handles storing and loading to a permanent storage for last.fm api requests. This could be * a file system or a sql database. * * @author Janni Kovacs * @see de.umass.lastfm.Caller#setCache(Cache) * @see de.umass.lastfm.cache.ExpirationPolicy */ public abstract class Cache { private static boolean hashCacheEntryNames = true; private ExpirationPolicy expirationPolicy; protected Cache() { expirationPolicy = new DefaultExpirationPolicy(); } /** * Returns the active {@link de.umass.lastfm.cache.ExpirationPolicy} * * @return the ExpirationPolicy */ public ExpirationPolicy getExpirationPolicy() { return expirationPolicy; } /** * Sets the active {@link de.umass.lastfm.cache.ExpirationPolicy}. * * @param expirationPolicy An ExpirationPolicy, not null */ public void setExpirationPolicy(ExpirationPolicy expirationPolicy) { if (expirationPolicy == null) throw new NullPointerException("policy == null"); this.expirationPolicy = expirationPolicy; } /** * Checks if the cache contains an entry with the given name. * * @param cacheEntryName An entry name * @return true if the cache contains this entry */ public abstract boolean contains(String cacheEntryName); /** * Loads the specified entry from the cache and returns an InputStream to be read from. Returns null * if the cache does not contain the specified cacheEntryName. * * @param cacheEntryName An entry name * @return an InputStream or null */ public abstract InputStream load(String cacheEntryName); /** * Removes the specified entry from the cache if available. Does nothing if no such entry is * available. * * @param cacheEntryName An entry name */ public abstract void remove(String cacheEntryName); /** * Stores a request in the cache. * * @param cacheEntryName The entry name to be stored to * @param inputStream An InputStream containing the data to be cached * @param expirationDate The date of expiration represented in milliseconds since 1.1.1970 */ public abstract void store(String cacheEntryName, InputStream inputStream, long expirationDate); /** * Checks if the specified entry is expired. * * @param cacheEntryName An entry name * @return true if the entry is expired */ public abstract boolean isExpired(String cacheEntryName); /** * Clears the cache by effectively removing all cached data. */ public abstract void clear(); /** * Finds the expiration date, returned as a unix timestamp, for a given method/parameters combination, or -1 if * there's no expiration time found in this Cache's {@link ExpirationPolicy}.
* It uses this cache's {@link ExpirationPolicy} and the current timestamp to calculate the expiration date. * * @param method The method called * @param params The parameters sent * @return The expiration date for this specific API call, or -1 if no expiration date was found * @see ExpirationPolicy#getExpirationTime(String, java.util.Map) */ public final long findExpirationDate(String method, Map params) { long expirationTime = this.getExpirationPolicy().getExpirationTime(method, params); long expirationDate = -1; if (expirationTime > 0) { if (expirationTime == Long.MAX_VALUE) { expirationDate = Long.MAX_VALUE; } else { expirationDate = System.currentTimeMillis() + expirationTime; } } return expirationDate; } /** * Creates a unique entry name string for a request. It consists of the method name and all the parameter names * and values concatenated in alphabetical order. It is used to identify cache entries in the backend storage. * If hashCacheEntryNames is set to true this method will return a MD5 hash of * the generated name. * * @param method The request method * @param params The request parameters * @return a cache entry name */ public static String createCacheEntryName(String method, Map params) { if (!(params instanceof SortedMap)) { params = new TreeMap(params); } StringBuilder b = new StringBuilder(100); b.append(method.toLowerCase()); b.append('.'); for (Map.Entry e : params.entrySet()) { b.append(e.getKey()); b.append(e.getValue()); } String name = b.toString(); if (hashCacheEntryNames) return StringUtilities.md5(name); return StringUtilities.cleanUp(name); } /** * If hashCacheEntryNames is set to true the {@link #createCacheEntryName} method will * return a hash of the original entry name instead of the name itself. * * @param hashCacheEntryNames true to generate hashes */ public static void setHashCacheEntryNames(boolean hashCacheEntryNames) { Cache.hashCacheEntryNames = hashCacheEntryNames; } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/cache/DatabaseCache.java000066400000000000000000000166351200137731100301300ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm.cache; import java.io.*; import java.sql.*; /** *

Generic class for caching into a database. Its constructor takes a {@link Connection} instance, which must be opened and closed by the * client. SQL code used in this class should work with all common databases (which support the varchar, timestamp and text * datatypes).

* For more specialized versions of this class for different databases one may extend this class and override methods as needed. In * most cases overriding {@link #createTable()} will be sufficient.
* The following databases are supported and tested with this class: *
    *
  • MySQL 5
  • *
  • H2 1.3
  • *
* Not supported by this base class: *
    *
  • Apache Derby/JavaDB - a long varchar in Derby can only hold up to 32700 characters of text, which is too little for some requests
  • *
  • HSQLDB - does not support the text datatype
  • *
* * @author Janni Kovacs */ public class DatabaseCache extends Cache { protected static final String DEFAULT_TABLE_NAME = "LASTFM_CACHE"; protected String tableName; protected Connection connection; public DatabaseCache(Connection connection) throws SQLException { this(connection, DEFAULT_TABLE_NAME); } /** * Creates a new DatabaseCache with the supplied database {@link Connection} and the specified table name. A new table with * tableName will be created in the constructor, if none exists. Note that tableName will not be * sanitized for usage in SQL, so in the rare case you're using user input for a table name make sure to sanitize the input before * passing it on to prevent SQL injections. * * @param connection The database connection * @param tableName the name for the database table to use * @throws SQLException When initializing/creating the table fails * @see #createTable() */ public DatabaseCache(Connection connection, String tableName) throws SQLException { this.connection = connection; this.tableName = tableName; // We need this, because some databases do not support CREATE TABLE IF NOT EXISTS, which is a non standard addition ResultSet tables = this.connection.getMetaData().getTables(null, null, tableName, null); if (!tables.next()) { createTable(); } } /** * This internal method creates a new table in the database for storing XML responses. You can override this method in a subclass if this * generic method does not work with the database server you are using, given that the following table columns are present: *
    *
  • id - The primary key, which is used to identify cache entries (see {@link Cache#createCacheEntryName}
  • *
  • expiration_date - A timestamp field for this cache entry's expiration date
  • *
  • response - The actual response XML
  • *
* * If you choose to use a different schema in your table you'll most likely have to override the other methods in this class, too. * * @throws SQLException When the generic SQL code in this method is not compatible with the database */ protected void createTable() throws SQLException { PreparedStatement stmt = connection.prepareStatement( "CREATE TABLE " + tableName + " (id VARCHAR(200) PRIMARY KEY, expiration_date TIMESTAMP, response TEXT)"); stmt.execute(); stmt.close(); } public boolean contains(String cacheEntryName) { try { PreparedStatement stmt = connection.prepareStatement("SELECT id FROM " + tableName + " WHERE id = ?"); stmt.setString(1, cacheEntryName); ResultSet result = stmt.executeQuery(); boolean b = result.next(); stmt.close(); return b; } catch (SQLException e) { return false; } } public InputStream load(String cacheEntryName) { try { PreparedStatement stmt = connection.prepareStatement("SELECT response FROM " + tableName + " WHERE id = ?"); stmt.setString(1, cacheEntryName); ResultSet result = stmt.executeQuery(); if (result.next()) { String s = result.getString("response"); stmt.close(); return new ByteArrayInputStream(s.getBytes("UTF-8")); } stmt.close(); } catch (SQLException e) { // ignore } catch (UnsupportedEncodingException e) { // won't happen } return null; } public void remove(String cacheEntryName) { try { PreparedStatement stmt = connection.prepareStatement("DELETE FROM " + tableName + " WHERE id = ?"); stmt.setString(1, cacheEntryName); stmt.execute(); stmt.close(); } catch (SQLException e) { // ignore } } public void store(String cacheEntryName, InputStream inputStream, long expirationDate) { try { PreparedStatement stmt = connection.prepareStatement( "INSERT INTO " + tableName + " (id, expiration_date, response) VALUES(?, ?, ?)"); stmt.setString(1, cacheEntryName); stmt.setTimestamp(2, new Timestamp(expirationDate)); stmt.setCharacterStream(3, new InputStreamReader(inputStream, "UTF-8"), -1); stmt.execute(); stmt.close(); } catch (SQLException e) { e.printStackTrace(); // ignore } catch (UnsupportedEncodingException e) { e.printStackTrace(); // won't happen } } public boolean isExpired(String cacheEntryName) { try { PreparedStatement stmt = connection.prepareStatement("SELECT expiration_date FROM " + tableName + " WHERE id = ?"); stmt.setString(1, cacheEntryName); ResultSet result = stmt.executeQuery(); if (result.next()) { Timestamp timestamp = result.getTimestamp("expiration_date"); long expirationDate = timestamp.getTime(); stmt.close(); return expirationDate < System.currentTimeMillis(); } } catch (SQLException e) { // ignore } return false; } public void clear() { try { PreparedStatement stmt = connection.prepareStatement("DELETE FROM " + tableName); stmt.execute(); stmt.close(); } catch (SQLException e) { // ignore } } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/cache/DefaultExpirationPolicy.java000066400000000000000000000100431200137731100322720ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm.cache; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * This Policy maintains a list of methods which should be cached one week. Everything else won't be cached if * using this policy. * * @author Janni Kovacs */ public class DefaultExpirationPolicy implements ExpirationPolicy { /** * One day in milliseconds */ protected static final long ONE_DAY = 1000 * 60 * 60 * 24; /** * One week in milliseconds */ protected static final long ONE_WEEK = ONE_DAY * 7; /** * Contains the lower case method names for all requests that should be cached 1 week */ protected static final Set ONE_WEEK_METHODS = new HashSet(); static { // similar data ONE_WEEK_METHODS.add("artist.getsimilar"); ONE_WEEK_METHODS.add("tag.getsimilar"); ONE_WEEK_METHODS.add("track.getsimilar"); // top chart data ONE_WEEK_METHODS.add("artist.gettopalbums"); ONE_WEEK_METHODS.add("artist.gettoptracks"); ONE_WEEK_METHODS.add("geo.gettopartists"); ONE_WEEK_METHODS.add("geo.gettoptracks"); ONE_WEEK_METHODS.add("tag.gettopalbums"); ONE_WEEK_METHODS.add("tag.gettopartists"); ONE_WEEK_METHODS.add("tag.gettoptags"); ONE_WEEK_METHODS.add("tag.gettoptracks"); ONE_WEEK_METHODS.add("user.gettopalbums"); ONE_WEEK_METHODS.add("user.gettopartists"); ONE_WEEK_METHODS.add("user.gettoptracks"); ONE_WEEK_METHODS.add("user.gettoptags"); } /** * Contains the expiration time for weekly chart data for the current week, which is * one week by default; last.fm TOS says: *
* You agree to cache similar artist and any chart data (top tracks, top artists, top albums) for a minimum of one week. *
* but they might be outdated the next day. * For now we will cache them one week. If you always need the latest charts but don't want to disable * caching use the {@link #setCacheRecentWeeklyCharts(long)} method to set this value. * This variable also applies to the getWeeklyChartList method */ protected long cacheRecentWeeklyCharts = ONE_WEEK; public long getExpirationTime(String method, Map params) { method = method.toLowerCase(); if (method.contains("weekly")) { if (!method.contains("list")) return params.containsKey("to") && params.containsKey("from") ? Long.MAX_VALUE : cacheRecentWeeklyCharts; else return cacheRecentWeeklyCharts; } return ONE_WEEK_METHODS.contains(method) ? ONE_WEEK : -1; } public void setCacheRecentWeeklyCharts(long cacheRecentWeeklyCharts) { this.cacheRecentWeeklyCharts = cacheRecentWeeklyCharts; } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/cache/ExpirationPolicy.java000066400000000000000000000040001200137731100307610ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm.cache; import java.util.Map; /** * The ExpirationPolicy decides if and how long a request should be cached. * * @author Janni Kovacs */ public interface ExpirationPolicy { /** * Returns the time in milliseconds a request of the given method should be cached. Returns -1 if this * method should not be cached. * * @param method The method called * @param params The parameters sent * @return the time the request should be cached in milliseconds */ public long getExpirationTime(String method, Map params); } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/cache/FileSystemCache.java000066400000000000000000000206141200137731100305000ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm.cache; import java.io.*; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Properties; import de.umass.lastfm.Session; import de.umass.lastfm.Track; import de.umass.lastfm.scrobble.ScrobbleData; import de.umass.lastfm.scrobble.ScrobbleResult; import de.umass.lastfm.scrobble.Scrobbler; import de.umass.lastfm.scrobble.SubmissionData; import de.umass.util.StringUtilities; /** * Standard {@link Cache} implementation which is used by default by the {@link de.umass.lastfm.Caller} class. * This implementation caches all responses in the file system. In addition to the raw responses it stores a * .meta file which contains the expiration date for the specified request. * * @author Janni Kovacs */ @SuppressWarnings({"ALL"}) public class FileSystemCache extends Cache implements ScrobbleCache { private static final String SUBMISSIONS_FILE = "submissions.txt"; private File cacheDir; public FileSystemCache() { this(new File(System.getProperty("user.home") + "/.last.fm-cache")); } public FileSystemCache(File cacheDir) { this.cacheDir = cacheDir; } public boolean contains(String cacheEntryName) { return new File(cacheDir, cacheEntryName + ".xml").exists(); } public void remove(String cacheEntryName) { new File(cacheDir, cacheEntryName + ".xml").delete(); new File(cacheDir, cacheEntryName + ".meta").delete(); } public boolean isExpired(String cacheEntryName) { File f = new File(cacheDir, cacheEntryName + ".meta"); if (!f.exists()) return false; try { Properties p = new Properties(); p.load(new FileInputStream(f)); long expirationDate = Long.valueOf(p.getProperty("expiration-date")); return expirationDate < System.currentTimeMillis(); } catch (IOException e) { return false; } } public void clear() { for (File file : cacheDir.listFiles()) { if (file.isFile()) { file.delete(); } } } public InputStream load(String cacheEntryName) { try { return new FileInputStream(new File(cacheDir, cacheEntryName + ".xml")); } catch (FileNotFoundException e) { return null; } } public void store(String cacheEntryName, InputStream inputStream, long expirationDate) { createCache(); File f = new File(cacheDir, cacheEntryName + ".xml"); try { BufferedInputStream is = new BufferedInputStream(inputStream); BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(f)); int read; byte[] buffer = new byte[4096]; while ((read = is.read(buffer)) != -1) { os.write(buffer, 0, read); } os.close(); is.close(); File fm = new File(cacheDir, cacheEntryName + ".meta"); Properties p = new Properties(); p.setProperty("expiration-date", Long.toString(expirationDate)); p.store(new FileOutputStream(fm), null); } catch (IOException e) { // we ignore the exception. if something went wrong we just don't cache it. } } private void createCache() { if (!cacheDir.exists()) { cacheDir.mkdirs(); if (!cacheDir.isDirectory()) { this.cacheDir = cacheDir.getParentFile(); } } } public void cacheScrobble(Collection submissions) { cacheScrobble(submissions.toArray(new SubmissionData[submissions.size()])); } public void cacheScrobble(SubmissionData... submissions) { createCache(); try { BufferedWriter w = new BufferedWriter(new FileWriter(new File(cacheDir, SUBMISSIONS_FILE), true)); for (SubmissionData submission : submissions) { w.append(submission.toString()); w.newLine(); } w.close(); } catch (IOException e) { // huh ? // e.printStackTrace(); } } public boolean isEmpty() { File file = new File(cacheDir, SUBMISSIONS_FILE); if (!file.exists()) return true; try { BufferedReader r = new BufferedReader(new FileReader(file)); String line = r.readLine(); r.close(); return line == null || "".equals(line); } catch (IOException e) { // huh // e.printStackTrace(); } return true; } public void scrobble(Scrobbler scrobbler) throws IOException { File file = new File(cacheDir, SUBMISSIONS_FILE); if (file.exists()) { BufferedReader r = new BufferedReader(new FileReader(file)); List list = new ArrayList(50); String line; while ((line = r.readLine()) != null) { SubmissionData d = new SubmissionData(line); list.add(d); if (list.size() == 50) { scrobbler.submit(list); list.clear(); } } if (list.size() > 0) scrobbler.submit(list); r.close(); FileWriter w = new FileWriter(file); w.close(); } } public void clearScrobbleCache() { File file = new File(cacheDir, SUBMISSIONS_FILE); file.delete(); } public void cacheScrobbles(Collection scrobbles) { cacheScrobbles(scrobbles.toArray(new ScrobbleData[scrobbles.size()])); } public void cacheScrobbles(ScrobbleData... scrobbles) { createCache(); try { BufferedWriter w = new BufferedWriter(new FileWriter(new File(cacheDir, SUBMISSIONS_FILE), true)); for (ScrobbleData scrobble : scrobbles) { w.append(encodeScrobbleData(scrobble)); w.newLine(); } w.close(); } catch (IOException e) { // huh ? // e.printStackTrace(); } } public List scrobble(Session session) throws IOException { File file = new File(cacheDir, SUBMISSIONS_FILE); List result = new ArrayList(); if (file.exists()) { BufferedReader r = new BufferedReader(new FileReader(file)); List list = new ArrayList(50); String line; while ((line = r.readLine()) != null) { ScrobbleData d = decodeScrobbleData(line); list.add(d); if (list.size() == 50) { result.addAll(Track.scrobble(list, session)); list.clear(); } } if (list.size() > 0) result.addAll(Track.scrobble(list, session)); r.close(); FileWriter w = new FileWriter(file); w.close(); } return result; } private static String encodeScrobbleData(ScrobbleData d) { String artist = StringUtilities.encode(d.getArtist()); String track = StringUtilities.encode(d.getTrack()); String album = StringUtilities.encode(d.getAlbum()); String albumArtist = StringUtilities.encode(d.getAlbumArtist()); return String.format("%s;%s;%s;%s;%s;%s;%s;%s;%s;%b", artist, track, d.getTimestamp(), d.getDuration(), album, albumArtist, d.getMusicBrainzId(), d.getTrackNumber(), d.getStreamId(), d.isChosenByUser()); } private static ScrobbleData decodeScrobbleData(String s) { String[] parts = s.split(";", 10); return new ScrobbleData(StringUtilities.decode(parts[0]), StringUtilities.decode(parts[1]), Integer.parseInt(parts[2]), Integer.parseInt(parts[3]), StringUtilities.decode(parts[4]), StringUtilities.decode(parts[5]), parts[6], Integer.parseInt(parts[7]), parts[8], Boolean.parseBoolean(parts[9])); } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/cache/MemoryCache.java000066400000000000000000000071111200137731100276610ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm.cache; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * This class is just for testing. You probably don't want to use it in production. * * @author Janni Kovacs */ public class MemoryCache extends Cache { private Map data = new HashMap(); private Map expirations = new HashMap(); public boolean contains(String cacheEntryName) { boolean contains = data.containsKey(cacheEntryName); System.out.println("MemoryCache.contains: " + cacheEntryName + " ? " + contains); return contains; } public InputStream load(String cacheEntryName) { System.out.println("MemoryCache.load: " + cacheEntryName); try { return new ByteArrayInputStream(data.get(cacheEntryName).getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return null; } public void remove(String cacheEntryName) { System.out.println("MemoryCache.remove: " + cacheEntryName); data.remove(cacheEntryName); expirations.remove(cacheEntryName); } public void store(String cacheEntryName, InputStream inputStream, long expirationDate) { System.out.println("MemoryCache.store: " + cacheEntryName + " Expires at: " + new Date(expirationDate)); StringBuilder b = new StringBuilder(); try { BufferedReader r = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); String l; while ((l = r.readLine()) != null) { b.append(l); } } catch (IOException e) { e.printStackTrace(); } data.put(cacheEntryName, b.toString()); expirations.put(cacheEntryName, expirationDate); } public boolean isExpired(String cacheEntryName) { boolean exp = expirations.get(cacheEntryName) < System.currentTimeMillis(); System.out.println("MemoryCache.isExpired: " + cacheEntryName + " ? " + exp); return exp; } public void clear() { data.clear(); expirations.clear(); } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/cache/ScrobbleCache.java000066400000000000000000000060611200137731100301470ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm.cache; import java.io.IOException; import java.util.Collection; import de.umass.lastfm.scrobble.Scrobbler; import de.umass.lastfm.scrobble.SubmissionData; /** * A ScrobbleCache is able to cache {@link SubmissionData} instances for later submission * to the Last.fm servers. * * @author Janni Kovacs * @deprecated The 1.2.x scrobble protocol has now been deprecated in favour of the 2.0 protocol which is part of the Last.fm web services * API. */ @Deprecated public interface ScrobbleCache { /** * Caches one or more {@link de.umass.lastfm.scrobble.SubmissionData}. * * @param submissions The submissions */ public void cacheScrobble(SubmissionData... submissions); /** * Caches a collection of {@link SubmissionData}. * * @param submissions The submissions */ public void cacheScrobble(Collection submissions); /** * Checks if the cache contains any scrobbles. * * @return true if this cache is empty */ public boolean isEmpty(); /** * Tries to scrobble all cached scrobbles. If it succeeds the cache will be empty afterwards. * If this method fails an IOException is thrown and no entries are removed from the cache. * * @param scrobbler A {@link Scrobbler} instance * @throws java.io.IOException on I/O errors * @throws IllegalStateException if the {@link Scrobbler} is not fully initialized (i.e. no handshake performed) */ public void scrobble(Scrobbler scrobbler) throws IOException; /** * Clears all cached scrobbles from this cache. */ public void clearScrobbleCache(); } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/scrobble/000077500000000000000000000000001200137731100253525ustar00rootroot00000000000000lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/scrobble/IgnoredMessageCode.java000066400000000000000000000053231200137731100317070ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm.scrobble; import java.util.HashMap; import java.util.Map; /** * Enumeration representing the ignored message code returned by scrobble and now playing requests. * * @author Adrian Woodhead */ public enum IgnoredMessageCode { ARTIST_IGNORED(1), TRACK_IGNORED(2), TIMESTAMP_TOO_OLD(3), TIMESTAMP_TOO_NEW(4), DAILY_SCROBBLE_LIMIT_EXCEEDED(5); /** * The ignored message error id returned by the Last.fm API. */ private int codeId; /** * A map which maps error codes against their corresponding enums for lookups by code. */ private static Map idToCodeMap = new HashMap(); static { for (IgnoredMessageCode code : IgnoredMessageCode.values()) { idToCodeMap.put(code.getCodeId(), code); } } private IgnoredMessageCode(int code) { this.codeId = code; } /** * Gets the IgnoredMessage enum value for the passed Last.fm error code. * * @param code The Last.fm error code. * @return The appopriate IgnoredMessage enum value. */ public static IgnoredMessageCode valueOfCode(int code) { IgnoredMessageCode messageCode = idToCodeMap.get(code); if (messageCode != null) { return messageCode; } throw new IllegalArgumentException("No IgnoredMessageCode for code " + code); } private int getCodeId() { return codeId; } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/scrobble/Rating.java000066400000000000000000000045221200137731100274440ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm.scrobble; /** * The rating of the track. See http://www.last.fm/api/submissions#subs * for more information. * * @author Lukasz Wisniewski * @deprecated The 1.2.x scrobble protocol has now been deprecated in favour of the 2.0 protocol which is part of the Last.fm web services API. */ @Deprecated public enum Rating { /** * Love (on any mode if the user has manually loved the track). This implies a listen. */ LOVE("L"), /** * Ban (only if source=L). This implies a skip, and the client should skip to the next track when a ban happens */ BAN("B"), /** * Skip (only if source=L) */ SKIP("S"); private String code; Rating(String code) { this.code = code; } /** * Returns the corresponding code for this rating. * * @return the code */ public String getCode() { return code; } }lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/scrobble/ResponseStatus.java000066400000000000000000000067141200137731100312270ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm.scrobble; /** * Contains information about the result of a scrobbling operation and an optional error message. * * @author Janni Kovacs * @see de.umass.lastfm.scrobble.ScrobbleResult * @see de.umass.lastfm.Track#scrobble(ScrobbleData, de.umass.lastfm.Session) * @deprecated The 1.2.x scrobble protocol has now been deprecated in favour of the 2.0 protocol which is part of the Last.fm web services * API. */ @Deprecated public class ResponseStatus { public static final int OK = 0; public static final int BANNED = 1; public static final int BADAUTH = 2; public static final int BADTIME = 3; public static final int BADSESSION = 4; public static final int FAILED = 5; private int status; private String message; public ResponseStatus(int status) { this(status, null); } public ResponseStatus(int status, String message) { this.status = status; this.message = message; } /** * Returns the optional error message, which is only available if status is FAILED, or * null, if no message is available. * * @return the error message or null */ public String getMessage() { return message; } /** * Returns the result status code of the operation, which is one of the integer constants defined in this class. * * @return the status code */ public int getStatus() { return status; } /** * Returns true if the operation was successful. Same as getStatus() == ResponseStatus.OK. * * @return true if status is OK */ public boolean ok() { return status == OK; } static int codeForStatus(String status) { if ("OK".equals(status)) return OK; if (status.startsWith("FAILED")) return FAILED; if ("BADAUTH".equals(status)) return BADAUTH; if ("BADSESSION".equals(status)) return BADSESSION; if ("BANNED".equals(status)) return BANNED; if ("BADTIME".equals(status)) return BADTIME; return -1; } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/scrobble/ScrobbleData.java000066400000000000000000000130671200137731100305510ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm.scrobble; /** * Class that holds all available fields for scrobble (and now playing) requests. * * @author Adrian Woodhead */ public class ScrobbleData { /** * The artist name. Required for scrobbling and now playing. */ private String artist; /** * The track name. Required for scrobbling and now playing. */ private String track; /** * The time the track started playing, in UNIX timestamp format (integer number of seconds since 00:00:00, January 1st 1970 UTC). This must * be in the UTC time zone. Required for scrobbling only. */ private int timestamp = -1; /** * The length of the track in seconds. Optional. */ private int duration = -1; /** * The album name. Optional. */ private String album; /** * The album artist, if this differs from the track artist. Optional. */ private String albumArtist; /** * The MusicBrainz track id. Optional. */ private String musicBrainzId; /** * The position of the track on the album. Optional. */ private int trackNumber = -1; /** * The stream id for this track if received from the radio.getPlaylist service. Optional. */ private String streamId; /** * Set to true if the user chose this song, or false if the song was chosen by someone else (such as a radio station or recommendation * service). Optional. */ private boolean chosenByUser = true; public ScrobbleData() { } public ScrobbleData(String artist, String track, int timestamp) { this.artist = artist; this.track = track; this.timestamp = timestamp; } public ScrobbleData(String artist, String track, int timestamp, int duration, String album, String albumArtist, String musicBrainzId, int trackNumber, String streamId) { this.artist = artist; this.track = track; this.timestamp = timestamp; this.duration = duration; this.album = album; this.albumArtist = albumArtist; this.musicBrainzId = musicBrainzId; this.trackNumber = trackNumber; this.streamId = streamId; } public ScrobbleData(String artist, String track, int timestamp, int duration, String album, String albumArtist, String musicBrainzId, int trackNumber, String streamId, boolean chosenByUser) { this.artist = artist; this.track = track; this.timestamp = timestamp; this.duration = duration; this.album = album; this.albumArtist = albumArtist; this.musicBrainzId = musicBrainzId; this.trackNumber = trackNumber; this.streamId = streamId; this.chosenByUser = chosenByUser; } public String getArtist() { return artist; } public void setArtist(String artist) { this.artist = artist; } public String getTrack() { return track; } public void setTrack(String track) { this.track = track; } public int getTimestamp() { return timestamp; } public void setTimestamp(int timestamp) { this.timestamp = timestamp; } public int getDuration() { return duration; } public void setDuration(int duration) { this.duration = duration; } public String getAlbum() { return album; } public void setAlbum(String album) { this.album = album; } public String getAlbumArtist() { return albumArtist; } public void setAlbumArtist(String albumArtist) { this.albumArtist = albumArtist; } public String getMusicBrainzId() { return musicBrainzId; } public void setMusicBrainzId(String musicBrainzId) { this.musicBrainzId = musicBrainzId; } public int getTrackNumber() { return trackNumber; } public void setTrackNumber(int trackNumber) { this.trackNumber = trackNumber; } public String getStreamId() { return streamId; } public void setStreamId(String streamId) { this.streamId = streamId; } public boolean isChosenByUser() { return chosenByUser; } public void setChosenByUser(boolean chosenByUser) { this.chosenByUser = chosenByUser; } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ @Override public String toString() { return "ScrobbleData [track=" + track + ", artist=" + artist + ", album=" + album + ", albumArtist=" + albumArtist + ", duration=" + duration + ", musicBrainzId=" + musicBrainzId + ", timestamp=" + timestamp + ", trackNumber=" + trackNumber + ", streamId=" + streamId + ", chosenByUser=" + chosenByUser + "]"; } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/scrobble/ScrobbleResult.java000066400000000000000000000111541200137731100311510ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm.scrobble; import de.umass.lastfm.Result; /** * Result object which contains extra information returned by scrobble and now playing requests. * * @author Adrian Woodhead */ public class ScrobbleResult extends Result { private String track; private String artist; private String album; private String albumArtist; private int timestamp; private boolean trackCorrected; private boolean artistCorrected; private boolean albumCorrected; private boolean albumArtistCorrected; private boolean ignored; private IgnoredMessageCode ignoredMessageCode; private String ignoredMessage; public ScrobbleResult(Result result) { super(result.getResultDocument()); super.status = result.getStatus(); super.errorMessage = result.getErrorMessage(); super.errorCode = result.getErrorCode(); super.httpErrorCode = result.getHttpErrorCode(); } public String getTrack() { return track; } public void setTrack(String track) { this.track = track; } public String getArtist() { return artist; } public void setArtist(String artist) { this.artist = artist; } public String getAlbum() { return album; } public void setAlbum(String album) { this.album = album; } public String getAlbumArtist() { return albumArtist; } public void setAlbumArtist(String albumArtist) { this.albumArtist = albumArtist; } public long getTimestamp() { return timestamp; } public void setTimestamp(int timestamp) { this.timestamp = timestamp; } public boolean isTrackCorrected() { return trackCorrected; } public void setTrackCorrected(boolean trackCorrected) { this.trackCorrected = trackCorrected; } public boolean isArtistCorrected() { return artistCorrected; } public void setArtistCorrected(boolean artistCorrected) { this.artistCorrected = artistCorrected; } public boolean isAlbumCorrected() { return albumCorrected; } public void setAlbumCorrected(boolean albumCorrected) { this.albumCorrected = albumCorrected; } public boolean isAlbumArtistCorrected() { return albumArtistCorrected; } public void setAlbumArtistCorrected(boolean albumArtistCorrected) { this.albumArtistCorrected = albumArtistCorrected; } public boolean isIgnored() { return ignored; } public void setIgnored(boolean ignored) { this.ignored = ignored; } public IgnoredMessageCode getIgnoredMessageCode() { return ignoredMessageCode; } public void setIgnoredMessageCode(IgnoredMessageCode ignoredMessageCode) { this.ignoredMessageCode = ignoredMessageCode; } public String getIgnoredMessage() { return ignoredMessage; } public void setIgnoredMessage(String ignoredMessage) { this.ignoredMessage = ignoredMessage; } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ @Override public String toString() { return "ScrobbleResult [" + super.toString() + ", track=" + track + ", trackCorrected=" + trackCorrected + ", artist=" + artist + ", artistCorrected=" + artistCorrected + ", album=" + album + ", albumCorrected=" + albumCorrected + ", albumArtist=" + albumArtist + ", albumArtistCorrected=" + albumArtistCorrected + ", ignored=" + ignored + ", ignoredMessageCode=" + ignoredMessageCode + ", ignoredMessage=" + ignoredMessage + ", timestamp=" + timestamp + "]"; } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/scrobble/Scrobbler.java000066400000000000000000000274411200137731100301420ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm.scrobble; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.HttpURLConnection; import java.net.Proxy; import java.util.Collection; import java.util.Collections; import de.umass.lastfm.Caller; import de.umass.lastfm.Session; import static de.umass.util.StringUtilities.encode; import static de.umass.util.StringUtilities.md5; /** * This class manages communication with the server for scrobbling songs. You can retrieve an instance of this class by calling * {@link #newScrobbler(String, String, String) newScrobbler}.
* It contains methods to perform the handshake, notify Last.fm about a now playing song and submitting songs to a musical profile, aka * scrobbling songs.
* See http://www.last.fm/api/submissions for a deeper explanation of the protocol and * various guidelines on how to use the scrobbling service, since this class does not cover error handling or caching.
* All methods in this class, which are communicating with the server, return an instance of {@link ResponseStatus} which contains * information if the operation was successful or not.
* This class respects the proxy property in the {@link Caller} class in all its HTTP calls. If you need the * Scrobbler to use a Proxy server, set it with {@link Caller#setProxy(java.net.Proxy)}. * * @author Janni Kovacs * @see de.umass.lastfm.Track#scrobble(ScrobbleData, de.umass.lastfm.Session) * @see de.umass.lastfm.Track#scrobble(String, String, int, de.umass.lastfm.Session) * @see de.umass.lastfm.Track#scrobble(java.util.List, de.umass.lastfm.Session) * @deprecated The 1.2.x scrobble protocol has now been deprecated in favour of the 2.0 protocol which is part of the Last.fm web services * API. */ @Deprecated public class Scrobbler { private static final String DEFAULT_HANDSHAKE_URL = "http://post.audioscrobbler.com/"; private String handshakeUrl = DEFAULT_HANDSHAKE_URL; private final String clientId, clientVersion; private final String user; private String sessionId; private String nowPlayingUrl; private String submissionUrl; private Scrobbler(String clientId, String clientVersion, String user) { this.clientId = clientId; this.clientVersion = clientVersion; this.user = user; } /** * Sets the URL to use to perform a handshake. Use this method to redirect your scrobbles to another service, like Libre.fm. * * @param handshakeUrl The new handshake url. */ public void setHandshakeURL(String handshakeUrl) { this.handshakeUrl = handshakeUrl; } /** * Creates a new Scrobbler instance bound to the specified user. * * @param clientId The client id (or "tst") * @param clientVersion The client version (or "1.0") * @param user The last.fm user * @return a new Scrobbler instance */ public static Scrobbler newScrobbler(String clientId, String clientVersion, String user) { return new Scrobbler(clientId, clientVersion, user); } /** * Performs a standard handshake with the user's password. * * @param password The user's password * @return the status of the operation * @throws IOException on I/O errors */ public ResponseStatus handshake(String password) throws IOException { long time = System.currentTimeMillis() / 1000; String auth = md5(md5(password) + time); String url = String.format("%s?hs=true&p=1.2.1&c=%s&v=%s&u=%s&t=%s&a=%s", handshakeUrl, clientId, clientVersion, user, time, auth); return performHandshake(url); } /** * Performs a web-service handshake. * * @param session An authenticated Session. * @return the status of the operation * @throws IOException on I/O errors * @see de.umass.lastfm.Authenticator */ public ResponseStatus handshake(Session session) throws IOException { long time = System.currentTimeMillis() / 1000; String auth = md5(session.getSecret() + time); String url = String .format("%s?hs=true&p=1.2.1&c=%s&v=%s&u=%s&t=%s&a=%s&api_key=%s&sk=%s", handshakeUrl, clientId, clientVersion, user, time, auth, session.getApiKey(), session.getKey()); return performHandshake(url); } /** * Internally performs the handshake operation by calling the given url and examining the response. * * @param url The URL to call * @return the status of the operation * @throws IOException on I/O errors */ private ResponseStatus performHandshake(String url) throws IOException { HttpURLConnection connection = Caller.getInstance().openConnection(url); InputStream is = connection.getInputStream(); BufferedReader r = new BufferedReader(new InputStreamReader(is)); String status = r.readLine(); int statusCode = ResponseStatus.codeForStatus(status); ResponseStatus responseStatus; if (statusCode == ResponseStatus.OK) { this.sessionId = r.readLine(); this.nowPlayingUrl = r.readLine(); this.submissionUrl = r.readLine(); responseStatus = new ResponseStatus(statusCode); } else if (statusCode == ResponseStatus.FAILED) { responseStatus = new ResponseStatus(statusCode, status.substring(status.indexOf(' ') + 1)); } else { return new ResponseStatus(statusCode); } r.close(); return responseStatus; } /** * Submits 'now playing' information. This does not affect the musical profile of the user. * * @param artist The artist's name * @param track The track's title * @return the status of the operation * @throws IOException on I/O errors */ public ResponseStatus nowPlaying(String artist, String track) throws IOException { return nowPlaying(artist, track, null, -1, -1); } /** * Submits 'now playing' information. This does not affect the musical profile of the user. * * @param artist The artist's name * @param track The track's title * @param album The album or null * @param length The length of the track in seconds * @param tracknumber The position of the track in the album or -1 * @return the status of the operation * @throws IOException on I/O errors */ public ResponseStatus nowPlaying(String artist, String track, String album, int length, int tracknumber) throws IOException { if (sessionId == null) throw new IllegalStateException("Perform successful handshake first."); String b = album != null ? encode(album) : ""; String l = length == -1 ? "" : String.valueOf(length); String n = tracknumber == -1 ? "" : String.valueOf(tracknumber); String body = String .format("s=%s&a=%s&t=%s&b=%s&l=%s&n=%s&m=", sessionId, encode(artist), encode(track), b, l, n); if (Caller.getInstance().isDebugMode()) System.out.println("now playing: " + body); HttpURLConnection urlConnection = Caller.getInstance().openConnection(nowPlayingUrl); urlConnection.setRequestMethod("POST"); urlConnection.setDoOutput(true); OutputStream outputStream = urlConnection.getOutputStream(); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream)); writer.write(body); writer.close(); InputStream is = urlConnection.getInputStream(); BufferedReader r = new BufferedReader(new InputStreamReader(is)); String status = r.readLine(); r.close(); return new ResponseStatus(ResponseStatus.codeForStatus(status)); } /** * Scrobbles a song. * * @param artist The artist's name * @param track The track's title * @param album The album or null * @param length The length of the track in seconds * @param tracknumber The position of the track in the album or -1 * @param source The source of the track * @param startTime The time the track started playing in UNIX timestamp format and UTC time zone * @return the status of the operation * @throws IOException on I/O errors */ public ResponseStatus submit(String artist, String track, String album, int length, int tracknumber, Source source, long startTime) throws IOException { return submit(new SubmissionData(artist, track, album, length, tracknumber, source, startTime)); } /** * Scrobbles a song. * * @param data Contains song information * @return the status of the operation * @throws IOException on I/O errors */ public ResponseStatus submit(SubmissionData data) throws IOException { return submit(Collections.singletonList(data)); } /** * Scrobbles up to 50 songs at once. Song info is contained in the Collection passed. Songs must be in * chronological order of their play, that means the track first in the list has been played before the track second * in the list and so on. * * @param data A list of song infos * @return the status of the operation * @throws IOException on I/O errors * @throws IllegalArgumentException if data contains more than 50 entries */ public ResponseStatus submit(Collection data) throws IOException { if (sessionId == null) throw new IllegalStateException("Perform successful handshake first."); if (data.size() > 50) throw new IllegalArgumentException("Max 50 submissions at once"); StringBuilder builder = new StringBuilder(data.size() * 100); int index = 0; for (SubmissionData submissionData : data) { builder.append(submissionData.toString(sessionId, index)); builder.append('\n'); index++; } String body = builder.toString(); if (Caller.getInstance().isDebugMode()) System.out.println("submit: " + body); HttpURLConnection urlConnection = Caller.getInstance().openConnection(submissionUrl); urlConnection.setRequestMethod("POST"); urlConnection.setDoOutput(true); OutputStream outputStream = urlConnection.getOutputStream(); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream)); writer.write(body); writer.close(); InputStream is = urlConnection.getInputStream(); BufferedReader r = new BufferedReader(new InputStreamReader(is)); String status = r.readLine(); r.close(); int statusCode = ResponseStatus.codeForStatus(status); if (statusCode == ResponseStatus.FAILED) { return new ResponseStatus(statusCode, status.substring(status.indexOf(' ') + 1)); } return new ResponseStatus(statusCode); } }lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/scrobble/Source.java000066400000000000000000000052441200137731100274620ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm.scrobble; /** * The source of the track. See http://www.last.fm/api/submissions#subs for more * information. * * @author Janni Kovacs * @deprecated The 1.2.x scrobble protocol has now been deprecated in favour of the 2.0 protocol which is part of the Last.fm web services * API. */ @Deprecated public enum Source { /** * Chosen by the user (the most common value, unless you have a reason for choosing otherwise, use this). */ USER("P"), /** * Non-personalised broadcast (e.g. Shoutcast, BBC Radio 1). */ NON_PERSONALIZED_BROADCAST("R"), /** * Personalised recommendation except Last.fm (e.g. Pandora, Launchcast). */ PERSONALIZED_BROADCAST("E"), /** * Last.fm (any mode). In this case, the 5-digit Last.fm recommendation key must be appended to this source ID * to prove the validity of the submission (for example, "o[0]=L1b48a"). */ LAST_FM("L"), /** * Source unknown. */ UNKNOWN("U"); private String code; Source(String code) { this.code = code; } /** * Returns the corresponding code for this source. * * @return the code */ public String getCode() { return code; } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/lastfm/scrobble/SubmissionData.java000066400000000000000000000131611200137731100311440ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.lastfm.scrobble; import static de.umass.util.StringUtilities.decode; import static de.umass.util.StringUtilities.encode; /** * Bean that contains track information. * * @author Janni Kovacs * @see de.umass.lastfm.scrobble.ScrobbleData * @see de.umass.lastfm.Track#scrobble(ScrobbleData, de.umass.lastfm.Session) * @deprecated The 1.2.x scrobble protocol has now been deprecated in favour of the 2.0 protocol which is part of the Last.fm web services * API. */ @Deprecated public class SubmissionData { private String artist; private String track; private String album; private long startTime; private Source source; private Rating rating; private String recommendationKey; private int length; private int tracknumber; public SubmissionData(String artist, String track, String album, int length, int tracknumber, Source source, long startTime) { this(artist, track, album, length, tracknumber, source, null, startTime); } public SubmissionData(String artist, String track, String album, int length, int tracknumber, Source source, Rating rating, long startTime) { this.artist = artist; this.track = track; this.album = album; this.length = length; this.tracknumber = tracknumber; this.source = source; this.rating = rating; this.startTime = startTime; } public SubmissionData(String artist, String track, String album, int length, int tracknumber, Source source, Rating rating, long startTime, String recommendationKey) { this(artist, track, album, length, tracknumber, source, rating, startTime); this.recommendationKey = recommendationKey; } /** * Creates a new SubmissionData object based on a String returned by {@link #toString()}. * * @param s A String */ public SubmissionData(String s) { String[] parts = s.split("&", 9); artist = decode(parts[0]); track = decode(parts[1]); startTime = parts[2].length() == 0 ? 0 : Long.valueOf(parts[2]); source = Source.valueOf(parts[3]); recommendationKey = parts[4].length() == 0 ? null : parts[4]; rating = parts[5].length() == 0 ? null : Rating.valueOf(parts[5]); length = parts[6].length() == 0 ? -1 : Integer.valueOf(parts[6]); album = parts[7].length() == 0 ? null : decode(parts[7]); tracknumber = parts[8].length() == 0 ? -1 : Integer.valueOf(parts[8]); } /** * Returns a String representation of this submission with the fields separated by &. * Order of the fields is:
* artist&track&startTime&Source&RecommendationKey&Rating&length&album&tracknumber
* Note that: * - Values may possibly be null or empty * - enum values such as Rating and Source are null or their constant name is used (i.e. "LOVE") * - all string values (artist, track, album) are utf8-url-encoded * * @return a String */ public String toString() { String b = encode(album != null ? album : ""); String artist = encode(this.artist); String track = encode(this.track); String l = length == -1 ? "" : String.valueOf(length); String n = tracknumber == -1 ? "" : String.valueOf(tracknumber); String r = ""; if (rating != null) r = rating.name(); String rec = ""; if (recommendationKey != null && source == Source.LAST_FM && recommendationKey.length() == 5) rec = recommendationKey; return String.format("%s&%s&%s&%s&%s&%s&%s&%s&%s", artist, track, startTime, source.name(), rec, r, l, b, n); } String toString(String sessionId, int index) { String b = encode(album != null ? album : ""); String artist = encode(this.artist); String track = encode(this.track); String l = length == -1 ? "" : String.valueOf(length); String n = tracknumber == -1 ? "" : String.valueOf(tracknumber); String r = ""; if (rating != null) r = rating.getCode(); String rec = ""; if (recommendationKey != null && source == Source.LAST_FM && recommendationKey.length() == 5) rec = recommendationKey; return String .format("s=%s&a[%10$d]=%s&t[%10$d]=%s&i[%10$d]=%s&o[%10$d]=%s&r[%10$d]=%s&l[%10$d]=%s&b[%10$d]=%s&n[%10$d]=%s&m[%10$d]=", sessionId, artist, track, startTime, source.getCode() + rec, r, l, b, n, index); } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/util/000077500000000000000000000000001200137731100232465ustar00rootroot00000000000000lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/util/MapUtilities.java000066400000000000000000000060351200137731100265260ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.util; import java.util.Map; /** * Utility class to perform various operations on Maps. * * @author Adrian Woodhead */ public final class MapUtilities { private MapUtilities() { } /** * Puts the passed key and value into the map only if the value is not null. * * @param map Map to add key and value to. * @param key Map key. * @param value Map value, if null will not be added to map. */ public static void nullSafePut(Map map, String key, String value) { if (value != null) { map.put(key, value); } } /** * Puts the passed key and value into the map only if the value is not null. * * @param map Map to add key and value to. * @param key Map key. * @param value Map value, if null will not be added to map. */ public static void nullSafePut(Map map, String key, Integer value) { if (value != null) { map.put(key, value.toString()); } } /** * Puts the passed key and value into the map only if the value is not -1. * * @param map Map to add key and value to. * @param key Map key. * @param value Map value, if -1 will not be added to map. */ public static void nullSafePut(Map map, String key, int value) { if (value != -1) { map.put(key, Integer.toString(value)); } } /** * Puts the passed key and value into the map only if the value is not -1. * * @param map Map to add key and value to. * @param key Map key. * @param value Map value, if -1 will not be added to map. */ public static void nullSafePut(Map map, String key, double value) { if (value != -1) { map.put(key, Double.toString(value)); } } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/util/StringUtilities.java000066400000000000000000000140751200137731100272620ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.util; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; /** * Utilitiy class with methods to calculate an md5 hash and to encode URLs. * * @author Janni Kovacs */ public final class StringUtilities { private static MessageDigest digest; private static Pattern MBID_PATTERN = Pattern .compile("^[0-9a-f]{8}\\-[0-9a-f]{4}\\-[0-9a-f]{4}\\-[0-9a-f]{4}\\-[0-9a-f]{12}$", Pattern.CASE_INSENSITIVE); private static final Pattern MD5_PATTERN = Pattern.compile("[a-fA-F0-9]{32}"); static { try { digest = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { // better never happens } } /** * Returns a 32 chararacter hexadecimal representation of an MD5 hash of the given String. * * @param s the String to hash * @return the md5 hash */ public static String md5(String s) { try { byte[] bytes = digest.digest(s.getBytes("UTF-8")); StringBuilder b = new StringBuilder(32); for (byte aByte : bytes) { String hex = Integer.toHexString((int) aByte & 0xFF); if (hex.length() == 1) b.append('0'); b.append(hex); } return b.toString(); } catch (UnsupportedEncodingException e) { // utf-8 always available } return null; } /** * URL Encodes the given String s using the UTF-8 character encoding. * * @param s a String * @return url encoded string */ public static String encode(String s) { if(s == null) return null; try { return URLEncoder.encode(s, "UTF-8"); } catch (UnsupportedEncodingException e) { // utf-8 always available } return null; } /** * Decodes an URL encoded String s using the UTF-8 character encoding. * * @param s an encoded String * @return the decoded String */ public static String decode(String s) { if(s == null) return null; try { return URLDecoder.decode(s, "UTF-8"); } catch (UnsupportedEncodingException e) { // utf-8 always available } return null; } /** * Checks if the supplied String may be a Musicbrainz ID. This method returns true for Strings that are * exactly 36 characters long and match the MBID pattern [0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12}. * * @param nameOrMbid a possible MBID * @return true if this String may be a MBID */ public static boolean isMbid(String nameOrMbid) { // example: bfcc6d75-a6a5-4bc6-8282-47aec8531818 return nameOrMbid != null && nameOrMbid.length() == 36 && MBID_PATTERN.matcher(nameOrMbid).matches(); } /** * Creates a Map out of an array with Strings. * * @param strings input strings, key-value alternating * @return a parameter map */ public static Map map(String... strings) { if (strings.length % 2 != 0) throw new IllegalArgumentException("strings.length % 2 != 0"); Map mp = new HashMap(); for (int i = 0; i < strings.length; i += 2) { mp.put(strings[i], strings[i + 1]); } return mp; } /** * Strips all characters from a String, that might be invalid to be used in file names. * By default : / \ < > | ? " * are all replaced by -. * Note that this is no guarantee that the returned name will be definately valid. * * @param s the String to clean up * @return the cleaned up String */ public static String cleanUp(String s) { return s.replaceAll("[*:/\\\\?|<>\"]", "-"); } /** * Tests if the given string might already be a 32-char md5 string. * * @param s String to test * @return true if the given String might be a md5 string */ public static boolean isMD5(String s) { return s.length() == 32 && MD5_PATTERN.matcher(s).matches(); } /** * Converts a Last.fm boolean result string to a boolean. * * @param resultString A Last.fm boolean result string. * @return true if the given String represents a true, false otherwise. */ public static boolean convertToBoolean(String resultString) { return "1".equals(resultString); } /** * Converts from a boolean to a Last.fm boolean result string. * * @param value A boolean value. * @return A string representing a Last.fm boolean. */ public static String convertFromBoolean(boolean value) { if (value) { return "1"; } else { return "0"; } } } lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/xml/000077500000000000000000000000001200137731100230715ustar00rootroot00000000000000lastfm-java-lastfm-java-0.1.2/src/main/java/de/umass/xml/DomElement.java000066400000000000000000000121701200137731100257660ustar00rootroot00000000000000/* * Copyright (c) 2012, the Last.fm Java Project and Committers * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.umass.xml; import java.util.ArrayList; import java.util.List; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * DomElement wraps around an {@link Element} and provides convenience methods. * * @author Janni Kovacs */ public class DomElement { private Element e; /** * Creates a new wrapper around the given {@link Element}. * * @param elem An w3c Element */ public DomElement(Element elem) { this.e = elem; } /** * @return the original Element */ public Element getElement() { return e; } /** * Tests if this element has an attribute with the specified name. * * @param name Name of the attribute. * @return true if this element has an attribute with the specified name. */ public boolean hasAttribute(String name) { return e.hasAttribute(name); } /** * Returns the attribute value to a given attribute name or null if the attribute doesn't exist. * * @param name The attribute's name * @return Attribute value or null */ public String getAttribute(String name) { return e.hasAttribute(name) ? e.getAttribute(name) : null; } /** * @return the text content of the element */ public String getText() { // XXX e.getTextContent() doesn't exsist under Android (Lukasz Wisniewski) /// getTextContent() is now available in at least Android 2.2 if not earlier, so we'll keep using that // return e.hasChildNodes() ? e.getFirstChild().getNodeValue() : null; return e.getTextContent(); } /** * Checks if this element has a child element with the given name. * * @param name The child's name * @return true if this element has a child element with the given name */ public boolean hasChild(String name) { NodeList list = e.getElementsByTagName(name); for (int i = 0, j = list.getLength(); i < j; i++) { Node item = list.item(i); if (item.getParentNode() == e) return true; } return false; } /** * Returns the child element with the given name or null if it doesn't exist. * * @param name The child's name * @return the child element or null */ public DomElement getChild(String name) { NodeList list = e.getElementsByTagName(name); if (list.getLength() == 0) return null; for (int i = 0, j = list.getLength(); i < j; i++) { Node item = list.item(i); if (item.getParentNode() == e) return new DomElement((Element) item); } return null; } /** * Returns the text content of a child node with the given name. If no such child exists or the child * does not have text content, null is returned. * * @param name The child's name * @return the child's text content or null */ public String getChildText(String name) { DomElement child = getChild(name); return child != null ? child.getText() : null; } /** * @return all children of this element */ public List getChildren() { return getChildren("*"); } /** * Returns all children of this element with the given tag name. * * @param name The children's tag name * @return all matching children */ public List getChildren(String name) { List l = new ArrayList(); NodeList list = e.getElementsByTagName(name); for (int i = 0; i < list.getLength(); i++) { Node node = list.item(i); if (node.getParentNode() == e) l.add(new DomElement((Element) node)); } return l; } /** * Returns this element's tag name. * * @return the tag name */ public String getTagName() { return e.getTagName(); } }