View Javadoc

1   /***
2    *     Ambient - A music player for the Android platform
3    Copyright (C) 2007 Martin Vysny
4    
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9    
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14  
15   You should have received a copy of the GNU General Public License
16   along with this program.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  package sk.baka.ambient.library;
19  
20  import java.io.File;
21  import java.io.IOException;
22  import java.net.URL;
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.Comparator;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Set;
29  
30  import org.jajuk.util.DownloadManager;
31  
32  import sk.baka.ambient.AmbientApplication;
33  import sk.baka.ambient.R;
34  import sk.baka.ambient.collection.CategoryEnum;
35  import sk.baka.ambient.collection.TrackMetadataBean;
36  import sk.baka.ambient.commons.AbstractFileStorage;
37  import sk.baka.ambient.commons.MiscUtils;
38  import sk.baka.ambient.commons.SimpleBus;
39  import sk.baka.ambient.lrc.lyrdb.LyrdbTrack;
40  import android.database.Cursor;
41  import android.graphics.Bitmap;
42  import android.graphics.BitmapFactory;
43  import android.widget.ImageView;
44  
45  /***
46   * Provides covers and manages cover cache. Thread-safe. Essentially a
47   * singleton, there must be at most one instance.
48   * 
49   * @author Martin Vysny
50   */
51  public final class CoverCache extends AbstractFileStorage implements
52  		ILibraryListener {
53  	private final Library library;
54  
55  	/***
56  	 * Creates new cache.
57  	 */
58  	public CoverCache() {
59  		super(AmbientApplication.getInstance().getString(R.string.covers),
60  				".png", ".jpg", ".gif");
61  		this.library = AmbientApplication.getInstance().getLibrary();
62  	}
63  
64  	/***
65  	 * If <code>true</code> then new covers will not be downloaded. If
66  	 * <code>false</code> then old covers will be purged.
67  	 */
68  	private boolean dontDownloadOnCacheFull = true;
69  
70  	/***
71  	 * Sets the cache behavior when the cache is full.
72  	 * 
73  	 * @param dontDownload
74  	 *            if <code>true</code> then new covers will not be downloaded.
75  	 *            If <code>false</code> then old covers will be purged.
76  	 */
77  	public synchronized void setCacheOverflowBehavior(final boolean dontDownload) {
78  		this.dontDownloadOnCacheFull = dontDownload;
79  	}
80  
81  	/***
82  	 * Fetches a cover. Attempts to fetch it from the internet if a cover is not
83  	 * found and we are online - in this case a background thread is run and the
84  	 * method returns <code>null</code>.
85  	 * 
86  	 * @param track
87  	 *            the track.
88  	 * @return cover or <code>null</code> if we are unable to provide any cover.
89  	 */
90  	private File getCover(final TrackMetadataBean track) {
91  		return getFile(track);
92  	}
93  
94  	@Override
95  	protected boolean isProceedWithDownload(Object fetchInfo) {
96  		removeObsoleteAlbums();
97  		final boolean proceed = !(dontDownloadOnCacheFull && isFull());
98  		if (proceed) {
99  			removeOldestCovers();
100 		}
101 		return proceed;
102 	}
103 
104 	@Override
105 	protected String[] getFilenameAndExt(URL url, Object fetchInfo) {
106 		final TrackMetadataBean track = (TrackMetadataBean) fetchInfo;
107 		if (MiscUtils.isEmptyOrWhitespace(track.getAlbum()))
108 			return null;
109 		final String ext = url == null ? null : "."
110 				+ DownloadManager.getExtension(url);
111 		final String name = track.getAlbum();
112 		return new String[] { name, ext };
113 	}
114 
115 	@Override
116 	protected void onFileDownloaded(URL url, Object fetchInfo,
117 			final boolean success) {
118 		if (!success) {
119 			return;
120 		}
121 		final SimpleBus bus = AmbientApplication.getInstance().getBus();
122 		bus.getInvocator(ILibraryListener.class, true).coverLoaded(
123 				(TrackMetadataBean) fetchInfo);
124 	}
125 
126 	@Override
127 	protected URL toURL(Object fetchInfo) throws IOException {
128 		final TrackMetadataBean track = (TrackMetadataBean) fetchInfo;
129 		// try to fetch from the internet
130 		final List<URL> covers = DownloadManager.getRemoteCoversList(track
131 				.getArtist()
132 				+ " " + track.getAlbum());
133 		if (covers.isEmpty())
134 			return null;
135 		if (Thread.currentThread().isInterrupted())
136 			return null;
137 		final URL cover = findSupported(covers);
138 		return cover;
139 	}
140 
141 	private URL findSupported(final List<URL> covers) {
142 		for (final URL cover : covers) {
143 			final String ext = "." + DownloadManager.getExtension(cover);
144 			if (supportsExtension(ext))
145 				return cover;
146 		}
147 		return null;
148 	}
149 
150 	/***
151 	 * Fetches a cover for a track and sets it into given view. If the cover is
152 	 * not present in the cache and we are not offline the cover is fetched in a
153 	 * worker thread and {@link ILibraryListener#coverLoaded(TrackMetadataBean)}
154 	 * is emitted.
155 	 * 
156 	 * @param track
157 	 *            the track.
158 	 * @param view
159 	 *            set the image to this view.
160 	 * @return loaded bitmap instance. The bitmap should be recycled when not
161 	 *         needed.
162 	 */
163 	public Bitmap setCover(final TrackMetadataBean track, final ImageView view) {
164 		final File file = getCover(track);
165 		if (file == null) {
166 			view.setImageResource(R.drawable.cover);
167 			return null;
168 		} else {
169 			final Bitmap bitmap = BitmapFactory.decodeFile(file
170 					.getAbsolutePath());
171 			view.setImageBitmap(bitmap);
172 			return bitmap;
173 		}
174 	}
175 
176 	@Override
177 	public void cleanup() {
178 		removeObsoleteAlbums();
179 		removeOldestCovers();
180 	}
181 
182 	private synchronized void removeOldestCovers() {
183 		if (!isFull())
184 			return;
185 		final List<File> files = new ArrayList<File>(theCache.values());
186 		// sort - oldest files first
187 		Collections.sort(files, new Comparator<File>() {
188 			public int compare(File object1, File object2) {
189 				if (object1.lastModified() < object2.lastModified())
190 					return -1;
191 				if (object1.lastModified() > object2.lastModified())
192 					return 1;
193 				return 0;
194 			}
195 		});
196 		for (final File coverFile : files) {
197 			removeFile(coverFile);
198 			if (!isFull())
199 				return;
200 		}
201 	}
202 
203 	private synchronized void removeObsoleteAlbums() {
204 		if (!isFull())
205 			return;
206 		if (!libraryUpdated)
207 			return;
208 		libraryUpdated = false;
209 		final Set<String> obsoleteCovers = new HashSet<String>(theCache
210 				.keySet());
211 		final Cursor c = library.getBackend().getCriteriaList(
212 				new CategoryEnum[] { CategoryEnum.Album }, null);
213 		while (c.moveToNext()) {
214 			final String album = c.getString(0);
215 			obsoleteCovers.remove(album);
216 		}
217 		c.close();
218 		// remove all obsolete covers
219 		for (final String album : obsoleteCovers) {
220 			final File coverFile = getCacheFile(album);
221 			removeFile(coverFile);
222 		}
223 	}
224 
225 	public void coverLoaded(TrackMetadataBean track) {
226 		// do nothing
227 	}
228 
229 	private volatile boolean libraryUpdated = false;
230 
231 	public void libraryUpdate(boolean updateStarted, boolean interrupted,
232 			boolean userNotified) {
233 		if (!updateStarted) {
234 			libraryUpdated = true;
235 		}
236 	}
237 
238 	public void lyricsLoaded(TrackMetadataBean track, List<LyrdbTrack> lyrics) {
239 		// do nothing
240 	}
241 }