From 058a039a827011e9f9db3685c7989ab291e7fa09 Mon Sep 17 00:00:00 2001 From: k3b Date: Thu, 7 Jan 2016 14:22:55 +0100 Subject: [PATCH] #143 #44 #42 #22: Fixed some download problems with invalid directories or filenames. Added user Feedback. Different settings for audio and video download dir. --- .../org/schabi/newpipe/DownloadDialog.java | 66 +++++-- .../java/org/schabi/newpipe/Downloader.java | 171 +++++++++++------- .../org/schabi/newpipe/NewPipeSettings.java | 72 ++++++++ .../org/schabi/newpipe/SettingsActivity.java | 19 +- app/src/main/res/values/settings_keys.xml | 1 + app/src/main/res/values/strings.xml | 13 +- app/src/main/res/xml/settings.xml | 6 + build.gradle | 2 +- 8 files changed, 252 insertions(+), 98 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/NewPipeSettings.java diff --git a/app/src/main/java/org/schabi/newpipe/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/DownloadDialog.java index aa56c9349..d30166dbb 100644 --- a/app/src/main/java/org/schabi/newpipe/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/DownloadDialog.java @@ -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 forbiddenCharsPatterns = new ArrayList (); + 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; + } } diff --git a/app/src/main/java/org/schabi/newpipe/Downloader.java b/app/src/main/java/org/schabi/newpipe/Downloader.java index cbc4c26c2..e0a9bf5d4 100644 --- a/app/src/main/java/org/schabi/newpipe/Downloader.java +++ b/app/src/main/java/org/schabi/newpipe/Downloader.java @@ -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 . */ -public class Downloader { +public class Downloader extends AsyncTask { 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() { + 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); } } diff --git a/app/src/main/java/org/schabi/newpipe/NewPipeSettings.java b/app/src/main/java/org/schabi/newpipe/NewPipeSettings.java new file mode 100644 index 000000000..15fa4e899 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/NewPipeSettings.java @@ -0,0 +1,72 @@ +/** + * Created by k3b on 07.01.2016. + * + * Copyright (C) Christian Schabesberger 2015 + * 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 . + */ + +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); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/SettingsActivity.java b/app/src/main/java/org/schabi/newpipe/SettingsActivity.java index 52f4a17a3..5f5d17339 100644 --- a/app/src/main/java/org/schabi/newpipe/SettingsActivity.java +++ b/app/src/main/java/org/schabi/newpipe/SettingsActivity.java @@ -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); } } diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 6f83e31a5..9004ed703 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -6,6 +6,7 @@ settings_category_other download_path + download_path_audio use_external_video_player use_external_audio_player autoplay_through_intent diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d7a551fcd..99d766b00 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -23,9 +23,15 @@ Settings Use external video player Use external audio player - Download path + + Download path video Path to store downloaded videos in - Enter download path + Enter download path for videos + + Download path audio + Path to store downloaded audio in + Enter download path for audios + Autoplay through Intent Automatically play a video when it\'s called from another app Default Resolution @@ -69,4 +75,7 @@ Dislikes Use Tor Force download traffic through Tor for increased privacy (streaming videos not yet supported) + + Cannot create download directory \'%1$s\' + Created download directory \'%1$s\' diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml index 22f9644fe..7dff0c918 100644 --- a/app/src/main/res/xml/settings.xml +++ b/app/src/main/res/xml/settings.xml @@ -81,6 +81,12 @@ android:summary="@string/download_path_summary" android:dialogTitle="@string/download_path_dialog_title" /> + +