private void goNext(final int nextIndex) { if (this.handler == null || this.list == null) { FileDownloadLog.w( this, "need go next %d, but params is not ready %s %s", nextIndex, this.handler, this.list); return; } Message nextMsg = this.handler.obtainMessage(); nextMsg.what = WHAT_SERIAL_NEXT; nextMsg.arg1 = nextIndex; if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d( SerialHandlerCallback.class, "start next %s %s", this.list == null ? null : this.list.get(0) == null ? null : this.list.get(0).getListener(), nextMsg.arg1); } this.handler.sendMessage(nextMsg); }
public static boolean isBreakpointAvailable( final int id, final FileDownloadModel model, final String path) { boolean result = false; do { if (path == null) { if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d(FileDownloadMgr.class, "can't continue %d path = null", id); } break; } File file = new File(path); final boolean isExists = file.exists(); final boolean isDirectory = file.isDirectory(); if (!isExists || isDirectory) { if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d( FileDownloadMgr.class, "can't continue %d file not suit, exists[%B], directory[%B]", id, isExists, isDirectory); } break; } final long fileLength = file.length(); if (model.getSoFar() == 0) { if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d( FileDownloadMgr.class, "can't continue %d the downloaded-record is zero.", id); } break; } if (fileLength < model.getSoFar() || (model.getTotal() != -1 // not chunk transfer encoding data && (fileLength > model.getTotal() || model.getSoFar() >= model.getTotal()))) { // dirty data. if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d( FileDownloadMgr.class, "can't continue %d dirty data" + " fileLength[%d] sofar[%d] total[%d]", id, fileLength, model.getSoFar(), model.getTotal()); } break; } result = true; } while (false); return result; }
@Override public boolean handleMessage(final Message msg) { if (msg.what == WHAT_SERIAL_NEXT) { if (msg.arg1 >= list.size()) { // final serial tasks if (this.handler != null && this.handler.getLooper() != null) { this.handler.getLooper().quit(); this.handler = null; this.list = null; } if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d( SerialHandlerCallback.class, "final serial %s %d", this.list == null ? null : this.list.get(0) == null ? null : this.list.get(0).getListener(), msg.arg1); } return true; } final BaseDownloadTask task = this.list.get(msg.arg1); synchronized (pauseLock) { if (!FileDownloadList.getImpl().contains(task)) { // pause? if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d( SerialHandlerCallback.class, "direct go next by not contains %s %d", task, msg.arg1); } goNext(msg.arg1 + 1); return true; } } list.get(msg.arg1) .addFinishListener( new BaseDownloadTask.FinishListener() { private int index; public BaseDownloadTask.FinishListener setIndex(int index) { this.index = index; return this; } @Override public void over() { goNext(this.index); } }.setIndex(msg.arg1 + 1)) .start(); } return true; }
public boolean clearTaskData(int id) { if (id == 0) { FileDownloadLog.w(this, "The task[%d] id is invalid, can't clear it.", id); return false; } if (isDownloading(id)) { FileDownloadLog.w(this, "The task[%d] is downloading, can't clear it.", id); return false; } mHelper.remove(id); return true; }
/** * Reuse this task withhold request params: path、url、header、isForceReDownloader、etc. * * @return Successful reuse or not. */ public boolean reuse() { if (isRunning()) { FileDownloadLog.w( this, "This task is running %d, if you want start the same task," + " please create a new one by FileDownloader.create", getId()); return false; } this.using = false; this.etag = null; this.resuming = false; this.retryingTimes = 0; this.isReusedOldFile = false; this.ex = null; resetSpeed(); clearMarkAdded2List(); setStatus(FileDownloadStatus.INVALID_STATUS); this.soFarBytes = 0; this.totalBytes = 0; messenger.reAppointment(this); return true; }
/** Sets the tag associated with this task, not be used by internal. */ public BaseDownloadTask setTag(final Object tag) { this.tag = tag; if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d(this, "setTag %s", tag); } return this; }
/** @param path Absolute path for save the download file */ public BaseDownloadTask setPath(final String path) { this.path = path; if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d(this, "setPath %s", path); } return this; }
public FileDownloadMgr() { final DownloadMgrInitialParams params = FileDownloadHelper.getDownloadMgrInitialParams(); mHelper = new FileDownloadDBHelper(); final OkHttpClient client; final int maxNetworkThreadCount; if (params != null) { client = params.makeCustomOkHttpClient(); maxNetworkThreadCount = params.getMaxNetworkThreadCount(); } else { client = null; maxNetworkThreadCount = 0; } if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d( this, "init the download manager with initialParams: " + "okhttpClient[is customize: %B], maxNetworkThreadCount[%d]", client != null, maxNetworkThreadCount); } // init client if (this.client != client) { this.client = client; } else { // in this case, the client must be null, see #41 this.client = new OkHttpClient(); } mThreadPool = new FileDownloadThreadPool(maxNetworkThreadCount); }
private void _start() { try { // Whether service was already started. if (!_checkCanStart()) { // Not ready return; } FileDownloadList.getImpl().add(this); if (_checkCanReuse()) { // Will be removed when the complete message is received in #update return; } if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d(this, "start downloaded by ui process %s", getUrl()); } if (!_startExecute()) { setEx(new RuntimeException("not run download, not got download id")); FileDownloadList.getImpl().removeByError(this); } } catch (Throwable e) { e.printStackTrace(); setEx(e); FileDownloadList.getImpl().removeByError(this); } }
private int startUnchecked() { if (FileDownloadMonitor.isValid()) { FileDownloadMonitor.getMonitor().onRequestStart(this); } if (FileDownloadLog.NEED_LOG) { FileDownloadLog.v( this, "call start " + "url[%s], setPath[%s] listener[%s], tag[%s]", url, path, listener, tag); } boolean ready = true; try { _adjust(); _checkFile(path); } catch (Throwable e) { ready = false; FileDownloadList.getImpl().add(this); FileDownloadList.getImpl().remove(this, catchException(e)); } if (ready) { FileDownloadTaskLauncher.getImpl().launch(this); } return getId(); }
/** * Why pause? not stop? because invoke this method(pause) will clear all data about this task in * memory, and stop the total processing about this task. but when you start the paused task, it * would be continue downloading from the breakpoint as default. * * @return If true, successful pause this task by status of pause, otherwise this task has already * in over status before invoke this method(Maybe occur high concurrent situation). * @see FileDownloader#pause(int) * @see FileDownloader#pause(FileDownloadListener) * @see FileDownloader#pauseAll() */ public boolean pause() { if (FileDownloadStatus.isOver(getStatus())) { if (FileDownloadLog.NEED_LOG) { /** * The over-status call-backed and set the over-status to this task between here area and * remove from the {@link FileDownloadList}. * * <p>High concurrent cause. */ FileDownloadLog.d( this, "High concurrent cause, Already is over, can't pause " + "again, %d %d", getStatus(), getId()); } return false; } setStatus(FileDownloadStatus.paused); _pauseExecute(); calcAverageSpeed(this.soFarBytes); // For make sure already added event listener for receive paused event FileDownloadList.getImpl().add(this); FileDownloadList.getImpl().remove(this, MessageSnapshotTaker.catchPause(this)); return true; }
void _start() { try { // Whether service was already started. if (!_checkCanStart()) { this.using = false; // Not ready return; } FileDownloadList.getImpl().add(this); if (_checkCanReuse()) { // Will be removed when the complete message is received in #update return; } if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d(this, "start downloaded by ui process %s", getUrl()); } _startExecute(); } catch (Throwable e) { e.printStackTrace(); FileDownloadList.getImpl().remove(this, catchException(e)); } }
/** * start download * * <p>用于启动一个单独任务 * * @return Download id */ public int start() { if (FileDownloadMonitor.isValid()) { FileDownloadMonitor.getMonitor().onRequestStart(this); } if (FileDownloadLog.NEED_LOG) { FileDownloadLog.v( this, "call start " + "url[%s], setPath[%s] listener[%s], tag[%s]", url, path, listener, tag); } boolean ready = true; try { _adjust(); _checkFile(path); } catch (Throwable e) { ready = false; setStatus(FileDownloadStatus.error); setEx(e); FileDownloadList.getImpl().add(this); FileDownloadList.getImpl().removeByError(this); } if (ready) { FileDownloadEventPool.getImpl().send2Service(new DownloadTaskEvent(this).requestStart()); } return getDownloadId(); }
/** * Start the download queue by the same listener * * @param listener start download by same listener * @param isSerial is execute them linearly */ public void start(final FileDownloadListener listener, final boolean isSerial) { if (listener == null) { return; } final List<BaseDownloadTask> list = FileDownloadList.getImpl().copy(listener); if (FileDownloadMonitor.isValid()) { FileDownloadMonitor.getMonitor().onRequestStart(list.size(), isSerial, listener); } if (FileDownloadLog.NEED_LOG) { FileDownloadLog.v( this, "start list size[%d] listener[%s] isSerial[%B]", list.size(), listener, isSerial); } if (isSerial) { // serial final Handler serialHandler = createSerialHandler(list); Message msg = serialHandler.obtainMessage(); msg.what = WHAT_SERIAL_NEXT; msg.arg1 = 0; serialHandler.sendMessage(msg); } else { // parallel for (final BaseDownloadTask downloadTask : list) { downloadTask.start(); } } }
// Clear References void clear() { if (finishListenerList != null) { finishListenerList.clear(); } if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d(this, "clear %s", this); } }
/** * @param listener For callback download status(pending,connected,progress, * blockComplete,retry,error,paused,completed,warn) */ public BaseDownloadTask setListener(final FileDownloadListener listener) { this.listener = listener; if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d(this, "setListener %s", listener); } return this; }
/** * Pause the download task by the downloadId * * @param downloadId pause download by download id * @see #pause(FileDownloadListener) */ public void pause(final int downloadId) { BaseDownloadTask downloadTask = FileDownloadList.getImpl().get(downloadId); if (downloadTask == null) { FileDownloadLog.w(this, "request pause but not exist %d", downloadId); return; } downloadTask.pause(); }
// Assign default value if need private void _adjust() { if (path == null) { path = FileDownloadUtils.getDefaultSaveFilePath(url); if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d(this, "save path is null to %s", path); } } }
/** @return can resume by break point */ public static boolean isBreakpointAvailable(final int id, final FileDownloadModel model) { if (model == null) { if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d(FileDownloadMgr.class, "can't continue %d model == null", id); } return false; } if (model.getTempFilePath() == null) { if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d(FileDownloadMgr.class, "can't continue %d temp path == null", id); } return false; } return isBreakpointAvailable(id, model, model.getTempFilePath()); }
// ------------------ // Begin task execute void begin() { if (FileDownloadMonitor.isValid()) { FileDownloadMonitor.getMonitor().onTaskBegin(this); } if (FileDownloadLog.NEED_LOG) { FileDownloadLog.v(this, "filedownloader:lifecycle:start %s by %d ", toString(), getStatus()); } }
/** * Ready task( For queue task ) * * <p>用于将几个task绑定为一个队列启动的结束符 * * @return downloadId * @see FileDownloader#start(FileDownloadListener, boolean) */ public int ready() { if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d(this, "ready 2 download %s", toString()); } FileDownloadList.getImpl().ready(this); return getId(); }
/** Pause all running task */ public void pauseAll() { List<Integer> list = mThreadPool.getAllExactRunningDownloadIds(); if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d(this, "pause all tasks %d", list.size()); } for (Integer id : list) { pause(id); } }
boolean updateKeepFlow(final MessageSnapshot snapshot) { final int currentStatus = getStatus(); final int nextStatus = snapshot.getStatus(); if (FileDownloadStatus.paused == currentStatus && FileDownloadStatus.isIng(nextStatus)) { if (FileDownloadLog.NEED_LOG) { /** * Occur such situation, must be the running-status waiting for turning up in flow thread * pool(or binder thread) when there is someone invoked the {@link #pause()} . * * <p>High concurrent cause. */ FileDownloadLog.d( this, "High concurrent cause, callback pending, but has already" + " be paused %d", getId()); } return true; } if (!FileDownloadStatus.isKeepFlow(currentStatus, nextStatus)) { if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d( this, "can't update status change by keep flow, %d, but the" + " current status is %d, %d", status, getStatus(), getId()); } return false; } update(snapshot); return true; }
boolean updateKeepAhead(final MessageSnapshot snapshot) { if (!FileDownloadStatus.isKeepAhead(getStatus(), snapshot.getStatus())) { if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d( this, "can't update status change by keep ahead, %d, but the" + " current status is %d, %d", status, getStatus(), getId()); } return false; } update(snapshot); return true; }
/** * Pause task * * <p>停止任务, 对于线程而言会直接关闭,清理所有相关数据,不会hold住任何东西 * * <p>如果重新启动,默认会断点续传,所以为pause */ public boolean pause() { setStatus(FileDownloadStatus.paused); final boolean result = _pauseExecute(); // For make sure already added event listener for receive paused event FileDownloadList.getImpl().add(this); if (result) { FileDownloadList.getImpl().removeByPaused(this); } else { FileDownloadLog.w(this, "paused false %s", toString()); // 一直依赖不在下载进程队列中 // 只有可能是 串行 还没有执行到 or 并行还没来得及加入进的 FileDownloadList.getImpl().removeByPaused(this); } return result; }
// End task void over() { if (FileDownloadMonitor.isValid()) { FileDownloadMonitor.getMonitor().onTaskOver(this); } if (FileDownloadLog.NEED_LOG) { FileDownloadLog.v(this, "filedownloader:lifecycle:over %s by %d ", toString(), getStatus()); } if (finishListenerList != null) { final ArrayList<FinishListener> listenersCopy = (ArrayList<FinishListener>) finishListenerList.clone(); final int numListeners = listenersCopy.size(); for (int i = 0; i < numListeners; ++i) { listenersCopy.get(i).over(); } } }
@Override public boolean isDownloading(FileDownloadModel model) { if (model == null) { return false; } final boolean isInPool = mThreadPool.isInThreadPool(model.getId()); boolean isDownloading; do { if (FileDownloadStatus.isOver(model.getStatus())) { //noinspection RedundantIfStatement if (isInPool) { // already finished, but still in the pool. // handle as downloading. isDownloading = true; } else { // already finished, and not in the pool. // make sense. isDownloading = false; } } else { if (isInPool) { // not finish, in the pool. // make sense. isDownloading = true; } else { // not finish, but not in the pool. // beyond expectation. FileDownloadLog.e( this, "%d status is[%s](not finish) & but not in the pool", model.getId(), model.getStatus()); // handle as not in downloading, going to re-downloading. isDownloading = false; } } } while (false); return isDownloading; }
public boolean pause(final int id) { final FileDownloadModel model = mHelper.find(id); if (model == null) { return false; } if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d(this, "paused %d", id); } mThreadPool.cancel(id); /** * 耦合 by {@link FileDownloadRunnable#run()} 中的 {@link * com.squareup.okhttp.Request.Builder#tag(Object)} 目前在okHttp里还是每个单独任务 */ // 之所以注释掉,不想这里回调error,okHttp中会根据okHttp所在被cancel的情况抛error // client.cancel(id); return true; }
// synchronize for safe: check downloading, check resume, update data, execute runnable public synchronized void start( final String url, final String path, final boolean pathAsDirectory, final int callbackProgressTimes, final int callbackProgressMinIntervalMillis, final int autoRetryTimes, final boolean forceReDownload, final FileDownloadHeader header) { final int id = FileDownloadUtils.generateId(url, path, pathAsDirectory); FileDownloadModel model = mHelper.find(id); if (!pathAsDirectory && model == null) { // try dir data. final int dirCaseId = FileDownloadUtils.generateId(url, FileDownloadUtils.getParent(path), true); model = mHelper.find(dirCaseId); if (model != null && path.equals(model.getTargetFilePath())) { if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d(this, "task[%d] find model by dirCaseId[%d]", id, dirCaseId); } } } if (FileDownloadHelper.inspectAndInflowDownloading(id, model, this)) { if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d(this, "has already started download %d", id); } return; } final String targetFilePath = model != null ? model.getTargetFilePath() : FileDownloadUtils.getTargetFilePath(path, pathAsDirectory, null); if (FileDownloadHelper.inspectAndInflowDownloaded(id, targetFilePath, forceReDownload)) { if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d(this, "has already completed downloading %d", id); } return; } // real start // - create model boolean needUpdate2DB; if (model != null && (model.getStatus() == FileDownloadStatus.paused || model.getStatus() == FileDownloadStatus.error) // FileDownloadRunnable invoke // #isBreakpointAvailable to determine whether it is really invalid. ) { if (model.getId() != id) { // in try dir case. mHelper.remove(model.getId()); model.setId(id); model.setPath(path, pathAsDirectory); needUpdate2DB = true; } else { needUpdate2DB = false; } } else { if (model == null) { model = new FileDownloadModel(); } model.setUrl(url); model.setPath(path, pathAsDirectory); model.setId(id); model.setSoFar(0); model.setTotal(0); model.setStatus(FileDownloadStatus.pending); needUpdate2DB = true; } // - update model to db if (needUpdate2DB) { mHelper.update(model); } // - execute mThreadPool.execute( new FileDownloadRunnable( client, this, model, mHelper, autoRetryTimes, header, callbackProgressMinIntervalMillis, callbackProgressTimes, forceReDownload)); }
/** * Just cache ApplicationContext * * <p>Proposed call at{@link Application#onCreate()} */ public static void init(final Application application) { if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d(FileDownloader.class, "init Downloader"); } FileDownloadHelper.initAppContext(application); }