xralf
xralf

Reputation: 3692

Storage Access Framework in Python

I have this small application and I'd like to rewrite it to use more privacy-friendly best practices, such as the Storage Access Framework.

How to do it in Python (Kivy)? I searched the web and haven't found any tutorial or examples with Python. I know only very little Java and Kotlin at all. So, I would like to read the examples in Python.

I would like to replace this code:

request_permissions([Permission.WRITE_EXTERNAL_STORAGE,
                             Permission.READ_EXTERNAL_STORAGE])
        try:
            if autoclass('android.os.Build$VERSION').SDK_INT >= 29:
                Context = autoclass('android.content.Context')
                self.working_directory = os.path.join(Context.getExternalFilesDir(None).getAbsolutePath(), "tdg_articles")
                self.data_dir = os.path.join(Context.getExternalFilesDir(None).getAbsolutePath(), "nltk")
            else:
                Environment = autoclass('android.os.Environment')
                self.working_directory = os.path.join(Environment.getExternalStorageDirectory().getAbsolutePath(), "tdg_articles")
                self.data_dir = os.path.join(Environment.getExternalStorageDirectory().getAbsolutePath(), "nltk")
        except:
            self.working_directory = os.path.join(App.get_running_app().user_data_dir, "tdg_articles")
            self.data_dir = os.path.join(App.get_running_app().user_data_dir, "nltk")
        
        if not os.path.exists(self.working_directory):
            os.makedirs(self.working_directory)
        
        if not os.path.exists(self.data_dir):
            os.makedirs(self.data_dir)
        
        os.chdir(self.working_directory)

Upvotes: 4

Views: 2377

Answers (3)

Anandakrishnan
Anandakrishnan

Reputation: 389

So if you want to access permission in kivy, the easiest way is to place the permissions in the permissions field in buildozer.spec file. You just need to place the permissions WRITE_EXTERNAL_STORAGE and READ_EXTERNAL_STORAGE in the buildozer file. Refer the kivy and buildozer docs. Or if you want to call the ask for permission from within the app, you can also use pyjnius library.

Upvotes: 0

Prokash Sarkar
Prokash Sarkar

Reputation: 11873

Scoped Storage brings two major changes:

  1. First, you no longer have access to a file via its path. Instead, you need to use its Uri.
  2. Second, if you want to modify a file not created by your app, you need to ask the user for permission.

By default, you are given access to an app-specific folder that can't be accessed by others apps. However, if you need to read/write the files in another location you have to explicitly ask for them. The scoped storage is similar to accessing a database. You request the Android API to perform the action and the system does it for you.

Similar Issues:

There's an open issue in Github that resembles a similar situation.

https://github.com/kivy/buildozer/issues/1304

Useful Resources:

I couldn't find any official documentation, but there's an experimental repository by Robert Flatt that uses the scope storage.

https://github.com/Android-for-Python/Storage-Example

He provided the storage.py class that implements an API for database access of this app's public storage. The shared storage operations provided are insert(), delete(), and recieve(), these copy files between this app's private and shared storage.

Suggestions for the code refactoring:

Scoped Storage isn't limited to asking the file permission only, hence you need to migrate all of your file operations this way.

  1. From Android API 29+ "WRITE_EXTERNAL_STORAGE" permission is redundant. Since you will be using the storage via the MediaStore/SAF you no longer need this permission.
  2. Whenever you are doing a file operation, check the Android version, and if it's below 29 continue with your existing code. Otherwise, use the storage.py class to read/write the file.
  3. If you already stored data outside of your app directory, you also need to move them to a folder inside your app directory.

Examples of using the storage.py class:

#######################
    #
    # Examples:
    # Where txt_file could be PrivateStorage().getFilesDir() + 'text.txt'
    # and so on.
    # All methods take a required file name, and optional directory parameters.
    #
    #   Insert:
    #   SharedStorage().insert(txt_file, 'Documents')
    #   SharedStorage().insert(txt_file, sub_dir= 'a/b')
    #   SharedStorage().insert(txt_file, 'Downloads')
    #   SharedStorage().insert(jpg_file, 'Pictures')
    #   SharedStorage().insert(mp3_file)
    #   SharedStorage().insert(ogg_file, 'Music')
    #
    #   Retrieve:
    #   path = SharedStorage().retrieve('test.txt')
    #   path = SharedStorage().retrieve('test.txt', 'Documents', 'a/b')
    #
    #   Delete:
    #   SharedStorage().delete('test.mp3', 'Music')
    #
    #   Retrieve from another app's storage (requires READ_EXTERNAL_STORAGE) :
    #   SharedStorage().retrieve('10_28_14.jpg', 'DCIM', '2021_03_12',
    #                            'CameraXF')
    #   SharedStorage().retrieve('10_33_48.mp4', 'DCIM', '2021_03_12',
    #                            'CameraXF')
    #
    #######################

Detailed instructions are given here.

Upvotes: 3

yum
yum

Reputation: 1263

After my research you should go with Pyjnius and directly access the SAF class from Python. Kivy also have some documentation pertaining to this: https://kivy.org/doc/stable/guide/android.html#pyjnius.

Upvotes: 2

Related Questions