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 }