diff --git a/app/src/main/java/com/monke/monkeybook/bean/ChapterListBean.java b/app/src/main/java/com/monke/monkeybook/bean/ChapterListBean.java index 6b0bbb0f3b..606c630b72 100644 --- a/app/src/main/java/com/monke/monkeybook/bean/ChapterListBean.java +++ b/app/src/main/java/com/monke/monkeybook/bean/ChapterListBean.java @@ -18,16 +18,18 @@ public class ChapterListBean implements Parcelable,Cloneable{ private String noteUrl; //对应BookInfoBean noteUrl; - private int durChapterIndex; //当前章节数 @Id private String durChapterUrl; //当前章节对应的文章地址 - private String durChapterName; //当前章节名称 - private String tag; - + //章节内容在文章中的起始位置(本地) + private Long start; + //章节内容在文章中的终止位置(本地) + private Long end; + //是否缓存 private Boolean hasCache = false; + @Transient private BookContentBean bookContentBean = new BookContentBean(); @@ -37,18 +39,22 @@ protected ChapterListBean(Parcel in) { durChapterUrl = in.readString(); durChapterName = in.readString(); tag = in.readString(); + start = in.readLong(); + end = in.readLong(); bookContentBean = in.readParcelable(BookContentBean.class.getClassLoader()); hasCache = in.readByte() != 0; } - @Generated(hash = 1225922702) - public ChapterListBean(String noteUrl, int durChapterIndex, String durChapterUrl, - String durChapterName, String tag, Boolean hasCache) { + @Generated(hash = 1638492836) + public ChapterListBean(String noteUrl, int durChapterIndex, String durChapterUrl, String durChapterName, String tag, + Long start, Long end, Boolean hasCache) { this.noteUrl = noteUrl; this.durChapterIndex = durChapterIndex; this.durChapterUrl = durChapterUrl; this.durChapterName = durChapterName; this.tag = tag; + this.start = start; + this.end = end; this.hasCache = hasCache; } @@ -63,6 +69,8 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeString(durChapterUrl); dest.writeString(durChapterName); dest.writeString(tag); + dest.writeLong(start); + dest.writeLong(end); dest.writeParcelable(bookContentBean, flags); dest.writeByte((byte)(hasCache?1:0)); } @@ -71,6 +79,41 @@ public void writeToParcel(Parcel dest, int flags) { public int describeContents() { return 0; } + + @Transient + public static final Creator CREATOR = new Creator() { + @Override + public ChapterListBean createFromParcel(Parcel in) { + return new ChapterListBean(in); + } + + @Override + public ChapterListBean[] newArray(int size) { + return new ChapterListBean[size]; + } + }; + + @Override + protected Object clone() throws CloneNotSupportedException { + ChapterListBean chapterListBean = (ChapterListBean) super.clone(); + chapterListBean.noteUrl = noteUrl; + chapterListBean.durChapterUrl = durChapterUrl; + chapterListBean.durChapterName = durChapterName; + chapterListBean.tag = tag; + chapterListBean.hasCache = hasCache; + chapterListBean.bookContentBean = new BookContentBean(); + return chapterListBean; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ChapterListBean) { + ChapterListBean chapterListBean = (ChapterListBean) obj; + return Objects.equals(chapterListBean.durChapterUrl, durChapterUrl); + } else { + return false; + } + } public BookContentBean getBookContentBean() { return bookContentBean; @@ -132,38 +175,20 @@ public void setNoteUrl(String noteUrl) { this.noteUrl = noteUrl; } - @Transient - public static final Creator CREATOR = new Creator() { - @Override - public ChapterListBean createFromParcel(Parcel in) { - return new ChapterListBean(in); - } + public Long getStart() { + return this.start; + } - @Override - public ChapterListBean[] newArray(int size) { - return new ChapterListBean[size]; - } - }; + public void setStart(Long start) { + this.start = start; + } - @Override - protected Object clone() throws CloneNotSupportedException { - ChapterListBean chapterListBean = (ChapterListBean) super.clone(); - chapterListBean.noteUrl = noteUrl; - chapterListBean.durChapterUrl = durChapterUrl; - chapterListBean.durChapterName = durChapterName; - chapterListBean.tag = tag; - chapterListBean.hasCache = hasCache; - chapterListBean.bookContentBean = new BookContentBean(); - return chapterListBean; + public Long getEnd() { + return this.end; } - @Override - public boolean equals(Object obj) { - if (obj instanceof ChapterListBean) { - ChapterListBean chapterListBean = (ChapterListBean) obj; - return Objects.equals(chapterListBean.durChapterUrl, durChapterUrl); - } else { - return false; - } + public void setEnd(Long end) { + this.end = end; } + } diff --git a/app/src/main/java/com/monke/monkeybook/dao/ChapterListBeanDao.java b/app/src/main/java/com/monke/monkeybook/dao/ChapterListBeanDao.java index 739b2e0ea7..127868d45f 100644 --- a/app/src/main/java/com/monke/monkeybook/dao/ChapterListBeanDao.java +++ b/app/src/main/java/com/monke/monkeybook/dao/ChapterListBeanDao.java @@ -29,7 +29,9 @@ public static class Properties { public final static Property DurChapterUrl = new Property(2, String.class, "durChapterUrl", true, "DUR_CHAPTER_URL"); public final static Property DurChapterName = new Property(3, String.class, "durChapterName", false, "DUR_CHAPTER_NAME"); public final static Property Tag = new Property(4, String.class, "tag", false, "TAG"); - public final static Property HasCache = new Property(5, Boolean.class, "hasCache", false, "HAS_CACHE"); + public final static Property Start = new Property(5, Long.class, "start", false, "START"); + public final static Property End = new Property(6, Long.class, "end", false, "END"); + public final static Property HasCache = new Property(7, Boolean.class, "hasCache", false, "HAS_CACHE"); } @@ -50,7 +52,9 @@ public static void createTable(Database db, boolean ifNotExists) { "\"DUR_CHAPTER_URL\" TEXT PRIMARY KEY NOT NULL ," + // 2: durChapterUrl "\"DUR_CHAPTER_NAME\" TEXT," + // 3: durChapterName "\"TAG\" TEXT," + // 4: tag - "\"HAS_CACHE\" INTEGER);"); // 5: hasCache + "\"START\" INTEGER," + // 5: start + "\"END\" INTEGER," + // 6: end + "\"HAS_CACHE\" INTEGER);"); // 7: hasCache } /** Drops the underlying database table. */ @@ -84,9 +88,19 @@ protected final void bindValues(DatabaseStatement stmt, ChapterListBean entity) stmt.bindString(5, tag); } + Long start = entity.getStart(); + if (start != null) { + stmt.bindLong(6, start); + } + + Long end = entity.getEnd(); + if (end != null) { + stmt.bindLong(7, end); + } + Boolean hasCache = entity.getHasCache(); if (hasCache != null) { - stmt.bindLong(6, hasCache ? 1L: 0L); + stmt.bindLong(8, hasCache ? 1L: 0L); } } @@ -115,9 +129,19 @@ protected final void bindValues(SQLiteStatement stmt, ChapterListBean entity) { stmt.bindString(5, tag); } + Long start = entity.getStart(); + if (start != null) { + stmt.bindLong(6, start); + } + + Long end = entity.getEnd(); + if (end != null) { + stmt.bindLong(7, end); + } + Boolean hasCache = entity.getHasCache(); if (hasCache != null) { - stmt.bindLong(6, hasCache ? 1L: 0L); + stmt.bindLong(8, hasCache ? 1L: 0L); } } @@ -134,7 +158,9 @@ public ChapterListBean readEntity(Cursor cursor, int offset) { cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2), // durChapterUrl cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3), // durChapterName cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4), // tag - cursor.isNull(offset + 5) ? null : cursor.getShort(offset + 5) != 0 // hasCache + cursor.isNull(offset + 5) ? null : cursor.getLong(offset + 5), // start + cursor.isNull(offset + 6) ? null : cursor.getLong(offset + 6), // end + cursor.isNull(offset + 7) ? null : cursor.getShort(offset + 7) != 0 // hasCache ); return entity; } @@ -146,7 +172,9 @@ public void readEntity(Cursor cursor, ChapterListBean entity, int offset) { entity.setDurChapterUrl(cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2)); entity.setDurChapterName(cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3)); entity.setTag(cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4)); - entity.setHasCache(cursor.isNull(offset + 5) ? null : cursor.getShort(offset + 5) != 0); + entity.setStart(cursor.isNull(offset + 5) ? null : cursor.getLong(offset + 5)); + entity.setEnd(cursor.isNull(offset + 6) ? null : cursor.getLong(offset + 6)); + entity.setHasCache(cursor.isNull(offset + 7) ? null : cursor.getShort(offset + 7) != 0); } @Override diff --git a/app/src/main/java/com/monke/monkeybook/help/BookshelfHelp.java b/app/src/main/java/com/monke/monkeybook/help/BookshelfHelp.java index 7383894334..514b1fd4cb 100644 --- a/app/src/main/java/com/monke/monkeybook/help/BookshelfHelp.java +++ b/app/src/main/java/com/monke/monkeybook/help/BookshelfHelp.java @@ -14,7 +14,9 @@ import com.monke.monkeybook.dao.BookmarkBeanDao; import com.monke.monkeybook.dao.ChapterListBeanDao; import com.monke.monkeybook.dao.DbHelper; +import com.monke.monkeybook.utils.FileUtils; +import java.io.File; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collections; @@ -27,6 +29,18 @@ */ public class BookshelfHelp { + /** + * 根据文件名判断是否被缓存过 (因为可能数据库显示被缓存过,但是文件中却没有的情况,所以需要根据文件判断是否被缓存 + * 过) + * @param folderName : bookId + * @param fileName: chapterName + * @return + */ + public static boolean isChapterCached(String folderName, String fileName){ + File file = new File(Constant.BOOK_CACHE_PATH + folderName + + File.separator + fileName + FileUtils.SUFFIX_NB); + return file.exists(); + } public static List getAllBook() { List bookShelfList = DbHelper.getInstance().getmDaoSession().getBookShelfBeanDao().queryBuilder() diff --git a/app/src/main/java/com/monke/monkeybook/help/Constant.java b/app/src/main/java/com/monke/monkeybook/help/Constant.java index c769f2e2b1..1a02755983 100644 --- a/app/src/main/java/com/monke/monkeybook/help/Constant.java +++ b/app/src/main/java/com/monke/monkeybook/help/Constant.java @@ -2,6 +2,8 @@ import android.support.annotation.StringDef; +import com.monke.monkeybook.utils.FileUtils; + import java.io.File; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -35,6 +37,13 @@ public class Constant { public static final String FORMAT_FILE_DATE = "yyyy-MM-dd"; //RxBus public static final int MSG_SELECTOR = 1; + //BookCachePath (因为getCachePath引用了Context,所以必须是静态变量,不能够是静态常量) + public static String BOOK_CACHE_PATH = FileUtils.getCachePath()+File.separator + + "book_cache"+ File.separator ; + //文件阅读记录保存的路径 + public static String BOOK_RECORD_PATH = FileUtils.getCachePath() + File.separator + + "book_record" + File.separator; + //BookType @StringDef({ diff --git a/app/src/main/java/com/monke/monkeybook/help/Void.java b/app/src/main/java/com/monke/monkeybook/help/Void.java new file mode 100644 index 0000000000..c77ac517a0 --- /dev/null +++ b/app/src/main/java/com/monke/monkeybook/help/Void.java @@ -0,0 +1,8 @@ +package com.monke.monkeybook.help; + +/** + * Created by newbiechen on 17-5-27. + */ + +public final class Void { +} diff --git a/app/src/main/java/com/monke/monkeybook/utils/Charset.java b/app/src/main/java/com/monke/monkeybook/utils/Charset.java new file mode 100644 index 0000000000..9f807dbeb7 --- /dev/null +++ b/app/src/main/java/com/monke/monkeybook/utils/Charset.java @@ -0,0 +1,22 @@ +package com.monke.monkeybook.utils; + +/** + * 编码类型 + */ +public enum Charset { + UTF8("UTF-8"), + UTF16LE("UTF-16LE"), + UTF16BE("UTF-16BE"), + GBK("GBK"); + + private String mName; + public static final byte BLANK = 0x0a; + + private Charset(String name) { + mName = name; + } + + public String getName() { + return mName; + } +} diff --git a/app/src/main/java/com/monke/monkeybook/utils/FileUtils.java b/app/src/main/java/com/monke/monkeybook/utils/FileUtils.java new file mode 100644 index 0000000000..8b936df485 --- /dev/null +++ b/app/src/main/java/com/monke/monkeybook/utils/FileUtils.java @@ -0,0 +1,271 @@ +package com.monke.monkeybook.utils; + +import android.os.Environment; + + +import com.monke.monkeybook.MApplication; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; + +import io.reactivex.Single; +import io.reactivex.SingleEmitter; +import io.reactivex.SingleOnSubscribe; + +/** + * Created by newbiechen on 17-5-11. + */ + +public class FileUtils { + //采用自己的格式去设置文件,防止文件被系统文件查询到 + public static final String SUFFIX_NB = ".nb"; + public static final String SUFFIX_TXT = ".txt"; + public static final String SUFFIX_EPUB = ".epub"; + public static final String SUFFIX_PDF = ".pdf"; + + //获取文件夹 + public static File getFolder(String filePath){ + File file = new File(filePath); + //如果文件夹不存在,就创建它 + if (!file.exists()){ + file.mkdirs(); + } + return file; + } + + //获取文件 + public static synchronized File getFile(String filePath){ + File file = new File(filePath); + try { + if (!file.exists()){ + //创建父类文件夹 + getFolder(file.getParent()); + //创建文件 + file.createNewFile(); + } + } catch (IOException e) { + e.printStackTrace(); + } + return file; + } + + //获取Cache文件夹 + public static String getCachePath(){ + if (isSdCardExist()){ + return MApplication.getInstance() + .getExternalCacheDir() + .getAbsolutePath(); + } + else{ + return MApplication.getInstance() + .getCacheDir() + .getAbsolutePath(); + } + } + + public static long getDirSize(File file){ + //判断文件是否存在 + if (file.exists()) { + //如果是目录则递归计算其内容的总大小 + if (file.isDirectory()) { + File[] children = file.listFiles(); + long size = 0; + for (File f : children) + size += getDirSize(f); + return size; + } else { + return file.length(); + } + } else { + return 0; + } + } + + public static String getFileSize(long size) { + if (size <= 0) return "0"; + final String[] units = new String[]{"b", "kb", "M", "G", "T"}; + //计算单位的,原理是利用lg,公式是 lg(1024^n) = nlg(1024),最后 nlg(1024)/lg(1024) = n。 + int digitGroups = (int) (Math.log10(size) / Math.log10(1024)); + //计算原理是,size/单位值。单位值指的是:比如说b = 1024,KB = 1024^2 + return new DecimalFormat("#,##0.##").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups]; + } + + /** + * 本来是获取File的内容的。但是为了解决文本缩进、换行的问题 + * 这个方法就是专门用来获取书籍的... + * + * 应该放在BookRepository中。。。 + * @param file + * @return + */ + public static String getFileContent(File file){ + Reader reader = null; + String str = null; + StringBuilder sb = new StringBuilder(); + try { + reader = new FileReader(file); + BufferedReader br = new BufferedReader(reader); + while ((str = br.readLine()) != null){ + //过滤空语句 + if (!str.equals("")){ + //由于sb会自动过滤\n,所以需要加上去 + sb.append(" "+str+"\n"); + } + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + }finally { + IOUtils.close(reader); + } + return sb.toString(); + } + + //判断是否挂载了SD卡 + public static boolean isSdCardExist(){ + if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){ + return true; + } + return false; + } + + //递归删除文件夹下的数据 + public static synchronized void deleteFile(String filePath){ + File file = new File(filePath); + if (!file.exists()) return; + + if (file.isDirectory()){ + File[] files = file.listFiles(); + for (File subFile : files){ + String path = subFile.getPath(); + deleteFile(path); + } + } + //删除文件 + file.delete(); + } + + //由于递归的耗时问题,取巧只遍历内部三层 + + //获取txt文件 + public static List getTxtFiles(String filePath,int layer){ + List txtFiles = new ArrayList(); + File file = new File(filePath); + + //如果层级为 3,则直接返回 + if (layer == 3){ + return txtFiles; + } + + //获取文件夹 + File[] dirs = file.listFiles( + pathname -> { + if (pathname.isDirectory() && !pathname.getName().startsWith(".")) { + return true; + } + //获取txt文件 + else if(pathname.getName().endsWith(".txt")){ + txtFiles.add(pathname); + return false; + } + else{ + return false; + } + } + ); + //遍历文件夹 + for (File dir : dirs){ + //递归遍历txt文件 + txtFiles.addAll(getTxtFiles(dir.getPath(),layer + 1)); + } + return txtFiles; + } + + //由于遍历比较耗时 + public static Single> getSDTxtFile(){ + //外部存储卡路径 + String rootPath = Environment.getExternalStorageDirectory().getPath(); + return Single.create(new SingleOnSubscribe>() { + @Override + public void subscribe(SingleEmitter> e) throws Exception { + List files = getTxtFiles(rootPath,0); + e.onSuccess(files); + } + }); + } + + //获取文件的编码格式 + public static Charset getCharset(String fileName) { + BufferedInputStream bis = null; + Charset charset = Charset.GBK; + byte[] first3Bytes = new byte[3]; + try { + boolean checked = false; + bis = new BufferedInputStream(new FileInputStream(fileName)); + bis.mark(0); + int read = bis.read(first3Bytes, 0, 3); + if (read == -1) + return charset; + if (first3Bytes[0] == (byte) 0xEF + && first3Bytes[1] == (byte) 0xBB + && first3Bytes[2] == (byte) 0xBF) { + charset = Charset.UTF8; + checked = true; + } + /* + * 不支持 UTF16LE 和 UTF16BE + else if (first3Bytes[0] == (byte) 0xFF && first3Bytes[1] == (byte) 0xFE) { + charset = Charset.UTF16LE; + checked = true; + } else if (first3Bytes[0] == (byte) 0xFE + && first3Bytes[1] == (byte) 0xFF) { + charset = Charset.UTF16BE; + checked = true; + } else */ + + bis.mark(0); + if (!checked) { + while ((read = bis.read()) != -1) { + if (read >= 0xF0) + break; + if (0x80 <= read && read <= 0xBF) // 单独出现BF以下的,也算是GBK + break; + if (0xC0 <= read && read <= 0xDF) { + read = bis.read(); + if (0x80 <= read && read <= 0xBF) // 双字节 (0xC0 - 0xDF) + // (0x80 - 0xBF),也可能在GB编码内 + continue; + else + break; + } else if (0xE0 <= read && read <= 0xEF) {// 也有可能出错,但是几率较小 + read = bis.read(); + if (0x80 <= read && read <= 0xBF) { + read = bis.read(); + if (0x80 <= read && read <= 0xBF) { + charset = Charset.UTF8; + break; + } else + break; + } else + break; + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + IOUtils.close(bis); + } + return charset; + } +} diff --git a/app/src/main/java/com/monke/monkeybook/utils/MD5Utils.java b/app/src/main/java/com/monke/monkeybook/utils/MD5Utils.java new file mode 100644 index 0000000000..16c51ada73 --- /dev/null +++ b/app/src/main/java/com/monke/monkeybook/utils/MD5Utils.java @@ -0,0 +1,43 @@ +package com.monke.monkeybook.utils; + +/** + * Created by newbiechen on 2018/1/1. + */ + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + *@Description: 将字符串转化为MD5 + */ + +public class MD5Utils { + + public static String strToMd5By32(String str){ + String reStr = null; + try { + MessageDigest md5 = MessageDigest.getInstance("MD5"); + byte[] bytes = md5.digest(str.getBytes()); + StringBuffer stringBuffer = new StringBuffer(); + for (byte b : bytes){ + int bt = b&0xff; + if (bt < 16){ + stringBuffer.append(0); + } + stringBuffer.append(Integer.toHexString(bt)); + } + reStr = stringBuffer.toString(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return reStr; + } + + public static String strToMd5By16(String str){ + String reStr = strToMd5By32(str); + if (reStr != null){ + reStr = reStr.substring(8, 24); + } + return reStr; + } +} diff --git a/app/src/main/java/com/monke/monkeybook/widget/page/LocalPageLoader.java b/app/src/main/java/com/monke/monkeybook/widget/page/LocalPageLoader.java new file mode 100644 index 0000000000..54b2f7f1dc --- /dev/null +++ b/app/src/main/java/com/monke/monkeybook/widget/page/LocalPageLoader.java @@ -0,0 +1,437 @@ +package com.monke.monkeybook.widget.page; + +import com.monke.monkeybook.bean.BookShelfBean; +import com.monke.monkeybook.bean.ChapterListBean; +import com.monke.monkeybook.dao.DbHelper; +import com.monke.monkeybook.help.Constant; +import com.monke.monkeybook.help.Void; +import com.monke.monkeybook.utils.Charset; +import com.monke.monkeybook.utils.FileUtils; +import com.monke.monkeybook.utils.IOUtils; +import com.monke.monkeybook.utils.MD5Utils; +import com.monke.monkeybook.utils.RxUtils; +import com.monke.monkeybook.utils.StringUtils; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.RandomAccessFile; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.reactivex.Single; +import io.reactivex.SingleEmitter; +import io.reactivex.SingleObserver; +import io.reactivex.SingleOnSubscribe; +import io.reactivex.disposables.Disposable; + +/** + * Created by newbiechen on 17-7-1. + * 问题: + * 1. 异常处理没有做好 + */ + +public class LocalPageLoader extends PageLoader { + private static final String TAG = "LocalPageLoader"; + //默认从文件中获取数据的长度 + private final static int BUFFER_SIZE = 512 * 1024; + //没有标题的时候,每个章节的最大长度 + private final static int MAX_LENGTH_WITH_NO_CHAPTER = 10 * 1024; + + // "序(章)|前言" + private final static Pattern mPreChapterPattern = Pattern.compile("^(\\s{0,10})((\u5e8f[\u7ae0\u8a00]?)|(\u524d\u8a00)|(\u6954\u5b50))(\\s{0,10})$", Pattern.MULTILINE); + + //正则表达式章节匹配模式 + // "(第)([0-9零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,10})([章节回集卷])(.*)" + private static final String[] CHAPTER_PATTERNS = new String[]{"^(.{0,8})(\u7b2c)([0-9\u96f6\u4e00\u4e8c\u4e24\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u58f9\u8d30\u53c1\u8086\u4f0d\u9646\u67d2\u634c\u7396\u62fe\u4f70\u4edf]{1,10})([\u7ae0\u8282\u56de\u96c6\u5377])(.{0,30})$", + "^(\\s{0,4})([\\(\u3010\u300a]?(\u5377)?)([0-9\u96f6\u4e00\u4e8c\u4e24\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u58f9\u8d30\u53c1\u8086\u4f0d\u9646\u67d2\u634c\u7396\u62fe\u4f70\u4edf]{1,10})([\\.:\uff1a\u0020\f\t])(.{0,30})$", + "^(\\s{0,4})([\\(\uff08\u3010\u300a])(.{0,30})([\\)\uff09\u3011\u300b])(\\s{0,2})$", + "^(\\s{0,4})(\u6b63\u6587)(.{0,20})$", + "^(.{0,4})(Chapter|chapter)(\\s{0,4})([0-9]{1,4})(.{0,30})$"}; + + //章节解析模式 + private Pattern mChapterPattern = null; + //获取书本的文件 + private File mBookFile; + //编码类型 + private Charset mCharset; + + private Disposable mChapterDisp = null; + + public LocalPageLoader(PageView pageView, BookShelfBean collBook) { + super(pageView, collBook); + mStatus = STATUS_PARING; + } + + private List convertTxtChapter(List bookChapters) { + List txtChapters = new ArrayList<>(bookChapters.size()); + for (ChapterListBean bean : bookChapters) { + TxtChapter chapter = new TxtChapter(); + chapter.title = bean.getDurChapterName(); + chapter.start = bean.getStart(); + chapter.end = bean.getEnd(); + txtChapters.add(chapter); + } + return txtChapters; + } + + /** + * 未完成的部分: + * 1. 序章的添加 + * 2. 章节存在的书本的虚拟分章效果 + * + * @throws IOException + */ + private void loadChapters() throws IOException { + List chapters = new ArrayList<>(); + //获取文件流 + RandomAccessFile bookStream = new RandomAccessFile(mBookFile, "r"); + //寻找匹配文章标题的正则表达式,判断是否存在章节名 + boolean hasChapter = checkChapterType(bookStream); + //加载章节 + byte[] buffer = new byte[BUFFER_SIZE]; + //获取到的块起始点,在文件中的位置 + long curOffset = 0; + //block的个数 + int blockPos = 0; + //读取的长度 + int length; + + //获取文件中的数据到buffer,直到没有数据为止 + while ((length = bookStream.read(buffer, 0, buffer.length)) > 0) { + ++blockPos; + //如果存在Chapter + if (hasChapter) { + //将数据转换成String + String blockContent = new String(buffer, 0, length, mCharset.getName()); + //当前Block下使过的String的指针 + int seekPos = 0; + //进行正则匹配 + Matcher matcher = mChapterPattern.matcher(blockContent); + //如果存在相应章节 + while (matcher.find()) { + //获取匹配到的字符在字符串中的起始位置 + int chapterStart = matcher.start(); + + //如果 seekPos == 0 && nextChapterPos != 0 表示当前block处前面有一段内容 + //第一种情况一定是序章 第二种情况可能是上一个章节的内容 + if (seekPos == 0 && chapterStart != 0) { + //获取当前章节的内容 + String chapterContent = blockContent.substring(seekPos, chapterStart); + //设置指针偏移 + seekPos += chapterContent.length(); + + //如果当前对整个文件的偏移位置为0的话,那么就是序章 + if (curOffset == 0) { + //创建序章 + TxtChapter preChapter = new TxtChapter(); + preChapter.title = "序章"; + preChapter.start = 0; + preChapter.end = chapterContent.getBytes(mCharset.getName()).length; //获取String的byte值,作为最终值 + + //如果序章大小大于30才添加进去 + if (preChapter.end - preChapter.start > 30) { + chapters.add(preChapter); + } + + //创建当前章节 + TxtChapter curChapter = new TxtChapter(); + curChapter.title = matcher.group(); + curChapter.start = preChapter.end; + chapters.add(curChapter); + } + //否则就block分割之后,上一个章节的剩余内容 + else { + //获取上一章节 + TxtChapter lastChapter = chapters.get(chapters.size() - 1); + //将当前段落添加上一章去 + lastChapter.end += chapterContent.getBytes(mCharset.getName()).length; + + //如果章节内容太小,则移除 + if (lastChapter.end - lastChapter.start < 30) { + chapters.remove(lastChapter); + } + + //创建当前章节 + TxtChapter curChapter = new TxtChapter(); + curChapter.title = matcher.group(); + curChapter.start = lastChapter.end; + chapters.add(curChapter); + } + } else { + //是否存在章节 + if (chapters.size() != 0) { + //获取章节内容 + String chapterContent = blockContent.substring(seekPos, matcher.start()); + seekPos += chapterContent.length(); + + //获取上一章节 + TxtChapter lastChapter = chapters.get(chapters.size() - 1); + lastChapter.end = lastChapter.start + chapterContent.getBytes(mCharset.getName()).length; + + //如果章节内容太小,则移除 + if (lastChapter.end - lastChapter.start < 30) { + chapters.remove(lastChapter); + } + + //创建当前章节 + TxtChapter curChapter = new TxtChapter(); + curChapter.title = matcher.group(); + curChapter.start = lastChapter.end; + chapters.add(curChapter); + } + //如果章节不存在则创建章节 + else { + TxtChapter curChapter = new TxtChapter(); + curChapter.title = matcher.group(); + curChapter.start = 0; + chapters.add(curChapter); + } + } + } + } + //进行本地虚拟分章 + else { + //章节在buffer的偏移量 + int chapterOffset = 0; + //当前剩余可分配的长度 + int strLength = length; + //分章的位置 + int chapterPos = 0; + + while (strLength > 0) { + ++chapterPos; + //是否长度超过一章 + if (strLength > MAX_LENGTH_WITH_NO_CHAPTER) { + //在buffer中一章的终止点 + int end = length; + //寻找换行符作为终止点 + for (int i = chapterOffset + MAX_LENGTH_WITH_NO_CHAPTER; i < length; ++i) { + if (buffer[i] == Charset.BLANK) { + end = i; + break; + } + } + TxtChapter chapter = new TxtChapter(); + chapter.title = "第" + blockPos + "章" + "(" + chapterPos + ")"; + chapter.start = curOffset + chapterOffset + 1; + chapter.end = curOffset + end; + chapters.add(chapter); + //减去已经被分配的长度 + strLength = strLength - (end - chapterOffset); + //设置偏移的位置 + chapterOffset = end; + } else { + TxtChapter chapter = new TxtChapter(); + chapter.title = "第" + blockPos + "章" + "(" + chapterPos + ")"; + chapter.start = curOffset + chapterOffset + 1; + chapter.end = curOffset + length; + chapters.add(chapter); + strLength = 0; + } + } + } + + //block的偏移点 + curOffset += length; + + if (hasChapter) { + //设置上一章的结尾 + TxtChapter lastChapter = chapters.get(chapters.size() - 1); + lastChapter.end = curOffset; + } + + //当添加的block太多的时候,执行GC + if (blockPos % 15 == 0) { + System.gc(); + System.runFinalization(); + } + } + + mChapterList = chapters; + IOUtils.close(bookStream); + + System.gc(); + System.runFinalization(); + } + + /** + * 从文件中提取一章的内容 + * + * @param chapter + * @return + */ + private byte[] getChapterContent(TxtChapter chapter) { + RandomAccessFile bookStream = null; + try { + bookStream = new RandomAccessFile(mBookFile, "r"); + bookStream.seek(chapter.start); + int extent = (int) (chapter.end - chapter.start); + byte[] content = new byte[extent]; + bookStream.read(content, 0, extent); + return content; + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + IOUtils.close(bookStream); + } + + return new byte[0]; + } + + /** + * 1. 检查文件中是否存在章节名 + * 2. 判断文件中使用的章节名类型的正则表达式 + * + * @return 是否存在章节名 + */ + private boolean checkChapterType(RandomAccessFile bookStream) throws IOException { + //首先获取128k的数据 + byte[] buffer = new byte[BUFFER_SIZE / 4]; + int length = bookStream.read(buffer, 0, buffer.length); + //进行章节匹配 + for (String str : CHAPTER_PATTERNS) { + Pattern pattern = Pattern.compile(str, Pattern.MULTILINE); + Matcher matcher = pattern.matcher(new String(buffer, 0, length, mCharset.getName())); + //如果匹配存在,那么就表示当前章节使用这种匹配方式 + if (matcher.find()) { + mChapterPattern = pattern; + //重置指针位置 + bookStream.seek(0); + return true; + } + } + + //重置指针位置 + bookStream.seek(0); + return false; + } + + @Override + public void saveRecord() { + super.saveRecord(); + //修改当前COllBook记录 + if (mCollBook != null && isChapterListPrepare) { + //表示当前CollBook已经阅读 +// mCollBook.setIsUpdate(false); +// mCollBook.setLastChapter(mChapterList.get(mCurChapterPos).getTitle()); +// mCollBook.setLastRead(StringUtils. +// dateConvert(System.currentTimeMillis(), Constant.FORMAT_BOOK_DATE)); +// //直接更新 +// BookRepository.getInstance() +// .saveCollBook(mCollBook); + } + } + + @Override + public void closeBook() { + super.closeBook(); + if (mChapterDisp != null) { + mChapterDisp.dispose(); + mChapterDisp = null; + } + } + + @Override + public void refreshChapterList() { + // 对于文件是否存在,或者为空的判断,不作处理。 ==> 在文件打开前处理过了。 + mBookFile = new File(mCollBook.getNoteUrl()); + //获取文件编码 + mCharset = FileUtils.getCharset(mBookFile.getAbsolutePath()); + + Long lastModified = mBookFile.lastModified(); + + // 判断文件是否已经加载过,并具有缓存 + if (!mCollBook.getHasUpdate() + && mCollBook.getChapterList() != null) { + + mChapterList = convertTxtChapter(mCollBook.getChapterList()); + isChapterListPrepare = true; + + //提示目录加载完成 + if (mPageChangeListener != null) { + mPageChangeListener.onCategoryFinish(mChapterList); + } + + // 加载并显示当前章节 + openChapter(); + + return; + } + + // 通过RxJava异步处理分章事件 + Single.create(new SingleOnSubscribe() { + @Override + public void subscribe(SingleEmitter e) throws Exception { + loadChapters(); + e.onSuccess(new Void()); + } + }).compose(RxUtils::toSimpleSingle) + .subscribe(new SingleObserver() { + @Override + public void onSubscribe(Disposable d) { + mChapterDisp = d; + } + + @Override + public void onSuccess(Void value) { + mChapterDisp = null; + isChapterListPrepare = true; + + // 提示目录加载完成 + if (mPageChangeListener != null) { + mPageChangeListener.onCategoryFinish(mChapterList); + } + + // 存储章节到数据库 + List bookChapterBeanList = new ArrayList<>(); + for (int i = 0; i < mChapterList.size(); ++i) { + TxtChapter chapter = mChapterList.get(i); + ChapterListBean bean = new ChapterListBean(); + bean.setDurChapterIndex(i); + bean.setDurChapterUrl(MD5Utils.strToMd5By16(mBookFile.getAbsolutePath() + + File.separator + chapter.title)); // 将路径+i 作为唯一值 + bean.setDurChapterName(chapter.getTitle()); + bean.setStart(chapter.getStart()); + bean.setHasCache(true); + bean.setEnd(chapter.getEnd()); + bookChapterBeanList.add(bean); + } + mCollBook.getBookInfoBean().setChapterList(bookChapterBeanList); + mCollBook.setFinalRefreshData(lastModified); + + DbHelper.getInstance().getmDaoSession().getChapterListBeanDao().insertOrReplaceInTx(bookChapterBeanList); + DbHelper.getInstance().getmDaoSession().getBookShelfBeanDao().insertOrReplaceInTx(mCollBook); + + // 加载并显示当前章节 + openChapter(); + } + + @Override + public void onError(Throwable e) { + chapterError(); + } + }); + } + + @Override + protected BufferedReader getChapterReader(TxtChapter chapter) throws Exception { + //从文件中获取数据 + byte[] content = getChapterContent(chapter); + ByteArrayInputStream bais = new ByteArrayInputStream(content); + BufferedReader br = new BufferedReader(new InputStreamReader(bais, mCharset.getName())); + return br; + } + + @Override + protected boolean hasChapterData(TxtChapter chapter) { + return true; + } +} diff --git a/app/src/main/java/com/monke/monkeybook/widget/page/NetPageLoader.java b/app/src/main/java/com/monke/monkeybook/widget/page/NetPageLoader.java index f118d8d8a0..53b1b08420 100644 --- a/app/src/main/java/com/monke/monkeybook/widget/page/NetPageLoader.java +++ b/app/src/main/java/com/monke/monkeybook/widget/page/NetPageLoader.java @@ -1,9 +1,10 @@ package com.monke.monkeybook.widget.page; - import com.monke.monkeybook.bean.BookShelfBean; import com.monke.monkeybook.bean.ChapterListBean; +import com.monke.monkeybook.help.BookshelfHelp; import com.monke.monkeybook.help.Constant; +import com.monke.monkeybook.utils.FileUtils; import java.io.BufferedReader; import java.io.File; @@ -58,7 +59,8 @@ public void refreshChapterList() { @Override protected BufferedReader getChapterReader(TxtChapter chapter) throws Exception { - File file = new File(""); + File file = new File(Constant.BOOK_CACHE_PATH + mCollBook.getNoteUrl() + + File.separator + chapter.title + FileUtils.SUFFIX_NB); if (!file.exists()) return null; Reader reader = new FileReader(file); @@ -68,7 +70,7 @@ protected BufferedReader getChapterReader(TxtChapter chapter) throws Exception { @Override protected boolean hasChapterData(TxtChapter chapter) { - return true; + return BookshelfHelp.isChapterCached(mCollBook.getNoteUrl(), chapter.title); } // 装载上一章节的内容 @@ -205,7 +207,15 @@ private void requestChapters(int start, int end) { @Override public void saveRecord() { super.saveRecord(); - + if (mCollBook != null && isChapterListPrepare) { + //表示当前CollBook已经阅读 +// mCollBook.setIsUpdate(false); +// mCollBook.setLastRead(StringUtils. +// dateConvert(System.currentTimeMillis(), Constant.FORMAT_BOOK_DATE)); +// //直接更新 +// BookRepository.getInstance() +// .saveCollBook(mCollBook); + } } } diff --git a/app/src/main/java/com/monke/monkeybook/widget/page/PageLoader.java b/app/src/main/java/com/monke/monkeybook/widget/page/PageLoader.java index 7cf0533a17..39f4d2832d 100644 --- a/app/src/main/java/com/monke/monkeybook/widget/page/PageLoader.java +++ b/app/src/main/java/com/monke/monkeybook/widget/page/PageLoader.java @@ -3,6 +3,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; @@ -11,6 +12,7 @@ import android.text.TextPaint; import com.monke.monkeybook.bean.BookShelfBean; +import com.monke.monkeybook.help.Constant; import com.monke.monkeybook.help.ReadBookControl; import com.monke.monkeybook.utils.IOUtils; import com.monke.monkeybook.utils.RxUtils; @@ -80,9 +82,11 @@ public abstract class PageLoader { // 绘制小说内容的画笔 private TextPaint mTextPaint; // 阅读器的配置选项 - private ReadBookControl readBookControl; + private ReadBookControl mSettingManager; // 被遮盖的页,或者认为被取消显示的页 private TxtPage mCancelPage; + // 存储阅读记录类 +// private BookRecordBean mBookRecord; private Disposable mPreLoadDisp; @@ -98,6 +102,8 @@ public abstract class PageLoader { private boolean isClose; // 页面的翻页效果模式 private PageMode mPageMode; + // 加载器的颜色主题 + private PageStyle mPageStyle; //当前是否是夜间模式 private boolean isNightMode; //书籍绘制区域的宽高 @@ -151,14 +157,15 @@ public PageLoader(PageView pageView, BookShelfBean collBook) { private void initData() { // 获取配置管理器 - readBookControl = ReadBookControl.getInstance(); + mSettingManager = ReadBookControl.getInstance(); // 获取配置参数 - mPageMode = readBookControl.getPageMode(readBookControl.getPageMode()) ; + mPageMode = mSettingManager.getPageMode(mSettingManager.getPageMode()); + mPageStyle = PageStyle.values()[1]; // 初始化参数 mMarginWidth = ScreenUtils.dpToPx(DEFAULT_MARGIN_WIDTH); mMarginHeight = ScreenUtils.dpToPx(DEFAULT_MARGIN_HEIGHT); // 配置文字有关的参数 - setUpTextParams(readBookControl.getTextSize()); + setUpTextParams(mSettingManager.getTextSize()); } /** @@ -205,14 +212,13 @@ private void initPaint() { mBgPaint = new Paint(); mBgPaint.setColor(mBgColor); - // 绘制电池的画笔 mBatteryPaint = new Paint(); mBatteryPaint.setAntiAlias(true); mBatteryPaint.setDither(true); // 初始化页面样式 - setNightMode(readBookControl.getIsNightTheme()); + setNightMode(mSettingManager.getIsNightTheme()); } private void initPageView() { @@ -390,25 +396,40 @@ public void setTextSize(int textSize) { * @param nightMode */ public void setNightMode(boolean nightMode) { + isNightMode = nightMode; - setPageStyle(readBookControl); - + if (isNightMode) { + mBatteryPaint.setColor(Color.WHITE); + setPageStyle(PageStyle.NIGHT); + } else { + mBatteryPaint.setColor(Color.BLACK); + setPageStyle(mPageStyle); + } } /** * 设置页面样式 + * + * @param pageStyle:页面样式 */ - public void setPageStyle(ReadBookControl readBookControl) { + public void setPageStyle(PageStyle pageStyle) { + if (pageStyle != PageStyle.NIGHT) { + mPageStyle = pageStyle; + } + + if (isNightMode && pageStyle != PageStyle.NIGHT) { + return; + } // 设置当前颜色样式 - mTextColor = ContextCompat.getColor(mContext, readBookControl.getTextColor()); -// mBgColor = ContextCompat.getColor(mContext, pageStyle.getBgColor()); + mTextColor = ContextCompat.getColor(mContext, pageStyle.getFontColor()); + mBgColor = ContextCompat.getColor(mContext, pageStyle.getBgColor()); mTipPaint.setColor(mTextColor); mTitlePaint.setColor(mTextColor); mTextPaint.setColor(mTextColor); -// mBgPaint.set(mBgColor); + mBgPaint.setColor(mBgColor); mPageView.drawCurPage(false); } @@ -518,11 +539,11 @@ public int getMarginHeight() { * 保存阅读记录 */ public void saveRecord() { -// -// if (mChapterList.isEmpty()) { -// return; -// } -// + + if (mChapterList.isEmpty()) { + return; + } + // mBookRecord.setBookId(mCollBook.get_id()); // mBookRecord.setChapter(mCurChapterPos); // @@ -541,15 +562,9 @@ public void saveRecord() { * 初始化书籍 */ private void prepareBook() { -// mBookRecord = BookRepository.getInstance() -// .getBookRecord(mCollBook.get_id()); -// -// if (mBookRecord == null) { -// mBookRecord = new BookRecordBean(); -// } -// -// mCurChapterPos = mBookRecord.getChapter(); -// mLastChapterPos = mCurChapterPos; + + mCurChapterPos = mCollBook.getDurChapter(); + mLastChapterPos = mCurChapterPos; } /** @@ -579,7 +594,7 @@ public void openChapter() { if (parseCurChapter()) { // 如果章节从未打开 if (!isChapterOpen) { - int position = mCollBook.getDurChapterPage(); + int position = mCollBook.getDurChapter(); // 防止记录页的页号,大于当前最大页号 if (position >= mCurPageList.size()) { @@ -772,7 +787,7 @@ private void drawBackground(Bitmap bitmap, boolean isUpdate) { /******绘制当前时间********/ //底部的字显示的位置Y float y = mDisplayHeight - mTipPaint.getFontMetrics().bottom - tipMarginHeight; - String time = StringUtils.dateConvert(System.currentTimeMillis(), "HH:mm"); + String time = StringUtils.dateConvert(System.currentTimeMillis(), Constant.FORMAT_TIME); float x = outFrameLeft - mTipPaint.measureText(time) - ScreenUtils.dpToPx(4); canvas.drawText(time, x, y, mTipPaint); } diff --git a/app/src/main/java/com/monke/monkeybook/widget/page/PageStyle.java b/app/src/main/java/com/monke/monkeybook/widget/page/PageStyle.java new file mode 100644 index 0000000000..457e814903 --- /dev/null +++ b/app/src/main/java/com/monke/monkeybook/widget/page/PageStyle.java @@ -0,0 +1,36 @@ +package com.monke.monkeybook.widget.page; + +import android.support.annotation.ColorRes; + +import com.monke.monkeybook.R; + + +/** + * Created by newbiechen on 2018/2/5. + * 作用:页面的展示风格。 + */ + +public enum PageStyle { + BG_0(R.color.nb_read_font_1, R.color.nb_read_bg_1), + BG_1(R.color.nb_read_font_2, R.color.nb_read_bg_2), + BG_2(R.color.nb_read_font_3, R.color.nb_read_bg_3), + BG_3(R.color.nb_read_font_4, R.color.nb_read_bg_4), + BG_4(R.color.nb_read_font_5, R.color.nb_read_bg_5), + NIGHT(R.color.nb_read_font_night, R.color.nb_read_bg_night),; + + private int fontColor; + private int bgColor; + + PageStyle(@ColorRes int fontColor, @ColorRes int bgColor) { + this.fontColor = fontColor; + this.bgColor = bgColor; + } + + public int getFontColor() { + return fontColor; + } + + public int getBgColor() { + return bgColor; + } +} diff --git a/app/src/main/java/com/monke/monkeybook/widget/page/PageView.java b/app/src/main/java/com/monke/monkeybook/widget/page/PageView.java index aea8a60b47..72ede500d4 100644 --- a/app/src/main/java/com/monke/monkeybook/widget/page/PageView.java +++ b/app/src/main/java/com/monke/monkeybook/widget/page/PageView.java @@ -18,6 +18,8 @@ import com.monke.monkeybook.widget.animation.SimulationPageAnim; import com.monke.monkeybook.widget.animation.SlidePageAnim; +import java.util.Objects; + /** * Created by Administrator on 2016/8/29 0029. @@ -113,7 +115,7 @@ void setPageMode(PageMode pageMode) { mPageLoader.getMarginHeight(), this, mPageAnimListener); break; default: - mPageAnim = new CoverPageAnim(mViewWidth, mViewHeight, this, mPageAnimListener); + mPageAnim = new SimulationPageAnim(mViewWidth, mViewHeight, this, mPageAnimListener); } } @@ -346,11 +348,11 @@ public PageLoader getPageLoader(BookShelfBean collBook) { return mPageLoader; } // 根据书籍类型,获取具体的加载器 -// if (collBook.isLocal()) { -// mPageLoader = new LocalPageLoader(this, collBook); -// } else { + if (Objects.equals(collBook.getTag(), BookShelfBean.LOCAL_TAG)) { + mPageLoader = new LocalPageLoader(this, collBook); + } else { mPageLoader = new NetPageLoader(this, collBook); -// } + } // 判断是否 PageView 已经初始化完成 if (mViewWidth != 0 || mViewHeight != 0) { // 初始化 PageLoader 的屏幕大小 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 048de0e647..22a5febd86 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -56,4 +56,22 @@ #FFD4D4D4 #64000000 + + + + #CEC29C + #CCEBCC + #AAAAAA + #D1CEC5 + #001C27 + #DDCEC29C + + #2C2C2C + #2F332D + #92918C + #383429 + #627176 + + #000000 + #99ffffff