package com.novelbook.android.utils; import android.app.ProgressDialog; import android.content.ContentValues; import android.content.Context; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.text.TextUtils; import android.util.Log; import android.widget.TextView; import android.widget.Toast; import com.google.gson.Gson; import com.novelbook.android.MyApp; import com.novelbook.android.bean.Cache; import com.novelbook.android.bean.NovelSites; import com.novelbook.android.bean.Site; import com.novelbook.android.db.SiteRule; import com.novelbook.android.db.Chapter; import com.novelbook.android.db.Novel; import com.novelbook.android.netsubscribe.BookSubscribe; import com.novelbook.android.netutils.HttpMethods; import com.novelbook.android.netutils.NetUtil; import com.novelbook.android.netutils.OnSuccessAndFaultListener; import com.novelbook.android.netutils.OnSuccessAndFaultSub; import org.json.JSONException; import org.json.JSONObject; import org.litepal.LitePal; import org.w3c.dom.Text; 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.nio.charset.Charset; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; public class BookUtil { public static final String TAG ="BookUtil"; public static final String storagePath = FileUtils.getDiskCacheDir(MyApp.applicationContext);//Environment.getExternalStorageDirectory() + "/zhuike"; public static final String cachedPath = storagePath + "/cache/"; public static final String chapterPath = storagePath + "/chapter/"; private static final String charachterType = "utf-8";//"UTF-16LE"; private Context mContext; private ProgressDialog progressDialog; MuluStatus mMuluStatus; //目录是否下载完成 private Gson gson = new Gson(); public void setContext(Context context) { this.mContext = context; } public boolean isReadingCatalogs() { return mMuluStatus == MuluStatus.isDownloading; } //存储的字符数 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 mChapters = new ArrayList<>(); //当前章节 // private Chapter mCurrentChapter; public List getChapters() { return mChapters; } public void setChapters(List chapters) { this.mChapters = chapters; } private final int MSG_FILLCONTENTDONE=1; private final int MSG_READCHAPTER_FAIL=2; private final int MSG_READCHAPTER_SUCCESS=3; private String m_strCharsetName; private String bookName; private String bookPath; public void setChapterLen(long chapterLen) { this.chapterLen = chapterLen; } private long bookLen; private long chapterLen; // private long position; private Map charPosition = new HashMap(); private Novel mNovel; public void setNovel(Novel novel) { this.mNovel = novel; } public Novel getNovel( ) { return mNovel ; } //当前目录网站列表 private NovelSites mNovelSites; //当前目录网站 private Site mSite; private SiteRule mSiteRule; private Map chaptCache = new HashMap(); private Map chaptDownStatus = new HashMap(); DownloadStatus downloadStatus = DownloadStatus.notStart; public Chapter getChapter(int chapId){ chapId = chapId >0 ?chapId : 1; Log.d(TAG, String.format("prepare book getChapter: chaptId %s,mChapters.size() %s ",chapId,mChapters.size())); if(chapId > mChapters.size() || mChapters.size() ==0){ return Chapter.getChapter(mNovel.getId(), mNovel.getDomain()==null?"":mNovel.getDomain(),chapId); }else{ return mChapters.get(chapId-1); } } public NovelSites getmNovelSites() { return mNovelSites; } public synchronized void openBook(Novel novel, long chapter) throws IOException, InterruptedException { this.mNovel = novel; //如果当前缓存不是要打开的书本就缓存书本同时删除缓存 //TODO 构建新的缓存策略,几个选项,1:每本书一个缓存 2:控制缓存总大小,超过限制删除旧缓存 3:网络小说的缓存 boolean isLocalImport = novel.isLocalBook(); boolean isOnShelf = isLocalImport || novel.isOnShelf(); boolean isLoadChaptsFromRemote = !isLocalImport ;// && !novel.isFinished() ; //是否从目标网站下载目录 // showProgressDialog(); if(isLocalImport) { mChapters = LitePal.where("novelId=?", mNovel.getId() + "").find(Chapter.class); /* for (Chapter c : mChapters) { Log.d(TAG, String.format("bookchapter :%s,fileName :%s, chapter Size %s", c.getChapterName(), c.getChapterPath(), c.getLength())); }*/ chaptCache = new HashMap(); if (mChapters.isEmpty()) { //1. 首次打开 本地导入的书 if (bookPath == null || !bookPath.equals(mNovel.getNovelPath())) { cleanCacheFile(); this.bookPath = mNovel.getNovelPath(); bookName = FileUtils.getFileName(bookPath); cacheBook(); } } }else{ //读取目录列表 Log.d(TAG, String.format("prepare book %s open chapter %s in background.... mMuluStatus %s,mSiteRule %s,thread %s",mNovel.getName(),chapter,mMuluStatus,mSiteRule,Thread.currentThread().getName()) ); File file =new File(fileChapterName((int)chapter)); if( file.exists()){ Log.d(TAG, String.format("prepare book open chapter file %s, exist,not waiting more...to open file...",fileChapterName((int)chapter) )); return; } MuluStatus m = mMuluStatus; // Log.d(TAG,String.format("mulu on Site %s download status %s",mSite.getDomain(),mMuluStatus)); Log.d(TAG, String.format("prepare book %s open book in background.... mMuluStatus %s,mSiteRule %s,thread %s",mNovel.getName(),mMuluStatus,mSiteRule,Thread.currentThread().getName()) ); int sleptTime =0; while( mSiteRule ==null || mMuluStatus==null || mMuluStatus == MuluStatus.isDownloading){ sleptTime++; if(sleptTime >400 || sleptTime >30 && !NetUtil.isNetworkConnected()){ break; } Thread.sleep(50); if(mMuluStatus == MuluStatus.failed){ Log.d(TAG,String.format("prepare book %s failed ,mMuluStatus %s,msiteRule %s,slept %s" ,mNovel.getName(),mMuluStatus,mSiteRule,sleptTime*50)); // throw new RuntimeException("读取资源失败,请检查网络"); } } Log.d(TAG,String.format("prepare book %s waiting for mulu downloading ,mMuluStatus %s,msiteRule %s,slept %s" ,mNovel.getName(),mMuluStatus,mSiteRule,sleptTime*50)); } // dismissProgressDialog(); } public void setNovelSites(NovelSites nvs) { this.mNovelSites = nvs; Log.d(TAG, String.format("prepare book %s get novel sites count %s .",mNovel.getName(), nvs.getSites().length) ); if(nvs.getSites().length ==0){ throw new RuntimeException("书本错误 code 001"); //无目标网站 // return; } if(nvs.getSites().length > 0){ for (Site site:nvs.getSites() ) { if(!TextUtils.isEmpty(mNovel.getDomain()) && site.getDomain().equals(mNovel.getDomain())){ mSite = site; break; } } if(mSite ==null) for (Site site:nvs.getSites() ) { if(site.getSelectedByDefault()){ mSite = site; break; } } if(mSite ==null) mSite =nvs.getSites()[0]; } getSiteRule(); } private void setSiteInfo() { File file = new File(getChapterPath() +mSite.getDomain()); if(!file.exists()){ file.mkdir(); } mNovel.setDomain(mSite.getDomain()); mNovel.setDomainName(mSite.getName()); mNovel.setMuluUrl(mSite.getMuluUrl()); mNovel.setToDefault("isUpdated"); mNovel.update(mNovel.getId()); } public void getTargetSites(){ Log.d(TAG, "prepare book: getTargetSites" ); BookSubscribe.getNovelSites(mNovel.getNovelId(),new OnSuccessAndFaultSub(new OnSuccessAndFaultListener() { @Override public void onSuccess(String result) { //成功 try { Log.d(TAG, String.format("prepare book %s get target sites done.thread %s",mNovel.getName(),Thread.currentThread().getName()) ); NovelSites nvs = (NovelSites) gson.fromJson(result,NovelSites.class); //pageFactory.prepareBook(mNovel,nvs, BookActivity.this); setNovelSites(nvs); } catch ( Exception e) { Log.d(TAG, String.format("prepare book %s get target sites fail.thread %s ,msg %s",mNovel.getName(),Thread.currentThread().getName(),e.getMessage()) ); Log.e(TAG, "prepare book fail", e); e.printStackTrace(); } // Toast.makeText(mContext,"getMuluInfo 请求成功 " ,Toast.LENGTH_SHORT).show(); } @Override public void onFault(String errorMsg) { Log.d(TAG, String.format("prepare book %s get target sites fail.thread %s ,msg %s",mNovel.getName(),Thread.currentThread().getName(),errorMsg) ); //失败 // Toast.makeText(mContext,"getMuluInfo 请求失败"+errorMsg,Toast.LENGTH_SHORT).show(); } },null)); } int siteRuleRetryCnt =0; public void getSiteRule() { mSiteRule = null; if(mSite==null){ return; } BookSubscribe.getSiteRule(mSite.getDomain(),new OnSuccessAndFaultSub(new OnSuccessAndFaultListener() { @Override public void onSuccess(String result) { siteRuleRetryCnt =0; Log.d(TAG, "prepare book siteRule:" +result); //成功 SiteRule sr= new SiteRule(); try { sr = (SiteRule) gson.fromJson(result, SiteRule.class); }catch (Exception e){ Log.e(TAG, "prepare book error on gson: ", e); } List srs = LitePal.where("domain=?",sr.getDomain()).limit(1).find(SiteRule.class); long id = srs.size()==1 ?srs.get(0).getId() :0; if(id>0 ){ sr.update(id); // mSiteRule =LitePal.find(SiteRule.class,id); }else { sr.save(); } mSiteRule =sr; if(TextUtils.isEmpty(mSite.getName())){ mSite.setName(sr.getName()); }; setSiteInfo(); Log.d(TAG, String.format("prepare book %s 目录正则表达式下载完成,开始读章节信息. muluRegex size %s, thread %s ",mNovel.getName(),mSiteRule.getChapterUrlRegexOnMulu().length, Thread.currentThread().getName()) ); Log.d(TAG, String.format("目录正则表达式下载完成,开始读取章节信息") ); if(mSiteRule.getChapterUrlRegexOnMulu().length>0) { mMuluStatus = MuluStatus.isDownloading; long startTime= new Date().getTime(); Log.d(TAG,String.format("prepare book loadChapts----start download %s,maxAge %s, 目录 from %s", mNovel.getName() ,mNovel.getMaxAge() ,mSite.getMuluUrl() )); new Thread(){ @Override public void run() { Log.d(TAG, "to get chaps............................>"); Log.d(TAG, "to get chaps siteRule:" +result); Log.d(TAG, "to get chaps mulu:" + mSite.getMuluUrl() ); String[] chaps = new String[0]; try { JSONObject siteJson = new JSONObject(result); mChapters = NovelParseUtil.getChapters(mSite.getMuluUrl(), siteJson,mSite.getDomain(),mNovel.getMaxAge(),mSiteRule); /* if (mChapters != null){ for (Chapter chapter:mChapters) { Log.i(TAG, String.format("prepare book to get chaps readChaptersAsync %s-->%s",chapter.getChapterUrl(), chapter.getChapterName())); } }*/ } catch (JSONException e) { Log.e(TAG, "prepare book error on parese :", e); } if (mChapters == null ||mChapters.size()== 0) { readChaptersAsync(); }else { handler.sendEmptyMessage(MSG_READCHAPTER_SUCCESS); Log.d(TAG,String.format("prepare book loadChapts----end download %s 目录, 目录数量 %s, cost %s", mNovel.getName() , mChapters.size(), new Date().getTime() -startTime )); mMuluStatus = MuluStatus.isDone; Log.d(TAG, String.format("prepare book %s 章节信息完成.",mNovel.getName()) ); } } }.start(); }else{ readChaptersAsync(); } } @Override public void onFault(String errorMsg) { //失败 Log.e(TAG,"error on get sitRule: "+errorMsg); siteRuleRetryCnt++; if(siteRuleRetryCnt srs = LitePal.where("domain=?", mSite.getDomain()).find(SiteRule.class); if (srs.size() > 0) { mSiteRule = srs.get(0); } }*/ } public void setChapterNo(int chapterNo) { this.chapterNo = chapterNo; /* if(chapterNo <= mChapters.size()) { this.chapterNo = chapterNo; }else{ Log.d(TAG, String.format("setChapterNo: wrong chapno for book %s,site %s,total chapts %s,chaptNo %s" ,mNovel.getName(),mNovel.getDomain(),getChapters().size(),chapterNo)); } */ } public int getChapterNo() { if(chapterNo > mChapters.size()){ // Log.d(TAG, String.format(" prepare book getChapterNo ,chapterNo %s, getChapters().size() %s ,mChangeChapId %s" ,chapterNo , mChapters.size(),mChangeChapId) ); if(mChangeChapId>0){ return mChangeChapId; } // chapterNo=1; } chapterNo = chapterNo<=0 ?1 :chapterNo; // Log.d(TAG, String.format(" prepare book getChapterNo ,chapterNo %s, getChapters().size() %s " ,chapterNo , mChapters.size()) ); return chapterNo; } private int chapterNo;//当前章节 public String getLineBreakChar(){ return "\n"; } public BookUtil(){ checkAndCreateDir(storagePath); checkAndCreateDir(chapterPath); checkAndCreateDir(cachedPath); } public boolean isBusy() { return false; } private long tmpChaptLen=0; public void setTmpChaptLen(long tmpChaptLen) { this.tmpChaptLen = tmpChaptLen; } private boolean isChangeSource =false; private int mChangeChapId; private String mChangeTitle; public void changeSite(String domain){ for (Site site:mNovelSites.getSites() ) { if(site.getDomain().equals(domain)){ mSite = site; break; } } setSiteInfo(); isChangeSource = true; mChapters.clear(); getSiteRule(); } public void changeSource(String domain,int chapId,String chapTitle) { Log.d(TAG, String.format("changing Source: target domain %s chaptId %s, chapt title %s ",domain,chapId,chapTitle) ); clearBook(); // isDownloadChapt =false; mChangeChapId = chapId; mChangeTitle =chapTitle; changeSite(domain); BookTask btsk = new BookTask(); btsk.execute( domain, chapId+"", chapTitle); } private void clearBook() { charPosition.clear(); this.muluRetryCount=0; this.downloadStatus = DownloadStatus.notStart; chaptDownStatus.clear(); chaptCache.clear(); fileRetryCnt.clear(); siteRuleRetryCnt=0; isChangeSource=false; } public Site getSite() { return mSite !=null? mSite :new Site(); } public boolean chaptCached(int num) { File f = new File(fileChapterName(num)); return f.exists(); } public boolean retryDownLoadContent(int chaptId) { if(chaptDownStatus.containsKey(chaptId)){ if( chaptDownStatus.get(chaptId) == DownloadStatus.failure){ if(fileRetryCnt.containsKey(chaptId)){ if(fileRetryCnt.get(chaptId) < Constants.retryCnt){ fileRetryCnt.put(chaptId,fileRetryCnt.get(chaptId)+1 ); }else{ return false; } }else { fileRetryCnt.put(chaptId, 1 ); } }else{ return false; } }else{ chaptDownStatus.put(chaptId,DownloadStatus.downloading); fileRetryCnt.put(chaptId,11 ); } if(fileRetryCnt.get(chaptId) < Constants.retryCnt) { try { loadChaptContent(chaptId); chaptDownStatus.put(chaptId,DownloadStatus.downloading); } catch (JSONException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } return true; } return false; } /** * delete cache chapter file * and reload the chapter */ public void refreshChapter() { File file = new File(fileChapterName(chapterNo)); if(file.exists()){ file.delete(); } if(chaptCache.containsKey(chapterNo)){ chaptCache.remove(chapterNo); } if(pagefactory!=null) { pagefactory.changeChapter(chapterNo); } } private class BookTask extends AsyncTask { private String domain; private int chapId; private String chapTitle; @Override protected void onPostExecute(Boolean result) { super.onPostExecute(result); Log.d("onPostExecute",isCancelled() + ""); if (isCancelled()){ return; } if (result) { Log.d(TAG, String.format("changing Source: target domain %s chaptId %s, chapt title %s,mChangeChapId %s " ,domain,chapId,chapTitle,mChangeChapId) ); int chId=chapId;//chapterNo; String title =""; mChangeChapId = mChangeChapId >=1 ?mChangeChapId :1; if( mChapters.size() >= mChangeChapId && mChapters.get(mChangeChapId-1)!=null ){ title= mChapters.get(mChangeChapId-1).getChapterName(); Log.d(TAG, "changing Source:chapter name in new site " + title ); } if(title.equals(mChangeTitle) || title.contains(mChangeTitle) || mChangeTitle.contains(title)) { Log.d(TAG, "changing Source:successed find chapter by original chaptId " + mChangeChapId + ":" + mChangeTitle); chId = mChangeChapId; } else { int i = 1; for (Chapter chapter : mChapters) { // Log.d(TAG, "changing Source: finding chapter " + i + ":" + chapter.getChapterName()); if (chapter.getChapterName().equals(mChangeTitle)) { Log.d(TAG, "changing Source:successed find chapter by original title " + i + ":" + mChangeTitle); chId = i; title = mChangeTitle; break; } i++; } if (!title.equals(mChangeTitle)) { i = 1; for (Chapter chapter : mChapters) { // Log.d(TAG, "changing Source: finding chapter " + i + ":" + chapter.getChapterName()); if ( mChangeTitle.contains(chapter.getChapterName()) ||chapter.getChapterName().contains(mChangeTitle) ){ Log.d(TAG, "changing Source:successed find chapter by original title " + i + ":" + mChangeTitle); chId = i; title = chapter.getChapterName(); break; } i++; } i = 1; for (Chapter chapter : mChapters) { // Log.d(TAG, "changing Source: finding chapter " + i + ":" + chapter.getChapterName()); if ( chapter.getChapterName().startsWith(mChangeTitle) ||mChangeTitle.startsWith(chapter.getChapterName() ) ){ Log.d(TAG, "changing Source:successed find chapter by original title " + i + ":" + mChangeTitle); chId = i; title = chapter.getChapterName(); break; } i++; } } /* if (!title.contains(mChangeTitle)) { i = 1; for (Chapter chapter : mChapters) { // Log.d(TAG, "changing Source: finding chapter " + i + ":" + chapter.getChapterName()); if (mChangeTitle.contains(chapter.getChapterName())) { Log.d(TAG, "changing Source:successed find chapter by original title " + i + ":" + mChangeTitle); chId = i; title = chapter.getChapterName(); break; } i++; } }*/ } chId = chId <= mChapters.size() ? chId: mChapters.size(); Log.d(TAG, "changing Source: to open chapter with new site source " + chId + " : "+ mChangeTitle ); if(pagefactory!=null) pagefactory.changeChapter(chId); // mChangeChapId=0; // Toast.makeText(mContext,"换源成功",Toast.LENGTH_LONG).show(); }else{ Log.d(TAG, "changing Source: failed " ); } } @Override protected Boolean doInBackground(String... params) { domain = params[0]; chapId = Integer.parseInt( params[1]); chapTitle = params[2]; int splet =0; while(isChangeSource && splet =3) { return; }*/ HttpMethods.getOkClient().newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { // Log.d(TAG, "onFailure: " + e.getMessage()); Log.e(TAG, "prepare book loadChapts---- failed: ", e); Log.d(TAG, String.format("prepare book loadChapts---- failed %s 目录 from %s", mNovel.getName(), url)); // handler.sendEmptyMessage(3); //TODO 如果是取消了访问,则返回 if (e.toString()!=null && e.toString().contains("closed") || e.getMessage()!=null && e.getMessage().contains("Canceled")) { Log.d(TAG, String.format("prepare book loadChapts---- canceled %s 目录 from %s", mNovel.getName(), url)); return; } mMuluStatus = MuluStatus.failed; if (muluRetryCount < Constants.muluRetryCnt) { try { long sleeptime =100; if(mSiteRule!=null) { sleeptime = mSiteRule.getMiniInterval4AccessChapter(); } Thread.sleep(sleeptime); } catch (InterruptedException e1) { e1.printStackTrace(); } muluRetryCount++; Log.d(TAG,String.format("prepare book loadChapts----failed, retrying count %s",muluRetryCount )); readChaptersAsync(); return; } Log.d(TAG,String.format("prepare book loadChapts----failed, site count %s",mNovelSites.getSites().length )); if (mNovelSites.getSites().length == 1) { //仅有一个rule,且失败了 // mMuluStatus = MuluStatus.failed; return; } //try next site Message msg =Message.obtain(); msg.what =MSG_READCHAPTER_FAIL; msg.arg1 =chapterNo; Bundle bundleData = new Bundle(); for (Site st : mNovelSites.getSites()) { if (!st.getDomain().equals(mSite.getDomain())) { //mSite = st; mNovel.setDomain(st.getDomain()); mNovel.setDomainName(mSite.getName()); bundleData.putString("siteName", st.getName()); msg.setData(bundleData); break; } } // mNovel.setDomain(mSite.getDomain()); muluRetryCount=0; fileRetryCnt.clear(); siteRuleRetryCnt=0; chaptCache.clear(); handler.sendMessage(msg); // handler.sendEmptyMessage(MSG_READCHAPTER_FAIL); // readChaptersAsync(); } @Override public void onResponse(Call call, Response response){ ResponseBody body = response.body(); if(response.code()!=200){ Log.d(TAG,String.format("prepare book loadChapts----failed, %s 目录 from %s,return code %s", mNovel.getName() ,url,response.code() )); // handler.sendEmptyMessage(3); mMuluStatus = MuluStatus.failed; if(muluRetryCount ]*href=\"(/book/[\\d]+/[\\d]+\\.html)\">([^<]+)"); }else{ siteJson.put("chapterUrlRegexOnMulu", mSiteRule.getChapterUrlRegexOnMulu()); }*/ siteJson.put("chapterUrlRegexOnMulu", ""); // mChapters = NovelParseUtil.getChapters(mSite.getDomain(),url, content, siteJson); Log.d(TAG,String.format("mulu on Site %s download status %s",mSite.getDomain(),mMuluStatus)); if( mChapters.size()> mNovel.getChaptCnt()){ mNovel.setChaptCnt(mChapters.size()); mNovel.update(mNovel.getId()); } } catch (JSONException e) { // } catch (JSONException | IOException e) { Log.e(TAG,String.format("prepare book, mulu on Site %s download status %s",mSite.getDomain(),mMuluStatus),e); e.printStackTrace(); } finally { // result.close(); // if (result2 != null) result2.close(); } } 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,int chaptId){ // Log.d(TAG, String.format(" loadchapt next(), back %s, chaptId %s, position %s, tmpChaptLen %s",back,chaptId,charPosition.get(chaptId),tmpChaptLen )); charPosition.put(chaptId,charPosition.get(chaptId)+1) ; if (charPosition.get(chaptId) > tmpChaptLen){ charPosition.put(chaptId,tmpChaptLen) ; return -1; } char result = chaptCurrent(chaptId); //current(); if (back) { charPosition.put(chaptId,charPosition.get(chaptId)-1) ; } return result; } /*public char[] nextLine(){ if (position >= tmpChaptLen){ return null; } String line = ""; while (position < tmpChaptLen){ 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(int chaptId){ // chapterNo = mChapters.size() < chapterNo ? 1 : chapterNo; // Log.d(TAG, String.format(" loadchapt chaptCurrent() ,chapterNo %s, getChapters().size() %s " ,chaptId , mChapters.size()) ); char[] charArray = chaptChars(chaptId); int i = (int) (charPosition.get(chaptId) -1);//(int)position-1; i =i>0?i:0; i = i< charArray.length? i:charArray.length-1; // Log.d(TAG, String.format(" loadchapt chaptCurrent(), char position %s - %s, char '%s' " ,i,charPosition.get(chaptId) -1,charArray[i]) ); return charArray[i]; } 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 >= charPosition.get(chaptId) ){ cachePos = i; pos = (int) (charPosition.get(chaptId) - 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(int chaptId){ return charPosition.get(chaptId); } public void setPostition(int chaptId,long position){ charPosition.put(chaptId,position) ; } //缓存书本 private void cacheBook() throws IOException { if (TextUtils.isEmpty(mNovel.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(Novel.class,values,mNovel.getId()); }else{ m_strCharsetName = mNovel.getCharset(); } File file = new File(bookPath); InputStreamReader reader = new InputStreamReader(new FileInputStream(file),m_strCharsetName); int index = 0; bookLen = 0; mChapters.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 getmChapters(){ return mChapters; } public long getChapterLen(){ return chapterLen; } protected String fileName(int index) { return cachedPath + mNovel.getName() + index ; } public String fileChapterName(int chaptId ) { if(mNovel!=null && !TextUtils.isEmpty(mNovel.getDomain())){ return getChapterPath() +mNovel.getDomain()+"/"+ chaptId ; } return getChapterPath() + chaptId ; } String getChapterPath(){ File file = new File(chapterPath +mNovel.getId()); if(!file.exists()){ file.mkdir(); } return chapterPath +mNovel.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; } /*boolean isDownloadChapt =false; synchronized boolean getDownloadStatus(){ return isDownloadChapt; } synchronized void setDownloadFlag(boolean flag){ isDownloadChapt = flag; Log.d(TAG,String.format("set download flat",isDownloadChapt) ); }*/ public ChangeSource pagefactory; Handler handler = new Handler() { @Override public void handleMessage(Message msg) { int wt = msg.what; Log.d(TAG, String.format("prepare book get message what %s ,obj %s",msg.what,msg.obj)); handlerMsg(msg); } }; void handlerMsg(Message msg){ if (msg.what == MSG_FILLCONTENTDONE) { // isDownloadChapt =true; Log.d(TAG,String.format("handler msg, download %s",true) ); }else if(msg.what==MSG_READCHAPTER_FAIL){ try { Toast.makeText(mContext, "网络拥堵,已帮您切换其它源", Toast.LENGTH_LONG).show(); }catch (Exception e){ Log.e(TAG, "handlerMsg:toast error ", e); } // getSiteRule(); // pagefactory .changeChapter(getChapterNo()); String targetSiteName = msg.getData().getString("siteName"); Log.d(TAG, String.format("prepare book changing Source:target %s -- %s to open chapter %s" ,mNovel.getDomain(),targetSiteName,msg.arg1)); if(pagefactory!=null) pagefactory.changeSource(targetSiteName, mNovel.getDomain(),msg.arg1,getChapter(msg.arg1).getChapterName()); // isDownloadChapt =true; // Toast.makeText(mContext,"网络错误",Toast.LENGTH_LONG).show(); }else if(msg.what==MSG_READCHAPTER_SUCCESS){ //change source isChangeSource =false; Log.d(TAG, "prepare book changing Source:successed get chapters for " + mSite.getDomain() ); /*if(isChangeSource){ Log.d(TAG, "changing Source:successed get chapters for " + mSite.getDomain() ); isChangeSource =false; int chapId=chapterNo; if( mChapters.size() >= mChangeChapId && mChapters.get(mChangeChapId-1)!=null ){ String title = mChapters.get(mChangeChapId-1).getChapterName(); Log.d(TAG, "changing Source:chapter name in new site " + title ); if(title.equals(mChangeTitle)) { Log.d(TAG, "changing Source:successed find chapter by original chaptId " + mChangeChapId + ":" + mChangeTitle); chapId = mChangeChapId; } }else{ int i =1; for (Chapter chapter : mChapters) { if (chapter.getChapterName().equals(mChangeTitle)) { Log.d(TAG, "changing Source:successed find chapter by original title " +i + ":"+ mChangeTitle ); chapId = i; break; } i++; } } chapId = chapId <= mChapters.size() ? chapId: mChapters.size(); Log.d(TAG, "changing Source: to open chapter with new site source " + chapId + " : "+ mChangeTitle ); pagefactory.changeChapter(chapId); } */ } } private enum DownloadStatus{ notStart, downloading, failure, success } public boolean isChapterContentExist(int index) { char[] block = null; if (chaptCache.containsKey(Integer.valueOf(index))) { block = chaptCache.get(index).getData().get(); } if (block == null) { // cleanCacheFile(); //to remove File file = new File(fileChapterName(index)); if (!file.exists()) { new Thread(){ @Override public void run() { try { loadChaptContent(index); } catch (JSONException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }.start(); return false; } } return true; } //获取chapter 缓存 public char[] chaptChars(final int index) { // Log.d(TAG, String.format("prepare book begin to load content for chapter %s ------------------------------------------------------------------------->", index)); char[] block=null; if(chaptCache.containsKey(Integer.valueOf(index))) { block = chaptCache .get(index).getData().get(); // Log.d(TAG, String.format("chaptChars get block in cache, chapter: %s", index)); } // Log.d(TAG, String.format("prepare book begin to load content for chapter %s", index)); if (block == null) { // cleanCacheFile(); //to remove try { File file = new File(fileChapterName(index)); Log.d(TAG, String.format("prepare book begin to load content for chapter %s,file exists?%s", index,file.exists())); if ( !file.exists()) { if(getNovel().isLocalBook()){ return "".toCharArray(); } Log.d(TAG, String.format("prepare book loadChapts---- %s, 目录数量 %s, MuluStatus %s , mChapters.size() %s, thread %s", mNovel.getName(), mChapters.size(), mMuluStatus , mChapters.size() , Thread.currentThread().getName())); if (mMuluStatus == null) { Log.e(TAG, String.format("prepare book loadChapts---- 还未有目录信息,出错了 %s 目录, 目录数量 %s, MuluStatus %s ,thread %s", mNovel.getName(), mChapters.size(), mMuluStatus, Thread.currentThread().getName())); getTargetSites(); } if( mChapters.size() ==0) { if (mMuluStatus != MuluStatus.isDownloading){ getSiteRule(); } } int maxSleep = Constants.MAX_SLEEP_4_CHAPT_DOWNLOAD; int slepttime = 0; while (NetUtil.isNetworkConnected() && slepttime < maxSleep &&(mMuluStatus ==null || mMuluStatus == MuluStatus.isDownloading)) { try { Thread.sleep(50); slepttime+=50; Log.d(TAG, String.format("prepare book loadChapts----等待中 %s 目录, 目录数量 %s, slept %s, MuluStatus %s", mNovel.getName(), mChapters.size(), slepttime, mMuluStatus)); } catch (InterruptedException e) { e.printStackTrace(); } } if (!NetUtil.isNetworkConnected() || muluRetryCount >= Constants.retryCnt && (mChapters == null || mChapters.size() == 0)) { Log.d(TAG, String.format("prepare book loadChapts----超时。。。或出错了 %s 目录, 目录数量 %s, slept %s, MuluStatus %s,thread %s", mNovel.getName(), mChapters.size(), slepttime, mMuluStatus, Thread.currentThread().getName())); String error = "网络不给力"; return error.toCharArray(); } Log.d(TAG, String.format("prepare book loadChaptContent----start %s", new Date().toString())); Log.d(TAG, String.format("prepare book chaptDownStatus.containsKey %s ? %s", Integer.valueOf(index),chaptDownStatus.containsKey(Integer.valueOf(index)))); if (!chaptDownStatus.containsKey(Integer.valueOf(index))) { chaptDownStatus.put(index, DownloadStatus.downloading); Log.d(TAG, String.format("prepare book put chaptDownStatus index %s,start to load chapcontent", index)); loadChaptContent(index); }else{ Log.d(TAG, String.format("prepare book chaptDownStatus for chapt %s status %s", Integer.valueOf(index),chaptDownStatus.get(Integer.valueOf(index)))); } Log.d(TAG, String.format(" prepare book loadChaptContent %s for downloading, chaptDownStatus %s, thread %s ", index, chaptDownStatus.get(Integer.valueOf(index)), Thread.currentThread().getName())); slepttime = 0; // while(!file.exists() && !getDownloadStatus()){//&& slepttime index && NetUtil.isNetworkConnected()) { if (!chaptDownStatus.containsKey(index + 1) || chaptDownStatus.get(index + 1).equals(DownloadStatus.failure)) { File file2 = new File(fileChapterName(index + 1)); if (!file2.exists()) { Log.d(TAG, String.format(" prepare book to load next chapt %s,down status %s ", index + 1, chaptDownStatus.get(index + 1))); chaptDownStatus.put(index + 1, DownloadStatus.downloading); loadChaptContent(index + 1); } } } if (index > 1 && index - 1 < mChapters.size() && NetUtil.isNetworkConnected()) { if (!chaptDownStatus.containsKey(index - 1) || chaptDownStatus.get(index - 1).equals(DownloadStatus.failure)) { File file2 = new File(fileChapterName(index - 1)); if (!file2.exists()) { Log.d(TAG, String.format(" prepare book to load pre chapt %s,down status %s ", index - 1, chaptDownStatus.get(index - 1))); chaptDownStatus.put(index - 1, DownloadStatus.downloading); loadChaptContent(index - 1); } } } } // mChangeChapId =0; int size = (int) file.length(); if (size < 0) { Log.e(TAG, "prepare book chaptChars: Error during reading"+ fileChapterName(index) ); // throw new RuntimeException("Error during reading " + fileChapterName(index)); } block = new char[size / 2]; InputStreamReader reader = null; try { reader = new InputStreamReader( new FileInputStream(file), charachterType // mSiteRule.getEncoding() ); long l = reader.read(block); // Log.d(TAG, String.format("loadchapt: load from file chaptId %s,--->%s",index, new String(block ))); /* for (char c :block ) { Log.d(TAG, String.valueOf(c)); }*/ if (reader.read(block) != block.length) { // throw new RuntimeException("Error during reading " + fileChapterName(index)); } } catch (IOException e) { e.printStackTrace(); } finally { if (reader != null) { reader.close(); } } } catch (JSONException | IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } Cache cache = new Cache(); cache.setSize(block.length); cache.setData(new WeakReference(block)); chaptCache.put(index, cache); // myArray.set(index, new WeakReference(block)); Log.d(TAG, String.format("prepare book content reading finish, chapter %s", index)); } return block; } Map fileRetryCnt = new HashMap(); private void loadChaptContent(final int chapterIndex) throws JSONException, InterruptedException { /* 章节内容没有缓存在本地 1. 根据本地的章节网络地址信息,读取章节内容到本地,若读取失败则 2. 查询主服务器,若有地址更新则更新本地信息,并重复1,若没有更新地址,则地址无效,返回章节内容正待手打 */ // if(!NetUtil.isNetworkConnected() || mChapters.size()==0||mSite ==null){ handler.sendEmptyMessage(1); return ; } final int index = mChapters.size() < chapterIndex ? 1 : chapterIndex; if(mChapters.size() 1){ refUrl = mChapters.get(index -2).getChapterUrl(); } String url = chapter.getChapterUrl(); if( TextUtils.isEmpty( url)){ handler.sendEmptyMessage(1); return ; } long startTime= new Date().getTime(); Log.d(TAG,String.format("prepare book loadChaptContent----start download %s from %s", chapter.getChapterName() ,url )); // setDownloadFlag(false); JSONObject siteJson = new JSONObject(); siteJson.put("chapterContentRegex", mSiteRule.getChapterContentRegex()); siteJson.put("chapterContentDumpRegex", mSiteRule.getChapterContentDumpRegex()); Request request = getTagRequest(url, refUrl,-1); HttpMethods.getOkClient().newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { /* if(e.toString().contains("closed") ||e.getMessage().contains("Canceled")) { // return; }*/ // handler.sendEmptyMessage(MSG_FILLCONTENTDONE); // handler.sendEmptyMessage(1); chaptDownStatus.put(index,DownloadStatus.failure); // setDownloadFlag(true); Log.d( TAG,String.format("prepare book loadChaptContent %s fail, isDownloadChapt: %s",index,false)); // e.printStackTrace(); // throw new RuntimeException("Error during writing " + fileChapterName( index)); } @Override public void onResponse(Call call, Response response){ ResponseBody body = response.body(); if (body != null ) { if(response.code()!=200){ Log.d(TAG, "prepare book loadChaptContent----network failure returnCode " + response.code()); // setDownloadFlag(true); chaptDownStatus.put(index,DownloadStatus.failure); Log.d( TAG,String.format("prepare book loadChaptContent error %s ,isDownloadChapt: %s", response.code(),false)); handler.sendEmptyMessage(1); return; } try { if(mSiteRule==null){ return; } /* Charset charset = body.contentType().charset(); if(charset!=null){ String name = charset.displayName(); }*/ // String bodyStr = body.string(); // bodyStr =NovelParseUtil.enconding(bodyStr,mSiteRule.getEncoding()); String bodyStr =NovelParseUtil.enconding(body,mSiteRule.getEncoding()); if(TextUtils.isEmpty(bodyStr)){ Log.d( TAG,String.format("prepare book loadChaptContent %s isEmpty,retry....", index, Thread.currentThread().getName())); chaptDownStatus.put(index,DownloadStatus.failure); return; } String title = chapter.getChapterName(); String chapterContent = title+ "\n" + NovelParseUtil.getChapterContent(bodyStr, siteJson); char[] buf = chapterContent.toCharArray(); File file = new File(fileChapterName(index)); file.createNewFile(); final OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(fileChapterName(index)), charachterType);//"UTF-16LE"); // UTF-16LE 比 utf-8 文件小 // final OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(fileChapterName(index)), mSiteRule.getEncoding() );//charachterType);//"UTF-16LE"); // UTF-16LE 比 utf-8 文件小 writer.write(buf); writer.close(); Log.d( TAG,String.format("prepare book loadChaptContent file created: %s, thread %s", file.getPath(), Thread.currentThread().getName())); handler.sendEmptyMessage(MSG_FILLCONTENTDONE); // setDownloadFlag(true); } catch (IOException | JSONException e) { e.printStackTrace(); Log.e(TAG, "onResponse: prepare book error ",e ); chaptDownStatus.put(index,DownloadStatus.failure); return; // throw new RuntimeException("Error during writing " + fileChapterName( index)); } finally { body.close(); handler.sendEmptyMessage(MSG_FILLCONTENTDONE); // setDownloadFlag(true); } chapter.setNovelId(mNovel.getId()); chapter.setChapterPath(fileChapterName(index)); chapter.setDomain(mSite.getDomain()); if(chapter.getId()>0) { chapter.update(chapter.getId()); }else{ chapter.save(); } //setDownloadFlag(true); chaptDownStatus.put(index,DownloadStatus.success); Log.d(TAG,String.format(" prepare book loadChaptContent---- finished download %s, cost time %s ,content path %s ,thread %s", chapter.getChapterName(), new Date().getTime() -startTime ,chapter.getChapterPath() , Thread.currentThread().getName() )); } } }); } /*** * * @param url * @param maxAge * @return */ private Request getTagRequest(String url, String refUrl ,int maxAge) { Request.Builder builder = new Request.Builder() .tag(mNovel.getNovelId()) //标记 请求的tag,切换小说或离开小说界面(BookActivity) 时 取消未执行完毕的 此tag的所有请求 .url(url) .removeHeader("Pragma"); if(!TextUtils.isEmpty(refUrl)){ builder.header("Referer",refUrl); } for (int i = 0; i < mSiteRule.getHeaders().length; i += 2) { builder.header(mSiteRule.getHeaders()[i], mSiteRule.getHeaders()[i + 1]); } if(mSiteRule.getUserAgents()!=null && mSiteRule.getUserAgents().length>0){ String siteAgent =mSiteRule.getUserAgents()[new Random().nextInt( mSiteRule.getUserAgents().length-1)]; Log.d(TAG, "prepare book on getTagRequest:add site user agent " + siteAgent); builder.removeHeader("User-Agent").addHeader("User-Agent",siteAgent ); //加 随机agent }else{ builder.removeHeader("User-Agent").addHeader("User-Agent", HttpMethods.USERAGENT); } // .header( "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8") // .header( "Upgrade-Insecure-Requests", "1") // .header("content-type", "text/html; charset=utf-8") // .header("Content-Type", "text/plain; charset=utf-8") // .header( "Accept", "*/*") ; /* if(mSiteRule!=null && !TextUtils.isEmpty(mSiteRule.getEncoding()) ){ builder.header("Accept-Encoding",mSiteRule.getEncoding()); } */ if (maxAge > 0) { builder.header("Cache-Control", "public, max-age=" + maxAge); } return builder.build(); } public boolean isChapterTitle(String line){ return (line.length() <= 30 && (line.matches(".*第.{1,8}章.*") || line.matches(".*第.{1,8}节.*"))) ; } void clear(){ clearBook(); mContext=null; this.pagefactory =null; } }