/* * aTunes 1.14.0 code adapted by Jajuk team * * Original copyright notice bellow : * * Copyright (C) 2006-2009 Alex Aranda, Sylvain Gaudard, Thomas Beckers and contributors * * See http://www.atunes.org/wiki/index.php?title=Contributing for information about contributors * * http://www.atunes.org * http://sourceforge.net/projects/atunes * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ package ext.services.lastfm; import de.umass.lastfm.*; import ext.services.network.Proxy; import java.awt.Image; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Set; import de.umass.lastfm.scrobble.ResponseStatus; import de.umass.lastfm.scrobble.Scrobbler; import de.umass.lastfm.scrobble.Source; import de.umass.lastfm.scrobble.SubmissionData; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.StringUtils; import org.jajuk.base.Track; import org.jajuk.util.Conf; import org.jajuk.util.Const; import org.jajuk.util.DownloadManager; import org.jajuk.util.LocaleManager; import org.jajuk.util.Messages; import org.jajuk.util.UtilString; import org.jajuk.util.log.Log; /** * The Class LastFmService. * * This class is responsible of retrieve information from Last.fm web services. * <singleton> */ public class LastFmService { /* * DO NOT USE THESE KEYS FOR OTHER APPLICATIONS THAN Jajuk! */ /** The Constant API_KEY. */ private static final String API_KEY = "711591ss6q695ps349o6681pr1oq1467"; /** The Constant CLIENT_ID. */ private static final String CLIENT_ID = "jaj"; /** The Constant CLIENT_VERSION. */ private static final String CLIENT_VERSION = "0.2"; // Assigned by Last.FM // team /** The Constant ARTIST_WILDCARD. */ private static final String ARTIST_WILDCARD = "(%ARTIST%)"; /** The Constant LANGUAGE_PARAM. */ private static final String LANGUAGE_PARAM = "?setlang="; /** The Constant LANGUAGE_WILDCARD. */ private static final String LANGUAGE_WILDCARD = "(%LANGUAGE%)"; /** The Constant ARTIST_WIKI_URL. */ private static final String ARTIST_WIKI_URL = UtilString.concat("http://www.lastfm.com/music/", ARTIST_WILDCARD, "/+wiki", LANGUAGE_PARAM, LANGUAGE_WILDCARD); /** The Constant VARIOUS_ARTISTS. */ private static final String VARIOUS_ARTISTS = "Various Artists"; /** The Constant MIN_DURATION_TO_SUBMIT. */ private static final int MIN_DURATION_TO_SUBMIT = 30; /** The Constant MAX_SUBMISSIONS. */ private static final int MAX_SUBMISSIONS = 50; private Scrobbler scrobbler; private boolean handshakePerformed; private Locale locale; private LastFmCache lastFmCache; /** The singleton. */ private static LastFmService self; /** * Instantiates a new Last.fm service * * @param proxy the proxy * @param user the Last.fm username * @param password the Last.fm password * @param locale * @param lastFmCache */ private LastFmService(Locale locale, LastFmCache lastFmCache) { Proxy proxy = DownloadManager.getProxy(); Caller.getInstance().setCache(null); Caller.getInstance().setProxy(proxy); Caller.getInstance().setUserAgent(CLIENT_ID); String user = Conf.getString(Const.CONF_LASTFM_USER); // Use encoded version name to avoid errors from server scrobbler = Scrobbler.newScrobbler(CLIENT_ID, ext.services.network.NetworkUtils.encodeString(CLIENT_VERSION), user); this.handshakePerformed = false; this.locale = locale; this.lastFmCache = lastFmCache; } /** * Return the LastFMService singleton. * * @return the LastFMService singleton */ static public LastFmService getInstance() { if (self == null) { LastFmCache cache = new LastFmCache(); Locale locale = LocaleManager.getLocale(); self = new LastFmService(locale, cache); } return self; } /** * Gets the artist. * * @param artist * * @return the artist */ public ArtistInfo getArtist(String artist) { try { // Try to get from cache ArtistInfo artistInfo = lastFmCache.retrieveArtistInfo(artist); if (artistInfo == null) { Artist a = Artist.getInfo(artist, UtilString.rot13(API_KEY)); if (a != null) { artistInfo = LastFmArtist.getArtist(a); lastFmCache.storeArtistInfo(artist, artistInfo); } } return artistInfo; } catch (Exception e) { Log.error(e); } return null; } /** * Gets the album. * * @param artist the artist * @param album the album * * @return the album */ public AlbumInfo getAlbum(String artist, String album) { try { // Try to get from cache AlbumInfo albumObject = lastFmCache.retrieveAlbumInfo(artist, album); if (albumObject == null) { Album a = Album.getInfo(artist, album, UtilString.rot13(API_KEY)); if (a != null) { Playlist pl = Playlist.fetchAlbumPlaylist(a.getId(), UtilString.rot13(API_KEY)); albumObject = LastFmAlbum.getAlbum(a, pl); lastFmCache.storeAlbumInfo(artist, album, albumObject); } } return albumObject; } catch (Exception e) { Log.error(e); } return null; } /** * Gets the album list. * * @param artist the artist * @param hideVariousArtists if <code>true</code> albums with artist name "Various Artists" * are nor returned * @param minimumSongNumber albums with less songs than this argument won't be returned * * @return the album list */ public AlbumListInfo getAlbumList(String artist, boolean hideVariousArtists, int minimumSongNumber) { try { // Try to get from cache AlbumListInfo albumList = lastFmCache.retrieveAlbumList(artist); if (albumList == null) { Collection<Album> as = Artist.getTopAlbums(artist, UtilString.rot13(API_KEY)); if (as != null) { AlbumListInfo albums = LastFmAlbumList.getAlbumList(as, artist); List<AlbumInfo> result = new ArrayList<AlbumInfo>(); for (AlbumInfo a : albums.getAlbums()) { if (a.getBigCoverURL() != null && !a.getBigCoverURL().isEmpty()) { //NOSONAR result.add(a); } } albumList = new LastFmAlbumList(); albumList.setArtist(artist); albumList.setAlbums(result); lastFmCache.storeAlbumList(artist, albumList); } } if (albumList != null) { List<AlbumInfo> albumsFiltered = null; // Apply filter to hide "Various Artists" albums if (hideVariousArtists) { albumsFiltered = new ArrayList<AlbumInfo>(); for (AlbumInfo albumInfo : albumList.getAlbums()) { if (!albumInfo.getArtist().equals(VARIOUS_ARTISTS)) { //NOSONAR albumsFiltered.add(albumInfo); } } albumList.setAlbums(albumsFiltered); } // Apply filter to hide albums with less than X songs if (minimumSongNumber > 0) { albumsFiltered = new ArrayList<AlbumInfo>(); for (AlbumInfo albumInfo : albumList.getAlbums()) { AlbumInfo extendedAlbumInfo = getAlbum(artist, albumInfo.getTitle()); if (extendedAlbumInfo != null && extendedAlbumInfo.getTracks() != null //NOSONAR && extendedAlbumInfo.getTracks().size() >= minimumSongNumber) { albumsFiltered.add(albumInfo); } } } if (albumsFiltered != null) { albumList.setAlbums(albumsFiltered); } } return albumList; } catch (Exception e) { Log.error(e); } return null; } /** * Gets the image. * * @param album the album * * @return the image */ public Image getImage(AlbumInfo album) { try { Image img = null; Proxy proxy = DownloadManager.getProxy(); // Try to retrieve from cache img = lastFmCache.retrieveAlbumCover(album); if (img == null && album.getBigCoverURL() != null && !album.getBigCoverURL().isEmpty()) { img = ext.services.network.NetworkUtils.getImage(ext.services.network.NetworkUtils .getConnection(album.getBigCoverURL(), proxy)); lastFmCache.storeAlbumCover(album, img); } return img; } catch (IOException e) { Log.error(e); } return null; } /** * Gets the image of an artist. * * @param artist the artist * * @return the image */ public Image getImage(ArtistInfo artist) { try { // Try to retrieve from cache Image img = lastFmCache.retrieveArtistThumbImage(artist); Proxy proxy = DownloadManager.getProxy(); if (img == null && artist.getImageUrl() != null && !artist.getImageUrl().isEmpty()) { // Try to get from Artist.getImages() method img = getArtistImageFromLastFM(artist.getName()); // if not then get from artist info if (img == null) { img = ext.services.network.NetworkUtils.getImage(ext.services.network.NetworkUtils .getConnection(artist.getImageUrl(), proxy)); } lastFmCache.storeArtistThumbImage(artist, img); } return img; } catch (Exception e) { Log.error(e); } return null; } /** * Gets the image of the artist. * * @param similar the similar * * @return the image */ public Image getImage(SimilarArtistsInfo similar) { try { // Try to retrieve from cache Image img = lastFmCache.retrieveArtistImage(similar); Proxy proxy = DownloadManager.getProxy(); if (img != null) { return img; } // Try to get from LastFM img = getArtistImageFromLastFM(similar.getArtistName()); // Get from similar artist info if (img == null) { String similarUrl = similar.getPicture(); if (!similarUrl.trim().isEmpty()) { img = ext.services.network.NetworkUtils.getImage(ext.services.network.NetworkUtils .getConnection(similarUrl, proxy)); } } if (img != null) { lastFmCache.storeArtistImage(similar, img); } return img; } catch (Exception e) { Log.error(e); } return null; } /** * Returns current artist image at LastFM. * * @param artistName * * @return the artist image from last fm */ private Image getArtistImageFromLastFM(String artistName) { try { Proxy proxy = DownloadManager.getProxy(); // Try to get from Artist.getImages() method PaginatedResult<de.umass.lastfm.Image> images = Artist.getImages(artistName, 1, 1, UtilString.rot13(API_KEY)); List<de.umass.lastfm.Image> imageList = new ArrayList<de.umass.lastfm.Image>( images.getPageResults()); if (!imageList.isEmpty()) { Set<ImageSize> sizes = imageList.get(0).availableSizes(); // Try to get original if (sizes.contains(ImageSize.ORIGINAL)) { return ext.services.network.NetworkUtils.getImage(ext.services.network.NetworkUtils .getConnection(imageList.get(0).getImageURL(ImageSize.ORIGINAL), proxy)); } } } catch (IOException e) { Log.error(e); } return null; } /** * Gets the similar artists. * * @param artist the artist * * @return the similar artists */ public SimilarArtistsInfo getSimilarArtists(String artist) { try { // Try to get from cache SimilarArtistsInfo similar = lastFmCache.retrieveArtistSimilar(artist); if (similar == null) { Collection<Artist> as = Artist.getSimilar(artist, UtilString.rot13(API_KEY)); Artist a = Artist.getInfo(artist, UtilString.rot13(API_KEY)); if (a != null) { similar = LastFmSimilarArtists.getSimilarArtists(as, a); lastFmCache.storeArtistSimilar(artist, similar); } } return similar; } catch (Exception e) { Log.error(e); } return null; } /** * Gets the wiki text. * * @param artist the artist * * @return the wiki text */ public String getWikiText(String artist) { try { // Try to get from cache String wikiText = lastFmCache.retrieveArtistWiki(artist); if (wikiText == null) { String userName = null; Artist a = Artist.getInfo(artist, locale, userName, UtilString.rot13(API_KEY)); wikiText = a != null ? a.getWikiSummary() : ""; if (wikiText != null) { wikiText = wikiText.replaceAll("<.*?>", ""); wikiText = StringEscapeUtils.unescapeHtml(wikiText); } lastFmCache.storeArtistWiki(artist, wikiText); } return wikiText; } catch (Exception e) { Log.error(e); } return null; } /** * Gets the wiki url. * * @param artist the artist * * @return the wiki url */ public String getWikiURL(String artist) { return ARTIST_WIKI_URL.replace(ARTIST_WILDCARD, ext.services.network.NetworkUtils.encodeString(artist)).replace(LANGUAGE_WILDCARD, locale.getLanguage()); } /** * Submits song to Last.fm * * @param track * @param millisPlayed ms the audio file has already played * @throws ScrobblerException the scrobbler exception */ public void submit(Track track, long millisPlayed) throws ScrobblerException { // Do all necessary checks if (!checkUser() || !checkPassword() || !checkArtist(track) || !checkTitle(track) || !checkDuration(track)) { return; } // Get started to play in secs UTC and not in MS (lastfm-bindings API was unclear about it) long startedToPlay = (System.currentTimeMillis() - millisPlayed) / 1000; Log.info("Trying to submit song to Last.fm, play time=" + millisPlayed / 1000 + " secs"); try { performHandshakeIfNeeded(); SubmissionData submissionData = new SubmissionData(track.getArtist().getName2(), track.getName(), track.getAlbum().getName2(), (int) track.getDuration(), (int) track.getOrder(), Source.USER, null, startedToPlay); ResponseStatus status = scrobbler.submit(submissionData); if (status.ok()) { Log.info("Song submitted to Last.fm"); } else { handshakePerformed = false; lastFmCache.addSubmissionData(new FullSubmissionData(track.getArtist().getName2(), track .getName(), track.getAlbum().getName2(), (int) track.getDuration(), (int) track .getOrder(), Source.USER.toString(), (int) startedToPlay)); throw new ScrobblerException(status.getStatus()); } } catch (IOException e) { Log.error(e); handshakePerformed = false; lastFmCache.addSubmissionData(new FullSubmissionData(track.getArtist().getName2(), track .getName(), track.getAlbum().getName2(), (int) track.getDuration(), (int) track .getOrder(), Source.USER.toString(), (int) startedToPlay)); throw new ScrobblerException(e.getMessage()); } } /** * Submits cache data to Last.fm * * @throws ScrobblerException the scrobbler exception */ public void submitCache() throws ScrobblerException { // Do all necessary checks if (!checkUser() || !checkPassword()) { return; } List<FullSubmissionData> collectionWithSubmissionData = lastFmCache.getSubmissionData(); if (!collectionWithSubmissionData.isEmpty()) { // More than MAX_SUBMISSIONS submissions at once are not allowed int size = collectionWithSubmissionData.size(); if (size > MAX_SUBMISSIONS) { collectionWithSubmissionData = collectionWithSubmissionData.subList(size - MAX_SUBMISSIONS, size); } Log.info("Trying to submit cache to Last.fm"); try { performHandshakeIfNeeded(); List<SubmissionData> submissionDataList = new ArrayList<SubmissionData>(); for (ext.services.lastfm.FullSubmissionData submissionData : collectionWithSubmissionData) { SubmissionData sd = new SubmissionData(submissionData.getArtist(), submissionData.getTitle(), submissionData.getAlbum(), submissionData.getDuration(), submissionData.getTrackNumber(), Source.valueOf(submissionData.getSource()), null, submissionData.getStartTime()); submissionDataList.add(sd); } ResponseStatus status = scrobbler.submit(submissionDataList); if (status.ok()) { lastFmCache.removeSubmissionData(); Log.info("Cache submitted to Last.fm"); } else { handshakePerformed = false; throw new ScrobblerException(status.getStatus()); } } catch (IOException e) { Log.error(e); handshakePerformed = false; throw new ScrobblerException(e.getMessage()); } } } /** * Submits now playing info to Last.fm * * @param track * * @throws ScrobblerException the scrobbler exception */ public void submitNowPlayingInfo(Track track) throws ScrobblerException { // Do all necessary checks if (!checkUser() || !checkPassword() || !checkArtist(track) || !checkTitle(track)) { return; } Log.info("Trying to submit now playing info to Last.fm"); try { performHandshakeIfNeeded(); ResponseStatus status = scrobbler.nowPlaying(track.getArtist().getName2(), track.getName(), track.getAlbum().getName2(), (int) track.getDuration(), (int) track.getOrder()); if (status.ok()) { Log.info("Now playing info submitted to Last.fm"); } else { handshakePerformed = false; throw new ScrobblerException(status.getStatus()); } } catch (IOException e) { Log.error(e); handshakePerformed = false; throw new ScrobblerException(e.getMessage()); } } /** * Performs handshake for submissions if needed. * * @throws IOException Signals that an I/O exception has occurred. * @throws ScrobblerException the scrobbler exception */ private void performHandshakeIfNeeded() throws IOException, ScrobblerException { if (!handshakePerformed) { String password = UtilString.rot13(Conf.getString(Const.CONF_LASTFM_PASSWORD)); ResponseStatus status = scrobbler.handshake(password); if (!status.ok()) { throw new ScrobblerException(status.getStatus()); } handshakePerformed = true; } } /** * Checks user. * * @return true, if check user */ private boolean checkUser() { String user = Conf.getString(Const.CONF_LASTFM_USER); if (user == null || user.equals("")) { Log.debug("Don't submit to Last.fm: Empty user"); return false; } return true; } /** * Check password. * * @return true, if check password */ private boolean checkPassword() { String password = UtilString.rot13(Conf.getString(Const.CONF_LASTFM_PASSWORD)); if (StringUtils.isBlank(password)) { Log.debug("Don't submit to Last.fm: Empty password"); return false; } return true; } /** * Check artist. * * @param track * * @return true, if check artist */ private boolean checkArtist(Track track) { String sArtist = track.getArtist().getName2(); if (StringUtils.isBlank(sArtist) || sArtist.equalsIgnoreCase(Messages.getString("unknown_artist"))) { Log.debug("Don't submit to Last.fm: Unknown artist"); return false; } return true; } /** * Check title. * * @param track * * @return true, if check title */ private boolean checkTitle(Track track) { if (StringUtils.isBlank(track.getName())) { Log.debug("Don't submit to Last.fm: Unknown Title"); return false; } return true; } /** * Check duration. * * @param track * * @return true, if check duration */ private boolean checkDuration(Track track) { if (track.getDuration() < MIN_DURATION_TO_SUBMIT) { Log.debug(UtilString.concat("Don't submit to Last.fm: Lenght < ", MIN_DURATION_TO_SUBMIT)); return false; } return true; } }