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:
Stypox 2021-06-06 22:15:32 +02:00
parent 5ffc667bea
commit 2a99e0e435
No known key found for this signature in database
GPG Key ID: 4BDF1B40A49FDD23
7 changed files with 155 additions and 55 deletions

View File

@ -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;
}

View File

@ -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)
)

View File

@ -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;
}
}

View File

@ -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();
}
}
}
}

View File

@ -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());
}
}
}

View File

@ -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

View File

@ -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>