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
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
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
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
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
240 }
241 }