/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.download;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.dao.DownloadInfo;
import com.hippo.ehviewer.dao.DownloadLabel;
import com.hippo.ehviewer.spider.SpiderQueen;
import com.hippo.image.Image;
import com.hippo.yorozuya.ConcurrentPool;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.ObjectUtils;
import com.hippo.yorozuya.SimpleHandler;
import com.hippo.yorozuya.collect.LongList;
import com.hippo.yorozuya.collect.SparseIJArray;
import com.hippo.yorozuya.collect.SparseJLArray;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class DownloadManager implements SpiderQueen.OnSpiderListener {
private static final String TAG = DownloadManager.class.getSimpleName();
private final Context mContext;
// All download info list
private final LinkedList<DownloadInfo> mAllInfoList;
// All download info map
private final SparseJLArray<DownloadInfo> mAllInfoMap;
// label and info list map, without default label info list
private final Map<String, LinkedList<DownloadInfo>> mMap;
// All labels without default label
private final List<DownloadLabel> mLabelList;
// Store download info with default label
private final LinkedList<DownloadInfo> mDefaultInfoList;
// Store download info wait to start
private final LinkedList<DownloadInfo> mWaitList;
private final SpeedReminder mSpeedReminder;
@Nullable
private DownloadListener mDownloadListener;
private final List<DownloadInfoListener> mDownloadInfoListeners;
@Nullable
private DownloadInfo mCurrentTask;
@Nullable
private SpiderQueen mCurrentSpider;
private final ConcurrentPool<NotifyTask> mNotifyTaskPool = new ConcurrentPool<>(5);
public DownloadManager(Context context) {
mContext = context;
// Get all labels
List<DownloadLabel> labels = EhDB.getAllDownloadLabelList();
mLabelList = labels;
// Create list for each label
HashMap<String, LinkedList<DownloadInfo>> map = new HashMap<>();
mMap = map;
for (DownloadLabel label : labels) {
map.put(label.getLabel(), new LinkedList<DownloadInfo>());
}
// Create default for non tag
mDefaultInfoList = new LinkedList<>();
// Get all info
List<DownloadInfo> allInfoList = EhDB.getAllDownloadInfo();
mAllInfoList = new LinkedList<>(allInfoList);
// Create all info map
SparseJLArray<DownloadInfo> allInfoMap = new SparseJLArray<>(allInfoList.size() + 10);
mAllInfoMap = allInfoMap;
for (int i = 0, n = allInfoList.size(); i < n; i++) {
DownloadInfo info = allInfoList.get(i);
// Add to all info map
allInfoMap.put(info.gid, info);
// Add to each label list
LinkedList<DownloadInfo> list = getInfoListForLabel(info.label);
if (list == null) {
// Can't find the label in label list
list = new LinkedList<>();
map.put(info.label, list);
if (!containLabel(info.label)) {
// Add label to DB and list
labels.add(EhDB.addDownloadLabel(info.label));
}
}
list.add(info);
}
mWaitList = new LinkedList<>();
mSpeedReminder = new SpeedReminder();
mDownloadInfoListeners = new ArrayList<>();
}
@Nullable
private LinkedList<DownloadInfo> getInfoListForLabel(String label) {
if (label == null) {
return mDefaultInfoList;
} else {
return mMap.get(label);
}
}
public boolean containLabel(String label) {
if (label == null) {
return false;
}
for (DownloadLabel raw: mLabelList) {
if (label.equals(raw.getLabel())) {
return true;
}
}
return false;
}
public boolean containDownloadInfo(long gid) {
return mAllInfoMap.indexOfKey(gid) >= 0;
}
@NonNull
public List<DownloadLabel> getLabelList() {
return mLabelList;
}
@NonNull
public List<DownloadInfo> getDefaultDownloadInfoList() {
return mDefaultInfoList;
}
@Nullable
public List<DownloadInfo> getLabelDownloadInfoList(String label) {
return mMap.get(label);
}
@Nullable
public DownloadInfo getDownloadInfo(long gid) {
return mAllInfoMap.get(gid);
}
public int getDownloadState(long gid) {
DownloadInfo info = mAllInfoMap.get(gid);
if (null != info) {
return info.state;
} else {
return DownloadInfo.STATE_INVALID;
}
}
public void addDownloadInfoListener(@Nullable DownloadInfoListener downloadInfoListener) {
mDownloadInfoListeners.add(downloadInfoListener);
}
public void removeDownloadInfoListener(@Nullable DownloadInfoListener downloadInfoListener) {
mDownloadInfoListeners.remove(downloadInfoListener);
}
public void setDownloadListener(@Nullable DownloadListener listener) {
mDownloadListener = listener;
}
private void ensureDownload() {
if (mCurrentTask != null) {
// Only one download
return;
}
// Get download from wait list
if (!mWaitList.isEmpty()) {
DownloadInfo info = mWaitList.removeFirst();
SpiderQueen spider = SpiderQueen.obtainSpiderQueen(mContext, info, SpiderQueen.MODE_DOWNLOAD);
mCurrentTask = info;
mCurrentSpider = spider;
spider.addOnSpiderListener(this);
info.state = DownloadInfo.STATE_DOWNLOAD;
info.speed = -1;
info.remaining = -1;
info.total = -1;
info.finished = 0;
info.downloaded = 0;
info.legacy = -1;
// Update in DB
EhDB.putDownloadInfo(info);
// Start speed count
mSpeedReminder.start();
// Notify start downloading
if (mDownloadListener != null) {
mDownloadListener.onStart(info);
}
// Notify state update
List<DownloadInfo> list = getInfoListForLabel(info.label);
if (list != null) {
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onUpdate(info, list);
}
}
}
}
void startDownload(GalleryInfo galleryInfo, @Nullable String label) {
if (mCurrentTask != null && mCurrentTask.gid == galleryInfo.gid) {
// It is current task
return;
}
// Check in download list
DownloadInfo info = mAllInfoMap.get(galleryInfo.gid);
if (info != null) { // Get it in download list
if (info.state != DownloadInfo.STATE_WAIT) {
// Set state DownloadInfo.STATE_WAIT
info.state = DownloadInfo.STATE_WAIT;
// Add to wait list
mWaitList.add(info);
// Update in DB
EhDB.putDownloadInfo(info);
// Notify state update
List<DownloadInfo> list = getInfoListForLabel(info.label);
if (list != null) {
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onUpdate(info, list);
}
}
// Make sure download is running
ensureDownload();
}
} else {
// It is new download info
info = new DownloadInfo(galleryInfo);
info.label = label;
info.state = DownloadInfo.STATE_WAIT;
info.time = System.currentTimeMillis();
// Add to label download list
LinkedList<DownloadInfo> list = getInfoListForLabel(info.label);
if (list == null) {
Log.e(TAG, "Can't find download info list with label: " + label);
return;
}
list.addFirst(info);
// Add to all download list and map
mAllInfoList.addFirst(info);
mAllInfoMap.put(galleryInfo.gid, info);
// Add to wait list
mWaitList.add(info);
// Save to
EhDB.putDownloadInfo(info);
// Notify
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onAdd(info, list, list.size() - 1);
}
// Make sure download is running
ensureDownload();
}
}
void startRangeDownload(LongList gidList) {
boolean update = false;
for (int i = 0, n = gidList.size(); i < n; i++) {
long gid = gidList.get(i);
DownloadInfo info = mAllInfoMap.get(gid);
if (null == info) {
Log.d(TAG, "Can't get download info with gid: " + gid);
continue;
}
if (info.state == DownloadInfo.STATE_NONE ||
info.state == DownloadInfo.STATE_FAILED ||
info.state == DownloadInfo.STATE_FINISH) {
update = true;
// Set state DownloadInfo.STATE_WAIT
info.state = DownloadInfo.STATE_WAIT;
// Add to wait list
mWaitList.add(info);
// Update in DB
EhDB.putDownloadInfo(info);
}
}
if (update) {
// Notify Listener
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onUpdateAll();
}
// Ensure download
ensureDownload();
}
}
void startAllDownload() {
boolean update = false;
// Start all STATE_NONE and STATE_FAILED item
LinkedList<DownloadInfo> allInfoList = mAllInfoList;
LinkedList<DownloadInfo> waitList = mWaitList;
for (DownloadInfo info: allInfoList) {
if (info.state == DownloadInfo.STATE_NONE || info.state == DownloadInfo.STATE_FAILED) {
update = true;
// Set state DownloadInfo.STATE_WAIT
info.state = DownloadInfo.STATE_WAIT;
// Add to wait list
waitList.add(info);
// Update in DB
EhDB.putDownloadInfo(info);
}
}
if (update) {
// Notify Listener
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onUpdateAll();
}
// Ensure download
ensureDownload();
}
}
public void addDownload(List<DownloadInfo> downloadInfoList) {
for (DownloadInfo info: downloadInfoList) {
if (containDownloadInfo(info.gid)) {
// Contain
return;
}
// Ensure download state
if (DownloadInfo.STATE_WAIT == info.state ||
DownloadInfo.STATE_DOWNLOAD == info.state) {
info.state = DownloadInfo.STATE_NONE;
}
// Add to label download list
LinkedList<DownloadInfo> list = getInfoListForLabel(info.label);
if (null == list) {
// Can't find the label in label list
list = new LinkedList<>();
mMap.put(info.label, list);
if (!containLabel(info.label)) {
// Add label to DB and list
mLabelList.add(EhDB.addDownloadLabel(info.label));
}
}
list.add(info);
// Sort
Collections.sort(list, DATE_DESC_COMPARATOR);
// Add to all download list and map
mAllInfoList.add(info);
mAllInfoMap.put(info.gid, info);
// Save to
EhDB.putDownloadInfo(info);
}
// Sort all download list
Collections.sort(mAllInfoList, DATE_DESC_COMPARATOR);
// Notify
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onReload();
}
}
public void addDownloadLabel(List<DownloadLabel> downloadLabelList) {
for (DownloadLabel label: downloadLabelList) {
String labelString = label.getLabel();
if (!containLabel(labelString)) {
mMap.put(labelString, new LinkedList<DownloadInfo>());
mLabelList.add(EhDB.addDownloadLabel(label));
}
}
}
public void addDownload(GalleryInfo galleryInfo, @Nullable String label) {
if (containDownloadInfo(galleryInfo.gid)) {
// Contain
return;
}
// It is new download info
DownloadInfo info = new DownloadInfo(galleryInfo);
info.label = label;
info.state = DownloadInfo.STATE_NONE;
info.time = System.currentTimeMillis();
// Add to label download list
LinkedList<DownloadInfo> list = getInfoListForLabel(info.label);
if (list == null) {
Log.e(TAG, "Can't find download info list with label: " + label);
return;
}
list.addFirst(info);
// Add to all download list and map
mAllInfoList.addFirst(info);
mAllInfoMap.put(galleryInfo.gid, info);
// Save to
EhDB.putDownloadInfo(info);
// Notify
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onAdd(info, list, list.size() - 1);
}
}
public void stopDownload(long gid) {
DownloadInfo info = stopDownloadInternal(gid);
if (info != null) {
// Update listener
List<DownloadInfo> list = getInfoListForLabel(info.label);
if (list != null) {
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onUpdate(info, list);
}
}
// Ensure download
ensureDownload();
}
}
void stopCurrentDownload() {
DownloadInfo info = stopCurrentDownloadInternal();
if (info != null) {
// Update listener
List<DownloadInfo> list = getInfoListForLabel(info.label);
if (list != null) {
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onUpdate(info, list);
}
}
// Ensure download
ensureDownload();
}
}
public void stopRangeDownload(LongList gidList) {
stopRangeDownloadInternal(gidList);
// Update listener
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onUpdateAll();
}
// Ensure download
ensureDownload();
}
public void stopAllDownload() {
// Stop all in wait list
for (DownloadInfo info : mWaitList) {
info.state = DownloadInfo.STATE_NONE;
// Update in DB
EhDB.putDownloadInfo(info);
}
mWaitList.clear();
// Stop current
stopCurrentDownloadInternal();
// Notify mDownloadInfoListener
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onUpdateAll();
}
}
public void deleteDownload(long gid) {
stopDownloadInternal(gid);
DownloadInfo info = mAllInfoMap.get(gid);
if (info != null) {
// Remove from DB
EhDB.removeDownloadInfo(info.gid);
// Remove all list and map
mAllInfoList.remove(info);
mAllInfoMap.remove(info.gid);
// Remove label list
LinkedList<DownloadInfo> list = getInfoListForLabel(info.label);
if (list != null) {
int index = list.indexOf(info);
if (index >= 0) {
list.remove(info);
// Update listener
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onRemove(info, list, index);
}
}
}
// Ensure download
ensureDownload();
}
}
public void deleteRangeDownload(LongList gidList) {
stopRangeDownloadInternal(gidList);
for (int i = 0, n = gidList.size(); i < n; i++) {
long gid = gidList.get(i);
DownloadInfo info = mAllInfoMap.get(gid);
if (null == info) {
Log.d(TAG, "Can't get download info with gid: " + gid);
continue;
}
// Remove from DB
EhDB.removeDownloadInfo(info.gid);
// Remove from all info map
mAllInfoList.remove(info);
mAllInfoMap.remove(info.gid);
// Remove from label list
LinkedList<DownloadInfo> list = getInfoListForLabel(info.label);
if (list != null) {
list.remove(info);
}
}
// Update listener
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onReload();
}
}
// Update in DB
// Update listener
// No ensureDownload
private DownloadInfo stopDownloadInternal(long gid) {
// Check current task
if (mCurrentTask != null && mCurrentTask.gid == gid) {
// Stop current
return stopCurrentDownloadInternal();
}
for (Iterator<DownloadInfo> iterator = mWaitList.iterator(); iterator.hasNext();) {
DownloadInfo info = iterator.next();
if (info.gid == gid) {
// Remove from wait list
iterator.remove();
// Update state
info.state = DownloadInfo.STATE_NONE;
// Update in DB
EhDB.putDownloadInfo(info);
return info;
}
}
return null;
}
// Update in DB
// Update mDownloadListener
private DownloadInfo stopCurrentDownloadInternal() {
DownloadInfo info = mCurrentTask;
SpiderQueen spider = mCurrentSpider;
// Release spider
if (spider != null) {
spider.removeOnSpiderListener(DownloadManager.this);
SpiderQueen.releaseSpiderQueen(spider, SpiderQueen.MODE_DOWNLOAD);
}
mCurrentTask = null;
mCurrentSpider = null;
// Stop speed reminder
mSpeedReminder.stop();
if (info == null) {
return null;
}
// Update state
info.state = DownloadInfo.STATE_NONE;
// Update in DB
EhDB.putDownloadInfo(info);
// Listener
if (mDownloadListener != null) {
mDownloadListener.onCancel(info);
}
return info;
}
// Update in DB
// Update mDownloadListener
private void stopRangeDownloadInternal(LongList gidList) {
// Two way
if (gidList.size() < mWaitList.size()) {
for (int i = 0, n = gidList.size(); i < n; i++) {
stopDownloadInternal(gidList.get(i));
}
} else {
// Check current task
if (mCurrentTask != null && gidList.contains(mCurrentTask.gid)) {
// Stop current
stopCurrentDownloadInternal();
}
// Check all in wait list
for (Iterator<DownloadInfo> iterator = mWaitList.iterator(); iterator.hasNext();) {
DownloadInfo info = iterator.next();
if (gidList.contains(info.gid)) {
// Remove from wait list
iterator.remove();
// Update state
info.state = DownloadInfo.STATE_NONE;
// Update in DB
EhDB.putDownloadInfo(info);
}
}
}
}
/**
* @param label Not allow new label
*/
public void changeLabel(List<DownloadInfo> list, String label) {
if (null != label && !containLabel(label)) {
Log.e(TAG, "Not exits label: " + label);
return;
}
List<DownloadInfo> dstList = getInfoListForLabel(label);
if (dstList == null) {
Log.e(TAG, "Can't find label with label: " + label);
return;
}
for (DownloadInfo info: list) {
if (ObjectUtils.equal(info.label, label)) {
continue;
}
List<DownloadInfo> srcList = getInfoListForLabel(info.label);
if (srcList == null) {
Log.e(TAG, "Can't find label with label: " + info.label);
continue;
}
srcList.remove(info);
dstList.add(info);
info.label = label;
Collections.sort(dstList, DATE_DESC_COMPARATOR);
// Save to DB
EhDB.putDownloadInfo(info);
}
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onReload();
}
}
public void addLabel(String label) {
if (label == null || containLabel(label)) {
return;
}
mLabelList.add(EhDB.addDownloadLabel(label));
mMap.put(label, new LinkedList<DownloadInfo>());
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onUpdateLabels();
}
}
public void moveLabel(int fromPosition, int toPosition) {
final DownloadLabel item = mLabelList.remove(fromPosition);
mLabelList.add(toPosition, item);
EhDB.moveDownloadLabel(fromPosition, toPosition);
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onUpdateLabels();
}
}
public void renameLabel(@NonNull String from, @NonNull String to) {
// Find in label list
boolean found = false;
for (DownloadLabel raw: mLabelList) {
if (from.equals(raw.getLabel())) {
found = true;
raw.setLabel(to);
// Update in DB
EhDB.updateDownloadLabel(raw);
break;
}
}
if (!found) {
return;
}
LinkedList<DownloadInfo> list = mMap.remove(from);
if (list == null) {
return;
}
// Update info label
for (DownloadInfo info: list) {
info.label = to;
// Update in DB
EhDB.putDownloadInfo(info);
}
// Put list back with new label
mMap.put(to, list);
// Notify listener
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onRenameLabel(from, to);
}
}
public void deleteLabel(@NonNull String label) {
// Find in label list and remove
boolean found = false;
for (Iterator<DownloadLabel> iterator = mLabelList.iterator(); iterator.hasNext();) {
DownloadLabel raw = iterator.next();
if (label.equals(raw.getLabel())) {
found = true;
iterator.remove();
EhDB.removeDownloadLabel(raw);
break;
}
}
if (!found) {
return;
}
LinkedList<DownloadInfo> list = mMap.remove(label);
if (list == null) {
return;
}
// Update info label
for (DownloadInfo info: list) {
info.label = null;
// Update in DB
EhDB.putDownloadInfo(info);
mDefaultInfoList.add(info);
}
// Sort
Collections.sort(mDefaultInfoList, DATE_DESC_COMPARATOR);
// Notify listener
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onChange();
}
}
boolean isIdle() {
return mCurrentTask == null && mWaitList.isEmpty();
}
@Override
public void onGetPages(int pages) {
NotifyTask task = mNotifyTaskPool.pop();
if (task == null) {
task = new NotifyTask();
}
task.setOnGetPagesData(pages);
SimpleHandler.getInstance().post(task);
}
@Override
public void onGet509(int index) {
NotifyTask task = mNotifyTaskPool.pop();
if (task == null) {
task = new NotifyTask();
}
task.setOnGet509Data(index);
SimpleHandler.getInstance().post(task);
}
@Override
public void onPageDownload(int index, long contentLength, long receivedSize, int bytesRead) {
NotifyTask task = mNotifyTaskPool.pop();
if (task == null) {
task = new NotifyTask();
}
task.setOnPageDownloadData(index, contentLength, receivedSize, bytesRead);
SimpleHandler.getInstance().post(task);
}
@Override
public void onPageSuccess(int index, int finished, int downloaded, int total) {
NotifyTask task = mNotifyTaskPool.pop();
if (task == null) {
task = new NotifyTask();
}
task.setOnPageSuccessData(index, finished, downloaded, total);
SimpleHandler.getInstance().post(task);
}
@Override
public void onPageFailure(int index, String error, int finished, int downloaded, int total) {
NotifyTask task = mNotifyTaskPool.pop();
if (task == null) {
task = new NotifyTask();
}
task.setOnPageFailureDate(index, error, finished, downloaded, total);
SimpleHandler.getInstance().post(task);
}
@Override
public void onFinish(int finished, int downloaded, int total) {
NotifyTask task = mNotifyTaskPool.pop();
if (task == null) {
task = new NotifyTask();
}
task.setOnFinishDate(finished, downloaded, total);
SimpleHandler.getInstance().post(task);
}
@Override
public void onGetImageSuccess(int index, Image image) {
// Ignore
}
@Override
public void onGetImageFailure(int index, String error) {
// Ignore
}
private class NotifyTask implements Runnable {
public static final int TYPE_ON_GET_PAGES = 0;
public static final int TYPE_ON_GET_509 = 1;
public static final int TYPE_ON_PAGE_DOWNLOAD = 2;
public static final int TYPE_ON_PAGE_SUCCESS = 3;
public static final int TYPE_ON_PAGE_FAILURE = 4;
public static final int TYPE_ON_FINISH = 5;
private int mType;
private int mPages;
private int mIndex;
private long mContentLength;
private long mReceivedSize;
private int mBytesRead;
@SuppressWarnings("unused")
private String mError;
private int mFinished;
private int mDownloaded;
private int mTotal;
public void setOnGetPagesData(int pages) {
mType = TYPE_ON_GET_PAGES;
mPages = pages;
}
public void setOnGet509Data(int index) {
mType = TYPE_ON_GET_509;
mIndex = index;
}
public void setOnPageDownloadData(int index, long contentLength, long receivedSize, int bytesRead) {
mType = TYPE_ON_PAGE_DOWNLOAD;
mIndex = index;
mContentLength = contentLength;
mReceivedSize = receivedSize;
mBytesRead = bytesRead;
}
public void setOnPageSuccessData(int index, int finished, int downloaded, int total) {
mType = TYPE_ON_PAGE_SUCCESS;
mIndex = index;
mFinished = finished;
mDownloaded = downloaded;
mTotal = total;
}
public void setOnPageFailureDate(int index, String error, int finished, int downloaded, int total) {
mType = TYPE_ON_PAGE_FAILURE;
mIndex = index;
mError = error;
mFinished = finished;
mDownloaded = downloaded;
mTotal = total;
}
public void setOnFinishDate(int finished, int downloaded, int total) {
mType = TYPE_ON_FINISH;
mFinished = finished;
mDownloaded = downloaded;
mTotal = total;
}
@Override
public void run() {
switch (mType) {
case TYPE_ON_GET_PAGES: {
DownloadInfo info = mCurrentTask;
if (info == null) {
Log.e(TAG, "Current task is null, but it should not be");
} else {
info.total = mPages;
List<DownloadInfo> list = getInfoListForLabel(info.label);
if (list != null) {
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onUpdate(info, list);
}
}
}
break;
}
case TYPE_ON_GET_509: {
if (mDownloadListener != null) {
mDownloadListener.onGet509();
}
break;
}
case TYPE_ON_PAGE_DOWNLOAD: {
mSpeedReminder.onDownload(mIndex, mContentLength, mReceivedSize, mBytesRead);
break;
}
case TYPE_ON_PAGE_SUCCESS: {
mSpeedReminder.onDone(mIndex);
DownloadInfo info = mCurrentTask;
if (info == null) {
Log.e(TAG, "Current task is null, but it should not be");
} else {
info.finished = mFinished;
info.downloaded = mDownloaded;
info.total = mTotal;
if (mDownloadListener != null) {
mDownloadListener.onGetPage(info);
}
List<DownloadInfo> list = getInfoListForLabel(info.label);
if (list != null) {
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onUpdate(info, list);
}
}
}
break;
}
case TYPE_ON_PAGE_FAILURE: {
mSpeedReminder.onDone(mIndex);
DownloadInfo info = mCurrentTask;
if (info == null) {
Log.e(TAG, "Current task is null, but it should not be");
} else {
info.finished = mFinished;
info.downloaded = mDownloaded;
info.total = mTotal;
List<DownloadInfo> list = getInfoListForLabel(info.label);
if (list != null) {
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onUpdate(info, list);
}
}
}
break;
}
case TYPE_ON_FINISH: {
mSpeedReminder.onFinish();
// Download done
DownloadInfo info = mCurrentTask;
mCurrentTask = null;
SpiderQueen spider = mCurrentSpider;
mCurrentSpider = null;
// Release spider
if (spider != null) {
spider.removeOnSpiderListener(DownloadManager.this);
SpiderQueen.releaseSpiderQueen(spider, SpiderQueen.MODE_DOWNLOAD);
}
// Check null
if (info == null || spider == null) {
Log.e(TAG, "Current stuff is null, but it should not be");
break;
}
// Stop speed count
mSpeedReminder.stop();
// Update state
info.finished = mFinished;
info.downloaded = mDownloaded;
info.total = mTotal;
info.legacy = mTotal - mFinished;
if (info.legacy == 0) {
info.state = DownloadInfo.STATE_FINISH;
} else {
info.state = DownloadInfo.STATE_FAILED;
}
// Update in DB
EhDB.putDownloadInfo(info);
// Notify
if (mDownloadListener != null) {
mDownloadListener.onFinish(info);
}
List<DownloadInfo> list = getInfoListForLabel(info.label);
if (list != null) {
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onUpdate(info, list);
}
}
// Start next download
ensureDownload();
break;
}
}
mNotifyTaskPool.push(this);
}
}
class SpeedReminder implements Runnable {
private boolean mStop = true;
private long mBytesRead;
private long oldSpeed = -1;
private final SparseIJArray mContentLengthMap = new SparseIJArray();
private final SparseIJArray mReceivedSizeMap = new SparseIJArray();
public void start() {
if (mStop) {
mStop = false;
SimpleHandler.getInstance().post(this);
}
}
public void stop() {
if (!mStop) {
mStop = true;
mBytesRead = 0;
oldSpeed = -1;
mContentLengthMap.clear();
mReceivedSizeMap.clear();
SimpleHandler.getInstance().removeCallbacks(this);
}
}
public void onDownload(int index, long contentLength, long receivedSize, int bytesRead) {
mContentLengthMap.put(index, contentLength);
mReceivedSizeMap.put(index, receivedSize);
mBytesRead += bytesRead;
}
public void onDone(int index) {
mContentLengthMap.delete(index);
mReceivedSizeMap.delete(index);
}
public void onFinish() {
mContentLengthMap.clear();
mReceivedSizeMap.clear();
}
@Override
public void run() {
DownloadInfo info = mCurrentTask;
if (info != null) {
long newSpeed = mBytesRead / 2;
if (oldSpeed != -1) {
newSpeed = (long) MathUtils.lerp(oldSpeed, newSpeed, 0.75f);
}
oldSpeed = newSpeed;
info.speed = newSpeed;
// Calculate remaining
if (info.total <= 0) {
info.remaining = -1;
} else if (newSpeed == 0) {
info.remaining = 300L * 24L * 60L * 60L * 1000L; // 300 days
} else {
int downloadingCount = 0;
long downloadingContentLengthSum = 0;
long totalSize = 0;
for (int i = 0, n = Math.max(mContentLengthMap.size(), mReceivedSizeMap.size()); i < n; i++) {
long contentLength = mContentLengthMap.valueAt(i);
long receivedSize = mReceivedSizeMap.valueAt(i);
downloadingCount++;
downloadingContentLengthSum += contentLength;
totalSize += contentLength - receivedSize;
}
if (downloadingCount != 0) {
totalSize += downloadingContentLengthSum * (info.total - info.downloaded - downloadingCount) / downloadingCount;
info.remaining = totalSize / newSpeed * 1000;
}
}
if (mDownloadListener != null) {
mDownloadListener.onDownload(info);
}
List<DownloadInfo> list = getInfoListForLabel(info.label);
if (list != null) {
for (DownloadInfoListener l: mDownloadInfoListeners) {
l.onUpdate(info, list);
}
}
}
mBytesRead = 0;
if (!mStop) {
SimpleHandler.getInstance().postDelayed(this, 2000);
}
}
}
private static final Comparator<DownloadInfo> DATE_DESC_COMPARATOR = new Comparator<DownloadInfo>() {
@Override
public int compare(DownloadInfo lhs, DownloadInfo rhs) {
return lhs.time - rhs.time > 0 ? -1 : 1;
}
};
public interface DownloadInfoListener {
/**
* Add the special info to the special position
*/
void onAdd(@NonNull DownloadInfo info, @NonNull List<DownloadInfo> list, int position);
/**
* The special info is changed
*/
void onUpdate(@NonNull DownloadInfo info, @NonNull List<DownloadInfo> list);
/**
* Maybe all data is changed, but size is the same
*/
void onUpdateAll();
/**
* Maybe all data is changed, maybe list is changed
*/
void onReload();
/**
* The list is gone, use default list please
*/
void onChange();
/**
* Rename label
*/
void onRenameLabel(String from, String to);
/**
* Remove the special info from the special position
*/
void onRemove(@NonNull DownloadInfo info, @NonNull List<DownloadInfo> list, int position);
void onUpdateLabels();
}
public interface DownloadListener {
/**
* Get 509 error
*/
void onGet509();
/**
* Start download
*/
void onStart(DownloadInfo info);
/**
* Update download speed
*/
void onDownload(DownloadInfo info);
/**
* Update page downloaded
*/
void onGetPage(DownloadInfo info);
/**
* Download done
*/
void onFinish(DownloadInfo info);
/**
* Download done
*/
void onCancel(DownloadInfo info);
}
}