Storage Functionalities

The page provides instructions on how to add storage/Vult functionalities to the sample app.

Storage Directories

The following directories lie inside the android studio project:

├── ui
│   ├── vult
│   │   ├── FilesAdapter.kt (Recycler view adapter for displaying uploaded files in VultFragment. )
│   │   ├── VultFragment.kt (Vult Fragment for creating, managing allocations and uploading, downloading files )
│   │   └── VultViewModel.kt (Handles data sources and core api methods for file upload, download and allocation creation.)

UI/vult/FilesAdapter.kt
package org.zus.bolt.helloworld.ui.vult

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import org.zus.bolt.helloworld.R
import org.zus.bolt.helloworld.models.vult.FileModel
import org.zus.bolt.helloworld.utils.Utils.Companion.getConvertedSize

class FilesAdapter(
    var files: List<FileModel>,
    private val onFileClickListener: FileClickListener,
) : RecyclerView.Adapter<FilesAdapter.ViewHolder>() {

    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {

        val fileName: TextView = view.findViewById(R.id.tvFolderName)
        val ivIcon: ImageView = view.findViewById(R.id.ivFileIcon)
        val downloadProgress: ProgressBar = view.findViewById(R.id.uploadsProgressBar)
        val fileSize: TextView = view.findViewById(R.id.textSize)
        val downloadButton: ImageView = view.findViewById(R.id.ivDownloadFile)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(
            LayoutInflater.from(parent.context).inflate(R.layout.row_view_files, parent, false)
        )
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.fileName.text = files[position].name
        holder.fileSize.text = files[position].size.getConvertedSize()
        if (files[position].mimetype.contains("image"))
            holder.ivIcon.setImageResource(R.drawable.ic_upload_image)
        else
            holder.ivIcon.setImageResource(R.drawable.ic_upload_document)
        holder.downloadButton.setOnClickListener {
            holder.downloadProgress.visibility = View.VISIBLE
            onFileClickListener.onDownloadFileClick(position)
        }
        holder.itemView.setOnClickListener {
            onFileClickListener.onFileClick(position)
        }
    }

    override fun getItemCount(): Int {
        return files.size
    }
}

interface FileClickListener {
    fun onDownloadFileClick(filePosition: Int)
    fun onFileClick(filePosition: Int)
}

Describing Code :

  • Line 3 to 12 import packages required to create view for displaying files

  • Line 14 defines a class FilesAdapter that aims to converts the list of files data into a RecyclerView.

    • In the class following variables are defined

      • files: To hold list of Files uploaded to Allocation(Line 15)

      • A private variable that holds value for list of files converted to recyclable view on a mouse click.(Line 16 )

      • Line 19 to 25 defines A ViewHolder class that describes how a file view and metadata about its place within the RecyclerView.

      • Line 28 to 32 overrides the method onCreateViewHolderwhich creates a new ViewHolder object and initializes some private fields to be used by RecyclerView.

      • Line 34 to 46 overrides onBindViewHolder method which is called by RecyclerView to display the files data at the specified position.

        • Also , If the download button is clicked for a document or image then also hold the view for download progress bar in a recyclable view.

    • Line 48 to 51 overrides the function getItemCount() to get the number of files in the list.

    • Line 53 to 55 defines an interface FileClickListener that executes functions on file click.

UI/vult/VultFragment.kt
package org.zus.bolt.helloworld.ui.vult

import android.app.Activity
import android.content.Intent
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.provider.MediaStore
import android.provider.OpenableColumns
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ProgressBar
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.zus.bolt.helloworld.R
import org.zus.bolt.helloworld.databinding.VultFragmentBinding
import org.zus.bolt.helloworld.ui.mainactivity.MainViewModel
import org.zus.bolt.helloworld.utils.Utils
import org.zus.bolt.helloworld.utils.Utils.Companion.getConvertedDateTime
import org.zus.bolt.helloworld.utils.Utils.Companion.getConvertedSize
import zcncore.Zcncore
import java.io.File
import java.io.FileOutputStream
import java.util.*


const val TAG_VULT = "VultFragment"

