mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-31 07:13:00 +00:00 
			
		
		
		
	Reimplement storing backup import/export path
#6319 and #6402 were reverted before adding SAF changes, and have been readded at the end of SAF changes
This commit is contained in:
		| @@ -702,9 +702,9 @@ public class DownloadDialog extends DialogFragment | ||||
|         } | ||||
|  | ||||
|         if (askForSavePath) { | ||||
|             final String startPath; | ||||
|             final Uri initialPath; | ||||
|             if (NewPipeSettings.useStorageAccessFramework(context)) { | ||||
|                 startPath = null; | ||||
|                 initialPath = null; | ||||
|             } else { | ||||
|                 final File initialSavePath; | ||||
|                 if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button) { | ||||
| @@ -712,11 +712,11 @@ public class DownloadDialog extends DialogFragment | ||||
|                 } else { | ||||
|                     initialSavePath = NewPipeSettings.getDir(Environment.DIRECTORY_MOVIES); | ||||
|                 } | ||||
|                 startPath = initialSavePath.getAbsolutePath(); | ||||
|                 initialPath = Uri.parse(initialSavePath.getAbsolutePath()); | ||||
|             } | ||||
|  | ||||
|             startActivityForResult(StoredFileHelper.getNewPicker(context, startPath, | ||||
|                     filenameTmp, mimeTmp), REQUEST_DOWNLOAD_SAVE_AS); | ||||
|             startActivityForResult(StoredFileHelper.getNewPicker(context, | ||||
|                     filenameTmp, mimeTmp, initialPath), REQUEST_DOWNLOAD_SAVE_AS); | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|   | ||||
| @@ -191,7 +191,10 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() { | ||||
|         val date = SimpleDateFormat("yyyyMMddHHmm", Locale.ENGLISH).format(Date()) | ||||
|         val exportName = "newpipe_subscriptions_$date.json" | ||||
|  | ||||
|         startActivityForResult(StoredFileHelper.getNewPicker(activity, null, exportName, "application/json"), REQUEST_EXPORT_CODE) | ||||
|         startActivityForResult( | ||||
|             StoredFileHelper.getNewPicker(activity, exportName, "application/json", null), | ||||
|             REQUEST_EXPORT_CODE | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     private fun openReorderDialog() { | ||||
| @@ -283,7 +286,8 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() { | ||||
|  | ||||
|     private fun showLongTapDialog(selectedItem: ChannelInfoItem) { | ||||
|         val commands = arrayOf( | ||||
|             getString(R.string.share), getString(R.string.open_in_browser), | ||||
|             getString(R.string.share), | ||||
|             getString(R.string.open_in_browser), | ||||
|             getString(R.string.unsubscribe) | ||||
|         ) | ||||
|  | ||||
|   | ||||
| @@ -6,12 +6,16 @@ import android.view.View; | ||||
|  | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.annotation.StringRes; | ||||
| import androidx.preference.Preference; | ||||
| import androidx.preference.PreferenceFragmentCompat; | ||||
| import androidx.preference.PreferenceManager; | ||||
|  | ||||
| import org.schabi.newpipe.MainActivity; | ||||
| import org.schabi.newpipe.util.ThemeHelper; | ||||
|  | ||||
| import java.util.Objects; | ||||
|  | ||||
| public abstract class BasePreferenceFragment extends PreferenceFragmentCompat { | ||||
|     protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()); | ||||
|     protected final boolean DEBUG = MainActivity.DEBUG; | ||||
| @@ -37,4 +41,11 @@ public abstract class BasePreferenceFragment extends PreferenceFragmentCompat { | ||||
|         super.onResume(); | ||||
|         ThemeHelper.setTitleToAppCompatActivity(getActivity(), getPreferenceScreen().getTitle()); | ||||
|     } | ||||
|  | ||||
|     @NonNull | ||||
|     public final Preference requirePreference(@StringRes final int resId) { | ||||
|         final Preference preference = findPreference(getString(resId)); | ||||
|         Objects.requireNonNull(preference); | ||||
|         return preference; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import android.content.Context; | ||||
| import android.content.DialogInterface; | ||||
| import android.content.Intent; | ||||
| import android.content.SharedPreferences; | ||||
| import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import android.util.Log; | ||||
| import android.widget.Toast; | ||||
| @@ -32,18 +33,25 @@ import java.io.File; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.Date; | ||||
| import java.util.Locale; | ||||
| import java.util.Objects; | ||||
|  | ||||
| import static org.schabi.newpipe.extractor.utils.Utils.isBlank; | ||||
| import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; | ||||
|  | ||||
| public class ContentSettingsFragment extends BasePreferenceFragment { | ||||
|     private static final int REQUEST_IMPORT_PATH = 8945; | ||||
|     private static final int REQUEST_EXPORT_PATH = 30945; | ||||
|     private static final String ZIP_MIME_TYPE = "application/zip"; | ||||
|     private static final SimpleDateFormat EXPORT_DATE_FORMAT | ||||
|             = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US); | ||||
|  | ||||
|     private ContentSettingsManager manager; | ||||
|  | ||||
|     private String importExportDataPathKey; | ||||
|     private String thumbnailLoadToggleKey; | ||||
|     private String youtubeRestrictedModeEnabledKey; | ||||
|  | ||||
|     @Nullable private Uri lastImportExportDataUri = null; | ||||
|     private Localization initialSelectedLocalization; | ||||
|     private ContentCountry initialSelectedContentCountry; | ||||
|     private String initialLanguage; | ||||
| @@ -51,29 +59,35 @@ public class ContentSettingsFragment extends BasePreferenceFragment { | ||||
|     @Override | ||||
|     public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) { | ||||
|         final File homeDir = ContextCompat.getDataDir(requireContext()); | ||||
|         Objects.requireNonNull(homeDir); | ||||
|         manager = new ContentSettingsManager(new NewPipeFileLocator(homeDir)); | ||||
|         manager.deleteSettingsFile(); | ||||
|  | ||||
|         importExportDataPathKey = getString(R.string.import_export_data_path); | ||||
|         thumbnailLoadToggleKey = getString(R.string.download_thumbnail_key); | ||||
|         youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled); | ||||
|  | ||||
|         addPreferencesFromResource(R.xml.content_settings); | ||||
|  | ||||
|         final Preference importDataPreference = findPreference(getString(R.string.import_data)); | ||||
|         final Preference importDataPreference = requirePreference(R.string.import_data); | ||||
|         importDataPreference.setOnPreferenceClickListener((Preference p) -> { | ||||
|             startActivityForResult(StoredFileHelper.getPicker(getContext()), REQUEST_IMPORT_PATH); | ||||
|             startActivityForResult( | ||||
|                     StoredFileHelper.getPicker(requireContext(), getImportExportDataUri()), | ||||
|                     REQUEST_IMPORT_PATH); | ||||
|             return true; | ||||
|         }); | ||||
|  | ||||
|         final Preference exportDataPreference = findPreference(getString(R.string.export_data)); | ||||
|         final Preference exportDataPreference = requirePreference(R.string.export_data); | ||||
|         exportDataPreference.setOnPreferenceClickListener((final Preference p) -> { | ||||
|             final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US); | ||||
|             startActivityForResult(StoredFileHelper.getNewPicker(getContext(), null, | ||||
|                     "NewPipeData-" + sdf.format(new Date()) + ".zip", "application/zip"), | ||||
|  | ||||
|             startActivityForResult( | ||||
|                     StoredFileHelper.getNewPicker(requireContext(), | ||||
|                             "NewPipeData-" + EXPORT_DATE_FORMAT.format(new Date()) + ".zip", | ||||
|                             ZIP_MIME_TYPE, getImportExportDataUri()), | ||||
|                     REQUEST_EXPORT_PATH); | ||||
|             return true; | ||||
|         }); | ||||
|  | ||||
|         thumbnailLoadToggleKey = getString(R.string.download_thumbnail_key); | ||||
|         youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled); | ||||
|  | ||||
|         initialSelectedLocalization = org.schabi.newpipe.util.Localization | ||||
|                 .getPreferredLocalization(requireContext()); | ||||
|         initialSelectedContentCountry = org.schabi.newpipe.util.Localization | ||||
| @@ -81,7 +95,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment { | ||||
|         initialLanguage = PreferenceManager | ||||
|                 .getDefaultSharedPreferences(requireContext()).getString("app_language_key", "en"); | ||||
|  | ||||
|         final Preference clearCookiePref = findPreference(getString(R.string.clear_cookie_key)); | ||||
|         final Preference clearCookiePref = requirePreference(R.string.clear_cookie_key); | ||||
|         clearCookiePref.setOnPreferenceClickListener(preference -> { | ||||
|             defaultPreferences.edit() | ||||
|                     .putString(getString(R.string.recaptcha_cookies_key), "").apply(); | ||||
| @@ -157,8 +171,11 @@ public class ContentSettingsFragment extends BasePreferenceFragment { | ||||
|  | ||||
|         if ((requestCode == REQUEST_IMPORT_PATH || requestCode == REQUEST_EXPORT_PATH) | ||||
|                 && resultCode == Activity.RESULT_OK && data != null && data.getData() != null) { | ||||
|             final StoredFileHelper file = new StoredFileHelper(getContext(), data.getData(), | ||||
|                     "application/zip"); | ||||
|  | ||||
|             lastImportExportDataUri = data.getData(); // will be saved only on success | ||||
|  | ||||
|             final StoredFileHelper file | ||||
|                     = new StoredFileHelper(getContext(), data.getData(), ZIP_MIME_TYPE); | ||||
|             if (requestCode == REQUEST_EXPORT_PATH) { | ||||
|                 exportDatabase(file); | ||||
|             } else { | ||||
| @@ -182,6 +199,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment { | ||||
|                 .getDefaultSharedPreferences(requireContext()); | ||||
|             manager.exportDatabase(preferences, file); | ||||
|  | ||||
|             saveLastImportExportDataUri(false); // save export path only on success | ||||
|             Toast.makeText(getContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT).show(); | ||||
|         } catch (final Exception e) { | ||||
|             ErrorActivity.reportUiErrorInSnackbar(this, "Exporting database", e); | ||||
| @@ -206,30 +224,55 @@ public class ContentSettingsFragment extends BasePreferenceFragment { | ||||
|                     .show(); | ||||
|             } | ||||
|  | ||||
|             //If settings file exist, ask if it should be imported. | ||||
|             // if settings file exist, ask if it should be imported. | ||||
|             if (manager.extractSettings(file)) { | ||||
|                 final AlertDialog.Builder alert = new AlertDialog.Builder(requireContext()); | ||||
|                 alert.setTitle(R.string.import_settings); | ||||
|  | ||||
|                 alert.setNegativeButton(android.R.string.no, (dialog, which) -> { | ||||
|                     dialog.dismiss(); | ||||
|                     // restart app to properly load db | ||||
|                     System.exit(0); | ||||
|                     finishImport(); | ||||
|                 }); | ||||
|                 alert.setPositiveButton(getString(R.string.finish), (dialog, which) -> { | ||||
|                     dialog.dismiss(); | ||||
|                     manager.loadSharedPreferences(PreferenceManager | ||||
|                         .getDefaultSharedPreferences(requireContext())); | ||||
|                     // restart app to properly load db | ||||
|                     System.exit(0); | ||||
|                     finishImport(); | ||||
|                 }); | ||||
|                 alert.show(); | ||||
|             } else { | ||||
|                 // restart app to properly load db | ||||
|                 System.exit(0); | ||||
|                 finishImport(); | ||||
|             } | ||||
|         } catch (final Exception e) { | ||||
|             ErrorActivity.reportUiErrorInSnackbar(this, "Importing database", e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Save import path and restart system. | ||||
|      */ | ||||
|     private void finishImport() { | ||||
|         // save import path only on success; save immediately because app is about to exit | ||||
|         saveLastImportExportDataUri(true); | ||||
|         // restart app to properly load db | ||||
|         System.exit(0); | ||||
|     } | ||||
|  | ||||
|     private Uri getImportExportDataUri() { | ||||
|         final String path = defaultPreferences.getString(importExportDataPathKey, null); | ||||
|         return isBlank(path) ? null : Uri.parse(path); | ||||
|     } | ||||
|  | ||||
|     private void saveLastImportExportDataUri(final boolean immediately) { | ||||
|         if (lastImportExportDataUri != null) { | ||||
|             final SharedPreferences.Editor editor = defaultPreferences.edit() | ||||
|                     .putString(importExportDataPathKey, lastImportExportDataUri.toString()); | ||||
|             if (immediately) { | ||||
|                 // noinspection ApplySharedPref | ||||
|                 editor.commit(); // app about to be restarted, commit immediately | ||||
|             } else { | ||||
|                 editor.apply(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -448,7 +448,7 @@ public class StoredFileHelper implements Serializable { | ||||
|         return !str1.equals(str2); | ||||
|     } | ||||
|  | ||||
|     public static Intent getPicker(final Context ctx) { | ||||
|     public static Intent getPicker(@NonNull final Context ctx) { | ||||
|         if (NewPipeSettings.useStorageAccessFramework(ctx)) { | ||||
|             return new Intent(Intent.ACTION_OPEN_DOCUMENT) | ||||
|                     .putExtra("android.content.extra.SHOW_ADVANCED", true) | ||||
| @@ -466,10 +466,14 @@ public class StoredFileHelper implements Serializable { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static Intent getPicker(@NonNull final Context ctx, @Nullable final Uri initialPath) { | ||||
|         return applyInitialPathToPickerIntent(ctx, getPicker(ctx), initialPath, null); | ||||
|     } | ||||
|  | ||||
|     public static Intent getNewPicker(@NonNull final Context ctx, | ||||
|                                       @Nullable final String startPath, | ||||
|                                       @Nullable final String filename, | ||||
|                                       @NonNull final String mimeType) { | ||||
|                                       @NonNull final String mimeType, | ||||
|                                       @Nullable final Uri initialPath) { | ||||
|         final Intent i; | ||||
|         if (NewPipeSettings.useStorageAccessFramework(ctx)) { | ||||
|             i = new Intent(Intent.ACTION_CREATE_DOCUMENT) | ||||
| @@ -478,10 +482,6 @@ public class StoredFileHelper implements Serializable { | ||||
|                     .addCategory(Intent.CATEGORY_OPENABLE) | ||||
|                     .addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | ||||
|                             | StoredDirectoryHelper.PERMISSION_FLAGS); | ||||
|  | ||||
|             if (startPath != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||||
|                 i.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Uri.parse(startPath)); | ||||
|             } | ||||
|             if (filename != null) { | ||||
|                 i.putExtra(Intent.EXTRA_TITLE, filename); | ||||
|             } | ||||
| @@ -492,21 +492,63 @@ public class StoredFileHelper implements Serializable { | ||||
|                     .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_EXISTING_FILE, true) | ||||
|                     .putExtra(FilePickerActivityHelper.EXTRA_MODE, | ||||
|                             FilePickerActivityHelper.MODE_NEW_FILE); | ||||
|  | ||||
|             if (startPath != null || filename != null) { | ||||
|                 File fullStartPath; | ||||
|                 if (startPath == null) { | ||||
|                     fullStartPath = Environment.getExternalStorageDirectory(); | ||||
|                 } else { | ||||
|                     fullStartPath = new File(startPath); | ||||
|                 } | ||||
|                 if (filename != null) { | ||||
|                     fullStartPath = new File(fullStartPath, filename); | ||||
|                 } | ||||
|                 i.putExtra(FilePickerActivityHelper.EXTRA_START_PATH, | ||||
|                         fullStartPath.getAbsolutePath()); | ||||
|             } | ||||
|         } | ||||
|         return i; | ||||
|         return applyInitialPathToPickerIntent(ctx, i, initialPath, filename); | ||||
|     } | ||||
|  | ||||
|     private static Intent applyInitialPathToPickerIntent(@NonNull final Context ctx, | ||||
|                                                          @NonNull final Intent intent, | ||||
|                                                          @Nullable final Uri initialPath, | ||||
|                                                          @Nullable final String filename) { | ||||
|  | ||||
|         if (NewPipeSettings.useStorageAccessFramework(ctx)) { | ||||
|             if (initialPath == null) { | ||||
|                 return intent; // nothing to do, no initial path provided | ||||
|             } | ||||
|  | ||||
|             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||||
|                 return intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initialPath); | ||||
|             } else { | ||||
|                 return intent; // can't set initial path on API < 26 | ||||
|             } | ||||
|  | ||||
|         } else { | ||||
|             if (initialPath == null && filename == null) { | ||||
|                 return intent; // nothing to do, no initial path and no file name provided | ||||
|             } | ||||
|  | ||||
|             File file; | ||||
|             if (initialPath == null) { | ||||
|                 // The only way to set the previewed filename in non-SAF FilePicker is to set a | ||||
|                 // starting path ending with that filename. So when the initialPath is null but | ||||
|                 // filename isn't just default to the external storage directory. | ||||
|                 file = Environment.getExternalStorageDirectory(); | ||||
|             } else { | ||||
|                 try { | ||||
|                     file = Utils.getFileForUri(initialPath); | ||||
|                 } catch (final Throwable ignored) { | ||||
|                     // getFileForUri() can't decode paths to 'storage', fallback to this | ||||
|                     file = new File(initialPath.toString()); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // remove any filename at the end of the path (get the parent directory in that case) | ||||
|             if (!file.exists() || !file.isDirectory()) { | ||||
|                 file = file.getParentFile(); | ||||
|                 if (file == null || !file.exists()) { | ||||
|                     // default to the external storage directory in case of an invalid path | ||||
|                     file = Environment.getExternalStorageDirectory(); | ||||
|                 } | ||||
|                 // else: file is surely a directory | ||||
|             } | ||||
|  | ||||
|             if (filename != null) { | ||||
|                 // append a filename so that the non-SAF FilePicker shows it as the preview | ||||
|                 file = new File(file, filename); | ||||
|             } | ||||
|  | ||||
|             return intent | ||||
|                     .putExtra(FilePickerActivityHelper.EXTRA_START_PATH, file.getAbsolutePath()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -29,14 +29,13 @@ import com.nononsenseapps.filepicker.Utils; | ||||
|  | ||||
| import org.schabi.newpipe.R; | ||||
| import org.schabi.newpipe.settings.NewPipeSettings; | ||||
| import org.schabi.newpipe.streams.io.StoredFileHelper; | ||||
| import org.schabi.newpipe.util.FilePickerActivityHelper; | ||||
| import org.schabi.newpipe.util.ThemeHelper; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
|  | ||||
| import us.shandian.giga.get.DownloadMission; | ||||
| import org.schabi.newpipe.streams.io.StoredFileHelper; | ||||
| import us.shandian.giga.service.DownloadManager; | ||||
| import us.shandian.giga.service.DownloadManagerService; | ||||
| import us.shandian.giga.service.DownloadManagerService.DownloadManagerBinder; | ||||
| @@ -242,9 +241,9 @@ public class MissionsFragment extends Fragment { | ||||
|     private void recoverMission(@NonNull DownloadMission mission) { | ||||
|         unsafeMissionTarget = mission; | ||||
|  | ||||
|         final String startPath; | ||||
|         final Uri initialPath; | ||||
|         if (NewPipeSettings.useStorageAccessFramework(mContext)) { | ||||
|             startPath = null; | ||||
|             initialPath = null; | ||||
|         } else { | ||||
|             final File initialSavePath; | ||||
|             if (DownloadManager.TAG_AUDIO.equals(mission.storage.getType())) { | ||||
| @@ -252,11 +251,11 @@ public class MissionsFragment extends Fragment { | ||||
|             } else { | ||||
|                 initialSavePath = NewPipeSettings.getDir(Environment.DIRECTORY_MOVIES); | ||||
|             } | ||||
|             startPath = initialSavePath.getAbsolutePath(); | ||||
|             initialPath = Uri.parse(initialSavePath.getAbsolutePath()); | ||||
|         } | ||||
|  | ||||
|         startActivityForResult(StoredFileHelper.getNewPicker(mContext, startPath, | ||||
|                 mission.storage.getName(), mission.storage.getType()), REQUEST_DOWNLOAD_SAVE_AS); | ||||
|         startActivityForResult(StoredFileHelper.getNewPicker(mContext, mission.storage.getName(), | ||||
|                 mission.storage.getType(), initialPath), REQUEST_DOWNLOAD_SAVE_AS); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|   | ||||
| @@ -265,6 +265,7 @@ | ||||
|     </string-array> | ||||
|     <string name="feed_use_dedicated_fetch_method_key" translatable="false">feed_use_dedicated_fetch_method</string> | ||||
|  | ||||
|     <string name="import_export_data_path" translatable="false">import_export_data_path</string> | ||||
|     <string name="import_data" translatable="false">import_data</string> | ||||
|     <string name="export_data" translatable="false">export_data</string> | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Stypox
					Stypox