package com.novelbook.android.utils; import android.content.ContentValues; import android.os.Environment; import android.text.TextUtils; import android.util.Log; import com.novelbook.android.bean.Cache; import com.novelbook.android.db.BookChapter; import com.novelbook.android.db.Book; import org.litepal.LitePal; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class BookUtil { public static final String TAG ="BookUtil"; private static final String storagePath = Environment.getExternalStorageDirectory() + "/zhuike"; private static final String cachedPath = storagePath + "/cache/"; private static final String chapterPath = storagePath + "/chapter/"; private static final String charachterType = "utf-8";//"UTF-16LE"; //存储的字符数 public static final int cachedSize = 30000; // protected final ArrayList> myArray = new ArrayList<>(); public static final String lineBreakChar ="\n"; protected final ArrayList myArray = new ArrayList<>(); //目录 private List directoryList = new ArrayList<>(); private String m_strCharsetName; private String bookName; private String bookPath; public void setBookLen(long bookLen) { this.bookLen = bookLen; } private long bookLen; private long position; private Book book; public void setChapterNo(int chapterNo) { this.chapterNo = chapterNo; } public int getChapterNo() { return chapterNo; } private int chapterNo;//当前章节 public String getLineBreakChar(){ return "\n"; } public BookUtil(){ checkAndCreateDir(storagePath); checkAndCreateDir(chapterPath); checkAndCreateDir(cachedPath); } private void checkAndCreateDir(String path){ File file = new File(path); if (!file.exists()){ file.mkdir(); } } public synchronized void openBook(Book book) throws IOException { this.book = book; //如果当前缓存不是要打开的书本就缓存书本同时删除缓存 //TODO 构建新的缓存策略,几个选项,1:每本书一个缓存 2:控制缓存总大小,超过限制删除旧缓存 3:网络小说的缓存 directoryList = LitePal.where("bookId=?",book.getId()+"").find(BookChapter.class); for(BookChapter c :directoryList){ Log.d(TAG, String.format("bookchapter :%s,fileName :%s, chapter Size %s",c.getChapterName(),c.getChapterPath(),c.getLength())); } chaptCache = new HashMap(); if(directoryList.isEmpty()) { if (bookPath == null || !bookPath.equals(book.getBookpath())) { cleanCacheFile(); this.bookPath = book.getBookpath(); bookName = FileUtils.getFileName(bookPath); cacheBook(); } } } private void cleanCacheFile(){ File file = new File(cachedPath ); if (!file.exists()){ file.mkdir(); }else{ File[] files = file.listFiles(); for (int i = 0; i < files.length;i++){ files[i].delete(); } } file = new File(getChapterPath()); if (!file.exists()){ file.mkdir(); }else{ File[] files = file.listFiles(); for (int i = 0; i < files.length;i++){ files[i].delete(); } } } public int next(boolean back){ position += 1; if (position > bookLen){ position = bookLen; return -1; } char result = chaptCurrent(); //current(); if (back) { position -= 1; } return result; } public char[] nextLine(){ if (position >= bookLen){ return null; } String line = ""; while (position < bookLen){ int word = next(false); if (word == -1){ break; } char wordChar = (char) word; if ((wordChar + "").equals("\n") ){// if ((wordChar + "").equals("\r") && (((char)next(true)) + "").equals("\n")){ // next(false); break; } line += wordChar; } return line.toCharArray(); } public char[] preLine(){ if (position <= 0){ return null; } String line = ""; while (position >= 0){ int word = pre(false); if (word == -1){ break; } char wordChar = (char) word; if ((wordChar + "").equals("\n") ){ // if ((wordChar + "").equals("\n") && (((char)pre(true)) + "").equals("\r")){ // pre(false); // /r/n ->/n 不需要再往前读一个字符了 // line = "\r\n" + line; break; } line = wordChar + line; } return line.toCharArray(); } public char chaptCurrent(){ char[] charArray = chaptChars(chapterNo); return charArray[(int)position-1]; } public char current(){ // int pos = (int) (position % cachedSize); // int cachePos = (int) (position / cachedSize); int cachePos = 0; int pos = 0; int len = 0; for (int i = 0;i < myArray.size();i++){ long size = myArray.get(i).getSize(); if (size + len - 1 >= position){ cachePos = i; pos = (int) (position - len); break; } len += size; } char[] charArray = block(cachePos); return charArray[pos]; } public int pre(boolean back){ position -= 1; if (position < 0){ position = 0; return -1; } char result = current(); if (back) { position += 1; } return result; } public long getPosition(){ return position; } public void setPostition(long position){ this.position = position; } //缓存书本 private void cacheBook() throws IOException { if (TextUtils.isEmpty(book.getCharset())) { m_strCharsetName = FileUtils.getCharset(bookPath); if (m_strCharsetName == null) { m_strCharsetName = "utf-8"; } ContentValues values = new ContentValues(); values.put("charset",m_strCharsetName); LitePal.update(Book.class,values,book.getId()); }else{ m_strCharsetName = book.getCharset(); } File file = new File(bookPath); InputStreamReader reader = new InputStreamReader(new FileInputStream(file),m_strCharsetName); int index = 0; bookLen = 0; directoryList.clear(); myArray.clear(); while (true){ char[] buf = new char[cachedSize]; int result = reader.read(buf); if (result == -1){ reader.close(); break; } String bufStr = new String(buf); // Log.e(TAG,String.format("缓存的内容是\n %s",bufStr)); bufStr = bufStr.replaceAll("\r\n","\n"); // bufStr = bufStr.replaceAll("\u3000\u3000+[ ]*","\u3000\u3000"); bufStr = bufStr.replaceAll("\n+\\s*","\n\u3000\u3000");// bufStr = bufStr.replaceAll("\r\n+\\s*","\r\n\u3000\u3000"); // bufStr = bufStr.replaceAll("\r\n[ {0,}]","\r\n\u3000\u3000"); // bufStr = bufStr.replaceAll(" ",""); bufStr = bufStr.replaceAll("\u0000",""); buf = bufStr.toCharArray(); bookLen += buf.length; // Log.e(TAG,String.format("缓存的内容脱空格处理后\n %s",bufStr)); Cache cache = new Cache(); cache.setSize(buf.length); cache.setData(new WeakReference(buf)); // bookLen += result; myArray.add(cache); // myArray.add(new WeakReference(buf)); // myArray.set(index,); Log.e(TAG,String.format("缓存的内容写入文件\n %s",fileName(index))); Log.e(TAG,"---------------------------------------------------------------------------------------------------------"); try { File cacheBook = new File(fileName(index)); if (!cacheBook.exists()){ cacheBook.createNewFile(); } final OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(fileName(index)), "UTF-16LE"); // UTF-16LE 比 utf-8 文件小 writer.write(buf); writer.close(); } catch (IOException e) { throw new RuntimeException("Error during writing " + fileName(index)); } index ++; } chaptId =0; //初始化导入的chapid int endchp = myArray.size()>3 ?3:myArray.size(); getChapter(1,3); //先导入2个部分 立即进行阅读 new Thread(){ @Override public void run() { getChapter(4,myArray.size()); //剩余部分后台导入 } }.start(); } int chaptId =0; //获取章节 public synchronized void getChapter(int startblk,int endblk){ if(endblk getDirectoryList(){ return directoryList; } public long getBookLen(){ return bookLen; } protected String fileName(int index) { return cachedPath + bookName + index ; } protected String fileChapterName(int chaptId ) { return getChapterPath() + chaptId ; } String getChapterPath(){ return chapterPath +book.getId()+"/"; } //获取书本缓存 public char[] block(int index) { if (myArray.size() == 0){ return new char[1]; } char[] block = myArray.get(index).getData().get(); if (block == null) { try { File file = new File(fileName(index)); int size = (int)file.length(); if (size < 0) { throw new RuntimeException("Error during reading " + fileName(index)); } block = new char[size / 2]; InputStreamReader reader = new InputStreamReader( new FileInputStream(file), "UTF-16LE" ); if (reader.read(block) != block.length) { throw new RuntimeException("Error during reading " + fileName(index)); } reader.close(); } catch (IOException e) { throw new RuntimeException("Error during reading " + fileName(index)); } Cache cache = myArray.get(index); cache.setData(new WeakReference(block)); // myArray.set(index, new WeakReference(block)); } return block; } private Map chaptCache = new HashMap(); //获取chapter 缓存 public char[] chaptChars(int index) { char[] block=null; if(chaptCache.containsKey(Integer.valueOf(index))) { block = chaptCache .get(index).getData().get(); } if (block == null) { try { File file = new File(fileChapterName(index)); if(!file.exists()){ /* 章节内容没有缓存在本地 1. 根据本地的章节网络地址信息,读取章节内容到本地,若读取失败则 2. 查询主服务器,若有地址更新则更新本地信息,并重复1,若没有更新地址,则地址无效,返回章节内容正待手打 */ } int size = (int)file.length(); if (size < 0) { throw new RuntimeException("Error during reading " + fileChapterName(index)); } block = new char[size / 2]; InputStreamReader reader = new InputStreamReader( new FileInputStream(file), charachterType ); long l = reader.read(block); if (reader.read(block) != block.length) { // throw new RuntimeException("Error during reading " + fileChapterName(index)); } reader.close(); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("Error during reading " + fileChapterName(index)); } Cache cache = new Cache(); cache.setSize(block.length); cache.setData(new WeakReference(block)); chaptCache.put(index, cache); // myArray.set(index, new WeakReference(block)); } return block; } public boolean isChapterTitle(String line){ return (line.length() <= 30 && (line.matches(".*第.{1,8}章.*") || line.matches(".*第.{1,8}节.*"))) ; } }