class VultFragment : Fragment(), FileClickListener {
    private lateinit var binding: VultFragmentBinding
    private lateinit var vultViewModel: VultViewModel
    private lateinit var mainViewModel: MainViewModel
    var downloadPath = ""

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View {
        // Inflate the layout for this fragment
        binding = VultFragmentBinding.inflate(inflater, container, false)
        vultViewModel = ViewModelProvider(requireActivity())[VultViewModel::class.java]
        mainViewModel = ViewModelProvider(requireActivity())[MainViewModel::class.java]

        downloadPath =
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath

        val documentPicker =
            registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
                if (result.resultCode == Activity.RESULT_OK) {
                    val resultIntent: Intent? = result.data
                    if (resultIntent != null) {
                        /* Uploading files. */
                        val uri = resultIntent.data!!
                        val fileName = Utils(requireContext()).getFileName(uri)
                        val filePath = makeFileCopyInCacheDir(uri)

                        Log.i(TAG_VULT, "Uri: $uri")
                        Log.i(TAG_VULT, "Uri path: ${uri.path}")
                        Log.i(TAG_VULT, "File name: $fileName")
                        Log.i(TAG_VULT, "File path: $filePath")

                        CoroutineScope(Dispatchers.IO).launch {
                            isRefresh(true)
                            vultViewModel.uploadFile(
                                requireContext().filesDir.absolutePath,
                                fileName,
                                filePath,
                                ""
                            )
                            isRefresh(false)
                        }
                    }
                }
            }

        val photoPicker =
            registerForActivityResult(PickVisualMedia()) { uri ->
                if (uri != null) {
                    /* Uploading files. */
                    val fileName = Utils(requireContext()).getFileName(uri)
                    val filePath = makeFileCopyInCacheDir(uri)

                    Log.i(TAG_VULT, "Uri: $uri")
                    Log.i(TAG_VULT, "Uri path: ${uri.path}")
                    Log.i(TAG_VULT, "File name: $fileName")
                    Log.i(TAG_VULT, "File path: $filePath")

                    CoroutineScope(Dispatchers.IO).launch {
                        isRefresh(true)
                        vultViewModel.uploadFile(
                            requireContext().filesDir.absolutePath,
                            fileName,
                            filePath,
                            ""
                        )
                        isRefresh(false)
                    }
                }
            }


        val filesAdapter = FilesAdapter(mutableListOf(), this)
        binding.rvAllFiles.layoutManager = LinearLayoutManager(requireContext())
        binding.rvAllFiles.adapter = filesAdapter


        vultViewModel.files.observe(viewLifecycleOwner) { files ->
            if (files != null)
                filesAdapter.files = files
            else
                filesAdapter.files = mutableListOf()
            filesAdapter.notifyDataSetChanged()
        }

        binding.cvUploadImage.setOnClickListener {
            photoPicker.launch(PickVisualMediaRequest(PickVisualMedia.ImageAndVideo))
        }

