realm / realm-java

Realm is a mobile database: a replacement for SQLite & ORMs
http://realm.io
Apache License 2.0
11.45k stars 1.75k forks source link

Crashes by trying to copy files on storage #7620

Closed 100CPU closed 2 years ago

100CPU commented 2 years ago

How frequently does the bug occur?

All the time

Description


public static String getStorageDirForBackup(){
        String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/my_Backup";
        File f = new File(path);

         //now
        if(!f.exists()) if(!f.mkdirs()) return null;

        // before 
        f.mkdirs();

        return f.getAbsolutePath();
    }

private String EXPORT_REALM_FILE_NAME = "backup.realm";

    private Activity activity;
    private Realm realm;

    public RealmBackupRestore(Activity activity, Realm realm) {
        this.activity = activity;
        this.realm = realm;
    }

    public void backup() {
        new AsyncTask<Void, Void, Boolean>() {

            private SweetAlertDialog dialog;

            @Override
            protected void onPreExecute() {
                dialog = new SweetAlertDialog(activity, SweetAlertDialog.PROGRESS_TYPE);
                dialog.setContentText(activity.getString(R.string.settings_do_backup));
                dialog.setTitleText("");
                dialog.show();
            }

            @Override
            protected Boolean doInBackground(Void... params) {
                Realm realm = Realm.getDefaultInstance();

                File exportRealmFile;

                exportRealmFile = new File(Utils.getStorageDir(), EXPORT_REALM_FILE_NAME);
                exportRealmFile.delete();
                realm.writeCopyTo(exportRealmFile);

                File zipFile = new File(Utils.getStorageDirForBackup(), "backup_"+System.currentTimeMillis()+".zip");
                zipFile.delete();

                File wholeDirectory = new File(Utils.getStorageDir()+"/");

                ZipUtil.pack(wholeDirectory, zipFile);

                exportRealmFile.delete();
                realm.beginTransaction();
                Settings.getSettings(realm).setLastBackup(new Date());
                realm.commitTransaction();

                realm.close();
                return true;
            }

Stacktrace & log output

Caused by io.realm.exceptions.RealmFileException
Unable to open a realm at path '/storage/emulated/0/myFile/backup.realm': open() failed: Operation not permitted. (open("/storage/emulated/0/myfile/backup.realm") failed: Operation not permitted) (/storage/emulated/0/myFile/backup.realm) in /home/cc/repo/realm/release/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp line 407

Can you reproduce the bug?

Yes, always

Reproduction Steps

Trying to backup my files with this code (relevant lines added), the app crashes.

Version

target SDK 31, min SDK 21

What SDK flavour are you using?

Local Database only

Are you using encryption?

No, not using encryption

Platform OS and version(s)

Android 8-11

Build environment

Android Studio version: 4.2.2 Android Build Tools version: Gradle version:

rorbech commented 2 years ago

Hi @100CPU. I suspect that this could be an issue with realm-internals of creating named pipes in external storage. Will try to see if I can dig up some of the issues. In the meantime I think you should be able to backup the file temporarily in the internal storage and then delete it after zipping it.

100CPU commented 2 years ago

Hi @rorbech, thanks for your reply. Any news about the issue?

I've added a few lines to the code to make it a little more understandable what exactly it is supposed to do. As soon as the app user clicks on the corresponding button, the backup runs and all settings and data are saved as a zip. The internal storage /storage/emulated/0/ is used for this. Therefore, unfortunately, I do not understand your tip to temporarily store the backup file in the internal storage.

Before that it had worked without any problems. Is it maybe the SDK? It worked on SDK 29, but caused problems elsewhere.

rorbech commented 2 years ago

We have seen issues with permissions for non app-specific internal paths (https://developer.android.com/training/data-storage/app-specific).

Does it work if you use context.filesDir as in https://developer.android.com/training/data-storage/app-specific#internal-access-store-files as prefix for your path?

100CPU commented 2 years ago

No, unfortunately that doesn't work. Context cannot be resolved. It may be that something else has to be added/adjusted.

100CPU commented 2 years ago

@rorbech: After some code changes, now getting following error.

Caused by: io.realm.exceptions.RealmFileException: Unable to open a realm at path '/backup.realm'. Please use a path where your app has read-write permissions. (open("/backup.realm") failed: Read-only file system) (/backup.realm) in /home/cc/repo/realm/release/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp line 407 Kind: PERMISSION_DENIED.

100CPU commented 2 years ago

is there a solution to the problem? I would be very happy if you could help here.

rorbech commented 2 years ago

@100CPU Seems like you are now using the root of the file system /backup.realm. Maybe you could grab the context.filesDir somewhere where you have access to the context and propagate that into your code

exportRealmFile = new File(<propagated context.filesDir>, EXPORT_REALM_FILE_NAME);

to try out if it works with the app specific internal storage.

100CPU commented 2 years ago

Thanks. That didn't work.

I didn't change the code, it worked before. And it still works with Android <= 10. From 11 there es another permission concept needed. You can no longer just read or write data anywhere. I think, that's the cause problem here. I am in the process of implementing the new concept.

cmelchior commented 2 years ago

Hi @100CPU

This is a problem caused by Android Scoped Storage and their permissions model. The only work-around that exists is writing a local copy first before moving it to External storage. @rorbech linked to the relevant documentation in https://github.com/realm/realm-java/issues/7620#issuecomment-999605164

The problem you have with missing context mean you have to refactor your logic to pass it in from somewhere or maybe expose a static variable from you Application subclass.