#143 #44 #42 #22: Fixed some download problems with invalid directories or filenames. Added user Feedback. Different settings for audio and video download dir.

This commit is contained in:
k3b 2016-01-07 14:22:55 +01:00
parent dc56eab9b6
commit 058a039a82
8 changed files with 252 additions and 98 deletions

View File

@ -2,6 +2,7 @@ package org.schabi.newpipe;
import android.app.Dialog;
import android.app.DownloadManager;
import android.app.Notification;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
@ -13,8 +14,11 @@ import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import android.widget.Toast;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Christian Schabesberger on 21.09.15.
@ -61,53 +65,83 @@ public class DownloadDialog extends DialogFragment {
String suffix = "";
String title = arguments.getString(TITLE);
String url = "";
String downloadFolder = Environment.DIRECTORY_DOWNLOADS;
File downloadDir = NewPipeSettings.getDownloadFolder();
switch(which) {
case 0: // Video
suffix = arguments.getString(FILE_SUFFIX_VIDEO);
url = arguments.getString(VIDEO_URL);
downloadFolder = Environment.DIRECTORY_MOVIES;
downloadDir = NewPipeSettings.getVideoDownloadFolder(context);
break;
case 1:
suffix = arguments.getString(FILE_SUFFIX_AUDIO);
url = arguments.getString(AUDIO_URL);
downloadFolder = Environment.DIRECTORY_MUSIC;
downloadDir = NewPipeSettings.getAudioDownloadFolder(context);
break;
default:
Log.d(TAG, "lolz");
}
//to avoid hard-coded string like "/storage/emulated/0/Movies"
String downloadPath = prefs.getString(getString(R.string.download_path_key),
Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + downloadFolder);
final File dir = new File(downloadPath);
if(!dir.exists()) {
if(!downloadDir.exists()) {
//attempt to create directory
boolean mkdir = dir.mkdir();
if(!mkdir && !dir.isDirectory()) {
Log.e(TAG, "Cant' create directory named " + dir.toString());
//TODO notify user "download directory should be changed" ?
boolean mkdir = downloadDir.mkdirs();
if(!mkdir && !downloadDir.isDirectory()) {
String message = context.getString(R.string.err_dir_create,downloadDir.toString());
Log.e(TAG, message);
Toast.makeText(context,message , Toast.LENGTH_LONG).show();
return;
}
String message = context.getString(R.string.info_dir_created,downloadDir.toString());
Log.e(TAG, message);
Toast.makeText(context,message , Toast.LENGTH_LONG).show();
}
String saveFilePath = dir + "/" + title + suffix;
File saveFilePath = new File(downloadDir,createFileName(title) + suffix);
long id = 0;
if (App.isUsingTor()) {
// if using Tor, do not use DownloadManager because the proxy cannot be set
Downloader.downloadFile(getContext(), url, saveFilePath);
Downloader.downloadFile(getContext(), url, saveFilePath, title);
} else {
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(
Uri.parse(url));
request.setDestinationUri(Uri.fromFile(new File(saveFilePath)));
request.setDestinationUri(Uri.fromFile(saveFilePath));
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setTitle(title);
request.setDescription("'" + url +
"' => '" + saveFilePath + "'");
request.allowScanningByMediaScanner();
try {
dm.enqueue(request);
id = dm.enqueue(request);
} catch (Exception e) {
e.printStackTrace();
}
}
Log.i(TAG,"Started downloading '" + url +
"' => '" + saveFilePath + "' #" + id);
}
});
return builder.create();
}
/**
* #143 #44 #42 #22: make shure that the filename does not contain illegal chars.
* This should fix some of the "cannot download" problems.
* */
private String createFileName(String fName) {
// from http://eng-przemelek.blogspot.de/2009/07/how-to-create-valid-file-name.html
List<String> forbiddenCharsPatterns = new ArrayList<String> ();
forbiddenCharsPatterns.add("[:]+"); // Mac OS, but it looks that also Windows XP
forbiddenCharsPatterns.add("[\\*\"/\\\\\\[\\]\\:\\;\\|\\=\\,]+"); // Windows
forbiddenCharsPatterns.add("[^\\w\\d\\.]+"); // last chance... only latin letters and digits
String nameToTest = fName;
for (String pattern : forbiddenCharsPatterns) {
nameToTest = nameToTest.replaceAll(pattern, "_");
}
return nameToTest;
}
}

