1263 lines
41 KiB
Java
1263 lines
41 KiB
Java
/*
|
|
* 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.annotation.SuppressLint;
|
|
import android.content.Context;
|
|
import android.os.AsyncTask;
|
|
import android.util.Log;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
|
|
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.SpiderDen;
|
|
import com.hippo.ehviewer.spider.SpiderInfo;
|
|
import com.hippo.ehviewer.spider.SpiderQueen;
|
|
import com.hippo.image.Image;
|
|
import com.hippo.unifile.UniFile;
|
|
import com.hippo.util.IoThreadPoolExecutor;
|
|
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.io.IOException;
|
|
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 static final Comparator<DownloadInfo> DATE_DESC_COMPARATOR = (lhs, rhs) -> lhs.time - rhs.time > 0 ? -1 : 1;
|
|
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;
|
|
private final List<DownloadInfoListener> mDownloadInfoListeners;
|
|
private final ConcurrentPool<NotifyTask> mNotifyTaskPool = new ConcurrentPool<>(5);
|
|
@Nullable
|
|
private DownloadListener mDownloadListener;
|
|
@Nullable
|
|
private DownloadInfo mCurrentTask;
|
|
@Nullable
|
|
private SpiderQueen mCurrentSpider;
|
|
|
|
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<>());
|
|
}
|
|
|
|
// 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();
|
|
|
|
// Add it to history
|
|
EhDB.putHistoryInfo(info);
|
|
}
|
|
}
|
|
|
|
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
|
|
for (DownloadInfo info : mAllInfoList) {
|
|
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
|
|
mWaitList.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) {
|
|
addDownload(downloadInfoList, true);
|
|
}
|
|
|
|
public void addDownload(List<DownloadInfo> downloadInfoList, boolean notify) {
|
|
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
|
|
if (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<>());
|
|
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);
|
|
|
|
// 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);
|
|
|
|
// 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();
|
|
}
|
|
|
|
// Ensure download
|
|
ensureDownload();
|
|
}
|
|
|
|
@SuppressLint("StaticFieldLeak")
|
|
public void resetAllReadingProgress() {
|
|
LinkedList<DownloadInfo> list = new LinkedList<>(mAllInfoList);
|
|
|
|
new AsyncTask<Void, Void, Void>() {
|
|
@Override
|
|
protected Void doInBackground(Void... voids) {
|
|
GalleryInfo galleryInfo = new GalleryInfo();
|
|
for (DownloadInfo downloadInfo : list) {
|
|
galleryInfo.gid = downloadInfo.gid;
|
|
galleryInfo.token = downloadInfo.token;
|
|
galleryInfo.title = downloadInfo.title;
|
|
galleryInfo.thumb = downloadInfo.thumb;
|
|
galleryInfo.category = downloadInfo.category;
|
|
galleryInfo.posted = downloadInfo.posted;
|
|
galleryInfo.uploader = downloadInfo.uploader;
|
|
galleryInfo.rating = downloadInfo.rating;
|
|
|
|
UniFile downloadDir = SpiderDen.getGalleryDownloadDir(galleryInfo);
|
|
if (downloadDir == null) {
|
|
continue;
|
|
}
|
|
UniFile file = downloadDir.findFile(".ehviewer");
|
|
if (file == null) {
|
|
continue;
|
|
}
|
|
SpiderInfo spiderInfo = SpiderInfo.read(file);
|
|
if (spiderInfo == null) {
|
|
continue;
|
|
}
|
|
spiderInfo.startPage = 0;
|
|
|
|
try {
|
|
spiderInfo.write(file.openOutputStream());
|
|
} catch (IOException e) {
|
|
Log.e(TAG, "Can't write SpiderInfo", e);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}.executeOnExecutor(IoThreadPoolExecutor.getInstance());
|
|
}
|
|
|
|
// 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<>());
|
|
|
|
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
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
|
|
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 final SparseIJArray mContentLengthMap = new SparseIJArray();
|
|
private final SparseIJArray mReceivedSizeMap = new SparseIJArray();
|
|
private boolean mStop = true;
|
|
private long mBytesRead;
|
|
private long oldSpeed = -1;
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|