1 package org.coolreader.db;
2 
3 import android.app.Service;
4 import android.content.Intent;
5 import android.os.Binder;
6 import android.os.Environment;
7 import android.os.Handler;
8 import android.os.IBinder;
9 import android.util.Log;
10 
11 import org.coolreader.crengine.BookInfo;
12 import org.coolreader.crengine.Bookmark;
13 import org.coolreader.crengine.DeviceInfo;
14 import org.coolreader.crengine.FileInfo;
15 import org.coolreader.crengine.L;
16 import org.coolreader.crengine.Logger;
17 import org.coolreader.crengine.MountPathCorrector;
18 import org.coolreader.crengine.Utils;
19 
20 import java.io.File;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 
24 public class CRDBService extends Service {
25 	public static final Logger log = L.create("db");
26 	public static final Logger vlog = L.create("db", Log.ASSERT);
27 
28     private MainDB mainDB = new MainDB();
29     private CoverDB coverDB = new CoverDB();
30 
31     @Override
onCreate()32     public void onCreate() {
33     	log.i("onCreate()");
34     	mThread = new ServiceThread("crdb");
35     	mThread.start();
36     	execTask(new OpenDatabaseTask());
37     }
38 
reopenDatabase()39     public void reopenDatabase() {
40 		execTask(new ReOpenDatabaseTask());
41 	}
42 
43     @Override
onStartCommand(Intent intent, int flags, int startId)44     public int onStartCommand(Intent intent, int flags, int startId) {
45         log.i("Received start id " + startId + ": " + intent);
46         // We want this service to continue running until it is explicitly
47         // stopped, so return sticky.
48         return START_STICKY;
49     }
50 
51     @Override
onDestroy()52     public void onDestroy() {
53     	log.i("onDestroy()");
54     	execTask(new CloseDatabaseTask());
55     	mThread.stop(5000);
56     }
57 
getDatabaseDir()58     private File getDatabaseDir() {
59     	//File storage = Environment.getExternalStorageDirectory();
60     	File storage = DeviceInfo.EINK_NOOK ? new File("/media/") : Environment.getExternalStorageDirectory();
61     	File cr3dir = new File(storage, ".cr3");
62     	if (cr3dir.isDirectory())
63     		cr3dir.mkdirs();
64     	if (!cr3dir.isDirectory() || !cr3dir.canWrite()) {
65 	    	log.w("Cannot use " + cr3dir + " for writing database, will use data directory instead");
66 	    	log.w("getFilesDir=" + getFilesDir() + " getDataDirectory=" + Environment.getDataDirectory());
67     		cr3dir = getFilesDir(); //Environment.getDataDirectory();
68     	}
69     	log.i("DB directory: " + cr3dir);
70     	return cr3dir;
71     }
72 
73     private class OpenDatabaseTask extends Task {
OpenDatabaseTask()74     	public OpenDatabaseTask() {
75     		super("OpenDatabaseTask");
76     	}
77 
78 		@Override
work()79 		public void work() {
80 	    	open();
81 		}
82 
open()83 		private boolean open() {
84 	    	File dir = getDatabaseDir();
85 	    	boolean res = mainDB.open(dir);
86 	    	res = coverDB.open(dir) && res;
87 	    	if (!res) {
88 	    		mainDB.close();
89 	    		coverDB.close();
90 	    	}
91 	    	return res;
92 	    }
93 
94     }
95 
96     private class CloseDatabaseTask extends Task {
CloseDatabaseTask()97     	public CloseDatabaseTask() {
98     		super("CloseDatabaseTask");
99     	}
100 
101     	@Override
work()102 		public void work() {
103 	    	close();
104 		}
105 
close()106 		private void close() {
107 			clearCaches();
108     		mainDB.close();
109     		coverDB.close();
110 	    }
111     }
112 
113 	private class ReOpenDatabaseTask extends Task {
ReOpenDatabaseTask()114 		public ReOpenDatabaseTask() {
115 			super("ReOpenDatabaseTask");
116 		}
117 
118 		@Override
work()119 		public void work() {
120 			close();
121 			open();
122 		}
123 
open()124 		private boolean open() {
125 			File dir = getDatabaseDir();
126 			boolean res = mainDB.open(dir);
127 			res = coverDB.open(dir) && res;
128 			if (!res) {
129 				mainDB.close();
130 				coverDB.close();
131 			}
132 			return res;
133 		}
134 
close()135 		private void close() {
136 			clearCaches();
137 			mainDB.close();
138 			coverDB.close();
139 		}
140 	}
141 
142 	private FlushDatabaseTask lastFlushTask;
143     private class FlushDatabaseTask extends Task {
144     	private boolean force;
FlushDatabaseTask(boolean force)145     	public FlushDatabaseTask(boolean force) {
146     		super("FlushDatabaseTask");
147     		this.force = force;
148     		lastFlushTask = this;
149     	}
150 		@Override
work()151 		public void work() {
152 			long elapsed = Utils.timeInterval(lastFlushTime);
153 			if (force || (lastFlushTask == this && elapsed > MIN_FLUSH_INTERVAL)) {
154 		    	mainDB.flush();
155 		    	coverDB.flush();
156 		    	if (!force)
157 		    		lastFlushTime = Utils.timeStamp();
158 			}
159 		}
160     }
161 
clearCaches()162     private void clearCaches() {
163 		mainDB.clearCaches();
164 		coverDB.clearCaches();
165     }
166 
167     private static final long MIN_FLUSH_INTERVAL = 30000; // 30 seconds
168     private long lastFlushTime;
169 
170     /**
171      * Schedule flush.
172      */
flush()173     private void flush() {
174    		execTask(new FlushDatabaseTask(false), MIN_FLUSH_INTERVAL);
175     }
176 
177     /**
178      * Flush ASAP.
179      */
forceFlush()180     private void forceFlush() {
181    		execTask(new FlushDatabaseTask(true));
182     }
183 
184     public static class FileInfoCache {
185     	private ArrayList<FileInfo> list = new ArrayList<>();
add(FileInfo item)186     	public void add(FileInfo item) {
187     		list.add(item);
188     	}
clear()189     	public void clear() {
190     		list.clear();
191     	}
192     }
193 
194 	public interface SearchHistoryLoadingCallback {
onSearchHistoryLoaded(ArrayList<String> searches)195 		void onSearchHistoryLoaded(ArrayList<String> searches);
196 	}
197 	//=======================================================================================
198     // OPDS catalogs access code
199     //=======================================================================================
200     public interface OPDSCatalogsLoadingCallback {
onOPDSCatalogsLoaded(ArrayList<FileInfo> catalogs)201     	void onOPDSCatalogsLoaded(ArrayList<FileInfo> catalogs);
202     }
203 
saveOPDSCatalog(final Long id, final String url, final String name, final String username, final String password)204 	public void saveOPDSCatalog(final Long id, final String url, final String name, final String username, final String password) {
205 		execTask(new Task("saveOPDSCatalog") {
206 			@Override
207 			public void work() {
208 				mainDB.saveOPDSCatalog(id, url, name, username, password);
209 			}
210 		});
211 	}
212 
saveSearchHistory(final BookInfo book, final String sHist)213 	public void saveSearchHistory(final BookInfo book, final String sHist) {
214 		execTask(new Task("saveSearchHistory") {
215 			@Override
216 			public void work() {
217 				mainDB.saveSearchHistory(book, sHist);
218 			}
219 		});
220 	}
221 
updateOPDSCatalogLastUsage(final String url)222 	public void updateOPDSCatalogLastUsage(final String url) {
223 		execTask(new Task("saveOPDSCatalog") {
224 			@Override
225 			public void work() {
226 				mainDB.updateOPDSCatalogLastUsage(url);
227 			}
228 		});
229 	}
230 
loadOPDSCatalogs(final OPDSCatalogsLoadingCallback callback, final Handler handler)231 	public void loadOPDSCatalogs(final OPDSCatalogsLoadingCallback callback, final Handler handler) {
232 		execTask(new Task("loadOPDSCatalogs") {
233 			@Override
234 			public void work() {
235 				final ArrayList<FileInfo> list = new ArrayList<>();
236 				mainDB.loadOPDSCatalogs(list);
237 				sendTask(handler, () -> callback.onOPDSCatalogsLoaded(list));
238 			}
239 		});
240 	}
241 
loadSearchHistory(final BookInfo book, final SearchHistoryLoadingCallback callback, final Handler handler)242 	public void loadSearchHistory(final BookInfo book, final SearchHistoryLoadingCallback callback, final Handler handler) {
243 		execTask(new Task("loadSearchHistory") {
244 			@Override
245 			public void work() {
246 				final ArrayList<String> list = mainDB.loadSearchHistory(book);
247 				sendTask(handler, () -> callback.onSearchHistoryLoaded(list));
248 			}
249 		});
250 	}
251 
removeOPDSCatalog(final Long id)252 	public void removeOPDSCatalog(final Long id) {
253 		execTask(new Task("removeOPDSCatalog") {
254 			@Override
255 			public void work() {
256 				mainDB.removeOPDSCatalog(id);
257 			}
258 		});
259 	}
260 
261 	//=======================================================================================
262     // coverpage DB access code
263     //=======================================================================================
264     public interface CoverpageLoadingCallback {
onCoverpageLoaded(FileInfo fileInfo, byte[] data)265     	void onCoverpageLoaded(FileInfo fileInfo, byte[] data);
266     }
267 
saveBookCoverpage(final FileInfo fileInfo, final byte[] data)268 	public void saveBookCoverpage(final FileInfo fileInfo, final byte[] data) {
269 		if (data == null)
270 			return;
271 		execTask(new Task("saveBookCoverpage") {
272 			@Override
273 			public void work() {
274 				coverDB.saveBookCoverpage(fileInfo.getPathName(), data);
275 			}
276 		});
277 		flush();
278 	}
279 
loadBookCoverpage(final FileInfo fileInfo, final CoverpageLoadingCallback callback, final Handler handler)280 	public void loadBookCoverpage(final FileInfo fileInfo, final CoverpageLoadingCallback callback, final Handler handler)
281 	{
282 		execTask(new Task("loadBookCoverpage") {
283 			@Override
284 			public void work() {
285 				final byte[] data = coverDB.loadBookCoverpage(fileInfo.getPathName());
286 				sendTask(handler, () -> callback.onCoverpageLoaded(fileInfo, data));
287 			}
288 		});
289 	}
290 
deleteCoverpage(final String bookId)291 	public void deleteCoverpage(final String bookId) {
292 		execTask(new Task("deleteCoverpage") {
293 			@Override
294 			public void work() {
295 				coverDB.deleteCoverpage(bookId);
296 			}
297 		});
298 		flush();
299 	}
300 
301 	//=======================================================================================
302     // Item groups access code
303     //=======================================================================================
304     public interface ItemGroupsLoadingCallback {
onItemGroupsLoaded(FileInfo parent)305     	void onItemGroupsLoaded(FileInfo parent);
306     }
307 
308     public interface FileInfoLoadingCallback {
onFileInfoListLoaded(ArrayList<FileInfo> list)309     	void onFileInfoListLoaded(ArrayList<FileInfo> list);
310     }
311 
312     public interface RecentBooksLoadingCallback {
onRecentBooksListLoaded(ArrayList<BookInfo> bookList)313     	void onRecentBooksListLoaded(ArrayList<BookInfo> bookList);
314     }
315 
316     public interface BookInfoLoadingCallback {
onBooksInfoLoaded(BookInfo bookInfo)317     	void onBooksInfoLoaded(BookInfo bookInfo);
318     }
319 
320     public interface BookSearchCallback {
onBooksFound(ArrayList<FileInfo> fileList)321     	void onBooksFound(ArrayList<FileInfo> fileList);
322     }
323 
loadGenresList(FileInfo parent, boolean showEmptyGenres, final ItemGroupsLoadingCallback callback, final Handler handler)324 	public void loadGenresList(FileInfo parent, boolean showEmptyGenres, final ItemGroupsLoadingCallback callback, final Handler handler) {
325 		final FileInfo p = new FileInfo(parent);
326 		execTask(new Task("loadGenresList") {
327 			@Override
328 			public void work() {
329 				mainDB.loadGenresList(p, showEmptyGenres);
330 				sendTask(handler, () -> callback.onItemGroupsLoaded(p));
331 			}
332 		});
333 	}
334 
loadAuthorsList(FileInfo parent, final ItemGroupsLoadingCallback callback, final Handler handler)335 	public void loadAuthorsList(FileInfo parent, final ItemGroupsLoadingCallback callback, final Handler handler) {
336 		final FileInfo p = new FileInfo(parent);
337 		execTask(new Task("loadAuthorsList") {
338 			@Override
339 			public void work() {
340 				mainDB.loadAuthorsList(p);
341 				sendTask(handler, () -> callback.onItemGroupsLoaded(p));
342 			}
343 		});
344 	}
345 
loadSeriesList(FileInfo parent, final ItemGroupsLoadingCallback callback, final Handler handler)346 	public void loadSeriesList(FileInfo parent, final ItemGroupsLoadingCallback callback, final Handler handler) {
347 		final FileInfo p = new FileInfo(parent);
348 		execTask(new Task("loadSeriesList") {
349 			@Override
350 			public void work() {
351 				mainDB.loadSeriesList(p);
352 				sendTask(handler, () -> callback.onItemGroupsLoaded(p));
353 			}
354 		});
355 	}
356 
loadTitleList(FileInfo parent, final ItemGroupsLoadingCallback callback, final Handler handler)357 	public void loadTitleList(FileInfo parent, final ItemGroupsLoadingCallback callback, final Handler handler) {
358 		final FileInfo p = new FileInfo(parent);
359 		execTask(new Task("loadTitleList") {
360 			@Override
361 			public void work() {
362 				mainDB.loadTitleList(p);
363 				sendTask(handler, () -> callback.onItemGroupsLoaded(p));
364 			}
365 		});
366 	}
367 
findGenresBooks(final String genreCode, boolean showEmptyGenres, final FileInfoLoadingCallback callback, final Handler handler)368 	public void findGenresBooks(final String genreCode, boolean showEmptyGenres, final FileInfoLoadingCallback callback, final Handler handler) {
369 		execTask(new Task("findGenresBooks") {
370 			@Override
371 			public void work() {
372 				final ArrayList<FileInfo> list = mainDB.findByGenre(genreCode, showEmptyGenres);
373 				sendTask(handler, () -> callback.onFileInfoListLoaded(list));
374 			}
375 		});
376 	}
377 
findAuthorBooks(final long authorId, final FileInfoLoadingCallback callback, final Handler handler)378 	public void findAuthorBooks(final long authorId, final FileInfoLoadingCallback callback, final Handler handler) {
379 		execTask(new Task("findAuthorBooks") {
380 			@Override
381 			public void work() {
382 				final ArrayList<FileInfo> list = new ArrayList<>();
383 				mainDB.findAuthorBooks(list, authorId);
384 				sendTask(handler, () -> callback.onFileInfoListLoaded(list));
385 			}
386 		});
387 	}
388 
findSeriesBooks(final long seriesId, final FileInfoLoadingCallback callback, final Handler handler)389 	public void findSeriesBooks(final long seriesId, final FileInfoLoadingCallback callback, final Handler handler) {
390 		execTask(new Task("findSeriesBooks") {
391 			@Override
392 			public void work() {
393 				final ArrayList<FileInfo> list = new ArrayList<>();
394 				mainDB.findSeriesBooks(list, seriesId);
395 				sendTask(handler, () -> callback.onFileInfoListLoaded(list));
396 			}
397 		});
398 	}
399 
findBooksByRating(final int minRate, final int maxRate, final FileInfoLoadingCallback callback, final Handler handler)400 	public void findBooksByRating(final int minRate, final int maxRate, final FileInfoLoadingCallback callback, final Handler handler) {
401 		execTask(new Task("findBooksByRating") {
402 			@Override
403 			public void work() {
404 				final ArrayList<FileInfo> list = new ArrayList<>();
405 				mainDB.findBooksByRating(list, minRate, maxRate);
406 				sendTask(handler, () -> callback.onFileInfoListLoaded(list));
407 			}
408 		});
409 	}
410 
findBooksByState(final int state, final FileInfoLoadingCallback callback, final Handler handler)411 	public void findBooksByState(final int state, final FileInfoLoadingCallback callback, final Handler handler) {
412 		execTask(new Task("findBooksByState") {
413 			@Override
414 			public void work() {
415 				final ArrayList<FileInfo> list = new ArrayList<>();
416 				mainDB.findBooksByState(list, state);
417 				sendTask(handler, () -> callback.onFileInfoListLoaded(list));
418 			}
419 		});
420 	}
421 
loadRecentBooks(final int maxCount, final RecentBooksLoadingCallback callback, final Handler handler)422 	public void loadRecentBooks(final int maxCount, final RecentBooksLoadingCallback callback, final Handler handler) {
423 		execTask(new Task("loadRecentBooks") {
424 			@Override
425 			public void work() {
426 				final ArrayList<BookInfo> list = mainDB.loadRecentBooks(maxCount);
427 				sendTask(handler, () -> callback.onRecentBooksListLoaded(list));
428 			}
429 		});
430 	}
431 
sync(final Runnable callback, final Handler handler)432 	public void sync(final Runnable callback, final Handler handler) {
433 		execTask(new Task("sync") {
434 			@Override
435 			public void work() {
436 				sendTask(handler, callback);
437 			}
438 		});
439 	}
440 
findByPatterns(final int maxCount, final String authors, final String title, final String series, final String filename, final BookSearchCallback callback, final Handler handler)441 	public void findByPatterns(final int maxCount, final String authors, final String title, final String series, final String filename, final BookSearchCallback callback, final Handler handler) {
442 		execTask(new Task("findByPatterns") {
443 			@Override
444 			public void work() {
445 				final ArrayList<FileInfo> list = mainDB.findByPatterns(maxCount, authors, title, series, filename);
446 				sendTask(handler, () -> callback.onBooksFound(list));
447 			}
448 		});
449 	}
450 
findByFingerprints(final int maxCount, Collection<String> fingerprints, final BookSearchCallback callback, final Handler handler)451 	public void findByFingerprints(final int maxCount, Collection<String> fingerprints, final BookSearchCallback callback, final Handler handler) {
452 		execTask(new Task("findByFingerprint") {
453 			@Override
454 			public void work() {
455 				final ArrayList<FileInfo> list = mainDB.findByFingerprints(maxCount, fingerprints);
456 				sendTask(handler, () -> callback.onBooksFound(list));
457 			}
458 		});
459 	}
460 
deepCopyFileInfos(final Collection<FileInfo> src)461 	private ArrayList<FileInfo> deepCopyFileInfos(final Collection<FileInfo> src) {
462 		final ArrayList<FileInfo> list = new ArrayList<FileInfo>(src.size());
463 		for (FileInfo fi : src)
464 			list.add(new FileInfo(fi));
465 		return list;
466 	}
467 
saveFileInfos(final Collection<FileInfo> list)468 	public void saveFileInfos(final Collection<FileInfo> list) {
469 		execTask(new Task("saveFileInfos") {
470 			@Override
471 			public void work() {
472 				mainDB.saveFileInfos(list);
473 			}
474 		});
475 		flush();
476 	}
477 
loadBookInfo(final FileInfo fileInfo, final BookInfoLoadingCallback callback, final Handler handler)478 	public void loadBookInfo(final FileInfo fileInfo, final BookInfoLoadingCallback callback, final Handler handler) {
479 		execTask(new Task("loadBookInfo") {
480 			@Override
481 			public void work() {
482 				final BookInfo bookInfo = mainDB.loadBookInfo(fileInfo);
483 				sendTask(handler, () -> callback.onBooksInfoLoaded(bookInfo));
484 			}
485 		});
486 	}
487 
loadFileInfos(final ArrayList<String> pathNames, final FileInfoLoadingCallback callback, final Handler handler)488 	public void loadFileInfos(final ArrayList<String> pathNames, final FileInfoLoadingCallback callback, final Handler handler) {
489 		execTask(new Task("loadFileInfos") {
490 			@Override
491 			public void work() {
492 				final ArrayList<FileInfo> list = mainDB.loadFileInfos(pathNames);
493 				sendTask(handler, () -> callback.onFileInfoListLoaded(list));
494 			}
495 		});
496 	}
497 
saveBookInfo(final BookInfo bookInfo)498 	public void saveBookInfo(final BookInfo bookInfo) {
499 		execTask(new Task("saveBookInfo") {
500 			@Override
501 			public void work() {
502 				mainDB.saveBookInfo(bookInfo);
503 			}
504 		});
505 		flush();
506 	}
507 
deleteBook(final FileInfo fileInfo)508 	public void deleteBook(final FileInfo fileInfo)	{
509 		execTask(new Task("deleteBook") {
510 			@Override
511 			public void work() {
512 				mainDB.deleteBook(fileInfo);
513 				coverDB.deleteCoverpage(fileInfo.getPathName());
514 			}
515 		});
516 		flush();
517 	}
518 
deleteBookmark(final Bookmark bm)519 	public void deleteBookmark(final Bookmark bm) {
520 		execTask(new Task("deleteBookmark") {
521 			@Override
522 			public void work() {
523 				mainDB.deleteBookmark(bm);
524 			}
525 		});
526 		flush();
527 	}
528 
setPathCorrector(final MountPathCorrector corrector)529 	public void setPathCorrector(final MountPathCorrector corrector) {
530 		execTask(new Task("setPathCorrector") {
531 			@Override
532 			public void work() {
533 				mainDB.setPathCorrector(corrector);
534 			}
535 		});
536 	}
537 
deleteRecentPosition(final FileInfo fileInfo)538 	public void deleteRecentPosition(final FileInfo fileInfo) {
539 		execTask(new Task("deleteRecentPosition") {
540 			@Override
541 			public void work() {
542 				mainDB.deleteRecentPosition(fileInfo);
543 			}
544 		});
545 		flush();
546 	}
547 
548     //=======================================================================================
549     // Favorite folders access code
550     //=======================================================================================
createFavoriteFolder(final FileInfo folder)551     public void createFavoriteFolder(final FileInfo folder) {
552    		execTask(new Task("createFavoriteFolder") {
553    			@Override
554    			public void work() {
555    				mainDB.createFavoritesFolder(folder);
556    			}
557    		});
558         flush();
559    	}
560 
loadFavoriteFolders(final FileInfoLoadingCallback callback, final Handler handler)561     public void loadFavoriteFolders(final FileInfoLoadingCallback callback, final Handler handler) {
562    		execTask(new Task("loadFavoriteFolders") {
563             @Override
564             public void work() {
565                 final ArrayList<FileInfo> favorites = mainDB.loadFavoriteFolders();
566                 sendTask(handler, () -> callback.onFileInfoListLoaded(favorites));
567             }
568         });
569    	}
570 
updateFavoriteFolder(final FileInfo folder)571     public void updateFavoriteFolder(final FileInfo folder) {
572    		execTask(new Task("updateFavoriteFolder") {
573    			@Override
574    			public void work() {
575    				mainDB.updateFavoriteFolder(folder);
576    			}
577    		});
578         flush();
579    	}
580 
deleteFavoriteFolder(final FileInfo folder)581     public void deleteFavoriteFolder(final FileInfo folder) {
582    		execTask(new Task("deleteFavoriteFolder") {
583    			@Override
584    			public void work() {
585    				mainDB.deleteFavoriteFolder(folder);
586    			}
587    		});
588         flush();
589    	}
590 
591 	private abstract class Task implements Runnable {
592 		private final String name;
Task(String name)593 		public Task(String name) {
594 			this.name = name;
595 		}
596 
597 		@Override
toString()598 		public String toString() {
599 			return "Task[" + name + "]";
600 		}
601 
602 		@Override
run()603 		public void run() {
604 			long ts = Utils.timeStamp();
605 			vlog.v(toString() + " started");
606 			try {
607 				work();
608 			} catch (Exception e) {
609 				log.e("Exception while running DB task in background", e);
610 			}
611 			vlog.v(toString() + " finished in " + Utils.timeInterval(ts) + " ms");
612 		}
613 
work()614 		public abstract void work();
615 	}
616 
617 	/**
618 	 * Execute runnable in CDRDBService background thread.
619 	 * Exceptions will be ignored, just dumped into log.
620 	 * @param task is Runnable to execute
621 	 */
execTask(final Task task)622 	private void execTask(final Task task) {
623 		vlog.v("Posting task " + task);
624 		mThread.post(task);
625 	}
626 
627 	/**
628 	 * Execute runnable in CDRDBService background thread, delayed.
629 	 * Exceptions will be ignored, just dumped into log.
630 	 * @param task is Runnable to execute
631 	 */
execTask(final Task task, long delay)632 	private void execTask(final Task task, long delay) {
633 		vlog.v("Posting task " + task + " with delay " + delay);
634 		mThread.postDelayed(task, delay);
635 	}
636 
637 	/**
638 	 * Send task to handler, if specified, otherwise run immediately.
639 	 * Exceptions will be ignored, just dumped into log.
640 	 * @param handler is handler to send task to, null to run immediately
641 	 * @param task is Runnable to execute
642 	 */
sendTask(Handler handler, Runnable task)643 	private void sendTask(Handler handler, Runnable task) {
644 		try {
645 			if (handler != null) {
646 				vlog.v("Senging task to " + handler.toString());
647 				handler.post(task);
648 			} else {
649 				vlog.v("No Handler provided: executing task in current thread");
650 				task.run();
651 			}
652 		} catch (Exception e) {
653 			log.e("Exception in DB callback", e);
654 		}
655 	}
656 
657 	/**
658      * Class for clients to access.  Because we know this service always
659      * runs in the same process as its clients, we don't need to deal with
660      * IPC.
661      * Provides interface for asynchronous operations with database.
662      */
663     public class LocalBinder extends Binder {
getService()664         private CRDBService getService() {
665             return CRDBService.this;
666         }
667 
saveBookCoverpage(final FileInfo fileInfo, byte[] data)668     	public void saveBookCoverpage(final FileInfo fileInfo, byte[] data) {
669     		getService().saveBookCoverpage(fileInfo, data);
670     	}
671 
loadBookCoverpage(final FileInfo fileInfo, final CoverpageLoadingCallback callback)672     	public void loadBookCoverpage(final FileInfo fileInfo, final CoverpageLoadingCallback callback) {
673     		getService().loadBookCoverpage(new FileInfo(fileInfo), callback, new Handler());
674     	}
675 
loadOPDSCatalogs(final OPDSCatalogsLoadingCallback callback)676     	public void loadOPDSCatalogs(final OPDSCatalogsLoadingCallback callback) {
677     		getService().loadOPDSCatalogs(callback, new Handler());
678     	}
679 
saveOPDSCatalog(final Long id, final String url, final String name, final String username, final String password)680     	public void saveOPDSCatalog(final Long id, final String url, final String name, final String username, final String password) {
681     		getService().saveOPDSCatalog(id, url, name, username, password);
682     	}
683 
updateOPDSCatalogLastUsage(final String url)684     	public void updateOPDSCatalogLastUsage(final String url) {
685     		getService().updateOPDSCatalogLastUsage(url);
686     	}
687 
removeOPDSCatalog(final Long id)688     	public void removeOPDSCatalog(final Long id) {
689     		getService().removeOPDSCatalog(id);
690     	}
691 
loadGenresList(FileInfo parent, boolean showEmptyGenres, final ItemGroupsLoadingCallback callback)692 		public void loadGenresList(FileInfo parent, boolean showEmptyGenres, final ItemGroupsLoadingCallback callback) {
693 			getService().loadGenresList(parent, showEmptyGenres, callback, new Handler());
694 		}
695 
loadAuthorsList(FileInfo parent, final ItemGroupsLoadingCallback callback)696 		public void loadAuthorsList(FileInfo parent, final ItemGroupsLoadingCallback callback) {
697     		getService().loadAuthorsList(parent, callback, new Handler());
698     	}
699 
loadSeriesList(FileInfo parent, final ItemGroupsLoadingCallback callback)700     	public void loadSeriesList(FileInfo parent, final ItemGroupsLoadingCallback callback) {
701     		getService().loadSeriesList(parent, callback, new Handler());
702     	}
703 
loadTitleList(FileInfo parent, final ItemGroupsLoadingCallback callback)704     	public void loadTitleList(FileInfo parent, final ItemGroupsLoadingCallback callback) {
705     		getService().loadTitleList(parent, callback, new Handler());
706     	}
707 
loadGenresBooks(String genreCode, boolean showEmptyGenres, FileInfoLoadingCallback callback)708 		public void loadGenresBooks(String genreCode, boolean showEmptyGenres, FileInfoLoadingCallback callback) {
709 			getService().findGenresBooks(genreCode, showEmptyGenres, callback, new Handler());
710 		}
711 
loadAuthorBooks(long authorId, FileInfoLoadingCallback callback)712     	public void loadAuthorBooks(long authorId, FileInfoLoadingCallback callback) {
713     		getService().findAuthorBooks(authorId, callback, new Handler());
714     	}
715 
loadSeriesBooks(long seriesId, FileInfoLoadingCallback callback)716     	public void loadSeriesBooks(long seriesId, FileInfoLoadingCallback callback) {
717     		getService().findSeriesBooks(seriesId, callback, new Handler());
718     	}
719 
loadSearchHistory(BookInfo book, SearchHistoryLoadingCallback callback)720 		public void loadSearchHistory(BookInfo book, SearchHistoryLoadingCallback callback) {
721 			getService().loadSearchHistory(book, callback, new Handler());
722 		}
723 
loadBooksByRating(int minRating, int maxRating, FileInfoLoadingCallback callback)724     	public void loadBooksByRating(int minRating, int maxRating, FileInfoLoadingCallback callback) {
725     		getService().findBooksByRating(minRating, maxRating, callback, new Handler());
726     	}
727 
loadBooksByState(int state, FileInfoLoadingCallback callback)728     	public void loadBooksByState(int state, FileInfoLoadingCallback callback) {
729     		getService().findBooksByState(state, callback, new Handler());
730     	}
731 
loadRecentBooks(final int maxCount, final RecentBooksLoadingCallback callback)732     	public void loadRecentBooks(final int maxCount, final RecentBooksLoadingCallback callback) {
733     		getService().loadRecentBooks(maxCount, callback, new Handler());
734     	}
735 
sync(final Runnable callback)736     	public void sync(final Runnable callback) {
737     		getService().sync(callback, new Handler());
738     	}
739 
saveFileInfos(final Collection<FileInfo> list)740     	public void saveFileInfos(final Collection<FileInfo> list) {
741     		getService().saveFileInfos(deepCopyFileInfos(list));
742     	}
743 
findByFingerprints(final int maxCount, Collection<String> fingerprints, final BookSearchCallback callback)744     	public void findByFingerprints(final int maxCount, Collection<String> fingerprints, final BookSearchCallback callback) {
745     		getService().findByFingerprints(maxCount, fingerprints, callback, new Handler());
746     	}
747 
findByPatterns(final int maxCount, final String authors, final String title, final String series, final String filename, final BookSearchCallback callback)748 		public void findByPatterns(final int maxCount, final String authors, final String title, final String series, final String filename, final BookSearchCallback callback) {
749 			getService().findByPatterns(maxCount, authors, title, series, filename, callback, new Handler());
750 		}
751 
loadFileInfos(final ArrayList<String> pathNames, final FileInfoLoadingCallback callback)752 		public void loadFileInfos(final ArrayList<String> pathNames, final FileInfoLoadingCallback callback) {
753     		getService().loadFileInfos(pathNames, callback, new Handler());
754     	}
755 
deleteBook(final FileInfo fileInfo)756     	public void deleteBook(final FileInfo fileInfo)	{
757     		getService().deleteBook(new FileInfo(fileInfo));
758     	}
759 
saveBookInfo(final BookInfo bookInfo)760     	public void saveBookInfo(final BookInfo bookInfo) {
761     		getService().saveBookInfo(new BookInfo(bookInfo));
762     	}
763 
saveSearchHistory(final BookInfo book, String sHist)764 		public void saveSearchHistory(final BookInfo book, String sHist) {
765 			getService().saveSearchHistory(new BookInfo(book), sHist);
766 		}
767 
deleteRecentPosition(final FileInfo fileInfo)768     	public void deleteRecentPosition(final FileInfo fileInfo)	{
769     		getService().deleteRecentPosition(new FileInfo(fileInfo));
770     	}
771 
deleteBookmark(final Bookmark bm)772     	public void deleteBookmark(final Bookmark bm) {
773     		getService().deleteBookmark(new Bookmark(bm));
774     	}
775 
loadBookInfo(final FileInfo fileInfo, final BookInfoLoadingCallback callback)776     	public void loadBookInfo(final FileInfo fileInfo, final BookInfoLoadingCallback callback) {
777     		getService().loadBookInfo(new FileInfo(fileInfo), callback, new Handler());
778     	}
779 
createFavoriteFolder(final FileInfo folder)780         public void createFavoriteFolder(final FileInfo folder) {
781             getService().createFavoriteFolder(folder);
782        	}
783 
loadFavoriteFolders(FileInfoLoadingCallback callback)784         public void loadFavoriteFolders(FileInfoLoadingCallback callback) {
785             getService().loadFavoriteFolders(callback, new Handler());
786        	}
787 
updateFavoriteFolder(final FileInfo folder)788         public void updateFavoriteFolder(final FileInfo folder) {
789             getService().updateFavoriteFolder(folder);
790        	}
791 
deleteFavoriteFolder(final FileInfo folder)792         public void deleteFavoriteFolder(final FileInfo folder) {
793             getService().deleteFavoriteFolder(folder);
794        	}
795 
796 
setPathCorrector(MountPathCorrector corrector)797     	public void setPathCorrector(MountPathCorrector corrector) {
798     		getService().setPathCorrector(corrector);
799     	}
800 
flush()801     	public void flush() {
802     		getService().forceFlush();
803     	}
804 
reopenDatabase()805     	public void reopenDatabase() {
806         	getService().reopenDatabase();
807 		}
808     }
809 
810     @Override
onBind(Intent intent)811     public IBinder onBind(Intent intent) {
812 	    log.i("onBind(): " + intent);
813         return mBinder;
814     }
815 
816     @Override
onRebind(Intent intent)817     public void onRebind (Intent intent) {
818         log.i("onRebind(): " + intent);
819     }
820 
821     @Override
onUnbind(Intent intent)822     public boolean onUnbind(Intent intent) {
823         log.i("onUnbind(): intent=" + intent);
824         return true;
825     }
826 
827     private ServiceThread mThread;
828     private final IBinder mBinder = new LocalBinder();
829 
830 }
831