package org.ebookdroid.common.settings; import org.ebookdroid.common.settings.books.BookSettings; import org.ebookdroid.common.settings.books.DBSettingsManager; import org.ebookdroid.common.settings.listeners.IAppSettingsChangeListener; import org.ebookdroid.common.settings.listeners.IBackupSettingsChangeListener; import org.ebookdroid.common.settings.listeners.IBookSettingsChangeListener; import org.ebookdroid.common.settings.listeners.ILibSettingsChangeListener; import org.ebookdroid.common.settings.listeners.IOpdsSettingsChangeListener; import org.ebookdroid.common.settings.listeners.IRecentBooksChangedListener; import org.ebookdroid.common.settings.types.DocumentViewMode; import org.ebookdroid.common.settings.types.PageAlign; import org.ebookdroid.common.settings.types.RotationType; import org.ebookdroid.core.PageIndex; import org.ebookdroid.core.curl.PageAnimationType; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.preference.PreferenceManager; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.emdev.common.log.LogContext; import org.emdev.common.log.LogManager; import org.emdev.utils.FileUtils; import org.emdev.utils.LengthUtils; import org.emdev.utils.concurrent.Flag; import org.emdev.utils.listeners.ListenerProxy; public class SettingsManager { public static final LogContext LCTX = LogManager.root().lctx("SettingsManager", false); public static final int INITIAL_FONTS = 1 << 0; private static final String INITIAL_FLAGS = "initial_flags"; static Context ctx; static SharedPreferences prefs; static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private static DBSettingsManager db; private static final Map<String, BookSettings> bookSettings = new HashMap<String, BookSettings>(); static ListenerProxy listeners = new ListenerProxy(IAppSettingsChangeListener.class, IBackupSettingsChangeListener.class, ILibSettingsChangeListener.class, IOpdsSettingsChangeListener.class, IBookSettingsChangeListener.class, IRecentBooksChangedListener.class); private static BookSettingsUpdate updateThread; public static void init(final Context context) { if (ctx == null) { ctx = context; prefs = PreferenceManager.getDefaultSharedPreferences(context); db = new DBSettingsManager(context); AppSettings.init(); BackupSettings.init(); LibSettings.init(); OpdsSettings.init(); updateThread = new BookSettingsUpdate(); updateThread.start(); } } public static void onTerminate() { updateThread.run.clear(); try { updateThread.join(); } catch (final InterruptedException e) { Thread.interrupted(); } } public static BookSettings create(final long ownerId, final String fileName, final boolean temporaryBook, final Intent intent) { lock.writeLock().lock(); try { boolean created = false; BookSettings current = getBookSettingsImpl(fileName); if (current == null) { created = true; current = new BookSettings(fileName); AppSettings.setDefaultSettings(current); if (temporaryBook) { current.persistent = false; } } bookSettings.put(current.fileName, current); if (intent != null) { current.persistent = Boolean.parseBoolean(LengthUtils.safeString(intent.getStringExtra("persistent"), "true")); current.viewMode = DocumentViewMode.valueOf((LengthUtils.safeString(intent.getStringExtra("viewMode"), current.viewMode.toString()))); current.animationType = PageAnimationType.valueOf(LengthUtils.safeString( intent.getStringExtra("animationType"), current.animationType.toString())); current.pageAlign = PageAlign.valueOf(LengthUtils.safeString(intent.getStringExtra("pageAlign"), current.pageAlign.toString())); current.splitPages = Boolean.parseBoolean(LengthUtils.safeString(intent.getStringExtra("splitPages"), Boolean.toString(current.splitPages))); current.cropPages = Boolean.parseBoolean(LengthUtils.safeString(intent.getStringExtra("cropPages"), Boolean.toString(current.cropPages))); current.nightMode = Boolean.parseBoolean(LengthUtils.safeString(intent.getStringExtra("nightMode"), Boolean.toString(current.nightMode))); if (intent.hasExtra("pageIndex")) { final int pageIndex = Integer.parseInt(LengthUtils.safeString(intent.getStringExtra("pageIndex"), Integer.toString(current.currentPage.viewIndex))); current.currentPage = new PageIndex(current.splitPages ? current.currentPage.docIndex : pageIndex, pageIndex); current.offsetX = Float.parseFloat(LengthUtils.safeString(intent.getStringExtra("offsetX"), "0")); current.offsetY = Float.parseFloat(LengthUtils.safeString(intent.getStringExtra("offsetY"), "0")); } } if (created && current.persistent) { db.storeBookSettings(current); } return current; } finally { lock.writeLock().unlock(); } } public static boolean hasOpenedBooks() { lock.readLock().lock(); try { return !bookSettings.isEmpty(); } finally { lock.readLock().unlock(); } } public static BookSettings getBookSettings(final String fileName) { lock.writeLock().lock(); try { return getBookSettingsImpl(fileName); } finally { lock.writeLock().unlock(); } } private static BookSettings getBookSettingsImpl(final String fileName) { BookSettings bs = loadBookSettingsImpl(fileName); if (bs == null) { final String mpath = FileUtils.invertMountPrefix(fileName); final File f = mpath != null ? new File(mpath) : null; if (f != null && f.exists()) { bs = loadBookSettingsImpl(mpath); } } return bs; } private static BookSettings loadBookSettingsImpl(final String fileName) { BookSettings bs = bookSettings.get(fileName); if (bs == null) { bs = db.getBookSettings(fileName); } return bs; } public static void releaseBookSettings(final long ownerId, final BookSettings current) { if (current == null || !current.persistent) { return; } lock.writeLock().lock(); try { storeBookSettings(current); bookSettings.remove(current.fileName); } finally { lock.writeLock().unlock(); } onRecentBooksChanged(); } public static void clearAllRecentBookSettings() { lock.writeLock().lock(); try { db.clearRecent(); for (final BookSettings current : bookSettings.values()) { current.persistent = false; } } finally { lock.writeLock().unlock(); } } public static void removeBookFromRecents(final String path) { lock.writeLock().lock(); try { final BookSettings bs = db.getBookSettings(path); if (bs != null) { db.removeBookFromRecents(bs); } } finally { lock.writeLock().unlock(); } } public static void deleteBookSettings(final BookSettings bs) { lock.writeLock().lock(); try { db.delete(bs); bs.persistent = false; } finally { lock.writeLock().unlock(); } } public static void deleteAllBookSettings() { lock.writeLock().lock(); try { db.deleteAll(); for (final BookSettings current : bookSettings.values()) { current.persistent = false; } } finally { lock.writeLock().unlock(); } } public static void deleteAllBookmarks() { lock.writeLock().lock(); try { db.deleteAllBookmarks(); for (final BookSettings current : bookSettings.values()) { current.bookmarks.clear(); } } finally { lock.writeLock().unlock(); } } public static BookSettings getRecentBook() { lock.readLock().lock(); try { final Map<String, BookSettings> books = db.getRecentBooks(false); return books.isEmpty() ? null : books.values().iterator().next(); } finally { lock.readLock().unlock(); } } public static Map<String, BookSettings> getRecentBooks() { lock.writeLock().lock(); try { final Map<String, BookSettings> books = db.getRecentBooks(true); books.putAll(bookSettings); return books; } finally { lock.writeLock().unlock(); } } // TODO: Factor out common code from these public static void setBookRotation(final BookSettings current, final RotationType mode) { if (current == null) { return; } lock.writeLock().lock(); try { final BookSettings olds = new BookSettings(current); current.rotation = mode; current.lastChanged = System.currentTimeMillis(); db.storeBookSettings(current); applyBookSettingsChanges(olds, current); } finally { lock.writeLock().unlock(); } } public static void toggleNightMode(final BookSettings current) { if (current == null) { return; } lock.writeLock().lock(); try { final BookSettings olds = new BookSettings(current); current.nightMode = !current.nightMode; current.lastChanged = System.currentTimeMillis(); db.storeBookSettings(current); applyBookSettingsChanges(olds, current); } finally { lock.writeLock().unlock(); } } public static void toggleSplitPages(final BookSettings current) { if (current == null) { return; } lock.writeLock().lock(); try { final BookSettings olds = new BookSettings(current); current.splitPages = !current.splitPages; current.lastChanged = System.currentTimeMillis(); db.storeBookSettings(current); applyBookSettingsChanges(olds, current); } finally { lock.writeLock().unlock(); } } public static void toggleCropPages(final BookSettings current) { if (current == null) { return; } lock.writeLock().lock(); try { final BookSettings olds = new BookSettings(current); current.cropPages = !current.cropPages; current.lastChanged = System.currentTimeMillis(); db.storeBookSettings(current); applyBookSettingsChanges(olds, current); } finally { lock.writeLock().unlock(); } } public static void toggleSinglePage(final BookSettings current) { if (current == null) { return; } lock.writeLock().lock(); try { final BookSettings olds = new BookSettings(current); current.viewMode = (current.viewMode == DocumentViewMode.SINGLE_PAGE ? DocumentViewMode.VERTICALL_SCROLL : DocumentViewMode.SINGLE_PAGE); current.lastChanged = System.currentTimeMillis(); db.storeBookSettings(current); applyBookSettingsChanges(olds, current); } finally { lock.writeLock().unlock(); } } public static void currentPageChanged(final BookSettings current, final PageIndex oldIndex, final PageIndex newIndex) { if (current == null) { return; } lock.writeLock().lock(); try { current.currentPageChanged(oldIndex, newIndex); } finally { lock.writeLock().unlock(); } } public static void zoomChanged(final BookSettings current, final float zoom, final boolean committed) { if (current == null) { return; } lock.writeLock().lock(); try { current.setZoom(zoom, committed); } finally { lock.writeLock().unlock(); } } public static void positionChanged(final BookSettings current, final float offsetX, final float offsetY) { if (current == null) { return; } lock.writeLock().lock(); try { current.positionChanged(offsetX, offsetY); } finally { lock.writeLock().unlock(); } } public static void onSettingsChanged() { lock.writeLock().lock(); try { AppSettings.onSettingsChanged(); BackupSettings.onSettingsChanged(); LibSettings.onSettingsChanged(); OpdsSettings.onSettingsChanged(); } finally { lock.writeLock().unlock(); } } public static void onBookSettingsActivityCreated(final BookSettings current) { if (current == null) { return; } AppSettings.updatePseudoBookSettings(current); } public static void onBookSettingsActivityClosed(final BookSettings current) { if (current == null) { return; } lock.writeLock().lock(); try { final BookSettings oldBS = new BookSettings(current); AppSettings.fillBookSettings(current); if (current.persistent) { db.storeBookSettings(current); } applyBookSettingsChanges(oldBS, current); } finally { lock.writeLock().unlock(); } } public static BookSettings copyBookSettings(final File target, final BookSettings settings) { lock.writeLock().lock(); try { final BookSettings bs = new BookSettings(target.getAbsolutePath(), settings); db.storeBookSettings(bs); return bs; } finally { lock.writeLock().unlock(); } } public static void storeBookSettings(final BookSettings current) { if (current == null) { return; } lock.readLock().lock(); try { db.storeBookSettings(current); } finally { lock.readLock().unlock(); } } public static void applyBookSettingsChanges(final BookSettings oldSettings, final BookSettings newSettings) { final BookSettings.Diff diff = new BookSettings.Diff(oldSettings, newSettings); final IBookSettingsChangeListener l = listeners.getListener(); l.onBookSettingsChanged(oldSettings, newSettings, diff); } public static void addListener(final Object l) { listeners.addListener(l); } public static void removeListener(final Object l) { listeners.removeListener(l); } public static void onRecentBooksChanged() { final IRecentBooksChangedListener l = listeners.getListener(); l.onRecentBooksChanged(); } public static boolean isInitialFlagsSet(final int flag) { if (prefs.contains(INITIAL_FLAGS)) { return (prefs.getInt(INITIAL_FLAGS, 0) & flag) == flag; } return false; } public static void setInitialFlags(final int flag) { final int old = prefs.getInt(INITIAL_FLAGS, 0); prefs.edit().putInt(INITIAL_FLAGS, old | flag).commit(); } private static class BookSettingsUpdate extends Thread { private static final int INACTIVITY_PERIOD = 3000; final Flag run = new Flag(true); final List<BookSettings> list = new ArrayList<BookSettings>(); @Override public void run() { LCTX.i("BookSettingsUpdate thread started"); while (run.waitFor(TimeUnit.MILLISECONDS, INACTIVITY_PERIOD)) { boolean stored = false; lock.writeLock().lock(); try { list.clear(); final long now = run.get() ? System.currentTimeMillis() : 0; for (final BookSettings current : bookSettings.values()) { if (current.persistent && current.lastUpdated < current.lastChanged && now - current.lastChanged >= INACTIVITY_PERIOD) { list.add(current); } } if (!list.isEmpty()) { stored |= db.storeBookSettings(list); } } catch (final Throwable th) { LCTX.e("BookSettingsUpdate thread error: ", th); } finally { lock.writeLock().unlock(); } if (stored) { onRecentBooksChanged(); } } LCTX.i("BookSettingsUpdate thread finished"); } } }