        binding.cvUploadDocument.setOnClickListener {
            val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
                addCategory(Intent.CATEGORY_OPENABLE)
                type = "*/*"
            }
            documentPicker.launch(intent)
        }
        binding.swipeRefreshLayout.setOnRefreshListener {
            CoroutineScope(Dispatchers.IO).launch {
                vultViewModel.listFiles("/")
                isRefresh(false)
            }
        }
        CoroutineScope(Dispatchers.IO).launch {
            isRefresh(true)

         Storage SDK initialization and wallet initialization.
            vultViewModel.storageSDK =
                VultViewModel.initZboxStorageSDK(
                    Utils(requireContext()).config,
                    Utils(requireContext()).readWalletFromFileJSON()
                )

            if (vultViewModel.getAllocation() == null) {
                vultViewModel.createAllocation(
                    allocationName = "test allocation",
                    dataShards = 2,
                    parityShards = 2,
                    allocationSize = 2147483648,
                    expirationSeconds = Date().time / 1000 + 30000,
                    lockTokens = Zcncore.convertToValue(1.0),
                )
                requireActivity().runOnUiThread {
                    binding.allocationProgressView.progress = 0
                    binding.tvAllocationDate.text = getString(R.string.no_allocation)
                    binding.tvStorageUsed.text = getString(R.string.no_allocation)
                }

            } else {
                vultViewModel.getAllocation().let { allocation ->
                    if (allocation?.id == null) {
                        throw Exception("Allocation id is null")
                    } else {
                        val statsModel = vultViewModel.getStats(allocation.stats)
                        requireActivity().runOnUiThread {
                            binding.allocationProgressView.progress =
                                ((statsModel.used_size / allocation.size) * 100).toInt()
                            binding.tvAllocationDate.text =
                                allocation.expiration.getConvertedDateTime()
                            binding.tvStorageUsed.text = getString(
                                R.string.storage_used,
                                statsModel.used_size.getConvertedSize(),
                                allocation.size.getConvertedSize()
                            )
                        }
                    }
                }
            }
            vultViewModel.getAllocation()
            vultViewModel.listFiles("/")
            isRefresh(false)
        }

        return binding.root
    }

    private fun makeFileCopyInCacheDir(contentUri: Uri): String? {
        try {
            val filePathColumn = arrayOf(
                //Base File
                MediaStore.Files.FileColumns._ID,
                MediaStore.Files.FileColumns.TITLE,
                MediaStore.Files.FileColumns.DATA,
                MediaStore.Files.FileColumns.SIZE,
                MediaStore.Files.FileColumns.DATE_ADDED,
                MediaStore.Files.FileColumns.DISPLAY_NAME,
                //Normal File
                MediaStore.MediaColumns.DATA,
                MediaStore.MediaColumns.MIME_TYPE,
                MediaStore.MediaColumns.DISPLAY_NAME
            )
            //val contentUri = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", File(mediaUrl))
            val returnCursor =
                contentUri.let {
                    requireContext().contentResolver.query(it,
                        filePathColumn,
                        null,
                        null,
                        null)
                }
            if (returnCursor != null) {
                returnCursor.moveToFirst()
                val nameIndex = returnCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)
                val name = returnCursor.getString(nameIndex)
                val file = File(requireContext().cacheDir, name)
                val inputStream = requireContext().contentResolver.openInputStream(contentUri)
                val outputStream = FileOutputStream(file)
                var read = 0
                val maxBufferSize = 1 * 1024 * 1024
                val bytesAvailable = inputStream!!.available()

                //int bufferSize = 1024;
                val bufferSize = Math.min(bytesAvailable, maxBufferSize)
                val buffers = ByteArray(bufferSize)
                while (inputStream.read(buffers).also { read = it } != -1) {
                    outputStream.write(buffers, 0, read)
                }
                inputStream.close()
                outputStream.close()
                Log.e("File Path", "Path " + file.path)
                Log.e("File Size", "Size " + file.length())
                return file.absolutePath
            }
        } catch (ex: Exception) {
            Log.e("Exception", ex.message!!)
        }
        return contentUri.let { Utils(requireContext()).getRealPathFromURI(it) }
    }

    private fun isRefresh(bool: Boolean) {
        requireActivity().runOnUiThread {
            binding.swipeRefreshLayout.isRefreshing = bool
        }
    }

    override fun onDownloadFileClick(filePosition: Int) {
        runBlocking {
            val downloadProgressBar = binding.rvAllFiles.getChildAt(filePosition)
                .findViewById<ProgressBar>(R.id.uploadsProgressBar)
            downloadProgressBar.visibility = View.VISIBLE
            Log.i(
                TAG_VULT,
                "File clicked: ${vultViewModel.files.value!![filePosition].name}"
            )
            //Create new folder in external directory.
            CoroutineScope(Dispatchers.IO).launch {
                vultViewModel.downloadFile(
                    vultViewModel.files.value!![filePosition].name,
                    downloadPath,
                )
                CoroutineScope(Dispatchers.Main).launch {
                    downloadProgressBar.visibility = View.GONE
                    val intentOpenDownloadedFile = Intent(Intent.ACTION_VIEW).apply {
                        setDataAndType(
                            Utils(requireContext()).getUriForFile(
                                File(
                                    downloadPath,
                                    vultViewModel.files.value!![filePosition].name
                                )
                            ),
                            vultViewModel.files.value!![filePosition].mimetype
                        )
                        flags = Intent.FLAG_ACTIVITY_NO_HISTORY
                    }
                    try {
                        startActivity(intentOpenDownloadedFile)
                    } catch (e: Exception) {
                        Log.e(TAG_VULT, "Error: ${e.message}")
                    }
                }
            }
        }
    }

    override fun onFileClick(filePosition: Int) {
        val file = File(
            downloadPath,
            vultViewModel.files.value!![filePosition].name
        )
        Log.i(TAG_VULT, "File clicked: ${file.absolutePath}")
        MediaScannerConnection.scanFile(requireContext(), arrayOf(file.absolutePath), null
        ) { _, uri ->
            if (uri == null) {
                Snackbar.make(binding.root,
                    "No file found Please Download first",
                    Snackbar.LENGTH_SHORT).show()
            } else {
                Log.i("onScanCompleted", uri.path ?: "No file found")
                val intentOpenDownloadedFile = Intent(Intent.ACTION_VIEW).apply {
                    setDataAndType(uri, vultViewModel.files.value!![filePosition].mimetype)
                    flags = Intent.FLAG_ACTIVITY_NO_HISTORY
                }
                try {
                    startActivity(intentOpenDownloadedFile)
                } catch (e: Exception) {
                    Log.e(TAG_VULT, "Error: ${e.message}")
                }
            }
        }
    }
}

