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