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.FileNotFoundException; 22 import java.util.HashSet; 23 import java.util.Set; 24 import java.util.StringTokenizer; 25 26 import junit.framework.Assert; 27 import sk.baka.ambient.AmbientApplication; 28 import sk.baka.ambient.R; 29 import sk.baka.ambient.collection.Statistics; 30 import sk.baka.ambient.collection.TrackOriginEnum; 31 import android.content.Context; 32 import android.database.Cursor; 33 34 /*** 35 * The music library. Emits {@link ILibraryListener} messages on events. 36 * 37 * @author Martin Vysny 38 */ 39 public final class Library { 40 /*** 41 * The database back-end. 42 */ 43 final DBStrategy backend; 44 45 /*** 46 * Creates new library. There should be at most one library instance. 47 * 48 * @param ctx 49 * the context. 50 * @throws FileNotFoundException 51 * if database fails to initialize 52 */ 53 public Library(Context ctx) throws FileNotFoundException { 54 super(); 55 backend = new DBStrategy(ctx); 56 } 57 58 /*** 59 * List of watched directories. One directory should not be a subdirectory 60 * of another. 61 */ 62 public Set<String> watchedDirectories = new HashSet<String>(); 63 64 /*** 65 * Parses given directory list and sets {@link #watchedDirectories} 66 * accordingly. Does not rescan the collection. 67 * 68 * @param directoryList 69 * the list of directories, separated with ':' 70 */ 71 public void setWatchedDirectories(final String directoryList) { 72 watchedDirectories.clear(); 73 final StringTokenizer tokenizer = new StringTokenizer(directoryList, 74 ":"); 75 for (; tokenizer.hasMoreTokens();) { 76 watchedDirectories.add(tokenizer.nextToken()); 77 } 78 } 79 80 /*** 81 * Returns the {@link #watchedDirectories} set as a list of files. 82 * 83 * @return the list of watched directories. 84 */ 85 public File[] getWatchedDirectories() { 86 final File[] result = new File[watchedDirectories.size()]; 87 int i = 0; 88 for (final String dir : watchedDirectories) { 89 result[i++] = new File(dir); 90 } 91 return result; 92 } 93 94 /*** 95 * Rescans desired storage for music files and adds them to the library. 96 * Asynchronous operation. Does nothing when the rescan is already running. 97 * 98 * @param storage 99 * the storage type. 100 */ 101 public void queueScanner(final TrackOriginEnum storage) { 102 if (closing) 103 return; 104 final IFileScanner scanner; 105 switch (storage) { 106 case Magnatune: 107 scanner = new MagnatuneScanner(); 108 break; 109 default: 110 throw new IllegalArgumentException("Cannot rescan " + storage); 111 } 112 final String caption = AmbientApplication.getInstance().getString( 113 R.string.magnatune_rescanning); 114 AmbientApplication.getInstance().getBackgroundTasks().schedule( 115 new Scanner(scanner), scanner.getClass(), storage.online, 116 caption); 117 } 118 119 /*** 120 * Scanner runnable. 121 * 122 * @author Martin Vysny 123 */ 124 private class Scanner implements Runnable { 125 private final IFileScanner scanner; 126 127 /*** 128 * Creates new scanner. 129 * @param scanner 130 */ 131 public Scanner(final IFileScanner scanner) { 132 this.scanner = scanner; 133 } 134 135 public void run() { 136 final ILibraryListener invocator = AmbientApplication.getInstance() 137 .getBus().getInvocator(ILibraryListener.class, true); 138 invocator.libraryUpdate(true, false, false); 139 boolean userNotified = false; 140 try { 141 synchronized (Scanner.class) { 142 if (Thread.currentThread().isInterrupted()) 143 return; 144 scanner.init(Library.this, new GenreCache(backend)); 145 if (Thread.currentThread().isInterrupted()) 146 return; 147 scanner.run(); 148 userNotified = scanner.isUserNotified(); 149 } 150 } finally { 151 invocator.libraryUpdate(false, Thread.currentThread() 152 .isInterrupted(), userNotified); 153 } 154 } 155 } 156 157 /*** 158 * Returns database backend object. Useful for selections. 159 * 160 * @return db backend object. 161 */ 162 public DBStrategy getBackend() { 163 return backend; 164 } 165 166 private volatile boolean closing = false; 167 168 /*** 169 * Closes this instance of the library. 170 */ 171 public void close() { 172 closing = true; 173 backend.close(); 174 } 175 176 @Override 177 protected void finalize() throws Throwable { 178 try { 179 close(); 180 } finally { 181 super.finalize(); 182 } 183 } 184 185 /*** 186 * Returns statistics for storage for given origin. 187 * 188 * @param origin 189 * the track origin 190 * @return statistics. 191 */ 192 public Statistics getStatistics(final TrackOriginEnum origin) { 193 final Statistics result = new Statistics(); 194 { 195 final Cursor c = backend.getDb().rawQuery( 196 "select count(*), sum(length), sum(fileSize)" 197 + " from tracks where origin=?", 198 new String[] { String.valueOf(origin.ordinal()) }); 199 Assert.assertTrue(c.moveToFirst()); 200 result.tracks = c.getInt(0); 201 result.length = c.getInt(1); 202 result.fileSize = c.getLong(2); 203 c.close(); 204 } 205 { 206 final Cursor c = backend 207 .getDb() 208 .rawQuery( 209 "select count(*) from " 210 + "(select distinct artist from tracks where origin=?)", 211 new String[] { String.valueOf(origin.ordinal()) }); 212 Assert.assertTrue(c.moveToFirst()); 213 result.artists = c.getInt(0); 214 c.close(); 215 } 216 { 217 final Cursor c = backend 218 .getDb() 219 .rawQuery( 220 "select count(*) from " 221 + "(select distinct album from tracks where origin=?)", 222 new String[] { String.valueOf(origin.ordinal()) }); 223 Assert.assertTrue(c.moveToFirst()); 224 result.albums = c.getInt(0); 225 c.close(); 226 } 227 return result; 228 } 229 230 /*** 231 * Returns overall genre count. 232 * 233 * @return known genre count. 234 */ 235 public int getGenreCount() { 236 final Cursor c = backend.getDb().rawQuery( 237 "select count(*) from genres", null); 238 Assert.assertTrue(c.moveToFirst()); 239 final int result = c.getInt(0); 240 c.close(); 241 return result; 242 } 243 }