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.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 }