View File

@ -13,6 +13,7 @@ import android.util.Log;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@ -45,10 +46,32 @@ import info.guardianproject.netcipher.NetCipher;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class Downloader {
public class Downloader extends AsyncTask<Void, Integer, Void> {
public static final String TAG = "Downloader";
private static final String USER_AGENT = "Mozilla/5.0";
private NotificationManager nm;
private NotificationCompat.Builder builder;
private int notifyId = 0x1234;
private int fileSize = 0xffffffff;
private final Context context;
private final String fileURL;
private final File saveFilePath;
private final String title;
private final String debugContext;
public Downloader(Context context, String fileURL, File saveFilePath, String title) {
this.context = context;
this.fileURL = fileURL;
this.saveFilePath = saveFilePath;
this.title = title;
this.debugContext = "'" + fileURL +
"' => '" + saveFilePath + "'";
}
/**Download the text file at the supplied URL as in download(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
@ -118,86 +141,96 @@ public class Downloader {
*
* @param fileURL HTTP URL of the file to be downloaded
* @param saveFilePath path of the directory to save the file
* @param title
* @throws IOException
*/
public static void downloadFile(final Context context, final String fileURL, final String saveFilePath) {
new AsyncTask<Void, Integer, Void>() {
public static void downloadFile(final Context context, final String fileURL, final File saveFilePath, String title) {
new Downloader(context, fileURL, saveFilePath, title).execute();
}
private NotificationManager nm;
private NotificationCompat.Builder builder;
private int notifyId = 0x1234;
private int fileSize = 0xffffffff;
/** AsyncTask impl: executed in gui thread */
@Override
protected void onPreExecute() {
super.onPreExecute();
nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Drawable icon = context.getResources().getDrawable(R.mipmap.ic_launcher);
builder = new NotificationCompat.Builder(context)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setLargeIcon(((BitmapDrawable) icon).getBitmap())
.setContentTitle(saveFilePath.getName())
.setContentText(saveFilePath.getAbsolutePath())
.setProgress(fileSize, 0, false);
nm.notify(notifyId, builder.build());
}
@Override
protected void onPreExecute() {
super.onPreExecute();
nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Drawable icon = context.getResources().getDrawable(R.mipmap.ic_launcher);
builder = new NotificationCompat.Builder(context)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setLargeIcon(((BitmapDrawable) icon).getBitmap())
.setContentTitle(saveFilePath.substring(saveFilePath.lastIndexOf('/') + 1))
.setContentText(saveFilePath)
.setProgress(fileSize, 0, false);
nm.notify(notifyId, builder.build());
}
/** AsyncTask impl: executed in background thread does the download */
@Override
protected Void doInBackground(Void... voids) {
HttpsURLConnection con = null;
InputStream inputStream = null;
FileOutputStream outputStream = null;
try {
con = NetCipher.getHttpsURLConnection(fileURL);
int responseCode = con.getResponseCode();
@Override
protected Void doInBackground(Void... voids) {
HttpsURLConnection con = null;
try {
con = NetCipher.getHttpsURLConnection(fileURL);
int responseCode = con.getResponseCode();
// always check HTTP response code first
if (responseCode == HttpURLConnection.HTTP_OK) {
fileSize = con.getContentLength();
inputStream = new BufferedInputStream(con.getInputStream());
outputStream = new FileOutputStream(saveFilePath);
// always check HTTP response code first
if (responseCode == HttpURLConnection.HTTP_OK) {
fileSize = con.getContentLength();
InputStream inputStream = new BufferedInputStream(con.getInputStream());
FileOutputStream outputStream = new FileOutputStream(saveFilePath);
int bufferSize = 8192;
int downloaded = 0;
int bufferSize = 8192;
int downloaded = 0;
int bytesRead = -1;
byte[] buffer = new byte[bufferSize];
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
downloaded += bytesRead;
if (downloaded % 50000 < bufferSize) {
publishProgress(downloaded);
}
}
outputStream.close();
inputStream.close();
publishProgress(bufferSize);
} else {
Log.i(TAG, "No file to download. Server replied HTTP code: " + responseCode);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (con != null) {
con.disconnect();
con = null;
int bytesRead = -1;
byte[] buffer = new byte[bufferSize];
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
downloaded += bytesRead;
if (downloaded % 50000 < bufferSize) {
publishProgress(downloaded);
}
}
return null;
}
@Override
protected void onProgressUpdate(Integer... progress) {
builder.setProgress(fileSize, progress[0], false);
nm.notify(notifyId, builder.build());
}
publishProgress(bufferSize);
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
nm.cancel(notifyId);
} else {
Log.i(TAG, "No file to download. Server replied HTTP code: " + responseCode);
}
}.execute();
} catch (IOException e) {
Log.e(TAG, "No file to download. Server replied HTTP code: ", e);
e.printStackTrace();
} finally {
try {
if (outputStream != null) {
outputStream.close();
outputStream = null;
}
if (inputStream != null) {
inputStream.close();
inputStream = null;
}
} catch (IOException e) {
e.printStackTrace();
}
if (con != null) {
con.disconnect();
con = null;
}
}
return null;
}
@Override
protected void onProgressUpdate(Integer... progress) {
builder.setProgress(fileSize, progress[0], false);
nm.notify(notifyId, builder.build());
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
nm.cancel(notifyId);
}
}

View File

@ -0,0 +1,72 @@
/**
* Created by k3b on 07.01.2016.
*
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* NewPipeSettings.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/>.
*/
package org.schabi.newpipe;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.util.Log;
import java.io.File;
/**
* Helper for global settings
*/
public class NewPipeSettings {
public static void initSettings(Context context) {
PreferenceManager.setDefaultValues(context, R.xml.settings, false);
getVideoDownloadFolder(context);
getAudioDownloadFolder(context);
}
public static File getDownloadFolder() {
return getFolder(Environment.DIRECTORY_DOWNLOADS);
}
public static File getVideoDownloadFolder(Context context) {
return getFolder(context, R.string.download_path_key, Environment.DIRECTORY_MOVIES);
}
public static File getAudioDownloadFolder(Context context) {
return getFolder(context, R.string.download_path_audio_key, Environment.DIRECTORY_MUSIC);
}
private static File getFolder(Context context, int keyID, String defaultDirectoryName) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
final String key = context.getString(keyID);
String downloadPath = prefs.getString(key, null);
if ((downloadPath != null) && (!downloadPath.isEmpty())) return new File(downloadPath.trim());
final File folder = getFolder(defaultDirectoryName);
SharedPreferences.Editor spEditor = prefs.edit();
spEditor.putString(key
, new File(folder,"NewPipe").getAbsolutePath());
spEditor.apply();
return folder;
}
@NonNull
private static File getFolder(String defaultDirectoryName) {
return new File(Environment.getExternalStorageDirectory(),defaultDirectoryName);
}
}

View File

@ -71,12 +71,14 @@ public class SettingsActivity extends PreferenceActivity {
String DEFAULT_AUDIO_FORMAT_PREFERENCE;
String SEARCH_LANGUAGE_PREFERENCE;
String DOWNLOAD_PATH_PREFERENCE;
String DOWNLOAD_PATH_AUDIO_PREFERENCE;
String USE_TOR_KEY;
private ListPreference defaultResolutionPreference;
private ListPreference defaultAudioFormatPreference;
private ListPreference searchLanguagePreference;
private EditTextPreference downloadPathPreference;
private EditTextPreference downloadPathAudioPreference;
private CheckBoxPreference useTorCheckBox;
private SharedPreferences defaultPreferences;
@ -95,6 +97,7 @@ public class SettingsActivity extends PreferenceActivity {
DEFAULT_AUDIO_FORMAT_PREFERENCE =getString(R.string.default_audio_format_key);
SEARCH_LANGUAGE_PREFERENCE =getString(R.string.search_language_key);
DOWNLOAD_PATH_PREFERENCE = getString(R.string.download_path_key);
DOWNLOAD_PATH_AUDIO_PREFERENCE = getString(R.string.download_path_audio_key);
USE_TOR_KEY = getString(R.string.use_tor_key);
// get pref objects
@ -106,6 +109,8 @@ public class SettingsActivity extends PreferenceActivity {
(ListPreference) findPreference(SEARCH_LANGUAGE_PREFERENCE);
downloadPathPreference =
(EditTextPreference) findPreference(DOWNLOAD_PATH_PREFERENCE);
downloadPathAudioPreference =
(EditTextPreference) findPreference(DOWNLOAD_PATH_AUDIO_PREFERENCE);
useTorCheckBox = (CheckBoxPreference) findPreference(USE_TOR_KEY);
prefListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@ -147,6 +152,9 @@ public class SettingsActivity extends PreferenceActivity {
downloadPathPreference.setSummary(
defaultPreferences.getString(DOWNLOAD_PATH_PREFERENCE,
getString(R.string.download_path_summary)));
downloadPathAudioPreference.setSummary(
defaultPreferences.getString(DOWNLOAD_PATH_AUDIO_PREFERENCE,
getString(R.string.download_path_audio_summary)));
}
}
@ -246,15 +254,6 @@ public class SettingsActivity extends PreferenceActivity {
}
public static void initSettings(Context context) {
PreferenceManager.setDefaultValues(context, R.xml.settings, false);
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
if(sp.getString(context.getString(R.string.download_path_key), "").isEmpty()){
SharedPreferences.Editor spEditor = sp.edit();
String newPipeDownloadStorage =
Environment.getExternalStorageDirectory().getAbsolutePath() + "/NewPipe";
spEditor.putString(context.getString(R.string.download_path_key)
, newPipeDownloadStorage);
spEditor.apply();
}
NewPipeSettings.initSettings(context);
}
}

View File

@ -6,6 +6,7 @@
<string name="settings_category_other">settings_category_other</string>
<!-- Key values -->
<string name="download_path_key">download_path</string>
<string name="download_path_audio_key">download_path_audio</string>
<string name="use_external_video_player_key">use_external_video_player</string>
<string name="use_external_audio_player_key">use_external_audio_player</string>
<string name="autoplay_through_intent_key">autoplay_through_intent</string>

View File

@ -23,9 +23,15 @@
<string name="settings_activity_title">Settings</string>
<string name="use_external_video_player_title">Use external video player</string>
<string name="use_external_audio_player_title">Use external audio player</string>
<string name="download_path_title">Download path</string>
<string name="download_path_title">Download path video</string>
<string name="download_path_summary">Path to store downloaded videos in</string>
<string name="download_path_dialog_title">Enter download path</string>
<string name="download_path_dialog_title">Enter download path for videos</string>
<string name="download_path_audio_title">Download path audio</string>
<string name="download_path_audio_summary">Path to store downloaded audio in</string>
<string name="download_path_audio_dialog_title">Enter download path for audios</string>
<string name="autoplay_through_intent_title">Autoplay through Intent</string>
<string name="autoplay_through_intent_summary">Automatically play a video when it\'s called from another app</string>
<string name="default_resolution_title">Default Resolution</string>
@ -69,4 +75,7 @@
<string name="detail_dislikes_img_view_description">Dislikes</string>
<string name="use_tor_title">Use Tor</string>
<string name="use_tor_summary">Force download traffic through Tor for increased privacy (streaming videos not yet supported)</string>
<string name="err_dir_create">Cannot create download directory \'%1$s\'</string>
<string name="info_dir_created">Created download directory \'%1$s\'</string>
</resources>

View File

@ -81,6 +81,12 @@
android:summary="@string/download_path_summary"
android:dialogTitle="@string/download_path_dialog_title" />
<EditTextPreference
android:key="@string/download_path_audio_key"
android:title="@string/download_path_audio_title"
android:summary="@string/download_path_audio_summary"
android:dialogTitle="@string/download_path_audio_dialog_title" />
<CheckBoxPreference
android:key="@string/autoplay_through_intent_key"
android:title="@string/autoplay_through_intent_title"

View File

@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.5.0'
classpath 'com.android.tools.build:gradle:1.3.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files