Describing Code :

  • Line 3 to 36 import libraries required to create Vult UI .See Home page of app for reference

  • At line 41,Class VultFragment defines the following properties and fields for creating the Storage UI .

  • A late initialized private writable binding variable that can bind UI components in layouts to data sources in your app.(Line 42)

  • Line 43 and 44 defines variables to hold vultVIew and main app view.

  • Line 43 defines method for creating the Vult UI view

a) override fun. This is just us overriding the function called onCreateView that is inherited from the Fragment class.

b) onCreateView() method is called and assuming that we have provided our own VultFragment(Storage UI) with a non-null view(via view binding) the view returned from here will be visible to user.

  • Line 59 to 86 defines a file picker when user clicks on Upload File in the app. By default it takes file from our system home path. Check gif to see Upload File Picker

  • Line 88 to 111 defines a photo picker when user clicks on Upload Photo.Check gif to see Upload Photo Picker.

Line 114 to 192 make use of FilesAdapter and defines following

  • On mouse click listeners for Upload Image and Upload File and binds it to view(Line 127 to 137)

  • Line 148 to 152 makes use of functions defined by VultViewModel to retrieve allocation Data. If allocation data is not there create allocation.

  • Line 189 and 191 calls functions defined in VultVIew Model class to get allocation details and listed files

  • Line 197 defines function for making copies of file uploaded to Allocation.

  • Line 250 to 254 defines function onDownloadFileCliick for downloading the file on mouse click after uploading

  • Line 295 defines function onFIleClick for managing file click functionality in files uploaded to Allocation. In case file clicked does not exist prompts to download the file first and if file is not available for download then displays error no file found.

UI/vult/VultViewModel.kt
package org.zus.bolt.helloworld.ui.vult

import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.gson.Gson
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.zus.bolt.helloworld.models.blobber.BlobberNodeModel
import org.zus.bolt.helloworld.models.blobber.BlobbersUrlIdModel
import org.zus.bolt.helloworld.models.blobber.StatsModel
import org.zus.bolt.helloworld.models.vult.AllocationModel
import org.zus.bolt.helloworld.models.vult.FileModel
import org.zus.bolt.helloworld.models.vult.FileResponseModel
import sdk.Sdk
import sdk.StorageSDK
import zbox.Allocation
import zbox.StatusCallbackMocked

class VultViewModel : ViewModel() {
    lateinit var storageSDK: StorageSDK
    lateinit var allocationId: String
    lateinit var allocation: AllocationModel
    var files: MutableLiveData<List<FileModel>> = MutableLiveData()

    companion object {
        fun initZboxStorageSDK(config: String, walletJSON: String): StorageSDK =
            try {
                Sdk.init(config)
                Log.i(TAG_VULT, "initZboxStorageSDK: sdk initialized successfully")
                Sdk.initStorageSDK(walletJSON, config)
            } catch (e: Exception) {
                Log.e(TAG_VULT, "initZboxStorageSDK Exception: ", e)
                StorageSDK()
            }

    }

