Commit 52ccfdb8 authored by Thomas's avatar Thomas
Browse files

Export/Import all data

parent f858a870
Loading
Loading
Loading
Loading
+3 −17
Original line number Diff line number Diff line
@@ -54,9 +54,9 @@ public class LoginActivity extends BaseActivity {

    public static Account.API apiLogin;
    public static String currentInstanceLogin, client_idLogin, client_secretLogin, softwareLogin;
    private final int PICK_IMPORT = 5557;
    public static boolean requestedAdmin;


    @SuppressLint("ApplySharedPref")
    public void proceedLogin(Activity activity, Account account) {
        new Thread(() -> {
@@ -154,6 +154,8 @@ public class LoginActivity extends BaseActivity {
        //The activity handles a redirect URI, it will extract token code and will proceed to authentication
        //That happens when the user wants to use an external browser
        manageItent(getIntent());


    }


@@ -176,7 +178,6 @@ public class LoginActivity extends BaseActivity {
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        if (id == R.id.action_proxy) {
            Intent intent = new Intent(LoginActivity.this, ProxyActivity.class);
            startActivity(intent);
@@ -188,20 +189,5 @@ public class LoginActivity extends BaseActivity {
        return super.onOptionsItemSelected(item);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode,
                                    Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == PICK_IMPORT && resultCode == RESULT_OK) {
            if (data == null || data.getData() == null) {
                Toasty.error(LoginActivity.this, getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show();
                return;
            }
            //  String filename = Helper.getFilePathFromURI(LoginActivity.this, data.getData());
            //   Sqlite.importDB(LoginActivity.this, filename);
        } else {
            Toasty.error(LoginActivity.this, getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show();
        }
    }

}
 No newline at end of file
+49 −1
Original line number Diff line number Diff line
@@ -312,7 +312,7 @@ public class Helper {
    public static final String INTENT_SEND_MODIFIED_IMAGE = "INTENT_SEND_MODIFIED_IMAGE";
    public static final String INTENT_COMPOSE_ERROR_MESSAGE = "INTENT_COMPOSE_ERROR_MESSAGE";
    public static final String TEMP_MEDIA_DIRECTORY = "TEMP_MEDIA_DIRECTORY";

    public static final String TEMP_EXPORT_DATA = "TEMP_EXPORT_DATA";

    public static final int EXTERNAL_STORAGE_REQUEST_CODE = 84;
    public static final int EXTERNAL_STORAGE_REQUEST_CODE_MEDIA_SAVE = 85;
@@ -1276,6 +1276,50 @@ public class Helper {
        }).start();
    }


    public static void createFileFromUri(Context context, Uri uri, OnFileCopied callBack) {
        new Thread(() -> {
            InputStream selectedFileInputStream;
            File file = null;
            try {
                String uriFullPath = uri.getPath();
                String[] uriFullPathStr = uriFullPath.split(":");
                String fullPath = uriFullPath;
                if (uriFullPathStr.length > 1) {
                    fullPath = uriFullPathStr[1];
                }
                final String fileName = Helper.dateFileToString(context, new Date()) + ".zip";
                selectedFileInputStream = context.getContentResolver().openInputStream(uri);
                if (selectedFileInputStream != null) {
                    final File certCacheDir = new File(context.getCacheDir(), TEMP_EXPORT_DATA);
                    boolean isCertCacheDirExists = certCacheDir.exists();
                    if (!isCertCacheDirExists) {
                        isCertCacheDirExists = certCacheDir.mkdirs();
                    }
                    if (isCertCacheDirExists) {
                        String filePath = certCacheDir.getAbsolutePath() + "/" + fileName;
                        file = new File(filePath);
                        OutputStream selectedFileOutPutStream = new FileOutputStream(filePath);
                        byte[] buffer = new byte[1024];
                        int length;
                        while ((length = selectedFileInputStream.read(buffer)) > 0) {
                            selectedFileOutPutStream.write(buffer, 0, length);
                        }
                        selectedFileOutPutStream.flush();
                        selectedFileOutPutStream.close();
                    }
                    selectedFileInputStream.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            File finalFile = file;
            Runnable myRunnable = () -> callBack.onFileCopied(finalFile);
            mainHandler.post(myRunnable);
        }).start();
    }

    public static void createAttachmentFromPAth(String path, OnAttachmentCopied callBack) {
        new Thread(() -> {
            Attachment attachment = new Attachment();
@@ -1954,6 +1998,10 @@ public class Helper {
        void onAttachmentCopied(Attachment attachment);
    }

    public interface OnFileCopied {
        void onFileCopied(File file);
    }

    public static void addMutedAccount(app.fedilab.android.client.entities.api.Account target) {
        if (MainActivity.filteredAccounts == null) {
            MainActivity.filteredAccounts = new ArrayList<>();
+294 −0
Original line number Diff line number Diff line
@@ -14,8 +14,11 @@ package app.fedilab.android.helper;
 * You should have received a copy of the GNU General Public License along with Fedilab; if not,
 * see <http://www.gnu.org/licenses>. */


import static app.fedilab.android.BaseMainActivity.currentAccount;
import static app.fedilab.android.helper.LogoHelper.getMainLogo;
import static app.fedilab.android.sqlite.Sqlite.DB_NAME;
import static app.fedilab.android.sqlite.Sqlite.db;

import android.annotation.SuppressLint;
import android.content.Context;
@@ -24,30 +27,167 @@ import android.content.SharedPreferences;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;

import androidx.preference.PreferenceManager;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.channels.FileChannel;
import java.util.Date;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import app.fedilab.android.R;
import es.dmoral.toasty.Toasty;


public class ZipHelper {

    final static int BUFFER_SIZE = 2048;

    public static void exportData(Context context) throws IOException {
        String suffix = Helper.dateFileToString(context, new Date());
        String fileName = "Fedilab_data_export_" + suffix + ".zip";
        String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
        String zipFile = filePath + "/" + fileName;
        BufferedInputStream origin;
        try (ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)))) {
            byte[] data = new byte[BUFFER_SIZE];
            String settingsPath = storeSettings(context, suffix);
            if (settingsPath != null) {
                FileInputStream fi = new FileInputStream(settingsPath);
                origin = new BufferedInputStream(fi, BUFFER_SIZE);
                try {
                    ZipEntry entry = new ZipEntry(settingsPath.substring(settingsPath.lastIndexOf("/") + 1));
                    out.putNextEntry(entry);
                    int count;
                    while ((count = origin.read(data, 0, BUFFER_SIZE)) != -1) {
                        out.write(data, 0, count);
                    }
                } finally {
                    origin.close();
                }
                //noinspection ResultOfMethodCallIgnored
                new File(settingsPath).delete();
            } else {
                Toasty.error(context, context.getString(R.string.toast_error), Toasty.LENGTH_SHORT).show();
                return;
            }
            String dbPath = exportDB(context, suffix);
            if (dbPath != null) {
                FileInputStream fi = new FileInputStream(dbPath);
                origin = new BufferedInputStream(fi, BUFFER_SIZE);
                try {
                    ZipEntry entry = new ZipEntry(dbPath.substring(dbPath.lastIndexOf("/") + 1));
                    out.putNextEntry(entry);
                    int count;
                    while ((count = origin.read(data, 0, BUFFER_SIZE)) != -1) {
                        out.write(data, 0, count);
                    }
                } finally {
                    origin.close();
                }
                //noinspection ResultOfMethodCallIgnored
                new File(dbPath).delete();
            } else {
                Toasty.error(context, context.getString(R.string.toast_error), Toasty.LENGTH_SHORT).show();
                return;
            }
            String message = context.getString(R.string.data_export_settings_success);
            Intent intentOpen = new Intent();
            intentOpen.setAction(android.content.Intent.ACTION_VIEW);
            Uri uri = Uri.parse("file://" + zipFile);
            intentOpen.setDataAndType(uri, "application/zip");
            String title = context.getString(R.string.data_export_settings);
            Helper.notify_user(context, currentAccount, intentOpen, BitmapFactory.decodeResource(context.getResources(),
                    getMainLogo(context)), Helper.NotifType.BACKUP, title, message);
        }
    }

//From https://stackoverflow.com/a/10864463

public class SettingsStorage {
    @SuppressLint("UnspecifiedImmutableFlag")
    public static void importData(Context context, File file) {
        new Thread(() -> {
            try {
                int size;
                byte[] buffer = new byte[BUFFER_SIZE];

                String uriFullPath = file.getAbsolutePath();
                String[] uriFullPathStr = uriFullPath.split(":");
                String fullPath = uriFullPath;
                if (uriFullPathStr.length > 1) {
                    fullPath = uriFullPathStr[1];
                }
                fullPath = fullPath.replace(".zip", "");
                File f = new File(fullPath);
                if (!f.isDirectory()) {
                    //noinspection ResultOfMethodCallIgnored
                    f.mkdirs();
                }
                boolean successful = true;
                try (ZipInputStream zin = new ZipInputStream(new FileInputStream(fullPath + ".zip"))) {
                    ZipEntry ze;
                    while ((ze = zin.getNextEntry()) != null) {
                        if (!successful) {
                            break;
                        }
                        String path = fullPath + ze.getName();
                        File unzipFile = new File(path);
                        FileOutputStream out = new FileOutputStream(unzipFile, false);
                        BufferedOutputStream fout = new BufferedOutputStream(out, BUFFER_SIZE);
                        try {
                            while ((size = zin.read(buffer, 0, BUFFER_SIZE)) != -1) {
                                fout.write(buffer, 0, size);
                            }

    public static boolean saveSharedPreferencesToFile(Context context) {
                            zin.closeEntry();
                        } finally {
                            fout.flush();
                            fout.close();
                        }
                        if (ze.getName().contains("settings")) {
                            successful = restoreSettings(context, Uri.fromFile(new File(path)));
                        } else if (ze.getName().contains("database")) {
                            successful = importDB(context, path);
                        } else {
                            break;
                        }
                    }
                }
                Handler mainHandler = new Handler(Looper.getMainLooper());
                boolean finalSuccessful = successful;
                Runnable myRunnable = () -> {
                    if (finalSuccessful) {
                        Helper.restart(context);
                    } else {
                        Toasty.error(context, context.getString(R.string.toast_error), Toast.LENGTH_LONG).show();
                    }
                };
                mainHandler.post(myRunnable);

            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();

    }

    private static String storeSettings(Context context, String suffix) {
        boolean res = false;
        ObjectOutputStream output = null;
        String fileName = "Fedilab_settings_export_" + Helper.dateFileToString(context, new Date()) + ".txt";
        String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
        String fileName = "Fedilab_settings_export_" + suffix + ".fedilab";
        String filePath = context.getCacheDir().getAbsolutePath();
        String fullPath = filePath + "/" + fileName;
        File dst = new File(fullPath);
        try {
@@ -55,14 +195,6 @@ public class SettingsStorage {
            SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
            output.writeObject(sharedpreferences.getAll());
            res = true;
            String message = context.getString(R.string.data_export_settings_success);
            Intent intentOpen = new Intent();
            intentOpen.setAction(android.content.Intent.ACTION_VIEW);
            Uri uri = Uri.parse("file://" + fullPath);
            intentOpen.setDataAndType(uri, "text/txt");
            String title = context.getString(R.string.data_export_settings);
            Helper.notify_user(context, currentAccount, intentOpen, BitmapFactory.decodeResource(context.getResources(),
                    getMainLogo(context)), Helper.NotifType.BACKUP, title, message);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
@@ -75,12 +207,12 @@ public class SettingsStorage {
                ex.printStackTrace();
            }
        }
        return res;
        return res ? fullPath : null;
    }

    @SuppressLint("ApplySharedPref")
    @SuppressWarnings({"unchecked", "UnnecessaryUnboxing"})
    public static boolean loadSharedPreferencesFromFile(Context context, Uri srcUri) {
    @SuppressWarnings("UnnecessaryUnboxing")
    private static boolean restoreSettings(Context context, Uri srcUri) {
        boolean res = false;
        ObjectInputStream input = null;
        try {
@@ -88,23 +220,11 @@ public class SettingsStorage {
            SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
            SharedPreferences.Editor prefEdit = sharedpreferences.edit();
            prefEdit.clear();
            //noinspection unchecked
            Map<String, ?> entries = (Map<String, ?>) input.readObject();
            for (Map.Entry<String, ?> entry : entries.entrySet()) {
                Object v = entry.getValue();
                String key = entry.getKey();
                //We skip some values
                if (key.compareTo(Helper.PREF_USER_ID) == 0) {
                    continue;
                }
                if (key.compareTo(Helper.PREF_INSTANCE) == 0) {
                    continue;
                }
                if (key.compareTo(Helper.PREF_USER_INSTANCE) == 0) {
                    continue;
                }
                if (key.compareTo(Helper.PREF_USER_TOKEN) == 0) {
                    continue;
                }
                if (v instanceof Boolean)
                    prefEdit.putBoolean(key, ((Boolean) v).booleanValue());
                else if (v instanceof Float)
@@ -116,6 +236,7 @@ public class SettingsStorage {
                else if (v instanceof String)
                    prefEdit.putString(key, ((String) v));
            }

            prefEdit.commit();
            res = true;
        } catch (IOException | ClassNotFoundException e) {
@@ -131,4 +252,43 @@ public class SettingsStorage {
        }
        return res;
    }


    private static String exportDB(Context context, String suffix) {
        try {
            String fileName = "Fedilab_database_export_" + suffix + ".fedilab";
            String filePath = context.getCacheDir().getAbsolutePath();
            String fullPath = filePath + "/" + fileName;
            File dbSource = context.getDatabasePath(DB_NAME);
            File dbDest = new File(fullPath);
            FileChannel src = new FileInputStream(dbSource).getChannel();
            FileChannel dst = new FileOutputStream(dbDest).getChannel();
            dst.transferFrom(src, 0, src.size());
            src.close();
            dst.close();
            return fullPath;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private static boolean importDB(Context context, String backupDBPath) {
        try {
            if (db != null) {
                db.close();
            }
            File dbDest = context.getDatabasePath(DB_NAME);
            File dbSource = new File(backupDBPath);
            FileChannel src = new FileInputStream(dbSource).getChannel();
            FileChannel dst = new FileOutputStream(dbDest).getChannel();
            dst.transferFrom(src, 0, src.size());
            src.close();
            dst.close();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
}
+70 −4
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@ package app.fedilab.android.ui.fragment.login;
 * see <http://www.gnu.org/licenses>. */


import static android.app.Activity.RESULT_OK;
import static app.fedilab.android.activities.LoginActivity.apiLogin;
import static app.fedilab.android.activities.LoginActivity.client_idLogin;
import static app.fedilab.android.activities.LoginActivity.client_secretLogin;
@@ -22,8 +23,9 @@ import static app.fedilab.android.activities.LoginActivity.currentInstanceLogin;
import static app.fedilab.android.activities.LoginActivity.requestedAdmin;
import static app.fedilab.android.activities.LoginActivity.softwareLogin;

import android.Manifest;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
@@ -36,11 +38,13 @@ import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Toast;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.PopupMenu;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.preference.PreferenceManager;

import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
@@ -55,6 +59,7 @@ import app.fedilab.android.client.entities.app.InstanceSocial;
import app.fedilab.android.databinding.FragmentLoginMainBinding;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.MastodonHelper;
import app.fedilab.android.helper.ZipHelper;
import app.fedilab.android.viewmodel.mastodon.AppsVM;
import app.fedilab.android.viewmodel.mastodon.InstanceSocialVM;
import app.fedilab.android.viewmodel.mastodon.NodeInfoVM;
@@ -65,7 +70,9 @@ public class FragmentLoginMain extends Fragment {
    private FragmentLoginMainBinding binding;
    private boolean searchInstanceRunning = false;
    private String oldSearch;

    private static final int REQUEST_CODE = 5412;
    private final int PICK_IMPORT = 5557;
    private ActivityResultLauncher<String> permissionLauncher;

    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) {
@@ -76,6 +83,24 @@ public class FragmentLoginMain extends Fragment {
        InstanceSocialVM instanceSocialVM = new ViewModelProvider(FragmentLoginMain.this).get(InstanceSocialVM.class);
        binding.menuIcon.setOnClickListener(this::showMenu);
        binding.loginInstance.setOnItemClickListener((parent, view, position, id) -> oldSearch = parent.getItemAtPosition(position).toString().trim());

        permissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
            if (isGranted) {
                Intent openFileIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
                openFileIntent.addCategory(Intent.CATEGORY_OPENABLE);
                openFileIntent.setType("application/zip");
                String[] mimeTypes = new String[]{"application/zip"};
                openFileIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
                //noinspection deprecation
                startActivityForResult(
                        Intent.createChooser(
                                openFileIntent,
                                getString(R.string.load_settings)), PICK_IMPORT);
            } else {
                ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE);
            }
        });

        binding.loginInstance.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
@@ -184,7 +209,6 @@ public class FragmentLoginMain extends Fragment {
        MenuInflater menuInflater = popupMenu.getMenuInflater();
        menuInflater.inflate(R.menu.main_login, popupMenu.getMenu());
        MenuItem adminTabItem = popupMenu.getMenu().findItem(R.id.action_request_admin);
        SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity());
        adminTabItem.setChecked(requestedAdmin);
        popupMenu.setOnMenuItemClickListener(item -> {
            int itemId = item.getItemId();
@@ -208,6 +232,8 @@ public class FragmentLoginMain extends Fragment {
                        return false;
                    }
                });
            } else if (itemId == R.id.action_import_data) {
                permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE);
            }
            return false;
        });
@@ -258,4 +284,44 @@ public class FragmentLoginMain extends Fragment {

        });
    }


    @SuppressWarnings("deprecation")
    @Override
    public void onActivityResult(int requestCode, int resultCode,
                                 Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == PICK_IMPORT && resultCode == RESULT_OK) {
            if (data == null || data.getData() == null) {
                Toasty.error(requireActivity(), getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show();
                return;
            }
            Helper.createFileFromUri(requireActivity(), data.getData(), file -> ZipHelper.importData(requireActivity(), file));
        } else {
            Toasty.error(requireActivity(), getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show();
        }
    }


    @SuppressWarnings("deprecation")
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_CODE) {
            if (grantResults.length > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Intent openFileIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
                openFileIntent.addCategory(Intent.CATEGORY_OPENABLE);
                openFileIntent.setType("application/zip");
                String[] mimeTypes = new String[]{"application/zip"};
                openFileIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
                startActivityForResult(
                        Intent.createChooser(
                                openFileIntent,
                                getString(R.string.load_settings)), PICK_IMPORT);
            } else {
                Toasty.error(requireActivity(), getString(R.string.permission_missing), Toasty.LENGTH_SHORT).show();
            }
        }
    }
}
 No newline at end of file
+27 −8

File changed.

Preview size limit exceeded, changes collapsed.

Loading