mirror of
https://github.com/MaintainTeam/LastPipeBender.git
synced 2025-03-05 21:34:40 +03:00
671 lines
23 KiB
Java
671 lines
23 KiB
Java
|
package us.shandian.giga.service;
|
||
|
|
||
|
import android.content.Context;
|
||
|
import android.content.SharedPreferences;
|
||
|
import android.os.Handler;
|
||
|
import android.preference.PreferenceManager;
|
||
|
import android.support.annotation.NonNull;
|
||
|
import android.support.annotation.Nullable;
|
||
|
import android.support.v7.util.DiffUtil;
|
||
|
import android.util.Log;
|
||
|
import android.widget.Toast;
|
||
|
|
||
|
import org.schabi.newpipe.R;
|
||
|
|
||
|
import java.io.File;
|
||
|
import java.io.IOException;
|
||
|
import java.util.ArrayList;
|
||
|
import java.util.Arrays;
|
||
|
import java.util.Collections;
|
||
|
import java.util.Iterator;
|
||
|
|
||
|
import us.shandian.giga.get.DownloadMission;
|
||
|
import us.shandian.giga.get.FinishedMission;
|
||
|
import us.shandian.giga.get.Mission;
|
||
|
import us.shandian.giga.get.sqlite.DownloadDataSource;
|
||
|
import us.shandian.giga.util.Utility;
|
||
|
|
||
|
import static org.schabi.newpipe.BuildConfig.DEBUG;
|
||
|
|
||
|
public class DownloadManager {
|
||
|
private static final String TAG = DownloadManager.class.getSimpleName();
|
||
|
|
||
|
enum NetworkState {Unavailable, WifiOperating, MobileOperating, OtherOperating}
|
||
|
|
||
|
public final static int SPECIAL_NOTHING = 0;
|
||
|
public final static int SPECIAL_PENDING = 1;
|
||
|
public final static int SPECIAL_FINISHED = 2;
|
||
|
|
||
|
private final DownloadDataSource mDownloadDataSource;
|
||
|
|
||
|
private final ArrayList<DownloadMission> mMissionsPending = new ArrayList<>();
|
||
|
private final ArrayList<FinishedMission> mMissionsFinished;
|
||
|
|
||
|
private final Handler mHandler;
|
||
|
private final File mPendingMissionsDir;
|
||
|
|
||
|
private NetworkState mLastNetworkStatus = NetworkState.Unavailable;
|
||
|
|
||
|
private SharedPreferences mPrefs;
|
||
|
private String mPrefMaxRetry;
|
||
|
private String mPrefCrossNetwork;
|
||
|
|
||
|
/**
|
||
|
* Create a new instance
|
||
|
*
|
||
|
* @param context Context for the data source for finished downloads
|
||
|
* @param handler Thread required for Messaging
|
||
|
*/
|
||
|
DownloadManager(@NonNull Context context, Handler handler) {
|
||
|
if (DEBUG) {
|
||
|
Log.d(TAG, "new DownloadManager instance. 0x" + Integer.toHexString(this.hashCode()));
|
||
|
}
|
||
|
|
||
|
mDownloadDataSource = new DownloadDataSource(context);
|
||
|
mHandler = handler;
|
||
|
mMissionsFinished = loadFinishedMissions();
|
||
|
mPendingMissionsDir = getPendingDir(context);
|
||
|
mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||
|
mPrefMaxRetry = context.getString(R.string.downloads_max_retry);
|
||
|
mPrefCrossNetwork = context.getString(R.string.cross_network_downloads);
|
||
|
|
||
|
if (!Utility.mkdir(mPendingMissionsDir, false)) {
|
||
|
throw new RuntimeException("failed to create pending_downloads in data directory");
|
||
|
}
|
||
|
|
||
|
loadPendingMissions();
|
||
|
}
|
||
|
|
||
|
private static File getPendingDir(@NonNull Context context) {
|
||
|
//File dir = new File(ContextCompat.getDataDir(context), "pending_downloads");
|
||
|
File dir = context.getExternalFilesDir("pending_downloads");
|
||
|
|
||
|
if (dir == null) {
|
||
|
// One of the following paths are not accessible ¿unmounted internal memory?
|
||
|
// /storage/emulated/0/Android/data/org.schabi.newpipe[.debug]/pending_downloads
|
||
|
// /sdcard/Android/data/org.schabi.newpipe[.debug]/pending_downloads
|
||
|
Log.w(TAG, "path to pending downloads are not accessible");
|
||
|
}
|
||
|
|
||
|
return dir;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Loads finished missions from the data source
|
||
|
*/
|
||
|
private ArrayList<FinishedMission> loadFinishedMissions() {
|
||
|
ArrayList<FinishedMission> finishedMissions = mDownloadDataSource.loadFinishedMissions();
|
||
|
|
||
|
// missions always is stored by creation order, simply reverse the list
|
||
|
ArrayList<FinishedMission> result = new ArrayList<>(finishedMissions.size());
|
||
|
for (int i = finishedMissions.size() - 1; i >= 0; i--) {
|
||
|
FinishedMission mission = finishedMissions.get(i);
|
||
|
File file = mission.getDownloadedFile();
|
||
|
|
||
|
if (!file.isFile()) {
|
||
|
if (DEBUG) {
|
||
|
Log.d(TAG, "downloaded file removed: " + file.getAbsolutePath());
|
||
|
}
|
||
|
mDownloadDataSource.deleteMission(mission);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
result.add(mission);
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||
|
private void loadPendingMissions() {
|
||
|
File[] subs = mPendingMissionsDir.listFiles();
|
||
|
|
||
|
if (subs == null) {
|
||
|
Log.e(TAG, "listFiles() returned null");
|
||
|
return;
|
||
|
}
|
||
|
if (subs.length < 1) {
|
||
|
return;
|
||
|
}
|
||
|
if (DEBUG) {
|
||
|
Log.d(TAG, "Loading pending downloads from directory: " + mPendingMissionsDir.getAbsolutePath());
|
||
|
}
|
||
|
|
||
|
for (File sub : subs) {
|
||
|
if (sub.isFile()) {
|
||
|
DownloadMission mis = Utility.readFromFile(sub);
|
||
|
|
||
|
if (mis == null) {
|
||
|
sub.delete();
|
||
|
} else {
|
||
|
if (mis.isFinished()) {
|
||
|
sub.delete();
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
File dl = mis.getDownloadedFile();
|
||
|
boolean exists = dl.exists();
|
||
|
|
||
|
if (mis.postprocessingRunning && mis.postprocessingThis) {
|
||
|
// Incomplete post-processing results in a corrupted download file
|
||
|
// because the selected algorithm works on the same file to save space.
|
||
|
if (!dl.delete()) {
|
||
|
Log.w(TAG, "Unable to delete incomplete download file: " + sub.getPath());
|
||
|
}
|
||
|
exists = true;
|
||
|
mis.postprocessingRunning = false;
|
||
|
mis.errCode = DownloadMission.ERROR_POSTPROCESSING_FAILED;
|
||
|
mis.errObject = new RuntimeException("post-processing stopped unexpectedly");
|
||
|
}
|
||
|
|
||
|
if (exists && !dl.isFile()) {
|
||
|
// probably a folder, this should never happens
|
||
|
if (!sub.delete()) {
|
||
|
Log.w(TAG, "Unable to delete serialized file: " + sub.getPath());
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (!exists) {
|
||
|
// downloaded file deleted, reset mission state
|
||
|
DownloadMission m = new DownloadMission(mis.urls, mis.name, mis.location, mis.kind, mis.postprocessingName, mis.postprocessingArgs);
|
||
|
m.timestamp = mis.timestamp;
|
||
|
m.threadCount = mis.threadCount;
|
||
|
m.source = mis.source;
|
||
|
m.maxRetry = mis.maxRetry;
|
||
|
mis = m;
|
||
|
}
|
||
|
|
||
|
mis.running = false;
|
||
|
mis.recovered = exists;
|
||
|
mis.metadata = sub;
|
||
|
mis.mHandler = mHandler;
|
||
|
|
||
|
mMissionsPending.add(mis);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (mMissionsPending.size() > 1) {
|
||
|
Collections.sort(mMissionsPending, (mission1, mission2) -> Long.compare(mission1.timestamp, mission2.timestamp));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Start a new download mission
|
||
|
*
|
||
|
* @param urls the list of urls to download
|
||
|
* @param location the location
|
||
|
* @param name the name of the file to create
|
||
|
* @param kind type of file (a: audio v: video s: subtitle ?: file-extension defined)
|
||
|
* @param threads the number of threads maximal used to download chunks of the file.
|
||
|
* @param postprocessingName the name of the required post-processing algorithm, or {@code null} to ignore.
|
||
|
* @param source source url of the resource
|
||
|
* @param postProcessingArgs the arguments for the post-processing algorithm.
|
||
|
*/
|
||
|
void startMission(String[] urls, String location, String name, char kind, int threads, String source,
|
||
|
String postprocessingName, String[] postProcessingArgs) {
|
||
|
synchronized (this) {
|
||
|
// check for existing pending download
|
||
|
DownloadMission pendingMission = getPendingMission(location, name);
|
||
|
if (pendingMission != null) {
|
||
|
// generate unique filename (?)
|
||
|
try {
|
||
|
name = generateUniqueName(location, name);
|
||
|
} catch (Exception e) {
|
||
|
Log.e(TAG, "Unable to generate unique name", e);
|
||
|
name = System.currentTimeMillis() + name;
|
||
|
Log.i(TAG, "Using " + name);
|
||
|
}
|
||
|
} else {
|
||
|
// check for existing finished download
|
||
|
int index = getFinishedMissionIndex(location, name);
|
||
|
if (index >= 0) mDownloadDataSource.deleteMission(mMissionsFinished.remove(index));
|
||
|
}
|
||
|
|
||
|
DownloadMission mission = new DownloadMission(urls, name, location, kind, postprocessingName, postProcessingArgs);
|
||
|
mission.timestamp = System.currentTimeMillis();
|
||
|
mission.threadCount = threads;
|
||
|
mission.source = source;
|
||
|
mission.mHandler = mHandler;
|
||
|
mission.maxRetry = mPrefs.getInt(mPrefMaxRetry, 3);
|
||
|
|
||
|
while (true) {
|
||
|
mission.metadata = new File(mPendingMissionsDir, String.valueOf(mission.timestamp));
|
||
|
if (!mission.metadata.isFile() && !mission.metadata.exists()) {
|
||
|
try {
|
||
|
if (!mission.metadata.createNewFile())
|
||
|
throw new RuntimeException("Cant create download metadata file");
|
||
|
} catch (IOException e) {
|
||
|
throw new RuntimeException(e);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
mission.timestamp = System.currentTimeMillis();
|
||
|
}
|
||
|
|
||
|
mMissionsPending.add(mission);
|
||
|
|
||
|
// Before starting, save the state in case the internet connection is not available
|
||
|
Utility.writeToFile(mission.metadata, mission);
|
||
|
|
||
|
if (canDownloadInCurrentNetwork() && (getRunningMissionsCount() < 1)) {
|
||
|
mission.start();
|
||
|
mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_RUNNING);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
public void resumeMission(DownloadMission mission) {
|
||
|
if (!mission.running) {
|
||
|
mission.start();
|
||
|
mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_RUNNING);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void pauseMission(DownloadMission mission) {
|
||
|
if (mission.running) {
|
||
|
mission.pause();
|
||
|
mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PAUSED);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void deleteMission(Mission mission) {
|
||
|
synchronized (this) {
|
||
|
if (mission instanceof DownloadMission) {
|
||
|
mMissionsPending.remove(mission);
|
||
|
} else if (mission instanceof FinishedMission) {
|
||
|
mMissionsFinished.remove(mission);
|
||
|
mDownloadDataSource.deleteMission(mission);
|
||
|
}
|
||
|
|
||
|
mission.delete();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Get a pending mission by its location and name
|
||
|
*
|
||
|
* @param location the location
|
||
|
* @param name the name
|
||
|
* @return the mission or null if no such mission exists
|
||
|
*/
|
||
|
@Nullable
|
||
|
private DownloadMission getPendingMission(String location, String name) {
|
||
|
for (DownloadMission mission : mMissionsPending) {
|
||
|
if (location.equalsIgnoreCase(mission.location) && name.equalsIgnoreCase(mission.name)) {
|
||
|
return mission;
|
||
|
}
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get a finished mission by its location and name
|
||
|
*
|
||
|
* @param location the location
|
||
|
* @param name the name
|
||
|
* @return the mission index or -1 if no such mission exists
|
||
|
*/
|
||
|
private int getFinishedMissionIndex(String location, String name) {
|
||
|
for (int i = 0; i < mMissionsFinished.size(); i++) {
|
||
|
FinishedMission mission = mMissionsFinished.get(i);
|
||
|
if (location.equalsIgnoreCase(mission.location) && name.equalsIgnoreCase(mission.name)) {
|
||
|
return i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
public Mission getAnyMission(String location, String name) {
|
||
|
synchronized (this) {
|
||
|
Mission mission = getPendingMission(location, name);
|
||
|
if (mission != null) return mission;
|
||
|
|
||
|
int idx = getFinishedMissionIndex(location, name);
|
||
|
if (idx >= 0) return mMissionsFinished.get(idx);
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
int getRunningMissionsCount() {
|
||
|
int count = 0;
|
||
|
synchronized (this) {
|
||
|
for (DownloadMission mission : mMissionsPending) {
|
||
|
if (mission.running && mission.errCode != DownloadMission.ERROR_POSTPROCESSING_FAILED && !mission.isFinished())
|
||
|
count++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
void pauseAllMissions() {
|
||
|
synchronized (this) {
|
||
|
for (DownloadMission mission : mMissionsPending) mission.pause();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Splits the filename into name and extension
|
||
|
* <p>
|
||
|
* Dots are ignored if they appear: not at all, at the beginning of the file,
|
||
|
* at the end of the file
|
||
|
*
|
||
|
* @param name the name to split
|
||
|
* @return a string array with a length of 2 containing the name and the extension
|
||
|
*/
|
||
|
private static String[] splitName(String name) {
|
||
|
int dotIndex = name.lastIndexOf('.');
|
||
|
if (dotIndex <= 0 || (dotIndex == name.length() - 1)) {
|
||
|
return new String[]{name, ""};
|
||
|
} else {
|
||
|
return new String[]{name.substring(0, dotIndex), name.substring(dotIndex + 1)};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Generates a unique file name.
|
||
|
* <p>
|
||
|
* e.g. "myName (1).txt" if the name "myName.txt" exists.
|
||
|
*
|
||
|
* @param location the location (to check for existing files)
|
||
|
* @param name the name of the file
|
||
|
* @return the unique file name
|
||
|
* @throws IllegalArgumentException if the location is not a directory
|
||
|
* @throws SecurityException if the location is not readable
|
||
|
*/
|
||
|
private static String generateUniqueName(String location, String name) {
|
||
|
if (location == null) throw new NullPointerException("location is null");
|
||
|
if (name == null) throw new NullPointerException("name is null");
|
||
|
File destination = new File(location);
|
||
|
if (!destination.isDirectory()) {
|
||
|
throw new IllegalArgumentException("location is not a directory: " + location);
|
||
|
}
|
||
|
final String[] nameParts = splitName(name);
|
||
|
String[] existingName = destination.list((dir, name1) -> name1.startsWith(nameParts[0]));
|
||
|
Arrays.sort(existingName);
|
||
|
String newName;
|
||
|
int downloadIndex = 0;
|
||
|
do {
|
||
|
newName = nameParts[0] + " (" + downloadIndex + ")." + nameParts[1];
|
||
|
++downloadIndex;
|
||
|
if (downloadIndex == 1000) { // Probably an error on our side
|
||
|
throw new RuntimeException("Too many existing files");
|
||
|
}
|
||
|
} while (Arrays.binarySearch(existingName, newName) >= 0);
|
||
|
return newName;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set a pending download as finished
|
||
|
*
|
||
|
* @param mission the desired mission
|
||
|
* @return true if exits pending missions running, otherwise, false
|
||
|
*/
|
||
|
boolean setFinished(DownloadMission mission) {
|
||
|
synchronized (this) {
|
||
|
int i = mMissionsPending.indexOf(mission);
|
||
|
mMissionsPending.remove(i);
|
||
|
|
||
|
mMissionsFinished.add(0, new FinishedMission(mission));
|
||
|
mDownloadDataSource.addMission(mission);
|
||
|
|
||
|
if (mMissionsPending.size() < 1) return false;
|
||
|
|
||
|
i = getRunningMissionsCount();
|
||
|
if (i > 0) return true;
|
||
|
|
||
|
// before returning, check the queue
|
||
|
if (!canDownloadInCurrentNetwork()) return false;
|
||
|
|
||
|
for (DownloadMission mission1 : mMissionsPending) {
|
||
|
if (!mission1.running && mission.errCode != DownloadMission.ERROR_POSTPROCESSING_FAILED && mission1.enqueued) {
|
||
|
resumeMission(mMissionsPending.get(i));
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public MissionIterator getIterator() {
|
||
|
return new MissionIterator();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Forget all finished downloads, but, doesn't delete any file
|
||
|
*/
|
||
|
public void forgetFinishedDownloads() {
|
||
|
synchronized (this) {
|
||
|
for (FinishedMission mission : mMissionsFinished) {
|
||
|
mDownloadDataSource.deleteMission(mission);
|
||
|
}
|
||
|
mMissionsFinished.clear();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private boolean canDownloadInCurrentNetwork() {
|
||
|
if (mLastNetworkStatus == NetworkState.Unavailable) return false;
|
||
|
return !(mPrefs.getBoolean(mPrefCrossNetwork, false) && mLastNetworkStatus == NetworkState.MobileOperating);
|
||
|
}
|
||
|
|
||
|
void handleConnectivityChange(NetworkState currentStatus) {
|
||
|
if (currentStatus == mLastNetworkStatus) return;
|
||
|
|
||
|
mLastNetworkStatus = currentStatus;
|
||
|
boolean pauseOnMobile = mPrefs.getBoolean(mPrefCrossNetwork, false);
|
||
|
|
||
|
if (currentStatus == NetworkState.Unavailable) {
|
||
|
return;
|
||
|
} else if (currentStatus != NetworkState.MobileOperating || !pauseOnMobile) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
boolean flag = false;
|
||
|
synchronized (this) {
|
||
|
for (DownloadMission mission : mMissionsPending) {
|
||
|
if (mission.running && mission.isFinished() && !mission.postprocessingRunning) {
|
||
|
flag = true;
|
||
|
mission.pause();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (flag) mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PAUSED);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Fast check for pending downloads. If exists, the user will be notified
|
||
|
* TODO: call this method in somewhere
|
||
|
*
|
||
|
* @param context the application context
|
||
|
*/
|
||
|
public static void notifyUserPendingDownloads(Context context) {
|
||
|
int pending = getPendingDir(context).list().length;
|
||
|
if (pending < 1) return;
|
||
|
|
||
|
Toast.makeText(context, context.getString(
|
||
|
R.string.msg_pending_downloads,
|
||
|
String.valueOf(pending)
|
||
|
), Toast.LENGTH_LONG).show();
|
||
|
}
|
||
|
|
||
|
void checkForRunningMission(String location, String name, DownloadManagerService.DMChecker check) {
|
||
|
boolean listed;
|
||
|
boolean finished = false;
|
||
|
|
||
|
synchronized (this) {
|
||
|
DownloadMission mission = getPendingMission(location, name);
|
||
|
if (mission != null) {
|
||
|
listed = true;
|
||
|
} else {
|
||
|
listed = getFinishedMissionIndex(location, name) >= 0;
|
||
|
finished = listed;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
check.callback(listed, finished);
|
||
|
}
|
||
|
|
||
|
public class MissionIterator extends DiffUtil.Callback {
|
||
|
final Object FINISHED = new Object();
|
||
|
final Object PENDING = new Object();
|
||
|
|
||
|
ArrayList<Object> snapshot;
|
||
|
ArrayList<Object> current;
|
||
|
ArrayList<Mission> hidden;
|
||
|
|
||
|
private MissionIterator() {
|
||
|
hidden = new ArrayList<>(2);
|
||
|
current = null;
|
||
|
snapshot = getSpecialItems();
|
||
|
}
|
||
|
|
||
|
private ArrayList<Object> getSpecialItems() {
|
||
|
synchronized (DownloadManager.this) {
|
||
|
ArrayList<Mission> pending = new ArrayList<>(mMissionsPending);
|
||
|
ArrayList<Mission> finished = new ArrayList<>(mMissionsFinished);
|
||
|
ArrayList<Mission> remove = new ArrayList<>(hidden);
|
||
|
|
||
|
// hide missions (if required)
|
||
|
Iterator<Mission> iterator = remove.iterator();
|
||
|
while (iterator.hasNext()) {
|
||
|
Mission mission = iterator.next();
|
||
|
if (pending.remove(mission) || finished.remove(mission)) iterator.remove();
|
||
|
}
|
||
|
|
||
|
int fakeTotal = pending.size();
|
||
|
if (fakeTotal > 0) fakeTotal++;
|
||
|
|
||
|
fakeTotal += finished.size();
|
||
|
if (finished.size() > 0) fakeTotal++;
|
||
|
|
||
|
ArrayList<Object> list = new ArrayList<>(fakeTotal);
|
||
|
if (pending.size() > 0) {
|
||
|
list.add(PENDING);
|
||
|
list.addAll(pending);
|
||
|
}
|
||
|
if (finished.size() > 0) {
|
||
|
list.add(FINISHED);
|
||
|
list.addAll(finished);
|
||
|
}
|
||
|
|
||
|
|
||
|
return list;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public MissionItem getItem(int position) {
|
||
|
Object object = snapshot.get(position);
|
||
|
|
||
|
if (object == PENDING) return new MissionItem(SPECIAL_PENDING);
|
||
|
if (object == FINISHED) return new MissionItem(SPECIAL_FINISHED);
|
||
|
|
||
|
return new MissionItem(SPECIAL_NOTHING, (Mission) object);
|
||
|
}
|
||
|
|
||
|
public int getSpecialAtItem(int position) {
|
||
|
Object object = snapshot.get(position);
|
||
|
|
||
|
if (object == PENDING) return SPECIAL_PENDING;
|
||
|
if (object == FINISHED) return SPECIAL_FINISHED;
|
||
|
|
||
|
return SPECIAL_NOTHING;
|
||
|
}
|
||
|
|
||
|
public MissionItem getItemUnsafe(int position) {
|
||
|
synchronized (DownloadManager.this) {
|
||
|
int count = mMissionsPending.size();
|
||
|
int count2 = mMissionsFinished.size();
|
||
|
|
||
|
if (count > 0) {
|
||
|
position--;
|
||
|
if (position == -1)
|
||
|
return new MissionItem(SPECIAL_PENDING);
|
||
|
else if (position < count)
|
||
|
return new MissionItem(SPECIAL_NOTHING, mMissionsPending.get(position));
|
||
|
else if (position == count && count2 > 0)
|
||
|
return new MissionItem(SPECIAL_FINISHED);
|
||
|
else
|
||
|
position -= count;
|
||
|
} else {
|
||
|
if (count2 > 0 && position == 0) {
|
||
|
return new MissionItem(SPECIAL_FINISHED);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
position--;
|
||
|
|
||
|
if (count2 < 1) {
|
||
|
throw new RuntimeException(
|
||
|
String.format("Out of range. pending_count=%s finished_count=%s position=%s", count, count2, position)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return new MissionItem(SPECIAL_NOTHING, mMissionsFinished.get(position));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
public void start() {
|
||
|
current = getSpecialItems();
|
||
|
}
|
||
|
|
||
|
public void end() {
|
||
|
snapshot = current;
|
||
|
current = null;
|
||
|
}
|
||
|
|
||
|
public void hide(Mission mission) {
|
||
|
hidden.add(mission);
|
||
|
}
|
||
|
|
||
|
public void unHide(Mission mission) {
|
||
|
hidden.remove(mission);
|
||
|
}
|
||
|
|
||
|
|
||
|
@Override
|
||
|
public int getOldListSize() {
|
||
|
return snapshot.size();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getNewListSize() {
|
||
|
return current.size();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
||
|
return snapshot.get(oldItemPosition) == current.get(newItemPosition);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
|
||
|
return areItemsTheSame(oldItemPosition, newItemPosition);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public class MissionItem {
|
||
|
public int special;
|
||
|
public Mission mission;
|
||
|
|
||
|
MissionItem(int s, Mission m) {
|
||
|
special = s;
|
||
|
mission = m;
|
||
|
}
|
||
|
|
||
|
MissionItem(int s) {
|
||
|
this(s, null);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|