    private val statusCallbackMocked = object : StatusCallbackMocked {
        override fun commitMetaCompleted(p0: String?, p1: String?, p2: Exception?) {
            Log.d(TAG_VULT, "commitMetaCompleted: ")
            Log.d(TAG_VULT, "commitMetaCompleted: p0: $p0")
            Log.d(TAG_VULT, "commitMetaCompleted: p1: $p1")
            Log.d(TAG_VULT, "commitMetaCompleted: p2: $p2")
        }

        override fun completed(
            p0: String?,
            p1: String?,
            p2: String?,
            p3: String?,
            p4: Long,
            p5: Long,
        ) {
            Log.d(TAG_VULT, "completed: ")
            Log.d(TAG_VULT, "completed: p0: $p0")
            Log.d(TAG_VULT, "completed: p1: $p1")
            Log.d(TAG_VULT, "completed: p2: $p2")
            Log.d(TAG_VULT, "completed: p3: $p3")
            Log.d(TAG_VULT, "completed: p4: $p4")
            Log.d(TAG_VULT, "completed: p5: $p5")
            CoroutineScope(viewModelScope.coroutineContext).launch {
                listFiles("/")
            }
        }

        override fun error(p0: String?, p1: String?, p2: Long, p3: Exception?) {
            Log.d(TAG_VULT, "error: ")
            Log.d(TAG_VULT, "error: p0: $p0")
            Log.d(TAG_VULT, "error: p1: $p1")
            Log.d(TAG_VULT, "error: p2: $p2")
            Log.d(TAG_VULT, "error: p3: $p3")
        }

        override fun inProgress(p0: String?, p1: String?, p2: Long, p3: Long, p4: ByteArray?) {
            Log.d(TAG_VULT, "inProgress: ")
            Log.d(TAG_VULT, "inProgress: p0: $p0")
            Log.d(TAG_VULT, "inProgress: p1: $p1")
            Log.d(TAG_VULT, "inProgress: p2: $p2")
            Log.d(TAG_VULT, "inProgress: p3: $p3")
            Log.d(TAG_VULT, "inProgress: p4: $p4")
        }

        override fun repairCompleted(p0: Long) {
            Log.d(TAG_VULT, "repairCompleted: ")
            Log.d(TAG_VULT, "repairCompleted: p0: $p0")
        }

        override fun started(p0: String?, p1: String?, p2: Long, p3: Long) {
            Log.d(TAG_VULT, "started: ")
            Log.d(TAG_VULT, "started: p0: $p0")
            Log.d(TAG_VULT, "started: p1: $p1")
            Log.d(TAG_VULT, "started: p2: $p2")
            Log.d(TAG_VULT, "started: p3: $p3")
        }

    }

    fun createAllocation(
        allocationName: String,
        dataShards: Long,
        parityShards: Long,
        allocationSize: Long,
        expirationSeconds: Long,
        lockTokens: String,
    ) {
        Log.i(TAG_VULT, "createAllocation: ")
        Log.i(TAG_VULT, "createAllocation: allocationName: $allocationName")
        Log.i(TAG_VULT, "createAllocation: dataShards: $dataShards")
        Log.i(TAG_VULT, "createAllocation: parityShards: $parityShards")
        Log.i(TAG_VULT, "createAllocation: allocationSize: $allocationSize")
        Log.i(TAG_VULT, "createAllocation: expirationSeconds: $expirationSeconds")
        Log.i(TAG_VULT, "createAllocation: lockTokens: $lockTokens")

        try {
            storageSDK.createAllocation(
                allocationName,
                dataShards,
                parityShards,
                allocationSize,
                expirationSeconds,
                lockTokens
            )
            Log.i(TAG_VULT, "createAllocation: successfully created allocation")
        } catch (e: Exception) {
            Log.e(TAG_VULT, "createAllocation Exception: ", e)
        }
    }

    fun createAllocationWithBlobber(
        allocationName: String,
        dataShards: Long,
        parityShards: Long,
        allocationSize: Long,
        expirationSeconds: Long,
        lockTokens: String,
        blobbersUrls: String,
        blobberIds: String,
    ) {
        Log.i(TAG_VULT, "createAllocationWithBlobber: ")
        Log.i(TAG_VULT, "createAllocationWithBlobber: allocationName: $allocationName")
        Log.i(TAG_VULT, "createAllocationWithBlobber: dataShards: $dataShards")
        Log.i(TAG_VULT, "createAllocationWithBlobber: parityShards: $parityShards")
        Log.i(TAG_VULT, "createAllocationWithBlobber: allocationSize: $allocationSize")
        Log.i(
            TAG_VULT,
            "createAllocationWithBlobber: expirationSeconds: $expirationSeconds"
        )
        Log.i(TAG_VULT, "createAllocationWithBlobber: lockTokens: $lockTokens")
        Log.i(TAG_VULT, "createAllocationWithBlobber: blobbers: $blobbersUrls")
        Log.i(TAG_VULT, "createAllocationWithBlobber: blobberIds: $blobberIds")

        try {
            storageSDK.createAllocationWithBlobbers(
                allocationName,
                dataShards,
                parityShards,
                allocationSize,
                expirationSeconds,
                lockTokens,
                blobbersUrls,
                blobberIds
            )
            Log.i(TAG_VULT, "createAllocationWithBlobber: successfully created allocation")
        } catch (e: Exception) {
            Log.e(TAG_VULT, "createAllocationWithBlobber Exception: ", e)
        }
    }

