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