/* * Copyright (C) 2007-2015 FBReader.ORG Limited <contact@fbreader.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ package org.geometerplus.android.fbreader.libraryService; import java.util.*; import android.app.Service; import android.content.*; import android.os.IBinder; import android.os.RemoteException; import org.geometerplus.zlibrary.core.options.Config; import org.geometerplus.zlibrary.text.view.ZLTextFixedPosition; import org.geometerplus.zlibrary.text.view.ZLTextPosition; import org.geometerplus.fbreader.book.*; import org.geometerplus.android.fbreader.api.FBReaderIntents; public class BookCollectionShadow extends AbstractBookCollection<Book> implements ServiceConnection { private volatile Context myContext; private volatile LibraryInterface myInterface; private final List<Runnable> myOnBindActions = new LinkedList<Runnable>(); private final BroadcastReceiver myReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { if (!hasListeners()) { return; } try { final String type = intent.getStringExtra("type"); if (FBReaderIntents.Event.LIBRARY_BOOK.equals(intent.getAction())) { final Book book = SerializerUtil.deserializeBook(intent.getStringExtra("book"), BookCollectionShadow.this); fireBookEvent(BookEvent.valueOf(type), book); } else { fireBuildEvent(Status.valueOf(type)); } } catch (Exception e) { // ignore } } }; public synchronized boolean bindToService(Context context, Runnable onBindAction) { if (myInterface != null && myContext == context) { if (onBindAction != null) { Config.Instance().runOnConnect(onBindAction); } return true; } else { if (onBindAction != null) { synchronized (myOnBindActions) { myOnBindActions.add(onBindAction); } } final boolean result = context.bindService( FBReaderIntents.internalIntent(FBReaderIntents.Action.LIBRARY_SERVICE), this, Service.BIND_AUTO_CREATE ); if (result) { myContext = context; } return result; } } public synchronized void unbind() { if (myContext != null && myInterface != null) { try { myContext.unregisterReceiver(myReceiver); } catch (IllegalArgumentException e) { // called before regisration, that's ok } catch (Exception e) { e.printStackTrace(); } try { myContext.unbindService(this); } catch (Exception e) { e.printStackTrace(); } myInterface = null; myContext = null; } } public synchronized void reset(boolean force) { if (myInterface != null) { try { myInterface.reset(force); } catch (RemoteException e) { } } } public synchronized int size() { if (myInterface == null) { return 0; } try { return myInterface.size(); } catch (RemoteException e) { return 0; } } public synchronized Status status() { if (myInterface == null) { return Status.NotStarted; } try { return Status.valueOf(myInterface.status()); } catch (Throwable t) { return Status.NotStarted; } } public List<Book> books(final BookQuery query) { return listCall(new ListCallable<Book>() { public List<Book> call() throws RemoteException { return SerializerUtil.deserializeBookList( myInterface.books(SerializerUtil.serialize(query)), BookCollectionShadow.this ); } }); } public synchronized boolean hasBooks(Filter filter) { if (myInterface == null) { return false; } try { return myInterface.hasBooks(SerializerUtil.serialize(new BookQuery(filter, 1))); } catch (RemoteException e) { return false; } } public List<Book> recentlyAddedBooks(final int count) { return listCall(new ListCallable<Book>() { public List<Book> call() throws RemoteException { return SerializerUtil.deserializeBookList( myInterface.recentlyAddedBooks(count), BookCollectionShadow.this ); } }); } public List<Book> recentlyOpenedBooks(final int count) { return listCall(new ListCallable<Book>() { public List<Book> call() throws RemoteException { return SerializerUtil.deserializeBookList( myInterface.recentlyOpenedBooks(count), BookCollectionShadow.this ); } }); } public synchronized Book getRecentBook(int index) { if (myInterface == null) { return null; } try { return SerializerUtil.deserializeBook(myInterface.getRecentBook(index), this); } catch (RemoteException e) { e.printStackTrace(); return null; } } public synchronized Book getBookByFile(String path) { if (myInterface == null) { return null; } try { return SerializerUtil.deserializeBook(myInterface.getBookByFile(path), this); } catch (RemoteException e) { return null; } } public synchronized Book getBookById(long id) { if (myInterface == null) { return null; } try { return SerializerUtil.deserializeBook(myInterface.getBookById(id), this); } catch (RemoteException e) { return null; } } public synchronized Book getBookByUid(UID uid) { if (myInterface == null) { return null; } try { return SerializerUtil.deserializeBook(myInterface.getBookByUid(uid.Type, uid.Id), this); } catch (RemoteException e) { return null; } } public synchronized Book getBookByHash(String hash) { if (myInterface == null) { return null; } try { return SerializerUtil.deserializeBook(myInterface.getBookByHash(hash), this); } catch (RemoteException e) { return null; } } public List<Author> authors() { return listCall(new ListCallable<Author>() { public List<Author> call() throws RemoteException { final List<String> strings = myInterface.authors(); final List<Author> authors = new ArrayList<Author>(strings.size()); for (String s : strings) { authors.add(Util.stringToAuthor(s)); } return authors; } }); } public List<Tag> tags() { return listCall(new ListCallable<Tag>() { public List<Tag> call() throws RemoteException { final List<String> strings = myInterface.tags(); final List<Tag> tags = new ArrayList<Tag>(strings.size()); for (String s : strings) { tags.add(Util.stringToTag(s)); } return tags; } }); } public synchronized boolean hasSeries() { if (myInterface != null) { try { return myInterface.hasSeries(); } catch (RemoteException e) { } } return false; } public List<String> series() { return listCall(new ListCallable<String>() { public List<String> call() throws RemoteException { return myInterface.series(); } }); } public List<String> titles(final BookQuery query) { return listCall(new ListCallable<String>() { public List<String> call() throws RemoteException { return myInterface.titles(SerializerUtil.serialize(query)); } }); } public List<String> firstTitleLetters() { return listCall(new ListCallable<String>() { public List<String> call() throws RemoteException { return myInterface.firstTitleLetters(); } }); } public synchronized boolean saveBook(Book book) { if (myInterface == null) { return false; } try { return myInterface.saveBook(SerializerUtil.serialize(book)); } catch (RemoteException e) { return false; } } public synchronized boolean canRemoveBook(Book book, boolean deleteFromDisk) { if (myInterface == null) { return false; } try { return myInterface.canRemoveBook(SerializerUtil.serialize(book), deleteFromDisk); } catch (RemoteException e) { return false; } } public synchronized void removeBook(Book book, boolean deleteFromDisk) { if (myInterface != null) { try { myInterface.removeBook(SerializerUtil.serialize(book), deleteFromDisk); } catch (RemoteException e) { } } } public synchronized void addToRecentlyOpened(Book book) { if (myInterface != null) { try { myInterface.addToRecentlyOpened(SerializerUtil.serialize(book)); } catch (RemoteException e) { } } } public synchronized void removeFromRecentlyOpened(Book book) { if (myInterface != null) { try { myInterface.removeFromRecentlyOpened(SerializerUtil.serialize(book)); } catch (RemoteException e) { } } } public List<String> labels() { return listCall(new ListCallable<String>() { public List<String> call() throws RemoteException { return myInterface.labels(); } }); } public String getHash(Book book, boolean force) { if (myInterface == null) { return null; } try { return myInterface.getHash(SerializerUtil.serialize(book), force); } catch (RemoteException e) { return null; } } public void setHash(Book book, String hash) { if (myInterface == null) { return; } try { myInterface.setHash(SerializerUtil.serialize(book), hash); } catch (RemoteException e) { } } public synchronized ZLTextFixedPosition.WithTimestamp getStoredPosition(long bookId) { if (myInterface == null) { return null; } try { final PositionWithTimestamp pos = myInterface.getStoredPosition(bookId); if (pos == null) { return null; } return new ZLTextFixedPosition.WithTimestamp( pos.ParagraphIndex, pos.ElementIndex, pos.CharIndex, pos.Timestamp ); } catch (RemoteException e) { return null; } } public synchronized void storePosition(long bookId, ZLTextPosition position) { if (position != null && myInterface != null) { try { myInterface.storePosition(bookId, new PositionWithTimestamp(position)); } catch (RemoteException e) { } } } public synchronized boolean isHyperlinkVisited(Book book, String linkId) { if (myInterface == null) { return false; } try { return myInterface.isHyperlinkVisited(SerializerUtil.serialize(book), linkId); } catch (RemoteException e) { return false; } } public synchronized void markHyperlinkAsVisited(Book book, String linkId) { if (myInterface != null) { try { myInterface.markHyperlinkAsVisited(SerializerUtil.serialize(book), linkId); } catch (RemoteException e) { } } } @Override public String getCoverUrl(Book book) { if (myInterface == null) { return null; } try { return myInterface.getCoverUrl(book.getPath()); } catch (RemoteException e) { return null; } } @Override public String getDescription(Book book) { if (myInterface == null) { return null; } try { return myInterface.getDescription(SerializerUtil.serialize(book)); } catch (RemoteException e) { return null; } } @Override public List<Bookmark> bookmarks(final BookmarkQuery query) { return listCall(new ListCallable<Bookmark>() { public List<Bookmark> call() throws RemoteException { return SerializerUtil.deserializeBookmarkList( myInterface.bookmarks(SerializerUtil.serialize(query)) ); } }); } public synchronized void saveBookmark(Bookmark bookmark) { if (myInterface != null) { try { bookmark.update(SerializerUtil.deserializeBookmark( myInterface.saveBookmark(SerializerUtil.serialize(bookmark)) )); } catch (RemoteException e) { } } } public synchronized void deleteBookmark(Bookmark bookmark) { if (myInterface != null) { try { myInterface.deleteBookmark(SerializerUtil.serialize(bookmark)); } catch (RemoteException e) { } } } public synchronized List<String> deletedBookmarkUids() { return listCall(new ListCallable<String>() { public List<String> call() throws RemoteException { return myInterface.deletedBookmarkUids(); } }); } public void purgeBookmarks(List<String> uids) { if (myInterface != null) { try { myInterface.purgeBookmarks(uids); } catch (RemoteException e) { } } } public synchronized HighlightingStyle getHighlightingStyle(int styleId) { if (myInterface == null) { return null; } try { return SerializerUtil.deserializeStyle(myInterface.getHighlightingStyle(styleId)); } catch (RemoteException e) { return null; } } public List<HighlightingStyle> highlightingStyles() { return listCall(new ListCallable<HighlightingStyle>() { public List<HighlightingStyle> call() throws RemoteException { return SerializerUtil.deserializeStyleList(myInterface.highlightingStyles()); } }); } public synchronized void saveHighlightingStyle(HighlightingStyle style) { if (myInterface != null) { try { myInterface.saveHighlightingStyle(SerializerUtil.serialize(style)); } catch (RemoteException e) { // ignore } } } public int getDefaultHighlightingStyleId() { if (myInterface == null) { return 1; } try { return myInterface.getDefaultHighlightingStyleId(); } catch (RemoteException e) { return 1; } } public void setDefaultHighlightingStyleId(int styleId) { if (myInterface != null) { try { myInterface.setDefaultHighlightingStyleId(styleId); } catch (RemoteException e) { // ignore } } } public synchronized void rescan(String path) { if (myInterface != null) { try { myInterface.rescan(path); } catch (RemoteException e) { // ignore } } } public List<FormatDescriptor> formats() { return listCall(new ListCallable<FormatDescriptor>() { public List<FormatDescriptor> call() throws RemoteException { final List<String> serialized = myInterface.formats(); final List<FormatDescriptor> formats = new ArrayList<FormatDescriptor>(serialized.size()); for (String s : serialized) { formats.add(Util.stringToFormatDescriptor(s)); } return formats; } }); } public synchronized boolean setActiveFormats(List<String> formats) { if (myInterface != null) { try { return myInterface.setActiveFormats(formats); } catch (RemoteException e) { } } return false; } private interface ListCallable<T> { List<T> call() throws RemoteException; } private synchronized <T> List<T> listCall(ListCallable<T> callable) { if (myInterface == null) { return Collections.emptyList(); } try { return callable.call(); } catch (Exception e) { return Collections.emptyList(); } catch (Throwable e) { // TODO: report problem return Collections.emptyList(); } } // method from ServiceConnection interface public void onServiceConnected(ComponentName name, IBinder service) { synchronized (this) { myInterface = LibraryInterface.Stub.asInterface(service); } final List<Runnable> actions; synchronized (myOnBindActions) { actions = new ArrayList<Runnable>(myOnBindActions); myOnBindActions.clear(); } for (Runnable a : actions) { Config.Instance().runOnConnect(a); } if (myContext != null) { myContext.registerReceiver(myReceiver, new IntentFilter(FBReaderIntents.Event.LIBRARY_BOOK)); myContext.registerReceiver(myReceiver, new IntentFilter(FBReaderIntents.Event.LIBRARY_BUILD)); } } // method from ServiceConnection interface public synchronized void onServiceDisconnected(ComponentName name) { } public Book createBook(long id, String url, String title, String encoding, String language) { return new Book(id, url.substring("file://".length()), title, encoding, language); } }