package zlc.season.rxdownload2.function; import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.support.annotation.Nullable; import java.io.File; import java.util.List; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.Semaphore; import io.reactivex.Observable; import io.reactivex.ObservableEmitter; import io.reactivex.ObservableOnSubscribe; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; import io.reactivex.processors.FlowableProcessor; import io.reactivex.schedulers.Schedulers; import zlc.season.rxdownload2.db.DataBaseHelper; import zlc.season.rxdownload2.entity.DownloadEvent; import zlc.season.rxdownload2.entity.DownloadFlag; import zlc.season.rxdownload2.entity.DownloadMission; import zlc.season.rxdownload2.entity.DownloadRecord; import zlc.season.rxdownload2.entity.DownloadStatus; import zlc.season.rxdownload2.entity.MultiMission; import zlc.season.rxdownload2.entity.SingleMission; import static zlc.season.rxdownload2.function.Constant.WAITING_FOR_MISSION_COME; import static zlc.season.rxdownload2.function.DownloadEventFactory.createEvent; import static zlc.season.rxdownload2.function.DownloadEventFactory.normal; import static zlc.season.rxdownload2.function.Utils.createProcessor; import static zlc.season.rxdownload2.function.Utils.deleteFiles; import static zlc.season.rxdownload2.function.Utils.dispose; import static zlc.season.rxdownload2.function.Utils.getFiles; import static zlc.season.rxdownload2.function.Utils.log; /** * Author: Season(ssseasonnn@gmail.com) * Date: 2016/11/10 * Time: 09:49 * FIXME */ public class DownloadService extends Service { public static final String INTENT_KEY = "zlc_season_rxdownload_max_download_number"; private DownloadBinder mBinder; private Semaphore semaphore; private BlockingQueue<DownloadMission> downloadQueue; private Map<String, DownloadMission> missionMap; private Map<String, FlowableProcessor<DownloadEvent>> processorMap; private Disposable disposable; private DataBaseHelper dataBaseHelper; @Override public void onCreate() { super.onCreate(); mBinder = new DownloadBinder(); downloadQueue = new LinkedBlockingQueue<>(); processorMap = new ConcurrentHashMap<>(); missionMap = new ConcurrentHashMap<>(); dataBaseHelper = DataBaseHelper.getSingleton(getApplicationContext()); } @Override public int onStartCommand(Intent intent, int flags, int startId) { log("start Download Service"); dataBaseHelper.repairErrorFlag(); if (intent != null) { int maxDownloadNumber = intent.getIntExtra(INTENT_KEY, 5); semaphore = new Semaphore(maxDownloadNumber); } return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); log("destroy Download Service"); destroy(); dataBaseHelper.closeDataBase(); } @Nullable @Override public IBinder onBind(Intent intent) { log("bind Download Service"); startDispatch(); return mBinder; } /** * Receive the url download event. * <p> * Will receive the following event: * {@link DownloadFlag#NORMAL}、{@link DownloadFlag#WAITING}、 * {@link DownloadFlag#STARTED}、{@link DownloadFlag#PAUSED}、 * {@link DownloadFlag#COMPLETED}、{@link DownloadFlag#FAILED}; * <p> * Every event has {@link DownloadStatus}, you can get it and display it on the interface. * * @param url url * @return DownloadEvent */ public FlowableProcessor<DownloadEvent> receiveDownloadEvent(String url) { FlowableProcessor<DownloadEvent> processor = createProcessor(url, processorMap); DownloadMission mission = missionMap.get(url); if (mission == null) { //Not yet add this url mission. DownloadRecord record = dataBaseHelper.readSingleRecord(url); if (record == null) { processor.onNext(normal(null)); } else { File file = getFiles(record.getSaveName(), record.getSavePath())[0]; if (file.exists()) { processor.onNext(createEvent(record.getFlag(), record.getStatus())); } else { processor.onNext(normal(null)); } } } return processor; } /** * Add this mission into download queue. * * @param mission mission * @throws InterruptedException Blocking queue */ public void addDownloadMission(DownloadMission mission) throws InterruptedException { mission.init(missionMap, processorMap); mission.insertOrUpdate(dataBaseHelper); mission.sendWaitingEvent(dataBaseHelper); downloadQueue.put(mission); } /** * Pause download. * <p> * Pause a url or all tasks belonging to missionId. * * @param url url or missionId */ public void pauseDownload(String url) { DownloadMission mission = missionMap.get(url); if (mission != null && mission instanceof SingleMission) { mission.pause(dataBaseHelper); } } /** * Delete download. * <p> * Delete a url or all tasks belonging to missionId. * * @param url url or missionId * @param deleteFile whether delete file */ public void deleteDownload(String url, boolean deleteFile) { DownloadMission mission = missionMap.get(url); if (mission != null && mission instanceof SingleMission) { mission.delete(dataBaseHelper, deleteFile); missionMap.remove(url); } else { createProcessor(url, processorMap).onNext(normal(null)); if (deleteFile) { DownloadRecord record = dataBaseHelper.readSingleRecord(url); if (record != null) { deleteFiles(getFiles(record.getSaveName(), record.getSavePath())); } } dataBaseHelper.deleteRecord(url); } } /** * Start all mission. Not include MultiMission. * * @throws InterruptedException interrupt */ public void startAll() throws InterruptedException { for (DownloadMission each : missionMap.values()) { if (each.isCompleted()) { continue; } if (each instanceof SingleMission) { addDownloadMission(new SingleMission((SingleMission) each, null)); } // if (each instanceof MultiMission) { // addDownloadMission(new MultiMission((MultiMission) each)); // } } } /** * Pause all mission.Not include MultiMission. */ public void pauseAll() { for (DownloadMission each : missionMap.values()) { if (each instanceof SingleMission) { each.pause(dataBaseHelper); } } downloadQueue.clear(); } /** * Start all mission which associate with missionId. * * @param missionId missionId * @throws InterruptedException interrupt */ public void startAll(String missionId) throws InterruptedException { DownloadMission mission = missionMap.get(missionId); if (mission == null) { log("mission not exists"); return; } if (mission.isCompleted()) { log("mission complete"); return; } if (mission instanceof MultiMission) { addDownloadMission(new MultiMission((MultiMission) mission)); } } /** * Pause all mission which associate with missionId * * @param missionId missionId */ public void pauseAll(String missionId) { DownloadMission mission = missionMap.get(missionId); if (mission == null) { log("mission not exists"); return; } if (mission.isCompleted()) { log("mission complete"); return; } if (mission instanceof MultiMission) { mission.pause(dataBaseHelper); } } /** * Delete all mission which associate with missionId. * * @param missionId missionId * @param deleteFile deleteFile? */ public void deleteAll(String missionId, boolean deleteFile) { DownloadMission mission = missionMap.get(missionId); if (mission != null && mission instanceof MultiMission) { mission.delete(dataBaseHelper, deleteFile); missionMap.remove(missionId); } else { createProcessor(missionId, processorMap).onNext(normal(null)); if (deleteFile) { List<DownloadRecord> list = dataBaseHelper.readMissionsRecord(missionId); for (DownloadRecord each : list) { deleteFiles(getFiles(each.getSaveName(), each.getSavePath())); dataBaseHelper.deleteRecord(each.getUrl()); } } } } /** * start dispatch download queue. */ private void startDispatch() { disposable = Observable .create(new ObservableOnSubscribe<DownloadMission>() { @Override public void subscribe(ObservableEmitter<DownloadMission> emitter) throws Exception { DownloadMission mission; while (!emitter.isDisposed()) { try { log(WAITING_FOR_MISSION_COME); mission = downloadQueue.take(); log(Constant.MISSION_COMING); } catch (InterruptedException e) { log("Interrupt blocking queue."); continue; } emitter.onNext(mission); } emitter.onComplete(); } }) .subscribeOn(Schedulers.newThread()) .subscribe(new Consumer<DownloadMission>() { @Override public void accept(DownloadMission mission) throws Exception { mission.start(semaphore); } }, new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { log(throwable); } }); } /** * Call when service is onDestroy. */ private void destroy() { dispose(disposable); for (DownloadMission each : missionMap.values()) { each.pause(dataBaseHelper); } downloadQueue.clear(); } public class DownloadBinder extends Binder { public DownloadService getService() { return DownloadService.this; } } }