package com.quaap.bookymcbookface.book; import android.content.Context; import android.content.SharedPreferences; import android.net.Uri; import android.os.Build; import android.util.Log; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.zip.CRC32; import java.util.zip.Checksum; import com.quaap.bookymcbookface.FsTools; /** * Copyright (C) 2017 Tom Kliethermes * * This file is part of BookyMcBookface and is 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 3 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. */ public abstract class Book { private static final String FONTSIZE = "fontsize"; private static final String SECTION_ID_OFFSET = "sectionIDOffset"; private static final String SECTION_ID = "sectionID"; private static final String BG_COLOR = "BG_COLOR"; private String title; private File file; private final File dataDir; private SharedPreferences data; private final Context context; private List<String> sectionIDs; private int currentSectionIDPos = 0; private String subbook; private File thisBookDir; Book(Context context) { this.dataDir = context.getFilesDir(); this.context = context; sectionIDs = new ArrayList<>(); } protected abstract void load() throws IOException; public abstract Map<String,String> getToc(); protected abstract BookMetadata getMetaData() throws IOException; protected abstract List<String> getSectionIds(); protected abstract Uri getUriForSectionID(String id); //protected abstract Uri getUriForSection(String section); //protected abstract String getSectionIDForSection(String section); protected abstract ReadPoint locateReadPoint(String section); public void load(File file) { this.file = file; data = getStorage(context, file); thisBookDir = getBookDir(context, file); thisBookDir.mkdirs(); try { load(); } catch (IOException e) { e.printStackTrace(); } sectionIDs = getSectionIds(); restoreCurrentSectionID(); } public boolean hasDataDir() { return data!=null; } public Uri getFirstSection() { clearSectionOffset(); currentSectionIDPos = 0; saveCurrentSectionID(); return getUriForSectionID(sectionIDs.get(currentSectionIDPos)); } public Uri getCurrentSection() { try { restoreCurrentSectionID(); if (currentSectionIDPos >= sectionIDs.size()) { currentSectionIDPos = 0; saveCurrentSectionID(); } if (sectionIDs.size() == 0) { return null; } return getUriForSectionID(sectionIDs.get(currentSectionIDPos)); } catch (Throwable t) { Log.e("Booky", t.getMessage(), t); return null; } } public void setFontsize(int fontsize) { data.edit().putInt(FONTSIZE, fontsize).apply(); } public int getFontsize() { return data.getInt(FONTSIZE, -1); } public void clearFontsize() { data.edit().remove(FONTSIZE).apply(); } public void setSectionOffset(int offset) { data.edit().putInt(SECTION_ID_OFFSET, offset).apply(); } public int getSectionOffset() { if (data==null) { return 0; } return data.getInt(SECTION_ID_OFFSET, -1); } private void clearSectionOffset() { data.edit().remove(SECTION_ID_OFFSET).apply(); } public void setBackgroundColor(int color) { data.edit().putInt(BG_COLOR, color).apply(); } public int getBackgroundColor() { return data.getInt(BG_COLOR, Integer.MAX_VALUE); } public void clearBackgroundColor() { data.edit().remove(BG_COLOR).apply(); } public void setFlag(String key, boolean value) { data.edit().putBoolean(key, value).apply(); } public boolean getFlag(String key, boolean value) { return data.getBoolean(key, value); } public Uri getNextSection() { try { if (currentSectionIDPos + 1 < sectionIDs.size()) { clearSectionOffset(); currentSectionIDPos++; saveCurrentSectionID(); return getUriForSectionID(sectionIDs.get(currentSectionIDPos)); } } catch (Throwable t) { Log.e("Booky", t.getMessage(), t); } return null; } public Uri getPreviousSection() { try { if (currentSectionIDPos - 1 >= 0) { clearSectionOffset(); currentSectionIDPos--; saveCurrentSectionID(); return getUriForSectionID(sectionIDs.get(currentSectionIDPos)); } } catch (Throwable t) { Log.e("Booky", t.getMessage(), t); } return null; } private void gotoSectionID(String id) { try { int pos = sectionIDs.indexOf(id); if (pos > -1 && pos < sectionIDs.size()) { currentSectionIDPos = pos; saveCurrentSectionID(); getUriForSectionID(sectionIDs.get(currentSectionIDPos)); } } catch (Throwable t) { Log.e("Booky", t.getMessage(), t); } } public Uri handleClickedLink(String clickedLink) { ReadPoint readPoint = locateReadPoint(clickedLink); if (readPoint!=null) { gotoSectionID(readPoint.getId()); clearSectionOffset(); return readPoint.getPoint(); } return null; } private void saveCurrentSectionID() { Log.d("Book", "saving section " + currentSectionIDPos); data.edit().putInt(SECTION_ID, currentSectionIDPos).apply(); } private void restoreCurrentSectionID() { currentSectionIDPos = data.getInt(SECTION_ID, currentSectionIDPos); Log.d("Book", "Loaded section " + currentSectionIDPos); } private static String makeOldFName(File file) { return file.getPath().replaceAll("[/\\\\]","_"); } private static final String reservedChars = "[/\\\\:?\"'*|<>+\\[\\]()]"; private static String makeFName(File file) { String fname = file.getPath().replaceAll(reservedChars,"_"); if (fname.getBytes().length>60) { //for very long names, we take the first part and the last part and the crc. // should be unique. int len = 30; if (fname.length()<=len) { //just in case I'm missing some utf bytes vs length weirdness here len = fname.length()-1; } fname = fname.substring(0,len) + fname.substring(fname.length()-len/2) + crc32(fname); } return fname; } private static long crc32(String input) { byte[] bytes = input.getBytes(); Checksum checksum = new CRC32(); checksum.update(bytes, 0, bytes.length); return checksum.getValue(); } //fix long/invalid filenames while maintaining those that somehow worked. private static String getProperFName(Context context, File file) { String fname; if (hasOldBookDir(context, file)) { fname = makeOldFName(file); Log.d("Book", "using old fname " + fname); } else { fname = makeFName(file); Log.d("Book", "using new fname " + fname); } return fname; } private static boolean hasOldBookDir(Context context, File file) { String subbook = "book" + makeOldFName(file); return new File(context.getFilesDir(), subbook).exists(); } private static File getBookDir(Context context, File file) { String fname = getProperFName(context, file); String subbook = "book" + fname; return new File(context.getFilesDir(), subbook); } private static SharedPreferences getStorage(Context context, File file) { String fname = getProperFName(context, file); return context.getSharedPreferences(fname, Context.MODE_PRIVATE); } public static void remove(Context context, File file) { try { FsTools.deleteDir(getBookDir(context, file)); String fname = getProperFName(context, file); if (Build.VERSION.SDK_INT >= 24) { context.deleteSharedPreferences(fname); } else { getStorage(context, file).edit().clear().commit(); } } catch (Exception e) { Log.e("Book", e.getMessage(),e); } } public boolean remove() { FsTools.deleteDir(getThisBookDir()); return data.edit().clear().commit(); } File getThisBookDir() { return thisBookDir; } public String getTitle() { return title; } protected void setTitle(String title) { this.title = title; } File getFile() { return file; } private void setFile(File file) { this.file = file; } public File getDataDir() { return dataDir; } protected Context getContext() { return context; } SharedPreferences getSharedPreferences() { return data; } public static String getFileExtensionRX() { return ".*\\.(epub|txt|html?)"; } public static Book getBookHandler(Context context, String filename) throws IOException { Book book = null; if (filename.toLowerCase().endsWith(".epub")) { book = new EpubBook(context); } else if (filename.toLowerCase().endsWith(".txt")) { book = new TxtBook(context); } else if (filename.toLowerCase().endsWith(".html") || filename.toLowerCase().endsWith(".htm")) { book = new HtmlBook(context); } return book; } public static BookMetadata getBookMetaData(Context context, String filename) throws IOException { Book book = getBookHandler(context, filename); if (book!=null) { book.setFile(new File(filename)); return book.getMetaData(); } return null; } protected class ReadPoint { private String id; private Uri point; String getId() { return id; } void setId(String id) { this.id = id; } Uri getPoint() { return point; } void setPoint(Uri point) { this.point = point; } } }