Backup settings.

This commit is contained in:
Alex Ning 2021-03-06 11:33:35 +08:00
parent 2ef72bc798
commit 65e654a23c
6 changed files with 288 additions and 0 deletions

View File

@ -170,6 +170,9 @@ dependencies {
// Crash screen
implementation 'com.melegy.redscreenofdeath:red-screen-of-death:0.1.2'
implementation 'net.lingala.zip4j:zip4j:2.7.0'
implementation 'org.apache.commons:commons-io:1.3.2'
/**** Builds and flavors ****/
// debugImplementation because LeakCanary should only run in debug builds.

View File

@ -0,0 +1,191 @@
package ml.docilealligator.infinityforreddit.asynctasks;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Handler;
import androidx.documentfile.provider.DocumentFile;
import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.model.enums.EncryptionMethod;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Executor;
import ml.docilealligator.infinityforreddit.BuildConfig;
import ml.docilealligator.infinityforreddit.R;
import ml.docilealligator.infinityforreddit.utils.CustomThemeSharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
public class BackupSettings {
public static void backupSettings(Context context, Executor executor, Handler handler,
ContentResolver contentResolver, Uri destinationDirUri,
SharedPreferences defaultSharedPreferences,
SharedPreferences lightThemeSharedPreferences,
SharedPreferences darkThemeSharedPreferences,
SharedPreferences amoledThemeSharedPreferences,
SharedPreferences sortTypeSharedPreferences,
SharedPreferences postLayoutSharedPreferences,
SharedPreferences postFeedScrolledPositionSharedPreferences,
SharedPreferences mainActivityTabsSharedPreferences,
SharedPreferences nsfwAndSpoilerSharedPreferencs,
SharedPreferences bottomAppBarSharedPreferences,
SharedPreferences postHistorySharedPreferences,
BackupSettingsListener backupSettingsListener) {
executor.execute(() -> {
boolean res = saveSharedPreferencesToFile(context, defaultSharedPreferences,
SharedPreferencesUtils.DEFAULT_PREFERENCES_FILE);
boolean res1 = saveSharedPreferencesToFile(context, lightThemeSharedPreferences,
CustomThemeSharedPreferencesUtils.LIGHT_THEME_SHARED_PREFERENCES_FILE);
boolean res2 = saveSharedPreferencesToFile(context, darkThemeSharedPreferences,
CustomThemeSharedPreferencesUtils.DARK_THEME_SHARED_PREFERENCES_FILE);
boolean res3 = saveSharedPreferencesToFile(context, amoledThemeSharedPreferences,
CustomThemeSharedPreferencesUtils.AMOLED_THEME_SHARED_PREFERENCES_FILE);
boolean res4 = saveSharedPreferencesToFile(context, sortTypeSharedPreferences,
SharedPreferencesUtils.SORT_TYPE_SHARED_PREFERENCES_FILE);
boolean res5 = saveSharedPreferencesToFile(context, postLayoutSharedPreferences,
SharedPreferencesUtils.POST_LAYOUT_SHARED_PREFERENCES_FILE);
boolean res6 = saveSharedPreferencesToFile(context, postFeedScrolledPositionSharedPreferences,
SharedPreferencesUtils.FRONT_PAGE_SCROLLED_POSITION_SHARED_PREFERENCES_FILE);
boolean res7 = saveSharedPreferencesToFile(context, mainActivityTabsSharedPreferences,
SharedPreferencesUtils.MAIN_PAGE_TABS_SHARED_PREFERENCES_FILE);
boolean res8 = saveSharedPreferencesToFile(context, nsfwAndSpoilerSharedPreferencs,
SharedPreferencesUtils.NSFW_AND_SPOILER_SHARED_PREFERENCES_FILE);
boolean res9 = saveSharedPreferencesToFile(context, bottomAppBarSharedPreferences,
SharedPreferencesUtils.BOTTOM_APP_BAR_SHARED_PREFERENCES_FILE);
boolean res10 = saveSharedPreferencesToFile(context, postHistorySharedPreferences,
SharedPreferencesUtils.POST_HISTORY_SHARED_PREFERENCES_FILE);
boolean zipRes = zipAndMoveToDestinationDir(context, contentResolver, destinationDirUri);
try {
FileUtils.deleteDirectory(new File(context.getExternalCacheDir() + "/Backup/"));
} catch (IOException e) {
e.printStackTrace();
}
handler.post(() -> {
boolean finalResult = res && res1 && res2 && res3 && res4 && res5 && res6 && res7 && res8 && res9 && res10 && zipRes;
if (finalResult) {
backupSettingsListener.success();
} else {
if (!zipRes) {
backupSettingsListener.failed(context.getText(R.string.create_zip_in_destination_directory_failed).toString());
} else {
backupSettingsListener.failed(context.getText(R.string.backup_some_settings_failed).toString());
}
}
});
});
}
private static boolean saveSharedPreferencesToFile(Context context, SharedPreferences sharedPreferences,
String fileName) {
boolean result = false;
ObjectOutputStream output = null;
String backupDir = context.getExternalCacheDir() + "/Backup/" + BuildConfig.VERSION_NAME;
if (!new File(backupDir).exists()) {
new File(backupDir).mkdirs();
} else {
File backupDirFile = new File(backupDir);
backupDirFile.delete();
backupDirFile.mkdirs();
}
try {
output = new ObjectOutputStream(new FileOutputStream(new File(backupDir + "/" + fileName + ".txt")));
output.writeObject(sharedPreferences.getAll());
result = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (output != null) {
output.flush();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return result;
}
private static boolean zipAndMoveToDestinationDir(Context context, ContentResolver contentResolver, Uri destinationDirUri) {
OutputStream outputStream = null;
boolean result = false;
try {
String time = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(System.currentTimeMillis()));
String fileName = "Infinity_For_Reddit_Settings_Backup_v" + BuildConfig.VERSION_NAME + "-" + time + ".zip";
String filePath = context.getExternalCacheDir() + "/Backup/" + fileName;
ZipFile zip = new ZipFile(filePath, "123321".toCharArray());
ZipParameters zipParameters = new ZipParameters();
zipParameters.setEncryptFiles(true);
zipParameters.setEncryptionMethod(EncryptionMethod.AES);
zip.addFolder(new File(context.getExternalCacheDir() + "/Backup/" + BuildConfig.VERSION_NAME + "/"), zipParameters);
DocumentFile dir = DocumentFile.fromTreeUri(context, destinationDirUri);
if (dir == null) {
return false;
}
DocumentFile checkForDuplicate = dir.findFile(fileName);
if (checkForDuplicate != null) {
checkForDuplicate.delete();
}
DocumentFile destinationFile = dir.createFile("application/zip", fileName);
if (destinationFile == null) {
return false;
}
outputStream = contentResolver.openOutputStream(destinationFile.getUri());
if (outputStream == null) {
return false;
}
byte[] fileReader = new byte[1024];
FileInputStream inputStream = new FileInputStream(filePath);
while (true) {
int read = inputStream.read(fileReader);
if (read == -1) {
break;
}
outputStream.write(fileReader, 0, read);
}
result = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return result;
}
public interface BackupSettingsListener {
void success();
void failed(String errorMessage);
}
}

View File

@ -2,12 +2,15 @@ package ml.docilealligator.infinityforreddit.settings;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
@ -25,6 +28,7 @@ import javax.inject.Named;
import ml.docilealligator.infinityforreddit.Infinity;
import ml.docilealligator.infinityforreddit.R;
import ml.docilealligator.infinityforreddit.RedditDataRoomDatabase;
import ml.docilealligator.infinityforreddit.asynctasks.BackupSettings;
import ml.docilealligator.infinityforreddit.asynctasks.DeleteAllPostLayouts;
import ml.docilealligator.infinityforreddit.asynctasks.DeleteAllReadPosts;
import ml.docilealligator.infinityforreddit.asynctasks.DeleteAllSortTypes;
@ -34,11 +38,15 @@ import ml.docilealligator.infinityforreddit.asynctasks.DeleteAllUsers;
import ml.docilealligator.infinityforreddit.events.RecreateActivityEvent;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import static android.app.Activity.RESULT_OK;
import static android.content.Intent.ACTION_OPEN_DOCUMENT_TREE;
/**
* A simple {@link Fragment} subclass.
*/
public class AdvancedPreferenceFragment extends PreferenceFragmentCompat {
private static final int SELECT_BACKUP_SETTINGS_DIRECTORY_REQUEST_CODE = 1;
@Inject
RedditDataRoomDatabase mRedditDataRoomDatabase;
@Inject
@ -69,6 +77,12 @@ public class AdvancedPreferenceFragment extends PreferenceFragmentCompat {
@Named("nsfw_and_spoiler")
SharedPreferences nsfwAndBlurringSharedPreferences;
@Inject
@Named("bottom_app_bar")
SharedPreferences bottomAppBarSharedPreferences;
@Inject
@Named("post_history")
SharedPreferences postHistorySharedPreferences;
@Inject
Executor executor;
private Activity activity;
@ -87,6 +101,8 @@ public class AdvancedPreferenceFragment extends PreferenceFragmentCompat {
Preference deleteReadPostsPreference = findPreference(SharedPreferencesUtils.DELETE_READ_POSTS_IN_DATABASE);
Preference deleteAllLegacySettingsPreference = findPreference(SharedPreferencesUtils.DELETE_ALL_LEGACY_SETTINGS);
Preference resetAllSettingsPreference = findPreference(SharedPreferencesUtils.RESET_ALL_SETTINGS);
Preference backupSettingsPreference = findPreference(SharedPreferencesUtils.BACKUP_SETTINGS);
Preference restoreSettingsPreference = findPreference(SharedPreferencesUtils.RESTORE_SETTINGS);
if (deleteSubredditsPreference != null) {
deleteSubredditsPreference.setOnPreferenceClickListener(preference -> {
@ -252,6 +268,67 @@ public class AdvancedPreferenceFragment extends PreferenceFragmentCompat {
return true;
});
}
if (backupSettingsPreference != null) {
backupSettingsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Intent intent = new Intent(ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivityForResult(intent, SELECT_BACKUP_SETTINGS_DIRECTORY_REQUEST_CODE);
/*BackupSettings.backupSettings(activity, executor, new Handler(), activity.getContentResolver(), null,
mSharedPreferences, lightThemeSharedPreferences, darkThemeSharedPreferences,
amoledThemeSharedPreferences, mSortTypeSharedPreferences, mPostLayoutSharedPreferences,
postFeedScrolledPositionSharedPreferences, mainActivityTabsSharedPreferences,
nsfwAndBlurringSharedPreferences, bottomAppBarSharedPreferences, postHistorySharedPreferences,
new BackupSettings.BackupSettingsListener() {
@Override
public void success() {
}
@Override
public void failed() {
}
});*/
return true;
}
});
}
if (restoreSettingsPreference != null) {
restoreSettingsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
return true;
}
});
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == SELECT_BACKUP_SETTINGS_DIRECTORY_REQUEST_CODE && resultCode == RESULT_OK) {
Uri uri = data.getData();
BackupSettings.backupSettings(activity, executor, new Handler(), activity.getContentResolver(), uri,
mSharedPreferences, lightThemeSharedPreferences, darkThemeSharedPreferences,
amoledThemeSharedPreferences, mSortTypeSharedPreferences, mPostLayoutSharedPreferences,
postFeedScrolledPositionSharedPreferences, mainActivityTabsSharedPreferences,
nsfwAndBlurringSharedPreferences, bottomAppBarSharedPreferences, postHistorySharedPreferences,
new BackupSettings.BackupSettingsListener() {
@Override
public void success() {
Toast.makeText(activity, R.string.backup_settings_success, Toast.LENGTH_LONG).show();
}
@Override
public void failed(String errorMessage) {
Toast.makeText(activity, errorMessage, Toast.LENGTH_LONG).show();
}
});
}
}
@Override

View File

@ -169,7 +169,10 @@ public class SharedPreferencesUtils {
public static final String HIDE_SUBREDDIT_AND_USER_PREFIX = "hide_subreddit_and_user_prefix";
public static final String HIDE_THE_NUMBER_OF_VOTES = "hide_the_number_of_votes";
public static final String HIDE_THE_NUMBER_OF_COMMENTS = "hide_the_number_of_comments";
public static final String BACKUP_SETTINGS = "backup_settings";
public static final String RESTORE_SETTINGS = "restore_settings";
public static final String DEFAULT_PREFERENCES_FILE = "ml.docilealligator.infinityforreddit_preferences";
public static final String MAIN_PAGE_TABS_SHARED_PREFERENCES_FILE = "ml.docilealligator.infinityforreddit.main_page_tabs";
public static final String MAIN_PAGE_TAB_COUNT = "_main_page_tab_count";
public static final String MAIN_PAGE_SHOW_TAB_NAMES = "_main_page_show_tab_names";

View File

@ -541,6 +541,8 @@
<string name="settings_hide_the_number_of_votes">Hide the Number of Votes</string>
<string name="settings_hide_the_number_of_comments">Hide the Number of Comments</string>
<string name="settings_show_avatar_on_the_right_in_the_navigation_drawer">Show Avatar on the Left in the Navigation Drawer</string>
<string name="settings_backup_settings_title">Backup Settings</string>
<string name="settings_restore_settings_title">Restore Settings</string>
<string name="no_link_available">Cannot get the link</string>
@ -1037,4 +1039,8 @@
<string name="anonymous_front_page_no_subscriptions">Start by joining a subreddit!</string>
<string name="backup_settings_success">Successfully exported settings to the destination directory</string>
<string name="create_zip_in_destination_directory_failed">Could not create backup zip in the destination directory</string>
<string name="backup_some_settings_failed">Could not backup some settings but others were successfully exported to the destination directory</string>
</resources>

View File

@ -38,6 +38,14 @@
app:key="reset_all_settings"
app:title="@string/settings_reset_all_settings_title" />
<Preference
app:key="backup_settings"
app:title="@string/settings_backup_settings_title" />
<Preference
app:key="restore_settings"
app:title="@string/settings_restore_settings_title" />
<Preference
android:icon="@drawable/ic_info_preference_24dp"
app:summary="@string/settings_advanced_settings_summary"