resolve conflict
This commit is contained in:
commit
849a45a3ca
23 changed files with 1081 additions and 322 deletions
|
@ -46,5 +46,6 @@ dependencies {
|
||||||
compile 'com.google.code.gson:gson:2.4'
|
compile 'com.google.code.gson:gson:2.4'
|
||||||
compile 'com.nononsenseapps:filepicker:3.0.0'
|
compile 'com.nononsenseapps:filepicker:3.0.0'
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
|
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||||
compile 'ch.acra:acra:4.9.0'
|
compile 'ch.acra:acra:4.9.0'
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,6 +161,16 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".ReCaptchaActivity"
|
android:name=".ReCaptchaActivity"
|
||||||
android:label="@string/reCaptchaActivity" />
|
android:label="@string/reCaptchaActivity" />
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="android.support.v4.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.provider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/provider_paths"/>
|
||||||
|
</provider>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
|
@ -10,6 +10,7 @@ import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
import org.schabi.newpipe.settings.SettingsActivity;
|
import org.schabi.newpipe.settings.SettingsActivity;
|
||||||
|
import org.schabi.newpipe.util.PermissionHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Christian Schabesberger on 02.08.16.
|
* Created by Christian Schabesberger on 02.08.16.
|
||||||
|
@ -72,6 +73,9 @@ public class MainActivity extends ThemableActivity {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case R.id.action_show_downloads: {
|
case R.id.action_show_downloads: {
|
||||||
|
if(!PermissionHelper.checkStoragePermissions(this)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
Intent intent = new Intent(this, org.schabi.newpipe.download.DownloadActivity.class);
|
Intent intent = new Intent(this, org.schabi.newpipe.download.DownloadActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -57,8 +57,8 @@ import org.schabi.newpipe.player.BackgroundPlayer;
|
||||||
import org.schabi.newpipe.player.ExoPlayerActivity;
|
import org.schabi.newpipe.player.ExoPlayerActivity;
|
||||||
import org.schabi.newpipe.player.PlayVideoActivity;
|
import org.schabi.newpipe.player.PlayVideoActivity;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
|
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
import org.schabi.newpipe.util.PermissionHelper;
|
||||||
|
|
||||||
import static android.app.Activity.RESULT_OK;
|
import static android.app.Activity.RESULT_OK;
|
||||||
import static org.schabi.newpipe.ReCaptchaActivity.RECAPTCHA_REQUEST;
|
import static org.schabi.newpipe.ReCaptchaActivity.RECAPTCHA_REQUEST;
|
||||||
|
@ -393,6 +393,10 @@ public class VideoItemDetailFragment extends Fragment {
|
||||||
actionBarHandler.setOnDownloadListener(new ActionBarHandler.OnActionListener() {
|
actionBarHandler.setOnDownloadListener(new ActionBarHandler.OnActionListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onActionSelected(int selectedStreamId) {
|
public void onActionSelected(int selectedStreamId) {
|
||||||
|
if(!PermissionHelper.checkStoragePermissions(getActivity())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import android.preference.PreferenceManager;
|
||||||
import android.support.v4.app.NavUtils;
|
import android.support.v4.app.NavUtils;
|
||||||
import android.support.v7.app.ActionBar;
|
import android.support.v7.app.ActionBar;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
|
@ -30,6 +31,7 @@ import android.widget.Toast;
|
||||||
import org.schabi.newpipe.ThemableActivity;
|
import org.schabi.newpipe.ThemableActivity;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||||
import org.schabi.newpipe.settings.SettingsActivity;
|
import org.schabi.newpipe.settings.SettingsActivity;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -53,26 +55,11 @@ public class DownloadActivity extends ThemableActivity implements AdapterView.On
|
||||||
|
|
||||||
|
|
||||||
private MissionsFragment mFragment;
|
private MissionsFragment mFragment;
|
||||||
private DownloadManager mManager;
|
|
||||||
private DownloadManagerService.DMBinder mBinder;
|
|
||||||
|
|
||||||
private String mPendingUrl;
|
private String mPendingUrl;
|
||||||
private SharedPreferences mPrefs;
|
private SharedPreferences mPrefs;
|
||||||
|
|
||||||
private ServiceConnection mConnection = new ServiceConnection() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceConnected(ComponentName p1, IBinder binder) {
|
|
||||||
mBinder = (DownloadManagerService.DMBinder) binder;
|
|
||||||
mManager = mBinder.getDownloadManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceDisconnected(ComponentName p1) {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@TargetApi(21)
|
@TargetApi(21)
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
@ -83,7 +70,6 @@ public class DownloadActivity extends ThemableActivity implements AdapterView.On
|
||||||
Intent i = new Intent();
|
Intent i = new Intent();
|
||||||
i.setClass(this, DownloadManagerService.class);
|
i.setClass(this, DownloadManagerService.class);
|
||||||
startService(i);
|
startService(i);
|
||||||
bindService(i, mConnection, Context.BIND_AUTO_CREATE);
|
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_downloader);
|
setContentView(R.layout.activity_downloader);
|
||||||
|
@ -91,7 +77,7 @@ public class DownloadActivity extends ThemableActivity implements AdapterView.On
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
|
|
||||||
// its ok if this failes, we will catch that error later, and send it as report
|
// its ok if this fails, we will catch that error later, and send it as report
|
||||||
ActionBar actionBar = getSupportActionBar();
|
ActionBar actionBar = getSupportActionBar();
|
||||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||||
actionBar.setTitle(R.string.downloads_title);
|
actionBar.setTitle(R.string.downloads_title);
|
||||||
|
@ -202,22 +188,24 @@ public class DownloadActivity extends ThemableActivity implements AdapterView.On
|
||||||
@Override
|
@Override
|
||||||
public boolean onMenuItemClick(MenuItem item) {
|
public boolean onMenuItemClick(MenuItem item) {
|
||||||
if (item.getItemId() == R.id.okay) {
|
if (item.getItemId() == R.id.okay) {
|
||||||
|
|
||||||
|
String location;
|
||||||
|
if(audioButton.isChecked()) {
|
||||||
|
location = NewPipeSettings.getAudioDownloadPath(DownloadActivity.this);
|
||||||
|
} else {
|
||||||
|
location = NewPipeSettings.getVideoDownloadPath(DownloadActivity.this);
|
||||||
|
}
|
||||||
|
|
||||||
String fName = name.getText().toString().trim();
|
String fName = name.getText().toString().trim();
|
||||||
|
|
||||||
File f = new File(mManager.getLocation() + "/" + fName);
|
File f = new File(location, fName);
|
||||||
|
|
||||||
if (f.exists()) {
|
if (f.exists()) {
|
||||||
Toast.makeText(DownloadActivity.this, R.string.msg_exists, Toast.LENGTH_SHORT).show();
|
Toast.makeText(DownloadActivity.this, R.string.msg_exists, Toast.LENGTH_SHORT).show();
|
||||||
} else {
|
} else {
|
||||||
|
DownloadManagerService.startMission(
|
||||||
while (mBinder == null);
|
DownloadActivity.this,
|
||||||
|
getIntent().getData().toString(), location, fName,
|
||||||
int res = mManager.startMission(
|
audioButton.isChecked(), threads.getProgress() + 1);
|
||||||
getIntent().getData().toString(),
|
|
||||||
fName,
|
|
||||||
audioButton.isChecked(),
|
|
||||||
threads.getProgress() + 1);
|
|
||||||
mBinder.onMissionAdded(mManager.getMission(res));
|
|
||||||
mFragment.notifyChange();
|
mFragment.notifyChange();
|
||||||
|
|
||||||
mPrefs.edit().putInt(THREADS, threads.getProgress() + 1).commit();
|
mPrefs.edit().putInt(THREADS, threads.getProgress() + 1).commit();
|
||||||
|
@ -277,4 +265,5 @@ public class DownloadActivity extends ThemableActivity implements AdapterView.On
|
||||||
super.onOptionsItemSelected(item);
|
super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,8 @@ import android.widget.TextView;
|
||||||
|
|
||||||
import org.schabi.newpipe.App;
|
import org.schabi.newpipe.App;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
|
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -65,24 +67,6 @@ public class DownloadDialog extends DialogFragment {
|
||||||
public static final String AUDIO_URL = "audio_url";
|
public static final String AUDIO_URL = "audio_url";
|
||||||
public static final String VIDEO_URL = "video_url";
|
public static final String VIDEO_URL = "video_url";
|
||||||
|
|
||||||
private DownloadManager mManager;
|
|
||||||
private DownloadManagerService.DMBinder mBinder;
|
|
||||||
|
|
||||||
private ServiceConnection mConnection = new ServiceConnection() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceConnected(ComponentName p1, IBinder binder) {
|
|
||||||
mBinder = (DownloadManagerService.DMBinder) binder;
|
|
||||||
mManager = mBinder.getDownloadManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceDisconnected(ComponentName p1) {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
public DownloadDialog() {
|
public DownloadDialog() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -102,12 +86,6 @@ public class DownloadDialog extends DialogFragment {
|
||||||
if(ContextCompat.checkSelfPermission(this.getContext(),Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED)
|
if(ContextCompat.checkSelfPermission(this.getContext(),Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED)
|
||||||
ActivityCompat.requestPermissions(getActivity(),new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},0);
|
ActivityCompat.requestPermissions(getActivity(),new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},0);
|
||||||
|
|
||||||
Intent i = new Intent();
|
|
||||||
i.setClass(getContext(), DownloadManagerService.class);
|
|
||||||
getContext().startService(i);
|
|
||||||
getContext().bindService(i, mConnection, Context.BIND_AUTO_CREATE);
|
|
||||||
|
|
||||||
|
|
||||||
return inflater.inflate(R.layout.dialog_url, container);
|
return inflater.inflate(R.layout.dialog_url, container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,36 +197,22 @@ public class DownloadDialog extends DialogFragment {
|
||||||
|
|
||||||
String fName = name.getText().toString().trim();
|
String fName = name.getText().toString().trim();
|
||||||
|
|
||||||
// todo: add timeout? would be bad if the thread gets locked dueto this.
|
boolean isAudio = audioButton.isChecked();
|
||||||
while (mBinder == null);
|
String url, location, filename;
|
||||||
|
if(isAudio) {
|
||||||
if(audioButton.isChecked()){
|
url = arguments.getString(AUDIO_URL);
|
||||||
int res = mManager.startMission(
|
location = NewPipeSettings.getAudioDownloadPath(getContext());
|
||||||
arguments.getString(AUDIO_URL),
|
filename = fName + arguments.getString(FILE_SUFFIX_AUDIO);
|
||||||
fName + arguments.getString(FILE_SUFFIX_AUDIO),
|
} else {
|
||||||
audioButton.isChecked(),
|
url = arguments.getString(VIDEO_URL);
|
||||||
threads.getProgress() + 1);
|
location = NewPipeSettings.getVideoDownloadPath(getContext());
|
||||||
DownloadMission mission = mManager.getMission(res);
|
filename = fName + arguments.getString(FILE_SUFFIX_VIDEO);
|
||||||
mBinder.onMissionAdded(mission);
|
|
||||||
// add download listener to allow media scan notification
|
|
||||||
DownloadListener listener = new DownloadListener(getContext(), mission);
|
|
||||||
mission.addListener(listener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(videoButton.isChecked()){
|
DownloadManagerService.startMission(getContext(), url, location, filename, isAudio,
|
||||||
int res = mManager.startMission(
|
|
||||||
arguments.getString(VIDEO_URL),
|
|
||||||
fName + arguments.getString(FILE_SUFFIX_VIDEO),
|
|
||||||
audioButton.isChecked(),
|
|
||||||
threads.getProgress() + 1);
|
threads.getProgress() + 1);
|
||||||
DownloadMission mission = mManager.getMission(res);
|
|
||||||
mBinder.onMissionAdded(mission);
|
|
||||||
// add download listener to allow media scan notification
|
|
||||||
DownloadListener listener = new DownloadListener(getContext(), mission);
|
|
||||||
mission.addListener(listener);
|
|
||||||
}
|
|
||||||
getDialog().dismiss();
|
getDialog().dismiss();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void download(String url, String title,
|
private void download(String url, String title,
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
package org.schabi.newpipe.download;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
|
|
||||||
import us.shandian.giga.get.DownloadMission;
|
|
||||||
import us.shandian.giga.get.DownloadMission.MissionListener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by erwin on 06.11.16.
|
|
||||||
*
|
|
||||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
|
||||||
* DownloadListener.java is part of NewPipe.
|
|
||||||
*
|
|
||||||
* NewPipe is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* NewPipe is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
class DownloadListener implements MissionListener
|
|
||||||
{
|
|
||||||
DownloadMission mMission;
|
|
||||||
Context mContext;
|
|
||||||
|
|
||||||
public DownloadListener(Context context, DownloadMission mission)
|
|
||||||
{
|
|
||||||
super();
|
|
||||||
mMission = mission;
|
|
||||||
mContext = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onProgressUpdate(long done, long total)
|
|
||||||
{
|
|
||||||
// do nothing special ...
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFinish()
|
|
||||||
{
|
|
||||||
// notify media scanner on downloaded media file ...
|
|
||||||
mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
|
|
||||||
Uri.parse( "file://" + mMission.location
|
|
||||||
+ "/" + mMission.name)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(int errCode)
|
|
||||||
{
|
|
||||||
// do nothing special ...
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -42,7 +42,7 @@ import info.guardianproject.netcipher.NetCipher;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
// TODO: FOR HEVEN SAKE !!! DO NOT SIMPLY USE ASYNCTASK. MAKE THIS A PROPER SERVICE !!!
|
// TODO: FOR HEAVEN SAKE !!! DO NOT SIMPLY USE ASYNCTASK. MAKE THIS A PROPER SERVICE !!!
|
||||||
public class FileDownloader extends AsyncTask<Void, Integer, Void> {
|
public class FileDownloader extends AsyncTask<Void, Integer, Void> {
|
||||||
public static final String TAG = "FileDownloader";
|
public static final String TAG = "FileDownloader";
|
||||||
|
|
||||||
|
|
|
@ -88,7 +88,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
||||||
// $$el_type$$ will be replaced by the actual el_type (se the declarations below)
|
// $$el_type$$ will be replaced by the actual el_type (se the declarations below)
|
||||||
private static final String GET_VIDEO_INFO_URL =
|
private static final String GET_VIDEO_INFO_URL =
|
||||||
"https://www.youtube.com/get_video_info?video_id=%%video_id%%$$el_type$$&ps=default&eurl=&gl=US&hl=en";
|
"https://www.youtube.com/get_video_info?video_id=%%video_id%%$$el_type$$&ps=default&eurl=&gl=US&hl=en";
|
||||||
// eltype is nececeary for the url aboth
|
// eltype is necessary for the url above
|
||||||
private static final String EL_INFO = "el=info";
|
private static final String EL_INFO = "el=info";
|
||||||
|
|
||||||
public enum ItagType {
|
public enum ItagType {
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
package org.schabi.newpipe.util;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.support.annotation.RequiresApi;
|
||||||
|
import android.support.v4.app.ActivityCompat;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
|
|
||||||
|
public class PermissionHelper {
|
||||||
|
public static final int PERMISSION_WRITE_STORAGE = 778;
|
||||||
|
public static final int PERMISSION_READ_STORAGE = 777;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean checkStoragePermissions(Activity activity) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||||
|
if(!checkReadStoragePermissions(activity)) return false;
|
||||||
|
}
|
||||||
|
return checkWriteStoragePermissions(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
|
||||||
|
public static boolean checkReadStoragePermissions(Activity activity) {
|
||||||
|
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
|
ActivityCompat.requestPermissions(activity,
|
||||||
|
new String[]{
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||||
|
PERMISSION_READ_STORAGE);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean checkWriteStoragePermissions(Activity activity) {
|
||||||
|
// Here, thisActivity is the current activity
|
||||||
|
if (ContextCompat.checkSelfPermission(activity,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
|
|
||||||
|
// Should we show an explanation?
|
||||||
|
/*if (ActivityCompat.shouldShowRequestPermissionRationale(activity,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||||
|
|
||||||
|
// Show an explanation to the user *asynchronously* -- don't block
|
||||||
|
// this thread waiting for the user's response! After the user
|
||||||
|
// sees the explanation, try again to request the permission.
|
||||||
|
} else {*/
|
||||||
|
|
||||||
|
// No explanation needed, we can request the permission.
|
||||||
|
ActivityCompat.requestPermissions(activity,
|
||||||
|
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||||
|
PERMISSION_WRITE_STORAGE);
|
||||||
|
|
||||||
|
// PERMISSION_WRITE_STORAGE is an
|
||||||
|
// app-defined int constant. The callback method gets the
|
||||||
|
// result of the request.
|
||||||
|
/*}*/
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
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 downlaod 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);
|
||||||
|
}
|
|
@ -4,11 +4,45 @@ public interface DownloadManager
|
||||||
{
|
{
|
||||||
int BLOCK_SIZE = 512 * 1024;
|
int BLOCK_SIZE = 512 * 1024;
|
||||||
|
|
||||||
int startMission(String url, String name, boolean isAudio, int threads);
|
/**
|
||||||
|
* Start a new download mission
|
||||||
|
* @param url the url to download
|
||||||
|
* @param location the location
|
||||||
|
* @param name the name of the file to create
|
||||||
|
* @param isAudio true if the download is an audio file
|
||||||
|
* @param threads the number of threads maximal used to download chunks of the file. @return the identifier of the mission.
|
||||||
|
*/
|
||||||
|
int startMission(String url, String location, String name, boolean isAudio, int threads);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resume the execution of a download mission.
|
||||||
|
* @param id the identifier of the mission to resume.
|
||||||
|
*/
|
||||||
void resumeMission(int id);
|
void resumeMission(int id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pause the execution of a download mission.
|
||||||
|
* @param id the identifier of the mission to pause.
|
||||||
|
*/
|
||||||
void pauseMission(int id);
|
void pauseMission(int id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the mission from the downloaded list but keeps the downloaded file.
|
||||||
|
* @param id The mission identifier
|
||||||
|
*/
|
||||||
void deleteMission(int id);
|
void deleteMission(int id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the download mission by its identifier
|
||||||
|
* @param id the identifier of the download mission
|
||||||
|
* @return the download mission or null if the mission doesn't exist
|
||||||
|
*/
|
||||||
DownloadMission getMission(int id);
|
DownloadMission getMission(int id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of download missions.
|
||||||
|
* @return the number of download missions.
|
||||||
|
*/
|
||||||
int getCount();
|
int getCount();
|
||||||
String getLocation();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,21 @@
|
||||||
package us.shandian.giga.get;
|
package us.shandian.giga.get;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.support.annotation.Nullable;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
import org.schabi.newpipe.settings.NewPipeSettings;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FilenameFilter;
|
||||||
import java.io.RandomAccessFile;
|
import java.io.RandomAccessFile;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
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 us.shandian.giga.util.Utility;
|
||||||
import static org.schabi.newpipe.BuildConfig.DEBUG;
|
import static org.schabi.newpipe.BuildConfig.DEBUG;
|
||||||
|
@ -19,30 +23,45 @@ import static org.schabi.newpipe.BuildConfig.DEBUG;
|
||||||
public class DownloadManagerImpl implements DownloadManager
|
public class DownloadManagerImpl implements DownloadManager
|
||||||
{
|
{
|
||||||
private static final String TAG = DownloadManagerImpl.class.getSimpleName();
|
private static final String TAG = DownloadManagerImpl.class.getSimpleName();
|
||||||
|
private final DownloadDataSource mDownloadDataSource;
|
||||||
|
|
||||||
private Context mContext;
|
private final ArrayList<DownloadMission> mMissions = new ArrayList<DownloadMission>();
|
||||||
private String mLocation;
|
|
||||||
protected ArrayList<DownloadMission> mMissions = new ArrayList<DownloadMission>();
|
|
||||||
|
|
||||||
public DownloadManagerImpl(Context context, String location) {
|
/**
|
||||||
mContext = context;
|
* Create a new instance
|
||||||
mLocation = location;
|
* @param searchLocations the directories to search for unfinished downloads
|
||||||
loadMissions();
|
* @param downloadDataSource the data source for finished downloads
|
||||||
|
*/
|
||||||
|
public DownloadManagerImpl(Collection<String> searchLocations, DownloadDataSource downloadDataSource) {
|
||||||
|
mDownloadDataSource = downloadDataSource;
|
||||||
|
loadMissions(searchLocations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int startMission(String url, String name, boolean isAudio, int threads) {
|
public int startMission(String url, String location, String name, boolean isAudio, int threads) {
|
||||||
DownloadMission mission = new DownloadMission();
|
DownloadMission existingMission = getMissionByLocation(location, name);
|
||||||
mission.url = url;
|
if(existingMission != null) {
|
||||||
mission.name = name;
|
// Already downloaded or downloading
|
||||||
if(isAudio) {
|
if(existingMission.finished) {
|
||||||
mission.location = NewPipeSettings.getAudioDownloadPath(mContext);
|
// Overwrite mission
|
||||||
|
deleteMission(mMissions.indexOf(existingMission));
|
||||||
} else {
|
} else {
|
||||||
mission.location = NewPipeSettings.getVideoDownloadPath(mContext);
|
// 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.timestamp = System.currentTimeMillis();
|
||||||
mission.threadCount = threads;
|
mission.threadCount = threads;
|
||||||
new Initializer(mContext, mission).start();
|
mission.addListener(new MissionListener(mission));
|
||||||
|
new Initializer(mission).start();
|
||||||
return insertMission(mission);
|
return insertMission(mission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,22 +83,70 @@ public class DownloadManagerImpl implements DownloadManager
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteMission(int i) {
|
public void deleteMission(int i) {
|
||||||
getMission(i).delete();
|
DownloadMission mission = getMission(i);
|
||||||
|
if(mission.finished) {
|
||||||
|
mDownloadDataSource.deleteMission(mission);
|
||||||
|
}
|
||||||
|
mission.delete();
|
||||||
mMissions.remove(i);
|
mMissions.remove(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadMissions() {
|
private void loadMissions(Iterable<String> searchLocations) {
|
||||||
File f = new File(mLocation);
|
mMissions.clear();
|
||||||
|
loadFinishedMissions();
|
||||||
|
for(String location: searchLocations) {
|
||||||
|
loadMissions(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads finished missions from the data source
|
||||||
|
*/
|
||||||
|
private void loadFinishedMissions() {
|
||||||
|
List<DownloadMission> finishedMissions = mDownloadDataSource.loadMissions();
|
||||||
|
if(finishedMissions == null) {
|
||||||
|
finishedMissions = new ArrayList<>();
|
||||||
|
}
|
||||||
|
// Ensure its sorted
|
||||||
|
Collections.sort(finishedMissions, new Comparator<DownloadMission>() {
|
||||||
|
@Override
|
||||||
|
public int compare(DownloadMission o1, DownloadMission o2) {
|
||||||
|
return (int) (o1.timestamp - o2.timestamp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
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()) {
|
if (f.exists() && f.isDirectory()) {
|
||||||
File[] subs = f.listFiles();
|
File[] subs = f.listFiles();
|
||||||
|
|
||||||
for (File sub : subs) {
|
if(subs == null) {
|
||||||
if (sub.isDirectory()) {
|
Log.e(TAG, "listFiles() returned null");
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sub.getName().endsWith(".giga")) {
|
for (File sub : subs) {
|
||||||
|
if (sub.isFile() && sub.getName().endsWith(".giga")) {
|
||||||
String str = Utility.readFromFile(sub.getAbsolutePath());
|
String str = Utility.readFromFile(sub.getAbsolutePath());
|
||||||
if (str != null && !str.trim().equals("")) {
|
if (str != null && !str.trim().equals("")) {
|
||||||
|
|
||||||
|
@ -91,7 +158,9 @@ public class DownloadManagerImpl implements DownloadManager
|
||||||
DownloadMission mis = new Gson().fromJson(str, DownloadMission.class);
|
DownloadMission mis = new Gson().fromJson(str, DownloadMission.class);
|
||||||
|
|
||||||
if (mis.finished) {
|
if (mis.finished) {
|
||||||
sub.delete();
|
if(!sub.delete()) {
|
||||||
|
Log.w(TAG, "Unable to delete .giga file: " + sub.getPath());
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,17 +168,6 @@ public class DownloadManagerImpl implements DownloadManager
|
||||||
mis.recovered = true;
|
mis.recovered = true;
|
||||||
insertMission(mis);
|
insertMission(mis);
|
||||||
}
|
}
|
||||||
} else if (!sub.getName().startsWith(".") && !new File(sub.getPath() + ".giga").exists()) {
|
|
||||||
// Add a dummy mission for downloaded files
|
|
||||||
DownloadMission mis = new DownloadMission();
|
|
||||||
mis.length = sub.length();
|
|
||||||
mis.done = mis.length;
|
|
||||||
mis.finished = true;
|
|
||||||
mis.running = false;
|
|
||||||
mis.name = sub.getName();
|
|
||||||
mis.location = mLocation;
|
|
||||||
mis.timestamp = sub.lastModified();
|
|
||||||
insertMission(mis);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,17 +203,80 @@ public class DownloadManagerImpl implements DownloadManager
|
||||||
return i;
|
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
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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
|
@Override
|
||||||
public String getLocation() {
|
public boolean accept(File dir, String name) {
|
||||||
return mLocation;
|
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 class Initializer extends Thread {
|
||||||
private Context context;
|
|
||||||
private DownloadMission mission;
|
private DownloadMission mission;
|
||||||
|
|
||||||
public Initializer(Context context, DownloadMission mission) {
|
public Initializer(DownloadMission mission) {
|
||||||
this.context = context;
|
|
||||||
this.mission = mission;
|
this.mission = mission;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,4 +338,30 @@ public class DownloadManagerImpl implements DownloadManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package us.shandian.giga.get;
|
package us.shandian.giga.get;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
@ -10,12 +9,13 @@ import com.google.gson.Gson;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import us.shandian.giga.util.Utility;
|
import us.shandian.giga.util.Utility;
|
||||||
|
|
||||||
import static org.schabi.newpipe.BuildConfig.DEBUG;
|
import static org.schabi.newpipe.BuildConfig.DEBUG;
|
||||||
|
|
||||||
public class DownloadMission
|
public class DownloadMission
|
||||||
|
@ -25,24 +25,47 @@ public class DownloadMission
|
||||||
public interface MissionListener {
|
public interface MissionListener {
|
||||||
HashMap<MissionListener, Handler> handlerStore = new HashMap<>();
|
HashMap<MissionListener, Handler> handlerStore = new HashMap<>();
|
||||||
|
|
||||||
void onProgressUpdate(long done, long total);
|
void onProgressUpdate(DownloadMission downloadMission, long done, long total);
|
||||||
void onFinish();
|
void onFinish(DownloadMission downloadMission);
|
||||||
void onError(int errCode);
|
void onError(DownloadMission downloadMission, int errCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final int ERROR_SERVER_UNSUPPORTED = 206;
|
public static final int ERROR_SERVER_UNSUPPORTED = 206;
|
||||||
public static final int ERROR_UNKNOWN = 233;
|
public static final int ERROR_UNKNOWN = 233;
|
||||||
|
|
||||||
public String name = "";
|
/**
|
||||||
public String url = "";
|
* The filename
|
||||||
public String location = "";
|
*/
|
||||||
|
public String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The url of the file to download
|
||||||
|
*/
|
||||||
|
public String url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The directory to store the download
|
||||||
|
*/
|
||||||
|
public String location;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of blocks the size of {@link DownloadManager#BLOCK_SIZE}
|
||||||
|
*/
|
||||||
public long blocks;
|
public long blocks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of bytes
|
||||||
|
*/
|
||||||
public long length;
|
public long length;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of bytes downloaded
|
||||||
|
*/
|
||||||
public long done;
|
public long done;
|
||||||
public int threadCount = 3;
|
public int threadCount = 3;
|
||||||
public int finishCount;
|
public int finishCount;
|
||||||
public List<Long> threadPositions = new ArrayList<Long>();
|
private List<Long> threadPositions = new ArrayList<Long>();
|
||||||
public Map<Long, Boolean> blockState = new HashMap<Long, Boolean>();
|
public final Map<Long, Boolean> blockState = new HashMap<Long, Boolean>();
|
||||||
public boolean running;
|
public boolean running;
|
||||||
public boolean finished;
|
public boolean finished;
|
||||||
public boolean fallback;
|
public boolean fallback;
|
||||||
|
@ -54,22 +77,64 @@ public class DownloadMission
|
||||||
private transient ArrayList<WeakReference<MissionListener>> mListeners = new ArrayList<WeakReference<MissionListener>>();
|
private transient ArrayList<WeakReference<MissionListener>> mListeners = new ArrayList<WeakReference<MissionListener>>();
|
||||||
private transient boolean mWritingToFile;
|
private transient boolean mWritingToFile;
|
||||||
|
|
||||||
|
private static final int NO_IDENTIFIER = -1;
|
||||||
|
private long db_identifier = NO_IDENTIFIER;
|
||||||
|
|
||||||
|
public DownloadMission() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadMission(String name, String url, String location) {
|
||||||
|
if(name == null) throw new NullPointerException("name is null");
|
||||||
|
if(name.isEmpty()) throw new IllegalArgumentException("name is empty");
|
||||||
|
if(url == null) throw new NullPointerException("url is null");
|
||||||
|
if(url.isEmpty()) throw new IllegalArgumentException("url is empty");
|
||||||
|
if(location == null) throw new NullPointerException("location is null");
|
||||||
|
if(location.isEmpty()) throw new IllegalArgumentException("location is empty");
|
||||||
|
this.url = url;
|
||||||
|
this.name = name;
|
||||||
|
this.location = location;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void checkBlock(long block) {
|
||||||
|
if(block < 0 || block >= blocks) {
|
||||||
|
throw new IllegalArgumentException("illegal block identifier");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a block is reserved
|
||||||
|
* @param block the block identifier
|
||||||
|
* @return true if the block is reserved and false if otherwise
|
||||||
|
*/
|
||||||
public boolean isBlockPreserved(long block) {
|
public boolean isBlockPreserved(long block) {
|
||||||
|
checkBlock(block);
|
||||||
return blockState.containsKey(block) ? blockState.get(block) : false;
|
return blockState.containsKey(block) ? blockState.get(block) : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void preserveBlock(long block) {
|
public void preserveBlock(long block) {
|
||||||
|
checkBlock(block);
|
||||||
synchronized (blockState) {
|
synchronized (blockState) {
|
||||||
blockState.put(block, true);
|
blockState.put(block, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPosition(int id, long position) {
|
/**
|
||||||
threadPositions.set(id, position);
|
* Set the download position of the file
|
||||||
|
* @param threadId the identifier of the thread
|
||||||
|
* @param position the download position of the thread
|
||||||
|
*/
|
||||||
|
public void setPosition(int threadId, long position) {
|
||||||
|
threadPositions.set(threadId, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getPosition(int id) {
|
/**
|
||||||
return threadPositions.get(id);
|
* Get the position of a thread
|
||||||
|
* @param threadId the identifier of the thread
|
||||||
|
* @return the position for the thread
|
||||||
|
*/
|
||||||
|
public long getPosition(int threadId) {
|
||||||
|
return threadPositions.get(threadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void notifyProgress(long deltaLen) {
|
public synchronized void notifyProgress(long deltaLen) {
|
||||||
|
@ -95,13 +160,16 @@ public class DownloadMission
|
||||||
MissionListener.handlerStore.get(listener).post(new Runnable() {
|
MissionListener.handlerStore.get(listener).post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
listener.onProgressUpdate(done, length);
|
listener.onProgressUpdate(DownloadMission.this, done, length);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by a download thread when it finished.
|
||||||
|
*/
|
||||||
public synchronized void notifyFinished() {
|
public synchronized void notifyFinished() {
|
||||||
if (errCode > 0) return;
|
if (errCode > 0) return;
|
||||||
|
|
||||||
|
@ -112,6 +180,9 @@ public class DownloadMission
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when all parts are downloaded
|
||||||
|
*/
|
||||||
private void onFinish() {
|
private void onFinish() {
|
||||||
if (errCode > 0) return;
|
if (errCode > 0) return;
|
||||||
|
|
||||||
|
@ -130,7 +201,7 @@ public class DownloadMission
|
||||||
MissionListener.handlerStore.get(listener).post(new Runnable() {
|
MissionListener.handlerStore.get(listener).post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
listener.onFinish();
|
listener.onFinish(DownloadMission.this);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -147,7 +218,7 @@ public class DownloadMission
|
||||||
MissionListener.handlerStore.get(listener).post(new Runnable() {
|
MissionListener.handlerStore.get(listener).post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
listener.onError(errCode);
|
listener.onError(DownloadMission.this, errCode);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -170,6 +241,9 @@ public class DownloadMission
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start downloading with multiple threads.
|
||||||
|
*/
|
||||||
public void start() {
|
public void start() {
|
||||||
if (!running && !finished) {
|
if (!running && !finished) {
|
||||||
running = true;
|
running = true;
|
||||||
|
@ -201,11 +275,18 @@ public class DownloadMission
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the file and the meta file
|
||||||
|
*/
|
||||||
public void delete() {
|
public void delete() {
|
||||||
deleteThisFromFile();
|
deleteThisFromFile();
|
||||||
new File(location + "/" + name).delete();
|
new File(location, name).delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write this {@link DownloadMission} to the meta file asynchronously
|
||||||
|
* if no thread is already running.
|
||||||
|
*/
|
||||||
public void writeThisToFile() {
|
public void writeThisToFile() {
|
||||||
if (!mWritingToFile) {
|
if (!mWritingToFile) {
|
||||||
mWritingToFile = true;
|
mWritingToFile = true;
|
||||||
|
@ -219,13 +300,29 @@ public class DownloadMission
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write this {@link DownloadMission} to the meta file.
|
||||||
|
*/
|
||||||
private void doWriteThisToFile() {
|
private void doWriteThisToFile() {
|
||||||
synchronized (blockState) {
|
synchronized (blockState) {
|
||||||
Utility.writeToFile(location + "/" + name + ".giga", new Gson().toJson(this));
|
Utility.writeToFile(getMetaFilename(), new Gson().toJson(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteThisFromFile() {
|
private void deleteThisFromFile() {
|
||||||
new File(location + "/" + name + ".giga").delete();
|
new File(getMetaFilename()).delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the path of the meta file
|
||||||
|
* @return the path to the meta file
|
||||||
|
*/
|
||||||
|
private String getMetaFilename() {
|
||||||
|
return location + "/" + name + ".giga";
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getDownloadedFile() {
|
||||||
|
return new File(location, name);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,14 +9,19 @@ import java.net.URL;
|
||||||
|
|
||||||
import static org.schabi.newpipe.BuildConfig.DEBUG;
|
import static org.schabi.newpipe.BuildConfig.DEBUG;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runnable to download blocks of a file until the file is completely downloaded,
|
||||||
|
* an error occurs or the process is stopped.
|
||||||
|
*/
|
||||||
public class DownloadRunnable implements Runnable
|
public class DownloadRunnable implements Runnable
|
||||||
{
|
{
|
||||||
private static final String TAG = DownloadRunnable.class.getSimpleName();
|
private static final String TAG = DownloadRunnable.class.getSimpleName();
|
||||||
|
|
||||||
private DownloadMission mMission;
|
private final DownloadMission mMission;
|
||||||
private int mId;
|
private final int mId;
|
||||||
|
|
||||||
public DownloadRunnable(DownloadMission mission, int id) {
|
public DownloadRunnable(DownloadMission mission, int id) {
|
||||||
|
if(mission == null) throw new NullPointerException("mission is null");
|
||||||
mMission = mission;
|
mMission = mission;
|
||||||
mId = id;
|
mId = id;
|
||||||
}
|
}
|
||||||
|
@ -86,7 +91,7 @@ public class DownloadRunnable implements Runnable
|
||||||
Log.d(TAG, mId + ":Content-Length=" + conn.getContentLength() + " Code:" + conn.getResponseCode());
|
Log.d(TAG, mId + ":Content-Length=" + conn.getContentLength() + " Code:" + conn.getResponseCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
// A server may be ignoring the range requet
|
// A server may be ignoring the range request
|
||||||
if (conn.getResponseCode() != 206) {
|
if (conn.getResponseCode() != 206) {
|
||||||
mMission.errCode = DownloadMission.ERROR_SERVER_UNSUPPORTED;
|
mMission.errCode = DownloadMission.ERROR_SERVER_UNSUPPORTED;
|
||||||
notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED);
|
notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED);
|
||||||
|
@ -131,7 +136,7 @@ public class DownloadRunnable implements Runnable
|
||||||
notifyProgress(-total);
|
notifyProgress(-total);
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, mId + ":position " + position + " retrying");
|
Log.d(TAG, mId + ":position " + position + " retrying", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,11 @@ import java.net.URL;
|
||||||
// Single-threaded fallback mode
|
// Single-threaded fallback mode
|
||||||
public class DownloadRunnableFallback implements Runnable
|
public class DownloadRunnableFallback implements Runnable
|
||||||
{
|
{
|
||||||
private DownloadMission mMission;
|
private final DownloadMission mMission;
|
||||||
//private int mId;
|
//private int mId;
|
||||||
|
|
||||||
public DownloadRunnableFallback(DownloadMission mission) {
|
public DownloadRunnableFallback(DownloadMission mission) {
|
||||||
|
if(mission == null) throw new NullPointerException("mission is null");
|
||||||
//mId = id;
|
//mId = id;
|
||||||
mMission = mission;
|
mMission = mission;
|
||||||
}
|
}
|
||||||
|
@ -35,7 +36,7 @@ public class DownloadRunnableFallback implements Runnable
|
||||||
f.write(buf, 0, len);
|
f.write(buf, 0, len);
|
||||||
notifyProgress(len);
|
notifyProgress(len);
|
||||||
|
|
||||||
if (Thread.currentThread().interrupted()) {
|
if (Thread.interrupted()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
package us.shandian.giga.get.sqlite;
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
|
||||||
|
import us.shandian.giga.get.DownloadMission;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SqliteHelper to store {@link us.shandian.giga.get.DownloadMission}
|
||||||
|
*/
|
||||||
|
public class DownloadMissionSQLiteHelper extends SQLiteOpenHelper {
|
||||||
|
|
||||||
|
|
||||||
|
private final String TAG = "DownloadMissionHelper";
|
||||||
|
|
||||||
|
// TODO: use NewPipeSQLiteHelper ('s constants) when playlist branch is merged (?)
|
||||||
|
private static final String DATABASE_NAME = "downloads.db";
|
||||||
|
|
||||||
|
private static final int DATABASE_VERSION = 2;
|
||||||
|
/**
|
||||||
|
* The table name of download missions
|
||||||
|
*/
|
||||||
|
static final String MISSIONS_TABLE_NAME = "download_missions";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The key to the directory location of the mission
|
||||||
|
*/
|
||||||
|
static final String KEY_LOCATION = "location";
|
||||||
|
/**
|
||||||
|
* The key to the url of a mission
|
||||||
|
*/
|
||||||
|
static final String KEY_URL = "url";
|
||||||
|
/**
|
||||||
|
* The key to the name of a mission
|
||||||
|
*/
|
||||||
|
static final String KEY_NAME = "name";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The key to the done.
|
||||||
|
*/
|
||||||
|
static final String KEY_DONE = "bytes_downloaded";
|
||||||
|
|
||||||
|
static final String KEY_TIMESTAMP = "timestamp";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The statement to create the table
|
||||||
|
*/
|
||||||
|
private static final String MISSIONS_CREATE_TABLE =
|
||||||
|
"CREATE TABLE " + MISSIONS_TABLE_NAME + " (" +
|
||||||
|
KEY_LOCATION + " TEXT NOT NULL, " +
|
||||||
|
KEY_NAME + " TEXT NOT NULL, " +
|
||||||
|
KEY_URL + " TEXT NOT NULL, " +
|
||||||
|
KEY_DONE + " INTEGER NOT NULL, " +
|
||||||
|
KEY_TIMESTAMP + " INTEGER NOT NULL, " +
|
||||||
|
" UNIQUE(" + KEY_LOCATION + ", " + KEY_NAME + "));";
|
||||||
|
|
||||||
|
|
||||||
|
DownloadMissionSQLiteHelper(Context context) {
|
||||||
|
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all values of the download mission as ContentValues.
|
||||||
|
* @param downloadMission the download mission
|
||||||
|
* @return the content values
|
||||||
|
*/
|
||||||
|
public static ContentValues getValuesOfMission(DownloadMission downloadMission) {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(KEY_URL, downloadMission.url);
|
||||||
|
values.put(KEY_LOCATION, downloadMission.location);
|
||||||
|
values.put(KEY_NAME, downloadMission.name);
|
||||||
|
values.put(KEY_DONE, downloadMission.done);
|
||||||
|
values.put(KEY_TIMESTAMP, downloadMission.timestamp);
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(SQLiteDatabase db) {
|
||||||
|
db.execSQL(MISSIONS_CREATE_TABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
|
// Currently nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DownloadMission getMissionFromCursor(Cursor cursor) {
|
||||||
|
if(cursor == null) throw new NullPointerException("cursor is null");
|
||||||
|
int pos;
|
||||||
|
String name = cursor.getString(cursor.getColumnIndexOrThrow(KEY_NAME));
|
||||||
|
String location = cursor.getString(cursor.getColumnIndexOrThrow(KEY_LOCATION));
|
||||||
|
String url = cursor.getString(cursor.getColumnIndexOrThrow(KEY_URL));
|
||||||
|
DownloadMission mission = new DownloadMission(name, url, location);
|
||||||
|
mission.done = cursor.getLong(cursor.getColumnIndexOrThrow(KEY_DONE));
|
||||||
|
mission.timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(KEY_TIMESTAMP));
|
||||||
|
mission.finished = true;
|
||||||
|
return mission;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
package us.shandian.giga.get.sqlite;
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import us.shandian.giga.get.DownloadDataSource;
|
||||||
|
import us.shandian.giga.get.DownloadMission;
|
||||||
|
|
||||||
|
import static us.shandian.giga.get.sqlite.DownloadMissionSQLiteHelper.KEY_LOCATION;
|
||||||
|
import static us.shandian.giga.get.sqlite.DownloadMissionSQLiteHelper.KEY_NAME;
|
||||||
|
import static us.shandian.giga.get.sqlite.DownloadMissionSQLiteHelper.MISSIONS_TABLE_NAME;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-thread-safe implementation of {@link DownloadDataSource}
|
||||||
|
*/
|
||||||
|
public class SQLiteDownloadDataSource implements DownloadDataSource {
|
||||||
|
|
||||||
|
private static final String TAG = "DownloadDataSourceImpl";
|
||||||
|
private final DownloadMissionSQLiteHelper downloadMissionSQLiteHelper;
|
||||||
|
|
||||||
|
public SQLiteDownloadDataSource(Context context) {
|
||||||
|
downloadMissionSQLiteHelper = new DownloadMissionSQLiteHelper(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DownloadMission> loadMissions() {
|
||||||
|
ArrayList<DownloadMission> result;
|
||||||
|
SQLiteDatabase database = downloadMissionSQLiteHelper.getReadableDatabase();
|
||||||
|
Cursor cursor = database.query(MISSIONS_TABLE_NAME, null, null,
|
||||||
|
null, null, null, DownloadMissionSQLiteHelper.KEY_TIMESTAMP);
|
||||||
|
|
||||||
|
int count = cursor.getCount();
|
||||||
|
if(count == 0) return new ArrayList<>();
|
||||||
|
result = new ArrayList<>(count);
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
result.add(DownloadMissionSQLiteHelper.getMissionFromCursor(cursor));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addMission(DownloadMission downloadMission) {
|
||||||
|
if(downloadMission == null) throw new NullPointerException("downloadMission is null");
|
||||||
|
SQLiteDatabase database = downloadMissionSQLiteHelper.getWritableDatabase();
|
||||||
|
ContentValues values = DownloadMissionSQLiteHelper.getValuesOfMission(downloadMission);
|
||||||
|
database.insert(MISSIONS_TABLE_NAME, null, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateMission(DownloadMission downloadMission) {
|
||||||
|
if(downloadMission == null) throw new NullPointerException("downloadMission is null");
|
||||||
|
SQLiteDatabase database = downloadMissionSQLiteHelper.getWritableDatabase();
|
||||||
|
ContentValues values = DownloadMissionSQLiteHelper.getValuesOfMission(downloadMission);
|
||||||
|
String whereClause = KEY_LOCATION+ " = ? AND " +
|
||||||
|
KEY_NAME + " = ?";
|
||||||
|
int rowsAffected = database.update(MISSIONS_TABLE_NAME, values,
|
||||||
|
whereClause, new String[]{downloadMission.location, downloadMission.name});
|
||||||
|
if(rowsAffected != 1) {
|
||||||
|
Log.e(TAG, "Expected 1 row to be affected by update but got " + rowsAffected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteMission(DownloadMission downloadMission) {
|
||||||
|
if(downloadMission == null) throw new NullPointerException("downloadMission is null");
|
||||||
|
SQLiteDatabase database = downloadMissionSQLiteHelper.getWritableDatabase();
|
||||||
|
database.delete(MISSIONS_TABLE_NAME,
|
||||||
|
KEY_LOCATION + " = ? AND " +
|
||||||
|
KEY_NAME + " = ?",
|
||||||
|
new String[]{downloadMission.location, downloadMission.name});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,37 +1,72 @@
|
||||||
package us.shandian.giga.service;
|
package us.shandian.giga.service;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.support.v4.app.NotificationCompat.Builder;
|
import android.support.v4.app.NotificationCompat.Builder;
|
||||||
|
import android.support.v4.content.PermissionChecker;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.download.DownloadActivity;
|
import org.schabi.newpipe.download.DownloadActivity;
|
||||||
import org.schabi.newpipe.settings.NewPipeSettings;
|
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||||
import org.schabi.newpipe.R;
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import us.shandian.giga.get.DownloadDataSource;
|
||||||
import us.shandian.giga.get.DownloadManager;
|
import us.shandian.giga.get.DownloadManager;
|
||||||
import us.shandian.giga.get.DownloadManagerImpl;
|
import us.shandian.giga.get.DownloadManagerImpl;
|
||||||
import us.shandian.giga.get.DownloadMission;
|
import us.shandian.giga.get.DownloadMission;
|
||||||
|
import us.shandian.giga.get.sqlite.SQLiteDownloadDataSource;
|
||||||
|
|
||||||
import static org.schabi.newpipe.BuildConfig.DEBUG;
|
import static org.schabi.newpipe.BuildConfig.DEBUG;
|
||||||
|
|
||||||
public class DownloadManagerService extends Service implements DownloadMission.MissionListener
|
public class DownloadManagerService extends Service
|
||||||
{
|
{
|
||||||
|
|
||||||
private static final String TAG = DownloadManagerService.class.getSimpleName();
|
private static final String TAG = DownloadManagerService.class.getSimpleName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message code of update messages stored as {@link Message#what}.
|
||||||
|
*/
|
||||||
|
private static final int UPDATE_MESSAGE = 0;
|
||||||
|
private static final int NOTIFICATION_ID = 1000;
|
||||||
|
private static final String EXTRA_NAME = "DownloadManagerService.extra.name";
|
||||||
|
private static final String EXTRA_LOCATION = "DownloadManagerService.extra.location";
|
||||||
|
private static final String EXTRA_IS_AUDIO = "DownloadManagerService.extra.is_audio";
|
||||||
|
private static final String EXTRA_THREADS = "DownloadManagerService.extra.threads";
|
||||||
|
|
||||||
|
|
||||||
private DMBinder mBinder;
|
private DMBinder mBinder;
|
||||||
private DownloadManager mManager;
|
private DownloadManager mManager;
|
||||||
private Notification mNotification;
|
private Notification mNotification;
|
||||||
private Handler mHandler;
|
private Handler mHandler;
|
||||||
private long mLastTimeStamp = System.currentTimeMillis();
|
private long mLastTimeStamp = System.currentTimeMillis();
|
||||||
|
private DownloadDataSource mDataSource;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private MissionListener missionListener = new MissionListener();
|
||||||
|
|
||||||
|
|
||||||
|
private void notifyMediaScanner(DownloadMission mission) {
|
||||||
|
Uri uri = Uri.parse("file://" + mission.location + "/" + mission.name);
|
||||||
|
// notify media scanner on downloaded media file ...
|
||||||
|
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
|
@ -42,12 +77,17 @@ public class DownloadManagerService extends Service implements DownloadMission.M
|
||||||
}
|
}
|
||||||
|
|
||||||
mBinder = new DMBinder();
|
mBinder = new DMBinder();
|
||||||
|
if(mDataSource == null) {
|
||||||
|
mDataSource = new SQLiteDownloadDataSource(this);
|
||||||
|
}
|
||||||
if (mManager == null) {
|
if (mManager == null) {
|
||||||
String path = NewPipeSettings.getVideoDownloadPath(this);
|
ArrayList<String> paths = new ArrayList<>(2);
|
||||||
mManager = new DownloadManagerImpl(this, path);
|
paths.add(NewPipeSettings.getVideoDownloadPath(this));
|
||||||
|
paths.add(NewPipeSettings.getAudioDownloadPath(this));
|
||||||
|
mManager = new DownloadManagerImpl(paths, mDataSource);
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "mManager == null");
|
Log.d(TAG, "mManager == null");
|
||||||
Log.d(TAG, "Download directory: " + path);
|
Log.d(TAG, "Download directory: " + paths);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +123,8 @@ public class DownloadManagerService extends Service implements DownloadMission.M
|
||||||
mHandler = new Handler(thread.getLooper()) {
|
mHandler = new Handler(thread.getLooper()) {
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(Message msg) {
|
public void handleMessage(Message msg) {
|
||||||
if (msg.what == 0) {
|
switch (msg.what) {
|
||||||
|
case UPDATE_MESSAGE: {
|
||||||
int runningCount = 0;
|
int runningCount = 0;
|
||||||
|
|
||||||
for (int i = 0; i < mManager.getCount(); i++) {
|
for (int i = 0; i < mManager.getCount(); i++) {
|
||||||
|
@ -91,20 +132,41 @@ public class DownloadManagerService extends Service implements DownloadMission.M
|
||||||
runningCount++;
|
runningCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateState(runningCount);
|
updateState(runningCount);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void startMissionAsync(final String url, final String location, final String name,
|
||||||
|
final boolean isAudio, final int threads) {
|
||||||
|
mHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
int missionId = mManager.startMission(url, location, name, isAudio, threads);
|
||||||
|
mBinder.onMissionAdded(mManager.getMission(missionId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "Starting");
|
Log.d(TAG, "Starting");
|
||||||
}
|
}
|
||||||
|
Log.i(TAG, "Got intent: " + intent);
|
||||||
|
String action = intent.getAction();
|
||||||
|
if(action != null && action.equals(Intent.ACTION_RUN)) {
|
||||||
|
String name = intent.getStringExtra(EXTRA_NAME);
|
||||||
|
String location = intent.getStringExtra(EXTRA_LOCATION);
|
||||||
|
int threads = intent.getIntExtra(EXTRA_THREADS, 1);
|
||||||
|
boolean isAudio = intent.getBooleanExtra(EXTRA_IS_AUDIO, false);
|
||||||
|
String url = intent.getDataString();
|
||||||
|
startMissionAsync(url, location, name, isAudio, threads);
|
||||||
|
}
|
||||||
return START_NOT_STICKY;
|
return START_NOT_STICKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,17 +187,51 @@ public class DownloadManagerService extends Service implements DownloadMission.M
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IBinder onBind(Intent intent) {
|
public IBinder onBind(Intent intent) {
|
||||||
|
int permissionCheck;
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
|
||||||
|
permissionCheck = PermissionChecker.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
|
||||||
|
if(permissionCheck == PermissionChecker.PERMISSION_DENIED) {
|
||||||
|
Toast.makeText(this, "Permission denied (read)", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
permissionCheck = PermissionChecker.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||||
|
if(permissionCheck == PermissionChecker.PERMISSION_DENIED) {
|
||||||
|
Toast.makeText(this, "Permission denied (write)", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
|
||||||
return mBinder;
|
return mBinder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void postUpdateMessage() {
|
||||||
|
mHandler.sendEmptyMessage(UPDATE_MESSAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState(int runningCount) {
|
||||||
|
if (runningCount == 0) {
|
||||||
|
stopForeground(true);
|
||||||
|
} else {
|
||||||
|
startForeground(NOTIFICATION_ID, mNotification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void startMission(Context context, String url, String location, String name, boolean isAudio, int threads) {
|
||||||
|
Intent intent = new Intent(context, DownloadManagerService.class);
|
||||||
|
intent.setAction(Intent.ACTION_RUN);
|
||||||
|
intent.setData(Uri.parse(url));
|
||||||
|
intent.putExtra(EXTRA_NAME, name);
|
||||||
|
intent.putExtra(EXTRA_LOCATION, location);
|
||||||
|
intent.putExtra(EXTRA_IS_AUDIO, isAudio);
|
||||||
|
intent.putExtra(EXTRA_THREADS, threads);
|
||||||
|
context.startService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MissionListener implements DownloadMission.MissionListener {
|
||||||
@Override
|
@Override
|
||||||
public void onProgressUpdate(long done, long total) {
|
public void onProgressUpdate(DownloadMission downloadMission, long done, long total) {
|
||||||
|
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
long delta = now - mLastTimeStamp;
|
long delta = now - mLastTimeStamp;
|
||||||
|
|
||||||
if (delta > 2000) {
|
if (delta > 2000) {
|
||||||
postUpdateMessage();
|
postUpdateMessage();
|
||||||
mLastTimeStamp = now;
|
mLastTimeStamp = now;
|
||||||
|
@ -143,25 +239,15 @@ public class DownloadManagerService extends Service implements DownloadMission.M
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFinish() {
|
public void onFinish(DownloadMission downloadMission) {
|
||||||
postUpdateMessage();
|
postUpdateMessage();
|
||||||
|
notifyMediaScanner(downloadMission);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(int errCode) {
|
public void onError(DownloadMission downloadMission, int errCode) {
|
||||||
postUpdateMessage();
|
postUpdateMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void postUpdateMessage() {
|
|
||||||
mHandler.sendEmptyMessage(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateState(int runningCount) {
|
|
||||||
if (runningCount == 0) {
|
|
||||||
stopForeground(true);
|
|
||||||
} else {
|
|
||||||
startForeground(1000, mNotification);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -172,15 +258,13 @@ public class DownloadManagerService extends Service implements DownloadMission.M
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onMissionAdded(DownloadMission mission) {
|
public void onMissionAdded(DownloadMission mission) {
|
||||||
mission.addListener(DownloadManagerService.this);
|
mission.addListener(missionListener);
|
||||||
postUpdateMessage();
|
postUpdateMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onMissionRemoved(DownloadMission mission) {
|
public void onMissionRemoved(DownloadMission mission) {
|
||||||
mission.removeListener(DownloadManagerService.this);
|
mission.removeListener(missionListener);
|
||||||
postUpdateMessage();
|
postUpdateMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,9 @@ import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.support.v4.content.FileProvider;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
@ -19,6 +22,7 @@ import android.support.v7.widget.RecyclerView;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
@ -28,9 +32,13 @@ import us.shandian.giga.service.DownloadManagerService;
|
||||||
import us.shandian.giga.ui.common.ProgressDrawable;
|
import us.shandian.giga.ui.common.ProgressDrawable;
|
||||||
import us.shandian.giga.util.Utility;
|
import us.shandian.giga.util.Utility;
|
||||||
|
|
||||||
|
import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
|
||||||
|
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
|
||||||
|
|
||||||
public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHolder>
|
public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHolder>
|
||||||
{
|
{
|
||||||
private static final Map<Integer, String> ALGORITHMS = new HashMap<>();
|
private static final Map<Integer, String> ALGORITHMS = new HashMap<>();
|
||||||
|
private static final String TAG = "MissionAdapter";
|
||||||
|
|
||||||
static {
|
static {
|
||||||
ALGORITHMS.put(R.id.md5, "MD5");
|
ALGORITHMS.put(R.id.md5, "MD5");
|
||||||
|
@ -143,9 +151,8 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
|
||||||
h.status.setText(R.string.msg_error);
|
h.status.setText(R.string.msg_error);
|
||||||
} else {
|
} else {
|
||||||
float progress = (float) h.mission.done / h.mission.length;
|
float progress = (float) h.mission.done / h.mission.length;
|
||||||
h.status.setText(String.format("%.2f%%", progress * 100));
|
h.status.setText(String.format(Locale.US, "%.2f%%", progress * 100));
|
||||||
h.progress.setProgress(progress);
|
h.progress.setProgress(progress);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,23 +219,23 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
|
||||||
h.lastDone = -1;
|
h.lastDone = -1;
|
||||||
return true;
|
return true;
|
||||||
case R.id.view:
|
case R.id.view:
|
||||||
Intent i = new Intent();
|
File f = new File(h.mission.location, h.mission.name);
|
||||||
i.setAction(Intent.ACTION_VIEW);
|
|
||||||
File f = new File(h.mission.location + "/" + h.mission.name);
|
|
||||||
String ext = Utility.getFileExt(h.mission.name);
|
String ext = Utility.getFileExt(h.mission.name);
|
||||||
|
|
||||||
if (ext == null) return false;
|
Log.d(TAG, "Viewing file: " + f.getAbsolutePath() + " ext: " + ext);
|
||||||
|
|
||||||
|
if (ext == null) {
|
||||||
|
Log.w(TAG, "Can't view file because it has no extension: " +
|
||||||
|
h.mission.name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext.substring(1));
|
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext.substring(1));
|
||||||
|
Log.v(TAG, "Mime: " + mime + " package: " + mContext.getApplicationContext().getPackageName() + ".provider");
|
||||||
if (f.exists()) {
|
if (f.exists()) {
|
||||||
i.setDataAndType(Uri.fromFile(f), mime);
|
viewFileWithFileProvider(f, mime);
|
||||||
|
} else {
|
||||||
try {
|
Log.w(TAG, "File doesn't exist");
|
||||||
mContext.startActivity(i);
|
|
||||||
} catch (Exception e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -250,6 +257,34 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
|
||||||
popup.show();
|
popup.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void viewFile(File file, String mimetype) {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setAction(Intent.ACTION_VIEW);
|
||||||
|
intent.setDataAndType(Uri.fromFile(file), mimetype);
|
||||||
|
intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
intent.addFlags(FLAG_GRANT_PREFIX_URI_PERMISSION);
|
||||||
|
}
|
||||||
|
//mContext.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
Log.v(TAG, "Starting intent: " + intent);
|
||||||
|
mContext.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void viewFileWithFileProvider(File file, String mimetype) {
|
||||||
|
String ourPackage = mContext.getApplicationContext().getPackageName();
|
||||||
|
Uri uri = FileProvider.getUriForFile(mContext, ourPackage + ".provider", file);
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setAction(Intent.ACTION_VIEW);
|
||||||
|
intent.setDataAndType(uri, mimetype);
|
||||||
|
intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
intent.addFlags(FLAG_GRANT_PREFIX_URI_PERMISSION);
|
||||||
|
}
|
||||||
|
//mContext.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
Log.v(TAG, "Starting intent: " + intent);
|
||||||
|
mContext.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
private class ChecksumTask extends AsyncTask<String, Void, String> {
|
private class ChecksumTask extends AsyncTask<String, Void, String> {
|
||||||
ProgressDialog prog;
|
ProgressDialog prog;
|
||||||
|
|
||||||
|
@ -316,12 +351,12 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onProgressUpdate(long done, long total) {
|
public void onProgressUpdate(DownloadMission downloadMission, long done, long total) {
|
||||||
mAdapter.updateProgress(mHolder);
|
mAdapter.updateProgress(mHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFinish() {
|
public void onFinish(DownloadMission downloadMission) {
|
||||||
//mAdapter.mManager.deleteMission(mHolder.position);
|
//mAdapter.mManager.deleteMission(mHolder.position);
|
||||||
// TODO Notification
|
// TODO Notification
|
||||||
//mAdapter.notifyDataSetChanged();
|
//mAdapter.notifyDataSetChanged();
|
||||||
|
@ -332,7 +367,7 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(int errCode) {
|
public void onError(DownloadMission downloadMission, int errCode) {
|
||||||
mAdapter.updateProgress(mHolder);
|
mAdapter.updateProgress(mHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ public abstract class MissionsFragment extends Fragment
|
||||||
private MissionAdapter mAdapter;
|
private MissionAdapter mAdapter;
|
||||||
private GridLayoutManager mGridManager;
|
private GridLayoutManager mGridManager;
|
||||||
private LinearLayoutManager mLinearManager;
|
private LinearLayoutManager mLinearManager;
|
||||||
private Activity mActivity;
|
private Context mActivity;
|
||||||
|
|
||||||
private ServiceConnection mConnection = new ServiceConnection() {
|
private ServiceConnection mConnection = new ServiceConnection() {
|
||||||
|
|
||||||
|
@ -85,14 +85,15 @@ public abstract class MissionsFragment extends Fragment
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity) {
|
public void onAttach(Context activity) {
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
mActivity = activity;
|
mActivity = activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
getActivity().unbindService(mConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
4
app/src/main/res/xml/provider_paths.xml
Normal file
4
app/src/main/res/xml/provider_paths.xml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<external-path name="external_files" path="."/>
|
||||||
|
</paths>
|
|
@ -0,0 +1,156 @@
|
||||||
|
package us.shandian.giga.get.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 dowloadDataSource;
|
||||||
|
private ArrayList<DownloadMission> missions;
|
||||||
|
|
||||||
|
@org.junit.Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
dowloadDataSource = mock(DownloadDataSource.class);
|
||||||
|
missions = new ArrayList<>();
|
||||||
|
for(int i = 0; i < 50; ++i){
|
||||||
|
missions.add(generateFinishedDownloadMission());
|
||||||
|
}
|
||||||
|
when(dowloadDataSource.loadMissions()).thenReturn(new ArrayList<>(missions));
|
||||||
|
downloadManager = new DownloadManagerImpl(new ArrayList<String>(), dowloadDataSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = NullPointerException.class)
|
||||||
|
public void testConstructorWithNullAsDownloadDataSource() {
|
||||||
|
new DownloadManagerImpl(new ArrayList<String>(), 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
dowloadDataSource = mock(DownloadDataSource.class);
|
||||||
|
when(dowloadDataSource.loadMissions()).thenReturn(new ArrayList<>(missions));
|
||||||
|
downloadManager = new DownloadManagerImpl(new ArrayList<String>(), dowloadDataSource);
|
||||||
|
verify(dowloadDataSource, 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() throws Exception {
|
||||||
|
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() throws Exception {
|
||||||
|
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() throws Exception {
|
||||||
|
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() throws Exception {
|
||||||
|
downloadManager.getMission(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getMission() throws Exception {
|
||||||
|
assertSame(missions.get(0), downloadManager.getMission(0));
|
||||||
|
assertSame(missions.get(1), downloadManager.getMission(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue