package com.gzsll.hupu.service;
import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import com.alibaba.fastjson.JSON;
import com.facebook.cache.common.CacheKey;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.cache.DefaultCacheKeyFactory;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.core.ImagePipelineFactory;
import com.facebook.imagepipeline.request.ImageRequest;
import com.gzsll.hupu.BuildConfig;
import com.gzsll.hupu.Logger;
import com.gzsll.hupu.MyApplication;
import com.gzsll.hupu.api.forum.ForumApi;
import com.gzsll.hupu.bean.ThreadLightReplyData;
import com.gzsll.hupu.bean.ThreadListData;
import com.gzsll.hupu.bean.ThreadReplyData;
import com.gzsll.hupu.bean.ThreadReplyQuote;
import com.gzsll.hupu.bean.ThreadReplyResult;
import com.gzsll.hupu.components.notifier.OfflineNotifier;
import com.gzsll.hupu.components.okhttp.OkHttpHelper;
import com.gzsll.hupu.db.Forum;
import com.gzsll.hupu.db.ForumDao;
import com.gzsll.hupu.db.Thread;
import com.gzsll.hupu.db.ThreadDao;
import com.gzsll.hupu.db.ThreadInfo;
import com.gzsll.hupu.db.ThreadInfoDao;
import com.gzsll.hupu.db.ThreadReply;
import com.gzsll.hupu.db.ThreadReplyDao;
import com.gzsll.hupu.injector.component.DaggerServiceComponent;
import com.gzsll.hupu.injector.module.ServiceModule;
import com.gzsll.hupu.util.ConfigUtil;
import com.gzsll.hupu.util.FileUtil;
import com.gzsll.hupu.util.NetWorkUtil;
import com.gzsll.hupu.util.SettingPrefUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import rx.Observable;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.functions.Func3;
import rx.schedulers.Schedulers;
import rx.subscriptions.CompositeSubscription;
/**
* Created by sll on 2016/5/30.
*/
public class OffLineService extends Service {
public static final String START_DOWNLOAD = BuildConfig.APPLICATION_ID + ".action.START_DOWNLOAD";
public static final String EXTRA_FORUMS = "forums";
@Inject
ForumApi mForumApi;
@Inject
ForumDao mForumDao;
@Inject
ThreadDao mThreadDao;
@Inject
OfflineNotifier mOfflineNotifier;
@Inject
ThreadReplyDao mReplyDao;
@Inject
ThreadInfoDao mThreadInfoDao;
@Inject
OkHttpHelper mOkHttpHelper;
public static final int INIT = 0;
public static final int PREPARE = 1;
public static final int LOAD_THREAD = 2;
public static final int LOAD_REPLY = 3;
public static final int LOAD_PICTURE = 4;
public static final int CANCEL = 5;
public static final int FINISHED = 6;
private int mCurrentStatus = INIT;
private List<Forum> forums;
private List<Forum> unOfflineForums;
private int offlineThreadsCount = 0;// 离线的帖子数量
private int offlineRepliesCount = 0; //离线的回复数量
private long offlineThreadsLength = 0;// 离线的帖子总流量大小
private long offlineRepliesLength = 0;
private long offlinePictureLength = 0;// 离线的图片总流量大小
private int offlinePictureCount = 0;// 离线的图片数量
private LinkedBlockingQueue<Thread> threadList = new LinkedBlockingQueue<>();// 线程安全队列
private Map<String, String> lastTidMap = new HashMap<>();
private Map<String, String> lastStmMap = new HashMap<>();
private Map<String, Integer> threadCountMap = new HashMap<>();
private static final int TOTAL_COUNT = 100;
private CompositeSubscription mCompositeSubscription = new CompositeSubscription();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Logger.d("服务初始化");
DaggerServiceComponent.builder()
.serviceModule(new ServiceModule(this))
.applicationComponent(((MyApplication) getApplication()).getApplicationComponent())
.build()
.inject(this);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null || TextUtils.isEmpty(intent.getAction())) {
return super.onStartCommand(intent, flags, startId);
}
String action = intent.getAction();
if (action.equals(START_DOWNLOAD)) {
if (mCurrentStatus == INIT) {
forums = (List<Forum>) intent.getSerializableExtra(EXTRA_FORUMS);
prepareOffline();
} else {
//ignore
Logger.d("服务已启动,忽略请求");
}
}
return super.onStartCommand(intent, flags, startId);
}
private void prepareOffline() {
if (forums == null || forums.isEmpty()) {
return;
}
mCurrentStatus = PREPARE;
unOfflineForums = new ArrayList<>();
unOfflineForums.addAll(forums);
for (final Forum forum : unOfflineForums) {
if (isCanceled()) {
return;
}
getThreadList(forum, "", "");
}
}
private void getThreadList(final Forum forum, final String lastTid, final String lastStm) {
final String fid = forum.getFid();
if (threadCountMap.get(fid) != null && threadCountMap.get(fid) >= TOTAL_COUNT) {
mOfflineNotifier.notifyThreads(forum, offlineThreadsLength);
unOfflineForums.remove(forum);
prepareReplies();
return;
}
Subscription mSubscription = mForumApi.getThreadsList(fid, lastTid, lastStm,
SettingPrefUtil.getThreadSort(OffLineService.this))
.doOnNext(new Action1<ThreadListData>() {
@Override
public void call(ThreadListData threadListData) {
if (threadListData != null && threadListData.result != null) {
List<Thread> threads = threadListData.result.data;
offlineThreadsCount += threads.size();
offlineThreadsLength += JSON.toJSONString(threadListData).length();
lastStmMap.put(fid, threadListData.result.stamp);
threadList.addAll(threads);
lastTidMap.put(fid, threads.get(threads.size() - 1).getTid());
Integer count = threadCountMap.get(fid);
if (count == null) {
count = 0;
}
threadCountMap.put(fid, count + threads.size());
if (!threads.isEmpty()) {
int type = Integer.valueOf(threads.get(0).getFid());
if (TextUtils.isEmpty(lastTid) && TextUtils.isEmpty(lastStm)) {
mThreadDao.queryBuilder()
.where(ThreadDao.Properties.Type.eq(type))
.buildDelete()
.executeDeleteWithoutDetachingEntities();
}
for (Thread thread : threads) {
if (mThreadDao.queryBuilder()
.where(ThreadDao.Properties.Tid.eq(thread.getTid()),
ThreadDao.Properties.Type.eq(type))
.count() == 0) {
thread.setType(type);
mThreadDao.insert(thread);
}
}
}
}
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<ThreadListData>() {
@Override
public void call(ThreadListData threadListData) {
getThreadList(forum, lastTidMap.get(fid), lastStmMap.get(fid));
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
throwable.printStackTrace();
getThreadList(forum, lastTidMap.get(fid), lastStmMap.get(fid));
}
});
mCompositeSubscription.add(mSubscription);
}
private boolean isCanceled() {
return mCurrentStatus == CANCEL || mCurrentStatus == FINISHED;
}
private void prepareReplies() {
if (!unOfflineForums.isEmpty()) {
return;
}
mOfflineNotifier.notifyThreadsSuccess(forums.size(), offlineThreadsCount, offlineThreadsLength);
if (threadList.isEmpty()) {
stopSelf();
} else {
for (final Thread thread : threadList) {
Subscription mSubscription =
Observable.zip(mForumApi.getThreadInfo(thread.getTid(), thread.getFid(), 1, ""),
mForumApi.getThreadReplyList(thread.getTid(), thread.getFid(), 1),
mForumApi.getThreadLightReplyList(thread.getTid(), thread.getFid()),
new Func3<ThreadInfo, ThreadReplyData, ThreadLightReplyData, Boolean>() {
@Override
public Boolean call(ThreadInfo threadInfo, ThreadReplyData threadReplyData,
ThreadLightReplyData threadLightReplyData) {
if (threadInfo != null) {
offlineRepliesLength += JSON.toJSONString(threadInfo).length();
threadInfo.setForumName(threadInfo.getForum().getName());
mThreadInfoDao.queryBuilder()
.where(ThreadInfoDao.Properties.Tid.eq(thread.getTid()))
.buildDelete()
.executeDeleteWithoutDetachingEntities();
mThreadInfoDao.insert(threadInfo);
transImgToLocal(threadInfo.getContent());
}
if (threadReplyData != null && threadReplyData.status == 200) {
offlineRepliesLength += JSON.toJSONString(threadReplyData).length();
ThreadReplyResult result = threadReplyData.result;
if (result != null && !result.list.isEmpty()) {
for (ThreadReply reply : result.list) {
reply.setTid(thread.getTid());
reply.setPageIndex(1);
saveReply(reply, false);
}
}
}
if (threadLightReplyData != null && threadLightReplyData.status == 200) {
offlineRepliesLength += JSON.toJSONString(threadLightReplyData).length();
if (!threadLightReplyData.list.isEmpty()) {
for (ThreadReply reply : threadLightReplyData.list) {
saveReply(reply, true);
}
}
}
return true;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Boolean>() {
@Override
public void call(Boolean aBoolean) {
offlineReplyComplete(thread);
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
throwable.printStackTrace();
offlineReplyComplete(thread);
}
});
mCompositeSubscription.add(mSubscription);
}
}
}
private void offlineReplyComplete(Thread thread) {
mOfflineNotifier.notifyReplies(thread, offlineRepliesLength);
threadList.remove(thread);
if (threadList.isEmpty()) {
Logger.d("mThreads.isEmpty():" + offlineRepliesCount);
mOfflineNotifier.notifyRepliesSuccess(offlineThreadsCount, offlineRepliesCount,
offlineRepliesLength);
stopSelf();
}
stopSelfIfCan();
}
private boolean stopSelfIfCan() {
if (isCanceled()) {
Logger.d("isCanceled");
stopSelf();
return true;
}
if (!NetWorkUtil.isWifiConnected(this)) {
Logger.d("isWiFi");
mCurrentStatus = CANCEL;
stopSelf();
return true;
}
return false;
}
private void saveReply(ThreadReply reply, boolean isLight) {
offlineRepliesCount++;
if (!reply.getQuote().isEmpty()) {
ThreadReplyQuote quote = reply.getQuote().get(0);
reply.setQuoteHeader(quote.header.get(0));
if (!TextUtils.isEmpty(quote.togglecontent)) {
reply.setQuoteToggle(quote.togglecontent);
}
reply.setQuoteContent(quote.content);
}
reply.setIsLight(isLight);
mReplyDao.queryBuilder()
.where(ThreadReplyDao.Properties.Pid.eq(reply.getPid()))
.buildDelete()
.executeDeleteWithoutDetachingEntities();
mReplyDao.insert(reply);
transImgToLocal(reply.getContent());
transImgToLocal(reply.getQuoteContent());
transGifToLocal(reply.getContent());
transGifToLocal(reply.getQuoteContent());
}
private void transImgToLocal(String content) {
if (TextUtils.isEmpty(content)) {
return;
}
Pattern pattern = Pattern.compile("<img(.+?)data_url=\"(.+?)\"(.+?)src=\"(.+?)\"(.+?)>");
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
String thumb = matcher.group(4);
String original = matcher.group(2);
cacheImage(thumb, original);
}
matcher.reset();
}
private void transGifToLocal(String content) {
if (TextUtils.isEmpty(content)) {
return;
}
Pattern pattern = Pattern.compile("<img(.+?)data-gif=\"(.+?)\"(.+?)src=\"(.+?)\"(.+?)>");
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
String thumb = matcher.group(4);
String original = matcher.group(2);
cacheImage(thumb, original);
}
matcher.reset();
}
private void cacheImage(String thumb, String original) {
cacheImage(original);
String localUrl = thumb.substring(thumb.lastIndexOf("/") + 1);
String localPath = ConfigUtil.getCachePath() + File.separator + localUrl;
if (!FileUtil.exist(localPath)) {
try {
mOkHttpHelper.httpDownload(thumb, new File(localPath));
} catch (Exception e) {
Log.d("HtmlUtils", "图片下载失败:" + thumb);
}
offlinePictureCount++;
offlinePictureLength += new File(localPath).length();
}
}
private void cacheImage(String url) {
if (!isImageDownloaded(Uri.parse(url))) {
ImagePipeline imagePipeline = Fresco.getImagePipeline();
ImageRequest request = ImageRequest.fromUri(url);
imagePipeline.prefetchToDiskCache(request, this);
offlinePictureLength += request.getSourceFile().length();
offlinePictureCount++;
}
}
private boolean isImageDownloaded(Uri loadUri) {
if (loadUri == null) {
return false;
}
CacheKey cacheKey = DefaultCacheKeyFactory.getInstance()
.getEncodedCacheKey(ImageRequest.fromUri(loadUri), null);
return ImagePipelineFactory.getInstance().getMainFileCache().hasKey(cacheKey)
|| ImagePipelineFactory.getInstance().getSmallImageFileCache().hasKey(cacheKey);
}
@Override
public void onDestroy() {
super.onDestroy();
Logger.d("onDestroy");
mOfflineNotifier.notifyPictureSuccess(offlinePictureCount, offlinePictureLength);
mCurrentStatus = FINISHED;
if (mCompositeSubscription != null && !mCompositeSubscription.isUnsubscribed()) {
mCompositeSubscription.unsubscribe();
}
}
}