/*
This file is part of Subsonic.
Subsonic 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 3 of the License, or
(at your option) any later version.
Subsonic 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.
You should have received a copy of the GNU General Public License
along with Subsonic. If not, see .
Copyright 2009 (C) Sindre Mehus
*/
package github.daneren2005.dsub.service;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.http.HttpResponse;
import android.content.Context;
import android.graphics.Bitmap;
import github.daneren2005.dsub.domain.Bookmark;
import github.daneren2005.dsub.domain.ChatMessage;
import github.daneren2005.dsub.domain.Genre;
import github.daneren2005.dsub.domain.Indexes;
import github.daneren2005.dsub.domain.RemoteStatus;
import github.daneren2005.dsub.domain.Lyrics;
import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.domain.MusicFolder;
import github.daneren2005.dsub.domain.Playlist;
import github.daneren2005.dsub.domain.PodcastChannel;
import github.daneren2005.dsub.domain.SearchCritera;
import github.daneren2005.dsub.domain.SearchResult;
import github.daneren2005.dsub.domain.Share;
import github.daneren2005.dsub.domain.Version;
import github.daneren2005.dsub.util.SilentBackgroundTask;
import github.daneren2005.dsub.util.ProgressListener;
import github.daneren2005.dsub.util.TimeLimitedCache;
import github.daneren2005.dsub.util.FileUtil;
import github.daneren2005.dsub.util.Util;
/**
* @author Sindre Mehus
*/
public class CachedMusicService implements MusicService {
private static final String TAG = CachedMusicService.class.getSimpleName();
private static final int MUSIC_DIR_CACHE_SIZE = 20;
private static final int TTL_MUSIC_DIR = 5 * 60; // Five minutes
private final RESTMusicService musicService;
private final TimeLimitedCache cachedLicenseValid = new TimeLimitedCache(120, TimeUnit.SECONDS);
private final TimeLimitedCache cachedIndexes = new TimeLimitedCache(60 * 60, TimeUnit.SECONDS);
private final TimeLimitedCache> cachedPlaylists = new TimeLimitedCache>(3600, TimeUnit.SECONDS);
private final TimeLimitedCache> cachedMusicFolders = new TimeLimitedCache>(10 * 3600, TimeUnit.SECONDS);
private final TimeLimitedCache> cachedPodcastChannels = new TimeLimitedCache>(10 * 3600, TimeUnit.SECONDS);
private String restUrl;
private boolean isTagBrowsing = false;
public CachedMusicService(RESTMusicService musicService) {
this.musicService = musicService;
}
@Override
public void ping(Context context, ProgressListener progressListener) throws Exception {
checkSettingsChanged(context);
musicService.ping(context, progressListener);
}
@Override
public boolean isLicenseValid(Context context, ProgressListener progressListener) throws Exception {
checkSettingsChanged(context);
Boolean result = cachedLicenseValid.get();
if (result == null) {
result = FileUtil.deserialize(context, getCacheName(context, "license"), Boolean.class);
if(result == null) {
result = musicService.isLicenseValid(context, progressListener);
// Only save a copy license is valid
if(result == true) {
FileUtil.serialize(context, (Boolean) result, getCacheName(context, "license"));
}
}
cachedLicenseValid.set(result, result ? 30L * 60L : 2L * 60L, TimeUnit.SECONDS);
}
return result;
}
@Override
public List getMusicFolders(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
checkSettingsChanged(context);
if (refresh) {
cachedMusicFolders.clear();
}
List result = cachedMusicFolders.get();
if (result == null) {
if(!refresh) {
result = FileUtil.deserialize(context, getCacheName(context, "musicFolders"), ArrayList.class);
}
if(result == null) {
result = musicService.getMusicFolders(refresh, context, progressListener);
FileUtil.serialize(context, new ArrayList(result), getCacheName(context, "musicFolders"));
}
cachedMusicFolders.set(result);
}
return result;
}
@Override
public Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
checkSettingsChanged(context);
if (refresh) {
cachedIndexes.clear();
cachedMusicFolders.clear();
}
Indexes result = cachedIndexes.get();
if (result == null) {
result = musicService.getIndexes(musicFolderId, refresh, context, progressListener);
cachedIndexes.set(result);
}
return result;
}
@Override
public MusicDirectory getMusicDirectory(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
MusicDirectory dir = null;
if(!refresh) {
dir = FileUtil.deserialize(context, getCacheName(context, "directory", id), MusicDirectory.class);
}
if(dir == null) {
dir = musicService.getMusicDirectory(id, name, refresh, context, progressListener);
FileUtil.serialize(context, dir, getCacheName(context, "directory", id));
}
return dir;
}
@Override
public MusicDirectory getArtist(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
MusicDirectory dir = null;
if(!refresh) {
dir = FileUtil.deserialize(context, getCacheName(context, "artist", id), MusicDirectory.class);
}
if(dir == null) {
dir = musicService.getArtist(id, name, refresh, context, progressListener);
FileUtil.serialize(context, dir, getCacheName(context, "artist", id));
}
return dir;
}
@Override
public MusicDirectory getAlbum(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
MusicDirectory dir = null;
if(!refresh) {
dir = FileUtil.deserialize(context, getCacheName(context, "album", id), MusicDirectory.class);
}
if(dir == null) {
dir = musicService.getAlbum(id, name, refresh, context, progressListener);
FileUtil.serialize(context, dir, getCacheName(context, "album", id));
}
return dir;
}
@Override
public SearchResult search(SearchCritera criteria, Context context, ProgressListener progressListener) throws Exception {
return musicService.search(criteria, context, progressListener);
}
@Override
public MusicDirectory getPlaylist(boolean refresh, String id, String name, Context context, ProgressListener progressListener) throws Exception {
MusicDirectory dir = null;
MusicDirectory cachedPlaylist = FileUtil.deserialize(context, getCacheName(context, "playlist", id), MusicDirectory.class);
if(!refresh) {
dir = cachedPlaylist;
}
if(dir == null) {
dir = musicService.getPlaylist(refresh, id, name, context, progressListener);
FileUtil.serialize(context, dir, getCacheName(context, "playlist", id));
File playlistFile = FileUtil.getPlaylistFile(context, Util.getServerName(context, musicService.getInstance(context)), dir.getName());
if(cachedPlaylist == null || !playlistFile.exists() || !cachedPlaylist.getChildren().equals(dir.getChildren())) {
FileUtil.writePlaylistFile(context, playlistFile, dir);
}
}
return dir;
}
@Override
public List getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
checkSettingsChanged(context);
List result = refresh ? null : cachedPlaylists.get();
if (result == null) {
if(!refresh) {
result = FileUtil.deserialize(context, getCacheName(context, "playlist"), ArrayList.class);
}
if(result == null) {
result = musicService.getPlaylists(refresh, context, progressListener);
FileUtil.serialize(context, new ArrayList(result), getCacheName(context, "playlist"));
}
cachedPlaylists.set(result);
}
return result;
}
@Override
public void createPlaylist(String id, String name, List entries, Context context, ProgressListener progressListener) throws Exception {
cachedPlaylists.clear();
Util.delete(new File(context.getCacheDir(), getCacheName(context, "playlist")));
musicService.createPlaylist(id, name, entries, context, progressListener);
}
@Override
public void deletePlaylist(String id, Context context, ProgressListener progressListener) throws Exception {
Util.delete(new File(context.getCacheDir(), getCacheName(context, "playlist")));
musicService.deletePlaylist(id, context, progressListener);
}
@Override
public void addToPlaylist(String id, List toAdd, Context context, ProgressListener progressListener) throws Exception {
Util.delete(new File(context.getCacheDir(), getCacheName(context, "playlist", id)));
musicService.addToPlaylist(id, toAdd, context, progressListener);
}
@Override
public void removeFromPlaylist(String id, List toRemove, Context context, ProgressListener progressListener) throws Exception {
Util.delete(new File(context.getCacheDir(), getCacheName(context, "playlist", id)));
musicService.removeFromPlaylist(id, toRemove, context, progressListener);
}
@Override
public void overwritePlaylist(String id, String name, int toRemove, List toAdd, Context context, ProgressListener progressListener) throws Exception {
Util.delete(new File(context.getCacheDir(), getCacheName(context, "playlist", id)));
musicService.overwritePlaylist(id, name, toRemove, toAdd, context, progressListener);
}
@Override
public void updatePlaylist(String id, String name, String comment, boolean pub, Context context, ProgressListener progressListener) throws Exception {
Util.delete(new File(context.getCacheDir(), getCacheName(context, "playlist", id)));
musicService.updatePlaylist(id, name, comment, pub, context, progressListener);
}
@Override
public Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception {
return musicService.getLyrics(artist, title, context, progressListener);
}
@Override
public void scrobble(String id, boolean submission, Context context, ProgressListener progressListener) throws Exception {
musicService.scrobble(id, submission, context, progressListener);
}
@Override
public MusicDirectory getAlbumList(String type, int size, int offset, Context context, ProgressListener progressListener) throws Exception {
return musicService.getAlbumList(type, size, offset, context, progressListener);
}
@Override
public MusicDirectory getAlbumList(String type, String extra, int size, int offset, Context context, ProgressListener progressListener) throws Exception {
return musicService.getAlbumList(type, extra, size, offset, context, progressListener);
}
@Override
public MusicDirectory getStarredList(Context context, ProgressListener progressListener) throws Exception {
return musicService.getStarredList(context, progressListener);
}
@Override
public MusicDirectory getRandomSongs(int size, String folder, String genre, String startYear, String endYear, Context context, ProgressListener progressListener) throws Exception {
return musicService.getRandomSongs(size, folder, genre, startYear, endYear, context, progressListener);
}
@Override
public String getCoverArtUrl(Context context, MusicDirectory.Entry entry) throws Exception {
return musicService.getCoverArtUrl(context, entry);
}
@Override
public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, ProgressListener progressListener) throws Exception {
return musicService.getCoverArt(context, entry, size, progressListener);
}
@Override
public HttpResponse getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception {
return musicService.getDownloadInputStream(context, song, offset, maxBitrate, task);
}
@Override
public String getMusicUrl(Context context, MusicDirectory.Entry song, int maxBitrate) throws Exception {
return musicService.getMusicUrl(context, song, maxBitrate);
}
@Override
public Version getLocalVersion(Context context) throws Exception {
return musicService.getLocalVersion(context);
}
@Override
public Version getLatestVersion(Context context, ProgressListener progressListener) throws Exception {
return musicService.getLatestVersion(context, progressListener);
}
@Override
public String getVideoUrl(int maxBitrate, Context context, String id) {
return musicService.getVideoUrl(maxBitrate, context, id);
}
@Override
public String getVideoStreamUrl(String format, int maxBitrate, Context context, String id) throws Exception {
return musicService.getVideoStreamUrl(format, maxBitrate, context, id);
}
@Override
public String getHlsUrl(String id, int bitRate, Context context) throws Exception {
return musicService.getHlsUrl(id, bitRate, context);
}
@Override
public RemoteStatus updateJukeboxPlaylist(List ids, Context context, ProgressListener progressListener) throws Exception {
return musicService.updateJukeboxPlaylist(ids, context, progressListener);
}
@Override
public RemoteStatus skipJukebox(int index, int offsetSeconds, Context context, ProgressListener progressListener) throws Exception {
return musicService.skipJukebox(index, offsetSeconds, context, progressListener);
}
@Override
public RemoteStatus stopJukebox(Context context, ProgressListener progressListener) throws Exception {
return musicService.stopJukebox(context, progressListener);
}
@Override
public RemoteStatus startJukebox(Context context, ProgressListener progressListener) throws Exception {
return musicService.startJukebox(context, progressListener);
}
@Override
public RemoteStatus getJukeboxStatus(Context context, ProgressListener progressListener) throws Exception {
return musicService.getJukeboxStatus(context, progressListener);
}
@Override
public RemoteStatus setJukeboxGain(float gain, Context context, ProgressListener progressListener) throws Exception {
return musicService.setJukeboxGain(gain, context, progressListener);
}
@Override
public void setStarred(List id, List artistId, List albumId, boolean starred, Context context, ProgressListener progressListener) throws Exception {
musicService.setStarred(id, artistId, albumId, starred, context, progressListener);
}
@Override
public List getShares(Context context, ProgressListener progressListener) throws Exception {
return musicService.getShares(context, progressListener);
}
@Override
public List createShare(List ids, String description, Long expires, Context context, ProgressListener progressListener) throws Exception {
return musicService.createShare(ids, description, expires, context, progressListener);
}
@Override
public void deleteShare(String id, Context context, ProgressListener progressListener) throws Exception {
musicService.deleteShare(id, context, progressListener);
}
@Override
public void updateShare(String id, String description, Long expires, Context context, ProgressListener progressListener) throws Exception {
musicService.updateShare(id, description, expires, context, progressListener);
}
@Override
public List getChatMessages(Long since, Context context, ProgressListener progressListener) throws Exception {
return musicService.getChatMessages(since, context, progressListener);
}
@Override
public void addChatMessage(String message, Context context, ProgressListener progressListener) throws Exception {
musicService.addChatMessage(message, context, progressListener);
}
@Override
public List getGenres(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
List result = null;
if(!refresh) {
result = FileUtil.deserialize(context, getCacheName(context, "genre"), ArrayList.class);
}
if(result == null) {
result = musicService.getGenres(refresh, context, progressListener);
FileUtil.serialize(context, new ArrayList(result), getCacheName(context, "genre"));
}
return result;
}
@Override
public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception {
return musicService.getSongsByGenre(genre, count, offset, context, progressListener);
}
@Override
public List getPodcastChannels(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
checkSettingsChanged(context);
List result = refresh ? null : cachedPodcastChannels.get();
if (result == null) {
if(!refresh) {
result = FileUtil.deserialize(context, getCacheName(context, "podcast"), ArrayList.class);
}
if(result == null) {
result = musicService.getPodcastChannels(refresh, context, progressListener);
FileUtil.serialize(context, new ArrayList(result), getCacheName(context, "podcast"));
}
cachedPodcastChannels.set(result);
}
return result;
}
@Override
public MusicDirectory getPodcastEpisodes(boolean refresh, String id, Context context, ProgressListener progressListener) throws Exception {
String altId = "p-" + id;
MusicDirectory result = null;
if(!refresh) {
result = FileUtil.deserialize(context, getCacheName(context, "directory", altId), MusicDirectory.class, 10);
}
if(result == null) {
result = musicService.getPodcastEpisodes(refresh, id, context, progressListener);
FileUtil.serialize(context, result, getCacheName(context, "directory", altId));
}
return result;
}
@Override
public void refreshPodcasts(Context context, ProgressListener progressListener) throws Exception {
musicService.refreshPodcasts(context, progressListener);
}
@Override
public void createPodcastChannel(String url, Context context, ProgressListener progressListener) throws Exception{
Util.delete(new File(context.getCacheDir(), getCacheName(context, "podcast")));
cachedPodcastChannels.clear();
musicService.createPodcastChannel(url, context, progressListener);
}
@Override
public void deletePodcastChannel(String id, Context context, ProgressListener progressListener) throws Exception{
Util.delete(new File(context.getCacheDir(), getCacheName(context, "podcast")));
cachedPodcastChannels.clear();
musicService.deletePodcastChannel(id, context, progressListener);
}
@Override
public void downloadPodcastEpisode(String id, Context context, ProgressListener progressListener) throws Exception{
musicService.downloadPodcastEpisode(id, context, progressListener);
}
@Override
public void deletePodcastEpisode(String id, Context context, ProgressListener progressListener) throws Exception{
musicService.deletePodcastEpisode(id, context, progressListener);
}
@Override
public void setRating(String id, int rating, Context context, ProgressListener progressListener) throws Exception {
musicService.setRating(id, rating, context, progressListener);
}
@Override
public List getBookmarks(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
return musicService.getBookmarks(refresh, context, progressListener);
}
@Override
public void createBookmark(String id, int position, String comment, Context context, ProgressListener progressListener) throws Exception {
musicService.createBookmark(id, position, comment, context, progressListener);
}
@Override
public void deleteBookmark(String id, Context context, ProgressListener progressListener) throws Exception {
musicService.deleteBookmark(id, context, progressListener);
}
@Override
public int processOfflineSyncs(final Context context, final ProgressListener progressListener) throws Exception{
return musicService.processOfflineSyncs(context, progressListener);
}
@Override
public void setInstance(Integer instance) throws Exception {
musicService.setInstance(instance);
}
private String getCacheName(Context context, String name, String id) {
String s = musicService.getRestUrl(context, null, false) + id;
return name + "-" + s.hashCode() + ".ser";
}
private String getCacheName(Context context, String name) {
String s = musicService.getRestUrl(context, null, false);
return name + "-" + s.hashCode() + ".ser";
}
private void checkSettingsChanged(Context context) {
String newUrl = musicService.getRestUrl(context, null, false);
boolean newIsTagBrowsing = Util.isTagBrowsing(context);
if (!Util.equals(newUrl, restUrl) || isTagBrowsing != newIsTagBrowsing) {
cachedMusicFolders.clear();
cachedLicenseValid.clear();
cachedIndexes.clear();
cachedPlaylists.clear();
cachedPodcastChannels.clear();
restUrl = newUrl;
isTagBrowsing = newIsTagBrowsing;
}
}
}