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;
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.collection.CategoryEnum;
33  import sk.baka.ambient.collection.CategoryItem;
34  import sk.baka.ambient.collection.ICollection;
35  import sk.baka.ambient.collection.TrackMetadataBean;
36  import sk.baka.ambient.collection.local.LibraryCollection;
37  import sk.baka.ambient.collection.local.MediaStoreCollection;
38  import sk.baka.ambient.commons.AbstractFileStorage;
39  import sk.baka.ambient.commons.MiscUtils;
40  import sk.baka.ambient.commons.SimpleBus;
41  import sk.baka.ambient.library.ILibraryListener;
42  import android.graphics.Bitmap;
43  import android.graphics.BitmapFactory;
44  import android.widget.ImageView;
45  
46  /***
47   * Provides covers and manages cover cache. Thread-safe. Essentially a
48   * singleton, there must be at most one instance.
49   * 
50   * @author Martin Vysny
51   */
52  public final class CoverCache extends AbstractFileStorage implements
53  		ILibraryListener {
54  
55  	/***
56  	 * Creates new cache.
57  	 */
58  	public CoverCache() {
59  		super(AmbientApplication.getInstance().getString(R.string.covers),
60  				".png", ".jpg", ".gif");
61  	}
62  
63  	/***
64  	 * If <code>true</code> then new covers will not be downloaded. If
65  	 * <code>false</code> then old covers will be purged.
66  	 */
67  	private boolean dontDownloadOnCacheFull = true;
68  
69  	/***
70  	 * Sets the cache behavior when the cache is full.
71  	 * 
72  	 * @param dontDownload
73  	 *            if <code>true</code> then new covers will not be downloaded.
74  	 *            If <code>false</code> then old covers will be purged.
75  	 */
76  	public synchronized void setCacheOverflowBehavior(final boolean dontDownload) {
77  		this.dontDownloadOnCacheFull = dontDownload;
78  	}
79  
80  	/***
81  	 * Fetches a cover. Attempts to fetch it from the internet if a cover is not
82  	 * found and we are online - in this case a background thread is run and the
83  	 * method returns <code>null</code>.
84  	 * 
85  	 * @param track
86  	 *            the track.
87  	 * @return cover or <code>null</code> if we are unable to provide any cover.
88  	 */
89  	private File getCover(final TrackMetadataBean track) {
90  		return getFile(track);
91  	}
92  
93  	@Override
94  	protected boolean isProceedWithDownload(Object fetchInfo) {
95  		removeObsoleteAlbums();
96  		final boolean proceed = !(dontDownloadOnCacheFull && isFull());
97  		if (proceed) {
98  			removeOldestCovers();
99  		}
100 		return proceed;
101 	}
102 
103 	@Override
104 	protected String[] getFilenameAndExt(URL url, Object fetchInfo) {
105 		final TrackMetadataBean track = (TrackMetadataBean) fetchInfo;
106 		if (MiscUtils.isEmptyOrWhitespace(track.getAlbum()))
107 			return null;
108 		final String ext = url == null ? null : "."
109 				+ DownloadManager.getExtension(url);
110 		final String name = track.getAlbum();
111 		return new String[] { name, ext };
112 	}
113 
114 	@Override
115 	protected void onFileDownloaded(URL url, Object fetchInfo,
116 			final boolean success) {
117 		if (!success) {
118 			return;
119 		}
120 		final SimpleBus bus = AmbientApplication.getInstance().getBus();
121 		bus.getInvocator(IContentListener.class, true).coverLoaded(
122 				(TrackMetadataBean) fetchInfo);
123 	}
124 
125 	@Override
126 	protected URL toURL(Object fetchInfo) throws IOException {
127 		final TrackMetadataBean track = (TrackMetadataBean) fetchInfo;
128 		// try to fetch from the internet
129 		final List<URL> covers = DownloadManager.getRemoteCoversList(track
130 				.getArtist()
131 				+ " " + track.getAlbum());
132 		if (covers.isEmpty())
133 			return null;
134 		if (Thread.currentThread().isInterrupted())
135 			return null;
136 		final URL cover = findSupported(covers);
137 		return cover;
138 	}
139 
140 	private URL findSupported(final List<URL> covers) {
141 		for (final URL cover : covers) {
142 			final String ext = "." + DownloadManager.getExtension(cover);
143 			if (supportsExtension(ext))
144 				return cover;
145 		}
146 		return null;
147 	}
148 
149 	/***
150 	 * Fetches a cover for a track and sets it into given view. If the cover is
151 	 * not present in the cache and we are not offline the cover is fetched in a
152 	 * worker thread and {@link IContentListener#coverLoaded(TrackMetadataBean)}
153 	 * is emitted.
154 	 * 
155 	 * @param track
156 	 *            the track.
157 	 * @param view
158 	 *            set the image to this view.
159 	 * @return loaded bitmap instance. The bitmap should be recycled when not
160 	 *         needed.
161 	 */
162 	public Bitmap setCover(final TrackMetadataBean track, final ImageView view) {
163 		final File file = getCover(track);
164 		if (file == null) {
165 			view.setImageResource(R.drawable.cover);
166 			return null;
167 		} else {
168 			final Bitmap bitmap = BitmapFactory.decodeFile(file
169 					.getAbsolutePath());
170 			view.setImageBitmap(bitmap);
171 			return bitmap;
172 		}
173 	}
174 
175 	@Override
176 	public void cleanup() {
177 		removeObsoleteAlbums();
178 		removeOldestCovers();
179 	}
180 
181 	private synchronized void removeOldestCovers() {
182 		if (!isFull())
183 			return;
184 		final List<File> files = new ArrayList<File>(theCache.values());
185 		// sort - oldest files first
186 		Collections.sort(files, new Comparator<File>() {
187 			public int compare(File object1, File object2) {
188 				if (object1.lastModified() < object2.lastModified()) {
189 					return -1;
190 				}
191 				if (object1.lastModified() > object2.lastModified()) {
192 					return 1;
193 				}
194 				return 0;
195 			}
196 		});
197 		for (final File coverFile : files) {
198 			removeFile(coverFile);
199 			if (!isFull()) {
200 				return;
201 			}
202 		}
203 	}
204 
205 	private synchronized void removeObsoleteAlbums() {
206 		if (!isFull()) {
207 			return;
208 		}
209 		if (!libraryUpdated) {
210 			return;
211 		}
212 		libraryUpdated = false;
213 		final Set<String> obsoleteCovers = new HashSet<String>(theCache
214 				.keySet());
215 		removeKnownAlbums(obsoleteCovers, new LibraryCollection(AmbientApplication
216 				.getInstance().getLibrary()));
217 		removeKnownAlbums(obsoleteCovers, new MediaStoreCollection(
218 				AmbientApplication.getInstance().getContentResolver()));
219 		// remove all obsolete covers
220 		for (final String album : obsoleteCovers) {
221 			final File coverFile = getCacheFile(album);
222 			removeFile(coverFile);
223 		}
224 	}
225 
226 	private void removeKnownAlbums(Set<String> albums, ICollection c) {
227 		try {
228 			for (final CategoryItem item : c.getCategoryList(
229 					CategoryEnum.Album, null, null, false)) {
230 				albums.remove(item.name);
231 			}
232 		} catch (Exception e) {
233 			throw new RuntimeException(e);
234 		}
235 	}
236 	
237 	private volatile boolean libraryUpdated = false;
238 
239 	public void libraryUpdate(boolean updateStarted, boolean interrupted,
240 			boolean userNotified) {
241 		if (!updateStarted) {
242 			libraryUpdated = true;
243 		}
244 	}
245 }