mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2024-12-23 16:40:32 +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:
parent
5ffc667bea
commit
2a99e0e435
@ -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);
|
||||
}
|
||||
return applyInitialPathToPickerIntent(ctx, i, initialPath, filename);
|
||||
}
|
||||
|
||||
if (startPath != null || filename != null) {
|
||||
File fullStartPath;
|
||||
if (startPath == null) {
|
||||
fullStartPath = Environment.getExternalStorageDirectory();
|
||||
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 {
|
||||
fullStartPath = new File(startPath);
|
||||
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) {
|
||||
fullStartPath = new File(fullStartPath, filename);
|
||||
// append a filename so that the non-SAF FilePicker shows it as the preview
|
||||
file = new File(file, filename);
|
||||
}
|
||||
i.putExtra(FilePickerActivityHelper.EXTRA_START_PATH,
|
||||
fullStartPath.getAbsolutePath());
|
||||
|
||||
return intent
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_START_PATH, file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user