resbase (08/11/2018)
This commit is contained in:
parent
5825843f68
commit
eb1f56488f
26 changed files with 97 additions and 900 deletions
|
@ -105,13 +105,13 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
|
||||||
* but set the HTTP header field "Accept-Language" to the supplied string.
|
* but set the HTTP header field "Accept-Language" to the supplied string.
|
||||||
*
|
*
|
||||||
* @param siteUrl the URL of the text file to return the contents of
|
* @param siteUrl the URL of the text file to return the contents of
|
||||||
* @param localisation the language and country (usually a 2-character code) to set
|
* @param localization the language and country (usually a 2-character code) to set
|
||||||
* @return the contents of the specified text file
|
* @return the contents of the specified text file
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String download(String siteUrl, Localization localisation) throws IOException, ReCaptchaException {
|
public String download(String siteUrl, Localization localization) throws IOException, ReCaptchaException {
|
||||||
Map<String, String> requestProperties = new HashMap<>();
|
Map<String, String> requestProperties = new HashMap<>();
|
||||||
requestProperties.put("Accept-Language", localisation.getLanguage());
|
requestProperties.put("Accept-Language", localization.getLanguage());
|
||||||
return download(siteUrl, requestProperties);
|
return download(siteUrl, requestProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,163 +0,0 @@
|
||||||
package org.schabi.newpipe.download;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.design.widget.BaseTransientBottomBar;
|
|
||||||
import android.support.design.widget.Snackbar;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import io.reactivex.Completable;
|
|
||||||
import io.reactivex.Observable;
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
|
||||||
import io.reactivex.disposables.Disposable;
|
|
||||||
import io.reactivex.schedulers.Schedulers;
|
|
||||||
import io.reactivex.subjects.PublishSubject;
|
|
||||||
import us.shandian.giga.get.DownloadManager;
|
|
||||||
import us.shandian.giga.get.DownloadMission;
|
|
||||||
|
|
||||||
public class DeleteDownloadManager {
|
|
||||||
|
|
||||||
private static final String KEY_STATE = "delete_manager_state";
|
|
||||||
|
|
||||||
private View mView;
|
|
||||||
private ArrayList<Long> mPendingMap;
|
|
||||||
private List<Disposable> mDisposableList;
|
|
||||||
private DownloadManager mDownloadManager;
|
|
||||||
private final PublishSubject<DownloadMission> publishSubject = PublishSubject.create();
|
|
||||||
|
|
||||||
DeleteDownloadManager(Activity activity) {
|
|
||||||
mPendingMap = new ArrayList<>();
|
|
||||||
mDisposableList = new ArrayList<>();
|
|
||||||
mView = activity.findViewById(android.R.id.content);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Observable<DownloadMission> getUndoObservable() {
|
|
||||||
return publishSubject;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean contains(@NonNull DownloadMission mission) {
|
|
||||||
return mPendingMap.contains(mission.timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add(@NonNull DownloadMission mission) {
|
|
||||||
mPendingMap.add(mission.timestamp);
|
|
||||||
|
|
||||||
if (mPendingMap.size() == 1) {
|
|
||||||
showUndoDeleteSnackbar(mission);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDownloadManager(@NonNull DownloadManager downloadManager) {
|
|
||||||
mDownloadManager = downloadManager;
|
|
||||||
|
|
||||||
if (mPendingMap.size() < 1) return;
|
|
||||||
|
|
||||||
showUndoDeleteSnackbar();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void restoreState(@Nullable Bundle savedInstanceState) {
|
|
||||||
if (savedInstanceState == null) return;
|
|
||||||
|
|
||||||
long[] list = savedInstanceState.getLongArray(KEY_STATE);
|
|
||||||
if (list != null) {
|
|
||||||
mPendingMap.ensureCapacity(mPendingMap.size() + list.length);
|
|
||||||
for (long timestamp : list) mPendingMap.add(timestamp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void saveState(@Nullable Bundle outState) {
|
|
||||||
if (outState == null) return;
|
|
||||||
|
|
||||||
for (Disposable disposable : mDisposableList) {
|
|
||||||
disposable.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
long[] list = new long[mPendingMap.size()];
|
|
||||||
for (int i = 0; i < mPendingMap.size(); i++) list[i] = mPendingMap.get(i);
|
|
||||||
|
|
||||||
outState.putLongArray(KEY_STATE, list);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showUndoDeleteSnackbar() {
|
|
||||||
if (mPendingMap.size() < 1) return;
|
|
||||||
|
|
||||||
long timestamp = mPendingMap.iterator().next();
|
|
||||||
|
|
||||||
for (int i = 0; i < mDownloadManager.getCount(); i++) {
|
|
||||||
DownloadMission mission = mDownloadManager.getMission(i);
|
|
||||||
if (timestamp == mission.timestamp) {
|
|
||||||
showUndoDeleteSnackbar(mission);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showUndoDeleteSnackbar(@NonNull DownloadMission mission) {
|
|
||||||
final Snackbar snackbar = Snackbar.make(mView, mission.name, Snackbar.LENGTH_INDEFINITE);
|
|
||||||
final Disposable disposable = Observable.timer(3, TimeUnit.SECONDS)
|
|
||||||
.subscribeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(l -> snackbar.dismiss());
|
|
||||||
|
|
||||||
mDisposableList.add(disposable);
|
|
||||||
|
|
||||||
snackbar.setAction(R.string.undo, v -> {
|
|
||||||
mPendingMap.remove(mission.timestamp);
|
|
||||||
publishSubject.onNext(mission);
|
|
||||||
disposable.dispose();
|
|
||||||
snackbar.dismiss();
|
|
||||||
});
|
|
||||||
|
|
||||||
snackbar.addCallback(new BaseTransientBottomBar.BaseCallback<Snackbar>() {
|
|
||||||
@Override
|
|
||||||
public void onDismissed(Snackbar transientBottomBar, int event) {
|
|
||||||
// TODO: disposable.isDisposed() is always true. fix this
|
|
||||||
if (!disposable.isDisposed()) {
|
|
||||||
Completable.fromAction(() -> deletePending(mission))
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.subscribe();
|
|
||||||
}
|
|
||||||
mPendingMap.remove(mission.timestamp);
|
|
||||||
snackbar.removeCallback(this);
|
|
||||||
mDisposableList.remove(disposable);
|
|
||||||
showUndoDeleteSnackbar();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
snackbar.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deletePending() {
|
|
||||||
if (mPendingMap.size() < 1) return;
|
|
||||||
|
|
||||||
HashSet<Integer> idSet = new HashSet<>();
|
|
||||||
for (int i = 0; i < mDownloadManager.getCount(); i++) {
|
|
||||||
if (contains(mDownloadManager.getMission(i))) {
|
|
||||||
idSet.add(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Integer id : idSet) {
|
|
||||||
mDownloadManager.deleteMission(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
mPendingMap.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deletePending(@NonNull DownloadMission mission) {
|
|
||||||
for (int i = 0; i < mDownloadManager.getCount(); i++) {
|
|
||||||
if (mission.timestamp == mDownloadManager.getMission(i).timestamp) {
|
|
||||||
mDownloadManager.deleteMission(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -24,7 +24,6 @@ public class DownloadActivity extends AppCompatActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
|
||||||
// Service
|
// Service
|
||||||
Intent i = new Intent();
|
Intent i = new Intent();
|
||||||
i.setClass(this, DownloadManagerService.class);
|
i.setClass(this, DownloadManagerService.class);
|
||||||
|
|
|
@ -55,20 +55,13 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
private static final String TAG = "DialogFragment";
|
private static final String TAG = "DialogFragment";
|
||||||
private static final boolean DEBUG = MainActivity.DEBUG;
|
private static final boolean DEBUG = MainActivity.DEBUG;
|
||||||
|
|
||||||
@State
|
@State protected StreamInfo currentInfo;
|
||||||
protected StreamInfo currentInfo;
|
@State protected StreamSizeWrapper<AudioStream> wrappedAudioStreams = StreamSizeWrapper.empty();
|
||||||
@State
|
@State protected StreamSizeWrapper<VideoStream> wrappedVideoStreams = StreamSizeWrapper.empty();
|
||||||
protected StreamSizeWrapper<AudioStream> wrappedAudioStreams = StreamSizeWrapper.empty();
|
@State protected StreamSizeWrapper<SubtitlesStream> wrappedSubtitleStreams = StreamSizeWrapper.empty();
|
||||||
@State
|
@State protected int selectedVideoIndex = 0;
|
||||||
protected StreamSizeWrapper<VideoStream> wrappedVideoStreams = StreamSizeWrapper.empty();
|
@State protected int selectedAudioIndex = 0;
|
||||||
@State
|
@State protected int selectedSubtitleIndex = 0;
|
||||||
protected StreamSizeWrapper<SubtitlesStream> wrappedSubtitleStreams = StreamSizeWrapper.empty();
|
|
||||||
@State
|
|
||||||
protected int selectedVideoIndex = 0;
|
|
||||||
@State
|
|
||||||
protected int selectedAudioIndex = 0;
|
|
||||||
@State
|
|
||||||
protected int selectedSubtitleIndex = 0;
|
|
||||||
|
|
||||||
private StreamItemAdapter<AudioStream> audioStreamsAdapter;
|
private StreamItemAdapter<AudioStream> audioStreamsAdapter;
|
||||||
private StreamItemAdapter<VideoStream> videoStreamsAdapter;
|
private StreamItemAdapter<VideoStream> videoStreamsAdapter;
|
||||||
|
@ -151,8 +144,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
if (DEBUG)
|
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
|
||||||
Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
|
|
||||||
if (!PermissionHelper.checkStoragePermissions(getActivity(), PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
|
if (!PermissionHelper.checkStoragePermissions(getActivity(), PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
|
||||||
getDialog().dismiss();
|
getDialog().dismiss();
|
||||||
return;
|
return;
|
||||||
|
@ -168,8 +160,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
if (DEBUG)
|
if (DEBUG) Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]");
|
||||||
Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]");
|
|
||||||
return inflater.inflate(R.layout.download_dialog, container);
|
return inflater.inflate(R.layout.download_dialog, container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,8 +293,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) {
|
public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) {
|
||||||
if (DEBUG)
|
if (DEBUG) Log.d(TAG, "onCheckedChanged() called with: group = [" + group + "], checkedId = [" + checkedId + "]");
|
||||||
Log.d(TAG, "onCheckedChanged() called with: group = [" + group + "], checkedId = [" + checkedId + "]");
|
|
||||||
boolean flag = true;
|
boolean flag = true;
|
||||||
|
|
||||||
switch (checkedId) {
|
switch (checkedId) {
|
||||||
|
@ -328,8 +318,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
if (DEBUG)
|
if (DEBUG) Log.d(TAG, "onItemSelected() called with: parent = [" + parent + "], view = [" + view + "], position = [" + position + "], id = [" + id + "]");
|
||||||
Log.d(TAG, "onItemSelected() called with: parent = [" + parent + "], view = [" + view + "], position = [" + position + "], id = [" + id + "]");
|
|
||||||
switch (radioVideoAudioGroup.getCheckedRadioButtonId()) {
|
switch (radioVideoAudioGroup.getCheckedRadioButtonId()) {
|
||||||
case R.id.audio_button:
|
case R.id.audio_button:
|
||||||
selectedAudioIndex = position;
|
selectedAudioIndex = position;
|
||||||
|
|
|
@ -572,6 +572,9 @@ public class VideoDetailFragment
|
||||||
.show(getFragmentManager(), TAG);
|
.show(getFragmentManager(), TAG);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 3:
|
||||||
|
shareUrl(item.getName(), item.getUrl());
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,10 @@ import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.source.MergingMediaSource;
|
import com.google.android.exoplayer2.source.MergingMediaSource;
|
||||||
|
|
||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
|
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
||||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||||
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
|
||||||
import org.schabi.newpipe.player.helper.PlayerDataSource;
|
import org.schabi.newpipe.player.helper.PlayerDataSource;
|
||||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||||
import org.schabi.newpipe.util.ListHelper;
|
import org.schabi.newpipe.util.ListHelper;
|
||||||
|
|
|
@ -97,7 +97,7 @@ public class StreamItemAdapter<T extends Stream> extends BaseAdapter {
|
||||||
} else if (((VideoStream) stream).isVideoOnly()) {
|
} else if (((VideoStream) stream).isVideoOnly()) {
|
||||||
switch (stream.getFormat()) {
|
switch (stream.getFormat()) {
|
||||||
case WEBM:// fully supported
|
case WEBM:// fully supported
|
||||||
case MPEG_4:// ¿is DASH MPEG-4?
|
case MPEG_4:// ¿is DASH MPEG-4 format?
|
||||||
woSoundIconVisibility = View.INVISIBLE;
|
woSoundIconVisibility = View.INVISIBLE;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -143,7 +143,7 @@ public class StreamItemAdapter<T extends Stream> extends BaseAdapter {
|
||||||
public static class StreamSizeWrapper<T extends Stream> implements Serializable {
|
public static class StreamSizeWrapper<T extends Stream> implements Serializable {
|
||||||
private static final StreamSizeWrapper<Stream> EMPTY = new StreamSizeWrapper<>(Collections.emptyList(), null);
|
private static final StreamSizeWrapper<Stream> EMPTY = new StreamSizeWrapper<>(Collections.emptyList(), null);
|
||||||
private final List<T> streamsList;
|
private final List<T> streamsList;
|
||||||
private long[] streamSizes;
|
private final long[] streamSizes;
|
||||||
private final String unknownSize;
|
private final String unknownSize;
|
||||||
|
|
||||||
public StreamSizeWrapper(List<T> streamsList, Context context) {
|
public StreamSizeWrapper(List<T> streamsList, Context context) {
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
package us.shandian.giga.get;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides access to the storage of {@link DownloadMission}s
|
|
||||||
*/
|
|
||||||
public interface DownloadDataSource {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load all missions
|
|
||||||
*
|
|
||||||
* @return a list of download missions
|
|
||||||
*/
|
|
||||||
List<DownloadMission> loadMissions();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a download mission to the storage
|
|
||||||
*
|
|
||||||
* @param downloadMission the download mission to add
|
|
||||||
* @return the identifier of the mission
|
|
||||||
*/
|
|
||||||
void addMission(DownloadMission downloadMission);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a download mission which exists in the storage
|
|
||||||
*
|
|
||||||
* @param downloadMission the download mission to update
|
|
||||||
* @throws IllegalArgumentException if the mission was not added to storage
|
|
||||||
*/
|
|
||||||
void updateMission(DownloadMission downloadMission);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a download mission
|
|
||||||
*
|
|
||||||
* @param downloadMission the mission to delete
|
|
||||||
*/
|
|
||||||
void deleteMission(DownloadMission downloadMission);
|
|
||||||
}
|
|
|
@ -1,395 +0,0 @@
|
||||||
package us.shandian.giga.get;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.download.ExtSDDownloadFailedActivity;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FilenameFilter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.RandomAccessFile;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import us.shandian.giga.util.Utility;
|
|
||||||
|
|
||||||
import static org.schabi.newpipe.BuildConfig.DEBUG;
|
|
||||||
|
|
||||||
public class DownloadManagerImpl implements DownloadManager {
|
|
||||||
private static final String TAG = DownloadManagerImpl.class.getSimpleName();
|
|
||||||
private final DownloadDataSource mDownloadDataSource;
|
|
||||||
|
|
||||||
private final ArrayList<DownloadMission> mMissions = new ArrayList<>();
|
|
||||||
@NonNull
|
|
||||||
private final Context context;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new instance
|
|
||||||
*
|
|
||||||
* @param searchLocations the directories to search for unfinished downloads
|
|
||||||
* @param downloadDataSource the data source for finished downloads
|
|
||||||
*/
|
|
||||||
public DownloadManagerImpl(Collection<String> searchLocations, DownloadDataSource downloadDataSource) {
|
|
||||||
mDownloadDataSource = downloadDataSource;
|
|
||||||
this.context = null;
|
|
||||||
loadMissions(searchLocations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DownloadManagerImpl(Collection<String> searchLocations, DownloadDataSource downloadDataSource, Context context) {
|
|
||||||
mDownloadDataSource = downloadDataSource;
|
|
||||||
this.context = context;
|
|
||||||
loadMissions(searchLocations);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int startMission(String url, String location, String name, boolean isAudio, int threads) {
|
|
||||||
DownloadMission existingMission = getMissionByLocation(location, name);
|
|
||||||
if (existingMission != null) {
|
|
||||||
// Already downloaded or downloading
|
|
||||||
if (existingMission.finished) {
|
|
||||||
// Overwrite mission
|
|
||||||
deleteMission(mMissions.indexOf(existingMission));
|
|
||||||
} else {
|
|
||||||
// Rename file (?)
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DownloadMission mission = new DownloadMission(name, url, location);
|
|
||||||
mission.timestamp = System.currentTimeMillis();
|
|
||||||
mission.threadCount = threads;
|
|
||||||
mission.addListener(new MissionListener(mission));
|
|
||||||
new Initializer(mission).start();
|
|
||||||
return insertMission(mission);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void resumeMission(int i) {
|
|
||||||
DownloadMission d = getMission(i);
|
|
||||||
if (!d.running && d.errCode == -1) {
|
|
||||||
d.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void pauseMission(int i) {
|
|
||||||
DownloadMission d = getMission(i);
|
|
||||||
if (d.running) {
|
|
||||||
d.pause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deleteMission(int i) {
|
|
||||||
DownloadMission mission = getMission(i);
|
|
||||||
if (mission.finished) {
|
|
||||||
mDownloadDataSource.deleteMission(mission);
|
|
||||||
}
|
|
||||||
mission.delete();
|
|
||||||
mMissions.remove(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadMissions(Iterable<String> searchLocations) {
|
|
||||||
mMissions.clear();
|
|
||||||
loadFinishedMissions();
|
|
||||||
for (String location : searchLocations) {
|
|
||||||
loadMissions(location);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort a list of mission by its timestamp. Oldest first
|
|
||||||
* @param missions the missions to sort
|
|
||||||
*/
|
|
||||||
static void sortByTimestamp(List<DownloadMission> missions) {
|
|
||||||
Collections.sort(missions, new Comparator<DownloadMission>() {
|
|
||||||
@Override
|
|
||||||
public int compare(DownloadMission o1, DownloadMission o2) {
|
|
||||||
return Long.compare(o1.timestamp, o2.timestamp);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads finished missions from the data source
|
|
||||||
*/
|
|
||||||
private void loadFinishedMissions() {
|
|
||||||
List<DownloadMission> finishedMissions = mDownloadDataSource.loadMissions();
|
|
||||||
if (finishedMissions == null) {
|
|
||||||
finishedMissions = new ArrayList<>();
|
|
||||||
}
|
|
||||||
// Ensure its sorted
|
|
||||||
sortByTimestamp(finishedMissions);
|
|
||||||
|
|
||||||
mMissions.ensureCapacity(mMissions.size() + finishedMissions.size());
|
|
||||||
for (DownloadMission mission : finishedMissions) {
|
|
||||||
File downloadedFile = mission.getDownloadedFile();
|
|
||||||
if (!downloadedFile.isFile()) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "downloaded file removed: " + downloadedFile.getAbsolutePath());
|
|
||||||
}
|
|
||||||
mDownloadDataSource.deleteMission(mission);
|
|
||||||
} else {
|
|
||||||
mission.length = downloadedFile.length();
|
|
||||||
mission.finished = true;
|
|
||||||
mission.running = false;
|
|
||||||
mMissions.add(mission);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadMissions(String location) {
|
|
||||||
|
|
||||||
File f = new File(location);
|
|
||||||
|
|
||||||
if (f.exists() && f.isDirectory()) {
|
|
||||||
File[] subs = f.listFiles();
|
|
||||||
|
|
||||||
if (subs == null) {
|
|
||||||
Log.e(TAG, "listFiles() returned null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (File sub : subs) {
|
|
||||||
if (sub.isFile() && sub.getName().endsWith(".giga")) {
|
|
||||||
DownloadMission mis = Utility.readFromFile(sub.getAbsolutePath());
|
|
||||||
if (mis != null) {
|
|
||||||
if (mis.finished) {
|
|
||||||
if (!sub.delete()) {
|
|
||||||
Log.w(TAG, "Unable to delete .giga file: " + sub.getPath());
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
mis.running = false;
|
|
||||||
mis.recovered = true;
|
|
||||||
insertMission(mis);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DownloadMission getMission(int i) {
|
|
||||||
return mMissions.get(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getCount() {
|
|
||||||
return mMissions.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
private int insertMission(DownloadMission mission) {
|
|
||||||
int i = -1;
|
|
||||||
|
|
||||||
DownloadMission m = null;
|
|
||||||
|
|
||||||
if (mMissions.size() > 0) {
|
|
||||||
do {
|
|
||||||
m = mMissions.get(++i);
|
|
||||||
} while (m.timestamp > mission.timestamp && i < mMissions.size() - 1);
|
|
||||||
|
|
||||||
//if (i > 0) i--;
|
|
||||||
} else {
|
|
||||||
i = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
mMissions.add(i, mission);
|
|
||||||
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a mission by its location and name
|
|
||||||
*
|
|
||||||
* @param location the location
|
|
||||||
* @param name the name
|
|
||||||
* @return the mission or null if no such mission exists
|
|
||||||
*/
|
|
||||||
private
|
|
||||||
@Nullable
|
|
||||||
DownloadMission getMissionByLocation(String location, String name) {
|
|
||||||
for (DownloadMission mission : mMissions) {
|
|
||||||
if (location.equals(mission.location) && name.equals(mission.name)) {
|
|
||||||
return mission;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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(new FilenameFilter() {
|
|
||||||
@Override
|
|
||||||
public boolean accept(File dir, String name) {
|
|
||||||
return name.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Initializer extends Thread {
|
|
||||||
private final DownloadMission mission;
|
|
||||||
private final Handler handler;
|
|
||||||
|
|
||||||
public Initializer(DownloadMission mission) {
|
|
||||||
this.mission = mission;
|
|
||||||
this.handler = new Handler();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
URL url = new URL(mission.url);
|
|
||||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
|
||||||
mission.length = conn.getContentLength();
|
|
||||||
|
|
||||||
if (mission.length <= 0) {
|
|
||||||
mission.errCode = DownloadMission.ERROR_SERVER_UNSUPPORTED;
|
|
||||||
//mission.notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open again
|
|
||||||
conn = (HttpURLConnection) url.openConnection();
|
|
||||||
conn.setRequestProperty("Range", "bytes=" + (mission.length - 10) + "-" + mission.length);
|
|
||||||
|
|
||||||
if (conn.getResponseCode() != 206) {
|
|
||||||
// Fallback to single thread if no partial content support
|
|
||||||
mission.fallback = true;
|
|
||||||
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "falling back");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "response = " + conn.getResponseCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
mission.blocks = mission.length / BLOCK_SIZE;
|
|
||||||
|
|
||||||
if (mission.threadCount > mission.blocks) {
|
|
||||||
mission.threadCount = (int) mission.blocks;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mission.threadCount <= 0) {
|
|
||||||
mission.threadCount = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mission.blocks * BLOCK_SIZE < mission.length) {
|
|
||||||
mission.blocks++;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
new File(mission.location).mkdirs();
|
|
||||||
new File(mission.location + "/" + mission.name).createNewFile();
|
|
||||||
RandomAccessFile af = new RandomAccessFile(mission.location + "/" + mission.name, "rw");
|
|
||||||
af.setLength(mission.length);
|
|
||||||
af.close();
|
|
||||||
|
|
||||||
mission.start();
|
|
||||||
} catch (IOException ie) {
|
|
||||||
if(context == null) throw new RuntimeException(ie);
|
|
||||||
|
|
||||||
if(ie.getMessage().contains("Permission denied")) {
|
|
||||||
handler.post(() ->
|
|
||||||
context.startActivity(new Intent(context, ExtSDDownloadFailedActivity.class)));
|
|
||||||
} else throw new RuntimeException(ie);
|
|
||||||
} catch (Exception e) {
|
|
||||||
// TODO Notify
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Waits for mission to finish to add it to the {@link #mDownloadDataSource}
|
|
||||||
*/
|
|
||||||
private class MissionListener implements DownloadMission.MissionListener {
|
|
||||||
private final DownloadMission mMission;
|
|
||||||
|
|
||||||
private MissionListener(DownloadMission mission) {
|
|
||||||
if (mission == null) throw new NullPointerException("mission is null");
|
|
||||||
// Could the mission be passed in onFinish()?
|
|
||||||
mMission = mission;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onProgressUpdate(DownloadMission downloadMission, long done, long total) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFinish(DownloadMission downloadMission) {
|
|
||||||
mDownloadDataSource.addMission(mMission);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(DownloadMission downloadMission, int errCode) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -24,7 +24,7 @@ import us.shandian.giga.util.Utility;
|
||||||
import static org.schabi.newpipe.BuildConfig.DEBUG;
|
import static org.schabi.newpipe.BuildConfig.DEBUG;
|
||||||
|
|
||||||
public class DownloadMission extends Mission {
|
public class DownloadMission extends Mission {
|
||||||
private static final long serialVersionUID = 3L;// last bump: 16 october 2018
|
private static final long serialVersionUID = 3L;// last bump: 8 november 2018
|
||||||
|
|
||||||
static final int BUFFER_SIZE = 64 * 1024;
|
static final int BUFFER_SIZE = 64 * 1024;
|
||||||
final static int BLOCK_SIZE = 512 * 1024;
|
final static int BLOCK_SIZE = 512 * 1024;
|
||||||
|
|
|
@ -14,7 +14,7 @@ import static org.schabi.newpipe.BuildConfig.DEBUG;
|
||||||
|
|
||||||
// Single-threaded fallback mode
|
// Single-threaded fallback mode
|
||||||
public class DownloadRunnableFallback implements Runnable {
|
public class DownloadRunnableFallback implements Runnable {
|
||||||
private static final String TAG = "DownloadRunnableFallbac";
|
private static final String TAG = "DownloadRunnableFallback";
|
||||||
|
|
||||||
private final DownloadMission mMission;
|
private final DownloadMission mMission;
|
||||||
private int retryCount = 0;
|
private int retryCount = 0;
|
||||||
|
|
|
@ -73,6 +73,7 @@ public class DownloadManagerService extends Service {
|
||||||
private StringBuilder downloadDoneList = null;
|
private StringBuilder downloadDoneList = null;
|
||||||
NotificationManager notificationManager = null;
|
NotificationManager notificationManager = null;
|
||||||
private boolean mForeground = false;
|
private boolean mForeground = false;
|
||||||
|
|
||||||
private final ArrayList<Handler> mEchoObservers = new ArrayList<>(1);
|
private final ArrayList<Handler> mEchoObservers = new ArrayList<>(1);
|
||||||
|
|
||||||
private BroadcastReceiver mNetworkStateListener;
|
private BroadcastReceiver mNetworkStateListener;
|
||||||
|
|
|
@ -68,8 +68,8 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
|
||||||
private Deleter mDeleter;
|
private Deleter mDeleter;
|
||||||
private int mLayout;
|
private int mLayout;
|
||||||
private DownloadManager.MissionIterator mIterator;
|
private DownloadManager.MissionIterator mIterator;
|
||||||
private Handler mHandler;
|
|
||||||
private ArrayList<ViewHolderItem> mPendingDownloadsItems = new ArrayList<>();
|
private ArrayList<ViewHolderItem> mPendingDownloadsItems = new ArrayList<>();
|
||||||
|
private Handler mHandler;
|
||||||
private MenuItem mClear;
|
private MenuItem mClear;
|
||||||
private View mEmptyMessage;
|
private View mEmptyMessage;
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import us.shandian.giga.ui.adapter.MissionAdapter;
|
||||||
public class Deleter {
|
public class Deleter {
|
||||||
private static final int TIMEOUT = 5000;// ms
|
private static final int TIMEOUT = 5000;// ms
|
||||||
private static final int DELAY = 350;// ms
|
private static final int DELAY = 350;// ms
|
||||||
|
private static final int DELAY_RESUME = 400;// ms
|
||||||
private static final String BUNDLE_NAMES = "us.shandian.giga.ui.common.deleter.names";
|
private static final String BUNDLE_NAMES = "us.shandian.giga.ui.common.deleter.names";
|
||||||
private static final String BUNDLE_LOCATIONS = "us.shandian.giga.ui.common.deleter.locations";
|
private static final String BUNDLE_LOCATIONS = "us.shandian.giga.ui.common.deleter.locations";
|
||||||
|
|
||||||
|
@ -140,7 +141,7 @@ public class Deleter {
|
||||||
|
|
||||||
public void resume() {
|
public void resume() {
|
||||||
if (running) return;
|
if (running) return;
|
||||||
mHandler.postDelayed(rShow, (int) (DELAY * 1.5f));// 150% of the delay
|
mHandler.postDelayed(rShow, DELAY_RESUME);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispose(Bundle bundle) {
|
public void dispose(Bundle bundle) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package us.shandian.giga.ui.common;// TODO: ¡git it!
|
package us.shandian.giga.ui.common;
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.ColorFilter;
|
import android.graphics.ColorFilter;
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
package us.shandian.giga.ui.fragment;
|
|
||||||
|
|
||||||
import us.shandian.giga.get.DownloadManager;
|
|
||||||
import us.shandian.giga.service.DownloadManagerService;
|
|
||||||
|
|
||||||
public class AllMissionsFragment extends MissionsFragment {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected DownloadManager setupDownloadManager(DownloadManagerService.DMBinder binder) {
|
|
||||||
return binder.getDownloadManager();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,7 +15,6 @@ import android.support.v7.widget.LinearLayoutManager;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
@ -47,7 +46,7 @@ public class MissionsFragment extends Fragment {
|
||||||
private Bundle mBundle;
|
private Bundle mBundle;
|
||||||
private boolean mForceUpdate;
|
private boolean mForceUpdate;
|
||||||
|
|
||||||
private final ServiceConnection mConnection = new ServiceConnection() {
|
private ServiceConnection mConnection = new ServiceConnection() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onServiceConnected(ComponentName name, IBinder binder) {
|
public void onServiceConnected(ComponentName name, IBinder binder) {
|
||||||
|
@ -111,15 +110,6 @@ public class MissionsFragment extends Fragment {
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
|
||||||
if (menu != null) {
|
|
||||||
mSwitch = menu.findItem(R.id.switch_mode);
|
|
||||||
mClear = menu.findItem(R.id.clear_list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Added in API level 23.
|
* Added in API level 23.
|
||||||
*/
|
*/
|
||||||
|
@ -129,7 +119,7 @@ public class MissionsFragment extends Fragment {
|
||||||
|
|
||||||
// Bug: in api< 23 this is never called
|
// Bug: in api< 23 this is never called
|
||||||
// so mActivity=null
|
// so mActivity=null
|
||||||
// so app crashes with null-pointer exception
|
// so app crashes with nullpointer exception
|
||||||
mActivity = activity;
|
mActivity = activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,9 +130,11 @@ public class MissionsFragment extends Fragment {
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity) {
|
public void onAttach(Activity activity) {
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
|
|
||||||
mActivity = activity;
|
mActivity = activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
@ -157,28 +149,10 @@ public class MissionsFragment extends Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
public void onPrepareOptionsMenu(Menu menu) {
|
||||||
super.onSaveInstanceState(outState);
|
mSwitch = menu.findItem(R.id.switch_mode);
|
||||||
if (mAdapter != null) {
|
mClear = menu.findItem(R.id.clear_list);
|
||||||
mAdapter.deleterDispose(outState);
|
super.onPrepareOptionsMenu(menu);
|
||||||
mForceUpdate = true;
|
|
||||||
mBinder.removeMissionEventListener(mAdapter.getMessenger());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
if (mAdapter != null) {
|
|
||||||
mAdapter.deleterResume();
|
|
||||||
|
|
||||||
if (mForceUpdate) {
|
|
||||||
mForceUpdate = false;
|
|
||||||
mAdapter.forceUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
mBinder.addMissionEventListener(mAdapter.getMessenger());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -203,8 +177,11 @@ public class MissionsFragment extends Fragment {
|
||||||
mList.setLayoutManager(mGridManager);
|
mList.setLayoutManager(mGridManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// destroy all created views in the recycler
|
||||||
mList.setAdapter(null);
|
mList.setAdapter(null);
|
||||||
mAdapter.notifyDataSetChanged();
|
mAdapter.notifyDataSetChanged();
|
||||||
|
|
||||||
|
// re-attach the adapter in grid/lineal mode
|
||||||
mAdapter.setLinear(mLinear);
|
mAdapter.setLinear(mLinear);
|
||||||
mList.setAdapter(mAdapter);
|
mList.setAdapter(mAdapter);
|
||||||
|
|
||||||
|
@ -214,4 +191,32 @@ public class MissionsFragment extends Fragment {
|
||||||
mPrefs.edit().putBoolean("linear", mLinear).apply();
|
mPrefs.edit().putBoolean("linear", mLinear).apply();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
|
||||||
|
if (mAdapter != null) {
|
||||||
|
mAdapter.deleterDispose(outState);
|
||||||
|
mForceUpdate = true;
|
||||||
|
mBinder.removeMissionEventListener(mAdapter.getMessenger());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
if (mAdapter != null) {
|
||||||
|
mAdapter.deleterResume();
|
||||||
|
|
||||||
|
if (mForceUpdate) {
|
||||||
|
mForceUpdate = false;
|
||||||
|
mAdapter.forceUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
mBinder.addMissionEventListener(mAdapter.getMessenger());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import android.widget.Toast;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
|
|
@ -51,8 +51,8 @@
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
android:scaleType="fitXY"
|
android:scaleType="fitXY"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:contentDescription="TODO"
|
android:padding="10dp"
|
||||||
android:padding="10dp"/>
|
android:contentDescription="TODO" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/item_name"
|
android:id="@+id/item_name"
|
||||||
|
@ -60,14 +60,14 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/item_icon"
|
android:layout_below="@id/item_icon"
|
||||||
android:padding="6dp"
|
android:padding="6dp"
|
||||||
android:text="XXX.xx"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:ellipsize="marquee"
|
android:ellipsize="marquee"
|
||||||
android:marqueeRepeatLimit="marquee_forever"
|
android:marqueeRepeatLimit="marquee_forever"
|
||||||
android:scrollHorizontally="true"/>
|
android:scrollHorizontally="true"
|
||||||
|
android:text="XXX.xx"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/item_size"
|
android:id="@+id/item_size"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="fill_parent"
|
android:layout_height="fill_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
@ -11,7 +12,6 @@
|
||||||
<android.support.v7.widget.RecyclerView
|
<android.support.v7.widget.RecyclerView
|
||||||
android:id="@+id/mission_recycler"
|
android:id="@+id/mission_recycler"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"/>
|
||||||
/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
|
@ -1,25 +1,19 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
|
|
||||||
<item
|
<item android:id="@+id/switch_mode"
|
||||||
android:id="@+id/switch_mode"
|
|
||||||
android:icon="@drawable/list"
|
android:icon="@drawable/list"
|
||||||
android:title="@string/grid"
|
android:title="@string/grid"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
<item
|
<item android:id="@+id/action_settings"
|
||||||
android:id="@+id/action_settings"
|
app:showAsAction="never"
|
||||||
android:title="@string/settings"
|
android:title="@string/settings"/>
|
||||||
app:showAsAction="never" />
|
|
||||||
|
|
||||||
<item
|
<item android:id="@+id/clear_list"
|
||||||
android:visible="false"
|
android:visible="false"
|
||||||
android:id="@+id/clear_list"
|
|
||||||
android:icon="@drawable/ic_delete_sweep_white_24dp"
|
android:icon="@drawable/ic_delete_sweep_white_24dp"
|
||||||
android:title="@string/clear_finished_download"
|
app:showAsAction="ifRoom"
|
||||||
app:showAsAction="ifRoom" />
|
android:title="@string/clear_finished_download"/>
|
||||||
|
|
||||||
|
|
||||||
</menu>
|
</menu>
|
|
@ -16,7 +16,7 @@
|
||||||
<string name="controls_download_desc">Download stream file</string>
|
<string name="controls_download_desc">Download stream file</string>
|
||||||
<string name="search">Search</string>
|
<string name="search">Search</string>
|
||||||
<string name="settings">Settings</string>
|
<string name="settings">Settings</string>
|
||||||
<string name="did_you_mean">Did you mean: %1$s?</string>
|
<string name="did_you_mean">Did you mean: %1$s\?</string>
|
||||||
<string name="share_dialog_title">Share with</string>
|
<string name="share_dialog_title">Share with</string>
|
||||||
<string name="choose_browser">Choose browser</string>
|
<string name="choose_browser">Choose browser</string>
|
||||||
<string name="screen_rotation">rotation</string>
|
<string name="screen_rotation">rotation</string>
|
||||||
|
|
|
@ -1,186 +0,0 @@
|
||||||
package us.shandian.giga.get;
|
|
||||||
|
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.RandomAccessFile;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import us.shandian.giga.get.DownloadDataSource;
|
|
||||||
import us.shandian.giga.get.DownloadManagerImpl;
|
|
||||||
import us.shandian.giga.get.DownloadMission;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertNotEquals;
|
|
||||||
import static org.junit.Assert.assertSame;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.never;
|
|
||||||
import static org.mockito.Mockito.spy;
|
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test for {@link DownloadManagerImpl}
|
|
||||||
*
|
|
||||||
* TODO: test loading from .giga files, startMission and improve tests
|
|
||||||
*/
|
|
||||||
public class DownloadManagerImplTest {
|
|
||||||
|
|
||||||
private DownloadManagerImpl downloadManager;
|
|
||||||
private DownloadDataSource downloadDataSource;
|
|
||||||
private ArrayList<DownloadMission> missions;
|
|
||||||
|
|
||||||
@org.junit.Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
downloadDataSource = mock(DownloadDataSource.class);
|
|
||||||
missions = new ArrayList<>();
|
|
||||||
for(int i = 0; i < 50; ++i){
|
|
||||||
missions.add(generateFinishedDownloadMission());
|
|
||||||
}
|
|
||||||
when(downloadDataSource.loadMissions()).thenReturn(new ArrayList<>(missions));
|
|
||||||
downloadManager = new DownloadManagerImpl(new ArrayList<>(), downloadDataSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = NullPointerException.class)
|
|
||||||
public void testConstructorWithNullAsDownloadDataSource() {
|
|
||||||
new DownloadManagerImpl(new ArrayList<>(), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static DownloadMission generateFinishedDownloadMission() throws IOException {
|
|
||||||
File file = File.createTempFile("newpipetest", ".mp4");
|
|
||||||
file.deleteOnExit();
|
|
||||||
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
|
|
||||||
randomAccessFile.setLength(1000);
|
|
||||||
randomAccessFile.close();
|
|
||||||
DownloadMission downloadMission = new DownloadMission(file.getName(),
|
|
||||||
"http://google.com/?q=how+to+google", file.getParent());
|
|
||||||
downloadMission.blocks = 1000;
|
|
||||||
downloadMission.done = 1000;
|
|
||||||
downloadMission.finished = true;
|
|
||||||
return spy(downloadMission);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void assertMissionEquals(String message, DownloadMission expected, DownloadMission actual) {
|
|
||||||
if(expected == actual) return;
|
|
||||||
assertEquals(message + ": Name", expected.name, actual.name);
|
|
||||||
assertEquals(message + ": Location", expected.location, actual.location);
|
|
||||||
assertEquals(message + ": Url", expected.url, actual.url);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testThatMissionsAreLoaded() throws IOException {
|
|
||||||
ArrayList<DownloadMission> missions = new ArrayList<>();
|
|
||||||
long millis = System.currentTimeMillis();
|
|
||||||
for(int i = 0; i < 50; ++i){
|
|
||||||
DownloadMission mission = generateFinishedDownloadMission();
|
|
||||||
mission.timestamp = millis - i; // reverse order by timestamp
|
|
||||||
missions.add(mission);
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadDataSource = mock(DownloadDataSource.class);
|
|
||||||
when(downloadDataSource.loadMissions()).thenReturn(new ArrayList<>(missions));
|
|
||||||
downloadManager = new DownloadManagerImpl(new ArrayList<>(), downloadDataSource);
|
|
||||||
verify(downloadDataSource, times(1)).loadMissions();
|
|
||||||
|
|
||||||
assertEquals(50, downloadManager.getCount());
|
|
||||||
|
|
||||||
for(int i = 0; i < 50; ++i) {
|
|
||||||
assertMissionEquals("mission " + i, missions.get(50 - 1 - i), downloadManager.getMission(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore
|
|
||||||
@Test
|
|
||||||
public void startMission() throws Exception {
|
|
||||||
DownloadMission mission = missions.get(0);
|
|
||||||
mission = spy(mission);
|
|
||||||
missions.set(0, mission);
|
|
||||||
String url = "https://github.com/favicon.ico";
|
|
||||||
// create a temp file and delete it so we have a temp directory
|
|
||||||
File tempFile = File.createTempFile("favicon",".ico");
|
|
||||||
String name = tempFile.getName();
|
|
||||||
String location = tempFile.getParent();
|
|
||||||
assertTrue(tempFile.delete());
|
|
||||||
int id = downloadManager.startMission(url, location, name, true, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void resumeMission() {
|
|
||||||
DownloadMission mission = missions.get(0);
|
|
||||||
mission.running = true;
|
|
||||||
verify(mission, never()).start();
|
|
||||||
downloadManager.resumeMission(0);
|
|
||||||
verify(mission, never()).start();
|
|
||||||
mission.running = false;
|
|
||||||
downloadManager.resumeMission(0);
|
|
||||||
verify(mission, times(1)).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void pauseMission() {
|
|
||||||
DownloadMission mission = missions.get(0);
|
|
||||||
mission.running = false;
|
|
||||||
downloadManager.pauseMission(0);
|
|
||||||
verify(mission, never()).pause();
|
|
||||||
mission.running = true;
|
|
||||||
downloadManager.pauseMission(0);
|
|
||||||
verify(mission, times(1)).pause();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void deleteMission() {
|
|
||||||
DownloadMission mission = missions.get(0);
|
|
||||||
assertEquals(mission, downloadManager.getMission(0));
|
|
||||||
downloadManager.deleteMission(0);
|
|
||||||
verify(mission, times(1)).delete();
|
|
||||||
assertNotEquals(mission, downloadManager.getMission(0));
|
|
||||||
assertEquals(49, downloadManager.getCount());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = RuntimeException.class)
|
|
||||||
public void getMissionWithNegativeIndex() {
|
|
||||||
downloadManager.getMission(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getMission() {
|
|
||||||
assertSame(missions.get(0), downloadManager.getMission(0));
|
|
||||||
assertSame(missions.get(1), downloadManager.getMission(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void sortByTimestamp() {
|
|
||||||
ArrayList<DownloadMission> downloadMissions = new ArrayList<>();
|
|
||||||
DownloadMission mission = new DownloadMission();
|
|
||||||
mission.timestamp = 0;
|
|
||||||
|
|
||||||
DownloadMission mission1 = new DownloadMission();
|
|
||||||
mission1.timestamp = Integer.MAX_VALUE + 1L;
|
|
||||||
|
|
||||||
DownloadMission mission2 = new DownloadMission();
|
|
||||||
mission2.timestamp = 2L * Integer.MAX_VALUE ;
|
|
||||||
|
|
||||||
DownloadMission mission3 = new DownloadMission();
|
|
||||||
mission3.timestamp = 2L * Integer.MAX_VALUE + 5L;
|
|
||||||
|
|
||||||
|
|
||||||
downloadMissions.add(mission3);
|
|
||||||
downloadMissions.add(mission1);
|
|
||||||
downloadMissions.add(mission2);
|
|
||||||
downloadMissions.add(mission);
|
|
||||||
|
|
||||||
|
|
||||||
DownloadManagerImpl.sortByTimestamp(downloadMissions);
|
|
||||||
|
|
||||||
assertEquals(mission, downloadMissions.get(0));
|
|
||||||
assertEquals(mission1, downloadMissions.get(1));
|
|
||||||
assertEquals(mission2, downloadMissions.get(2));
|
|
||||||
assertEquals(mission3, downloadMissions.get(3));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in a new issue