    suspend fun getAllocation(): Allocation? {
        return withContext(Dispatchers.IO) {
            try {
                val allocations: AllocationModel?
                if (::allocation.isInitialized) {
                    allocations = allocation
                } else {
                    Log.i(TAG_VULT, "getAllocation: allocations json: ${storageSDK.allocations}")
                    allocations =
                        Gson().fromJson(storageSDK.allocations, AllocationModel::class.java)
                }
                storageSDK.getAllocation(allocations?.get(0)!!.id)
            } catch (e: Exception) {
                Log.e(TAG_VULT, "getAllocation Exception: ", e)
                null
            }
        }
    }

    suspend fun getAllocationModel(): AllocationModel? {
        return withContext(Dispatchers.IO) {
            try {
                val allocations: AllocationModel?
                if (::allocation.isInitialized) {
                    allocations = allocation
                } else {
                    Log.i(TAG_VULT, "getAllocation: allocations json: ${storageSDK.allocations}")
                    allocations =
                        Gson().fromJson(storageSDK.allocations, AllocationModel::class.java)
                }
                return@withContext allocations
            } catch (e: Exception) {
                Log.e(TAG_VULT, "getAllocation Exception: ", e)
                return@withContext null
            }
        }
    }

    suspend fun uploadFile(
        workDir: String,
        fileName: String,
        filePathURI: String?,
        fileAttr: String?,
    ) {
        withContext(Dispatchers.IO) {
            Log.i(TAG_VULT, "uploadFile: workDir: $workDir")
            Log.i(TAG_VULT, "uploadFile: fileName: $fileName")
            Log.i(TAG_VULT, "uploadFile: filePathURI: $filePathURI")
            Log.i(TAG_VULT, "uploadFile: fileAttr: $fileAttr")
            try {
                getAllocation()?.uploadFile(
                    /*work dir =*/
                    workDir,
                    /* local path =*/
                    filePathURI,
                    /* remote path =*/
                    "/$fileName",
                    /*file attrs =*/
                    fileAttr,
                    false,
                    statusCallbackMocked
                )
            } catch (e: Exception) {
                Log.e(TAG_VULT, "uploadFile Exception: ", e)
            }
        }
    }

    suspend fun downloadFile(fileName: String, downloadPath: String) {
        withContext(Dispatchers.IO) {
            Log.i(TAG_VULT, "downloadFile: ")
            Log.i(TAG_VULT, "downloadFile: fileName: $fileName")
            Log.i(TAG_VULT, "downloadFile: downloadPath: $downloadPath")
            try {
                getAllocation()?.downloadFile(
                    /* remote path =*/
                    "/$fileName",
                    /* file local download path =*/
                    downloadPath,
                    statusCallbackMocked
                )
            } catch (e: Exception) {
                Log.e(TAG_VULT, "downloadFile Exception: ", e)
            }
        }
    }

    suspend fun listFiles(remotePath: String) {
        return withContext(Dispatchers.IO) {
            getAllocation()?.let { allocation ->
                try {
                    val json = allocation.listDir(remotePath)
                    Log.i(TAG_VULT, "listFiles: json: $json")
                    val files = Gson().fromJson(json, FileResponseModel::class.java)
                    this@VultViewModel.files.postValue(files.list)
                } catch (e: Exception) {
                    Log.e(TAG_VULT, "listFiles Exception: ", e)
                }
            }
        }
    }

    fun getBlobberUrlsAndId(): BlobbersUrlIdModel {
        val blobbers = getBlobbers()
        val blobberUrls = blobbers.map { it.url }
        val blobberIds = blobbers.map { it.id }
        return BlobbersUrlIdModel(