mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-07-29 07:58:43 +02:00
Fix: download single track audiobooks #15, Change: check write permission when selecting folder #13, Add: Show folders and files list in user selected folder, Fix: Seek back only if audiobook was played #20
This commit is contained in:
@@ -2,28 +2,19 @@ package com.audiobookshelf.app
|
||||
|
||||
import android.app.DownloadManager
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import com.anggrayudi.storage.SimpleStorage
|
||||
import com.anggrayudi.storage.SimpleStorageHelper
|
||||
import com.anggrayudi.storage.callback.FileCallback
|
||||
import com.anggrayudi.storage.callback.FolderPickerCallback
|
||||
import com.anggrayudi.storage.callback.StorageAccessCallback
|
||||
import com.anggrayudi.storage.file.*
|
||||
import com.anggrayudi.storage.media.FileDescription
|
||||
|
||||
import com.getcapacitor.JSObject
|
||||
import com.getcapacitor.Plugin
|
||||
import com.getcapacitor.PluginCall
|
||||
import com.getcapacitor.PluginMethod
|
||||
import com.getcapacitor.annotation.CapacitorPlugin
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
|
||||
|
||||
@@ -34,219 +25,117 @@ class AudioDownloader : Plugin() {
|
||||
lateinit var mainActivity:MainActivity
|
||||
lateinit var downloadManager:DownloadManager
|
||||
|
||||
data class AudiobookItem(val uri: Uri, val name: String, val size: Long, val coverUrl: String) {
|
||||
fun toJSObject() : JSObject {
|
||||
var obj = JSObject()
|
||||
obj.put("uri", this.uri)
|
||||
obj.put("name", this.name)
|
||||
obj.put("size", this.size)
|
||||
obj.put("coverUrl", this.coverUrl)
|
||||
return obj
|
||||
}
|
||||
}
|
||||
// data class AudiobookItem(val uri: Uri, val name: String, val size: Long, val coverUrl: String) {
|
||||
// fun toJSObject() : JSObject {
|
||||
// var obj = JSObject()
|
||||
// obj.put("uri", this.uri)
|
||||
// obj.put("name", this.name)
|
||||
// obj.put("size", this.size)
|
||||
// obj.put("coverUrl", this.coverUrl)
|
||||
// return obj
|
||||
// }
|
||||
// }
|
||||
|
||||
override fun load() {
|
||||
mainActivity = (activity as MainActivity)
|
||||
downloadManager = activity.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
// storage = SimpleStorage(mainActivity)
|
||||
|
||||
var recieverEvent : (evt: String, id: Long) -> Unit = { evt: String, id: Long ->
|
||||
if (evt == "complete") {}
|
||||
var recieverEvent: (evt: String, id: Long) -> Unit = { evt: String, id: Long ->
|
||||
if (evt == "complete") {
|
||||
}
|
||||
if (evt == "clicked") {
|
||||
Log.d(tag, "Clicked $id back in the audiodownloader")
|
||||
}
|
||||
}
|
||||
mainActivity.registerBroadcastReceiver(recieverEvent)
|
||||
|
||||
|
||||
setupSimpleStorage()
|
||||
|
||||
Log.d(tag, "Build SDK ${Build.VERSION.SDK_INT}")
|
||||
// Android 9 OR Below Request Permissions
|
||||
// if (Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.P) {
|
||||
// Log.d(tag, "Requires Permission")
|
||||
//// storage.requestStorageAccess(9)
|
||||
// var jsobj = JSObject()
|
||||
// jsobj.put("value", "required")
|
||||
// notifyListeners("permission", jsobj)
|
||||
// } else {
|
||||
// Log.d(tag, "Does not request permission")
|
||||
}
|
||||
|
||||
|
||||
// @PluginMethod
|
||||
// fun load(call: PluginCall) {
|
||||
// var audiobookUrls = call.data.getJSONArray("audiobookUrls")
|
||||
// var len = audiobookUrls?.length()
|
||||
// if (len == null) {
|
||||
// len = 0
|
||||
// }
|
||||
}
|
||||
|
||||
private fun setupSimpleStorage() {
|
||||
mainActivity.storageHelper.onFolderSelected = { requestCode, folder ->
|
||||
Log.d(tag, "FOLDER SELECTED $requestCode ${folder.name} ${folder.uri}")
|
||||
var jsobj = JSObject()
|
||||
jsobj.put("value", "granted")
|
||||
jsobj.put("uri", folder.uri)
|
||||
jsobj.put("absolutePath", folder.getAbsolutePath(context))
|
||||
jsobj.put("storageId", folder.getStorageId(context))
|
||||
jsobj.put("storageType", folder.getStorageType(context))
|
||||
jsobj.put("simplePath", folder.getSimplePath(context))
|
||||
jsobj.put("basePath", folder.getBasePath(context))
|
||||
notifyListeners("permission", jsobj)
|
||||
}
|
||||
|
||||
mainActivity.storage.storageAccessCallback = object : StorageAccessCallback {
|
||||
override fun onRootPathNotSelected(
|
||||
requestCode: Int,
|
||||
rootPath: String,
|
||||
uri: Uri,
|
||||
selectedStorageType: StorageType,
|
||||
expectedStorageType: StorageType
|
||||
) {
|
||||
Log.d(tag, "STORAGE ACCESS CALLBACK")
|
||||
}
|
||||
|
||||
override fun onCanceledByUser(requestCode: Int) {
|
||||
Log.d(tag, "STORAGE ACCESS CALLBACK")
|
||||
}
|
||||
|
||||
override fun onExpectedStorageNotSelected(requestCode: Int, selectedFolder: DocumentFile, selectedStorageType: StorageType, expectedBasePath: String, expectedStorageType: StorageType) {
|
||||
Log.d(tag, "STORAGE ACCESS CALLBACK")
|
||||
}
|
||||
|
||||
override fun onStoragePermissionDenied(requestCode: Int) {
|
||||
Log.d(tag, "STORAGE ACCESS CALLBACK")
|
||||
}
|
||||
|
||||
override fun onRootPathPermissionGranted(requestCode: Int, root: DocumentFile) {
|
||||
Log.d(tag, "STORAGE ACCESS CALLBACK")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
@PluginMethod
|
||||
fun requestStoragePermission(call: PluginCall) {
|
||||
Log.d(tag, "Request Storage Permissions")
|
||||
mainActivity.storageHelper.requestStorageAccess()
|
||||
call.resolve()
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun checkStoragePermission(call: PluginCall) {
|
||||
var res = false
|
||||
|
||||
if (Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.P) {
|
||||
res = SimpleStorage.hasStoragePermission(context)
|
||||
Log.d(tag, "Check Storage Access $res")
|
||||
} else {
|
||||
Log.d(tag, "Has permission on Android 10 or up")
|
||||
res = true
|
||||
}
|
||||
|
||||
var jsobj = JSObject()
|
||||
jsobj.put("value", res)
|
||||
call.resolve(jsobj)
|
||||
}
|
||||
|
||||
fun checkUriExists(uri: Uri?): Boolean {
|
||||
if (uri == null) return false
|
||||
val resolver = context.contentResolver
|
||||
//1. Check Uri
|
||||
var cursor: Cursor? = null
|
||||
val isUriExist: Boolean = try {
|
||||
cursor = resolver.query(uri, null, null, null, null)
|
||||
//cursor null: content Uri was invalid or some other error occurred
|
||||
//cursor.moveToFirst() false: Uri was ok but no entry found.
|
||||
(cursor != null && cursor.moveToFirst())
|
||||
} catch (t: Throwable) {
|
||||
false
|
||||
} finally {
|
||||
try {
|
||||
cursor?.close()
|
||||
} catch (t: Throwable) {
|
||||
}
|
||||
false
|
||||
}
|
||||
return isUriExist
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun load(call: PluginCall) {
|
||||
var audiobookUrls = call.data.getJSONArray("audiobookUrls")
|
||||
var len = audiobookUrls?.length()
|
||||
if (len == null) {
|
||||
len = 0
|
||||
}
|
||||
Log.d(tag, "CALLED LOAD $len")
|
||||
var audiobookItems:MutableList<AudiobookItem> = mutableListOf()
|
||||
|
||||
(0 until len).forEach {
|
||||
var jsobj = audiobookUrls.get(it) as JSONObject
|
||||
var audiobookUrl = jsobj.get("contentUrl").toString()
|
||||
var coverUrl = jsobj.get("coverUrl").toString()
|
||||
var storageId = ""
|
||||
if(jsobj.has("storageId")) jsobj.get("storageId").toString()
|
||||
|
||||
var basePath = ""
|
||||
if(jsobj.has("basePath")) jsobj.get("basePath").toString()
|
||||
|
||||
var coverBasePath = ""
|
||||
if(jsobj.has("coverBasePath")) jsobj.get("coverBasePath").toString()
|
||||
|
||||
Log.d(tag, "LOOKUP $storageId $basePath $audiobookUrl")
|
||||
|
||||
var audiobookFile: DocumentFile? = null
|
||||
var coverFile: DocumentFile? = null
|
||||
|
||||
// Android 9 OR Below use storage id and base path
|
||||
if (Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.P) {
|
||||
audiobookFile = DocumentFileCompat.fromSimplePath(context, storageId, basePath)
|
||||
if (coverUrl != null && coverUrl != "") {
|
||||
coverFile = DocumentFileCompat.fromSimplePath(context, storageId, coverBasePath)
|
||||
}
|
||||
} else {
|
||||
// Android 10 and up manually deleting will still load the file causing crash
|
||||
var exists = checkUriExists(Uri.parse(audiobookUrl))
|
||||
if (exists) {
|
||||
Log.d(tag, "Audiobook exists")
|
||||
audiobookFile = DocumentFileCompat.fromUri(context, Uri.parse(audiobookUrl))
|
||||
} else {
|
||||
Log.e(tag, "Audiobook does not exist")
|
||||
}
|
||||
|
||||
var coverExists = checkUriExists(Uri.parse(coverUrl))
|
||||
if (coverExists) {
|
||||
Log.d(tag, "Cover Exists")
|
||||
coverFile = DocumentFileCompat.fromUri(context, Uri.parse(coverUrl))
|
||||
} else if (coverUrl != null && coverUrl != "") {
|
||||
Log.e(tag, "Cover does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
if (audiobookFile == null) {
|
||||
Log.e(tag, "Audiobook was not found $audiobookUrl")
|
||||
} else {
|
||||
Log.d(tag, "Audiobook File Found StorageId:${audiobookFile.getStorageId(context)} | AbsolutePath:${audiobookFile.getAbsolutePath(context)} | BasePath:${audiobookFile.getBasePath(context)}")
|
||||
|
||||
var _name = audiobookFile.name
|
||||
if (_name == null) _name = ""
|
||||
|
||||
var size = audiobookFile.length()
|
||||
|
||||
if (audiobookFile.uri.toString() !== audiobookUrl) {
|
||||
Log.d(tag, "Audiobook URI ${audiobookFile.uri} is different from $audiobookUrl => using the latter")
|
||||
}
|
||||
|
||||
// Use existing URI's - bug happening where new uri is different from initial
|
||||
var abItem = AudiobookItem(Uri.parse(audiobookUrl), _name, size, coverUrl)
|
||||
|
||||
Log.d(tag, "Setting AB ITEM ${abItem.name} | ${abItem.size} | ${abItem.uri} | ${abItem.coverUrl}")
|
||||
|
||||
audiobookItems.add(abItem)
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(tag, "Load Finished ${audiobookItems.size} found")
|
||||
|
||||
var audiobookObjs:List<JSObject> = audiobookItems.map{ it.toJSObject() }
|
||||
var mediaItemNoticePayload = JSObject()
|
||||
mediaItemNoticePayload.put("items", audiobookObjs)
|
||||
notifyListeners("onMediaLoaded", mediaItemNoticePayload)
|
||||
}
|
||||
// Log.d(tag, "CALLED LOAD $len")
|
||||
// var audiobookItems:MutableList<AudiobookItem> = mutableListOf()
|
||||
//
|
||||
// (0 until len).forEach {
|
||||
// var jsobj = audiobookUrls.get(it) as JSONObject
|
||||
// var audiobookUrl = jsobj.get("contentUrl").toString()
|
||||
// var coverUrl = jsobj.get("coverUrl").toString()
|
||||
// var storageId = ""
|
||||
// if(jsobj.has("storageId")) jsobj.get("storageId").toString()
|
||||
//
|
||||
// var basePath = ""
|
||||
// if(jsobj.has("basePath")) jsobj.get("basePath").toString()
|
||||
//
|
||||
// var coverBasePath = ""
|
||||
// if(jsobj.has("coverBasePath")) jsobj.get("coverBasePath").toString()
|
||||
//
|
||||
// Log.d(tag, "LOOKUP $storageId $basePath $audiobookUrl")
|
||||
//
|
||||
// var audiobookFile: DocumentFile? = null
|
||||
// var coverFile: DocumentFile? = null
|
||||
//
|
||||
// // Android 9 OR Below use storage id and base path
|
||||
// if (Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.P) {
|
||||
// audiobookFile = DocumentFileCompat.fromSimplePath(context, storageId, basePath)
|
||||
// if (coverUrl != null && coverUrl != "") {
|
||||
// coverFile = DocumentFileCompat.fromSimplePath(context, storageId, coverBasePath)
|
||||
// }
|
||||
// } else {
|
||||
// // Android 10 and up manually deleting will still load the file causing crash
|
||||
// var exists = checkUriExists(Uri.parse(audiobookUrl))
|
||||
// if (exists) {
|
||||
// Log.d(tag, "Audiobook exists")
|
||||
// audiobookFile = DocumentFileCompat.fromUri(context, Uri.parse(audiobookUrl))
|
||||
// } else {
|
||||
// Log.e(tag, "Audiobook does not exist")
|
||||
// }
|
||||
//
|
||||
// var coverExists = checkUriExists(Uri.parse(coverUrl))
|
||||
// if (coverExists) {
|
||||
// Log.d(tag, "Cover Exists")
|
||||
// coverFile = DocumentFileCompat.fromUri(context, Uri.parse(coverUrl))
|
||||
// } else if (coverUrl != null && coverUrl != "") {
|
||||
// Log.e(tag, "Cover does not exist")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (audiobookFile == null) {
|
||||
// Log.e(tag, "Audiobook was not found $audiobookUrl")
|
||||
// } else {
|
||||
// Log.d(tag, "Audiobook File Found StorageId:${audiobookFile.getStorageId(context)} | AbsolutePath:${audiobookFile.getAbsolutePath(context)} | BasePath:${audiobookFile.getBasePath(context)}")
|
||||
//
|
||||
// var _name = audiobookFile.name
|
||||
// if (_name == null) _name = ""
|
||||
//
|
||||
// var size = audiobookFile.length()
|
||||
//
|
||||
// if (audiobookFile.uri.toString() !== audiobookUrl) {
|
||||
// Log.d(tag, "Audiobook URI ${audiobookFile.uri} is different from $audiobookUrl => using the latter")
|
||||
// }
|
||||
//
|
||||
// // Use existing URI's - bug happening where new uri is different from initial
|
||||
// var abItem = AudiobookItem(Uri.parse(audiobookUrl), _name, size, coverUrl)
|
||||
//
|
||||
// Log.d(tag, "Setting AB ITEM ${abItem.name} | ${abItem.size} | ${abItem.uri} | ${abItem.coverUrl}")
|
||||
//
|
||||
// audiobookItems.add(abItem)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Log.d(tag, "Load Finished ${audiobookItems.size} found")
|
||||
//
|
||||
// var audiobookObjs:List<JSObject> = audiobookItems.map{ it.toJSObject() }
|
||||
// var mediaItemNoticePayload = JSObject()
|
||||
// mediaItemNoticePayload.put("items", audiobookObjs)
|
||||
// notifyListeners("onMediaLoaded", mediaItemNoticePayload)
|
||||
// }
|
||||
|
||||
@PluginMethod
|
||||
fun download(call: PluginCall) {
|
||||
@@ -399,74 +288,6 @@ class AudioDownloader : Plugin() {
|
||||
call.resolve(ret)
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun selectFolder(call: PluginCall) {
|
||||
mainActivity.storage.folderPickerCallback = object : FolderPickerCallback {
|
||||
override fun onFolderSelected(requestCode: Int, folder: DocumentFile) {
|
||||
Log.d(tag, "ONF OLDER SELECRTED ${folder.uri} ${folder.name}")
|
||||
|
||||
var absolutePath = folder.getAbsolutePath(activity)
|
||||
var storageId = folder.getStorageId(activity)
|
||||
var storageType = folder.getStorageType(activity)
|
||||
var simplePath = folder.getSimplePath(activity)
|
||||
var basePath = folder.getBasePath(activity)
|
||||
|
||||
var jsobj = JSObject()
|
||||
jsobj.put("uri", folder.uri)
|
||||
jsobj.put("absolutePath", absolutePath)
|
||||
jsobj.put("storageId", storageId)
|
||||
jsobj.put("storageType", storageType)
|
||||
jsobj.put("simplePath", simplePath)
|
||||
jsobj.put("basePath", basePath)
|
||||
call.resolve(jsobj)
|
||||
}
|
||||
|
||||
override fun onStorageAccessDenied(requestCode: Int, folder: DocumentFile?, storageType: StorageType) {
|
||||
Log.e(tag, "STORAGE ACCESS DENIED")
|
||||
var jsobj = JSObject()
|
||||
jsobj.put("error", "Access Denied")
|
||||
call.resolve(jsobj)
|
||||
}
|
||||
|
||||
override fun onStoragePermissionDenied(requestCode: Int) {
|
||||
Log.d(tag, "STORAGE PERMISSION DENIED $requestCode")
|
||||
var jsobj = JSObject()
|
||||
jsobj.put("error", "Permission Denied")
|
||||
call.resolve(jsobj)
|
||||
}
|
||||
}
|
||||
mainActivity.storage.openFolderPicker(6)
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun delete(call: PluginCall) {
|
||||
var url = call.data.getString("url", "").toString()
|
||||
var coverUrl = call.data.getString("coverUrl", "").toString()
|
||||
var folderUrl = call.data.getString("folderUrl", "").toString()
|
||||
|
||||
if (folderUrl != "") {
|
||||
Log.d(tag, "CALLED DELETE FIOLDER: $folderUrl")
|
||||
var folder = DocumentFileCompat.fromUri(context, Uri.parse(folderUrl))
|
||||
var success = folder?.deleteRecursively(context)
|
||||
var jsobj = JSObject()
|
||||
jsobj.put("success", success)
|
||||
call.resolve()
|
||||
} else {
|
||||
// Older audiobooks did not store a folder url, use cover and audiobook url
|
||||
var abExists = checkUriExists(Uri.parse(url))
|
||||
if (abExists) {
|
||||
var abfile = DocumentFileCompat.fromUri(context, Uri.parse(url))
|
||||
abfile?.delete()
|
||||
}
|
||||
|
||||
var coverExists = checkUriExists(Uri.parse(coverUrl))
|
||||
if (coverExists) {
|
||||
var coverfile = DocumentFileCompat.fromUri(context, Uri.parse(coverUrl))
|
||||
coverfile?.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class DownloadProgressUpdater(private val manager: DownloadManager, private val downloadId: Long, private var receiver: (Long, Long) -> Unit, private var doneReceiver: (Long, Boolean) -> Unit) : Thread() {
|
||||
private val query: DownloadManager.Query = DownloadManager.Query()
|
||||
private var totalBytes: Int = 0
|
||||
|
||||
@@ -43,6 +43,7 @@ class MainActivity : BridgeActivity() {
|
||||
Log.d(tag, "onCreate")
|
||||
registerPlugin(MyNativeAudio::class.java)
|
||||
registerPlugin(AudioDownloader::class.java)
|
||||
registerPlugin(StorageManager::class.java)
|
||||
|
||||
var filter = IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE).apply {
|
||||
addAction(DownloadManager.ACTION_NOTIFICATION_CLICKED)
|
||||
|
||||
@@ -263,7 +263,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
}
|
||||
|
||||
override fun getSupportedPrepareActions(): Long {
|
||||
Log.d(tag, "GET SUPORTED ACITONS")
|
||||
return PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID or
|
||||
PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID or
|
||||
PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH or
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
package com.audiobookshelf.app
|
||||
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
|
||||
import com.anggrayudi.storage.SimpleStorage
|
||||
import com.anggrayudi.storage.callback.FolderPickerCallback
|
||||
import com.anggrayudi.storage.callback.StorageAccessCallback
|
||||
import com.anggrayudi.storage.file.*
|
||||
import com.getcapacitor.JSObject
|
||||
import com.getcapacitor.Plugin
|
||||
import com.getcapacitor.PluginCall
|
||||
import com.getcapacitor.PluginMethod
|
||||
import com.getcapacitor.annotation.CapacitorPlugin
|
||||
|
||||
@CapacitorPlugin(name = "StorageManager")
|
||||
class StorageManager : Plugin() {
|
||||
private val TAG = "StorageManager"
|
||||
|
||||
lateinit var mainActivity:MainActivity
|
||||
|
||||
data class MediaFile(val uri: Uri, val name: String, val simplePath: String, val size: Long, val type: String, val isAudio: Boolean) {
|
||||
fun toJSObject() : JSObject {
|
||||
var obj = JSObject()
|
||||
obj.put("uri", this.uri)
|
||||
obj.put("name", this.name)
|
||||
obj.put("simplePath", this.simplePath)
|
||||
obj.put("size", this.size)
|
||||
obj.put("type", this.type)
|
||||
obj.put("isAudio", this.isAudio)
|
||||
return obj
|
||||
}
|
||||
}
|
||||
|
||||
data class MediaFolder(val uri: Uri, val name: String, val simplePath: String, val mediaFiles:List<MediaFile>) {
|
||||
fun toJSObject() : JSObject {
|
||||
var obj = JSObject()
|
||||
obj.put("uri", this.uri)
|
||||
obj.put("name", this.name)
|
||||
obj.put("simplePath", this.simplePath)
|
||||
obj.put("files", this.mediaFiles.map { it.toJSObject() })
|
||||
return obj
|
||||
}
|
||||
}
|
||||
|
||||
override fun load() {
|
||||
mainActivity = (activity as MainActivity)
|
||||
|
||||
mainActivity.storage.storageAccessCallback = object : StorageAccessCallback {
|
||||
override fun onRootPathNotSelected(
|
||||
requestCode: Int,
|
||||
rootPath: String,
|
||||
uri: Uri,
|
||||
selectedStorageType: StorageType,
|
||||
expectedStorageType: StorageType
|
||||
) {
|
||||
Log.d(TAG, "STORAGE ACCESS CALLBACK")
|
||||
}
|
||||
|
||||
override fun onCanceledByUser(requestCode: Int) {
|
||||
Log.d(TAG, "STORAGE ACCESS CALLBACK")
|
||||
}
|
||||
|
||||
override fun onExpectedStorageNotSelected(requestCode: Int, selectedFolder: DocumentFile, selectedStorageType: StorageType, expectedBasePath: String, expectedStorageType: StorageType) {
|
||||
Log.d(TAG, "STORAGE ACCESS CALLBACK")
|
||||
}
|
||||
|
||||
override fun onStoragePermissionDenied(requestCode: Int) {
|
||||
Log.d(TAG, "STORAGE ACCESS CALLBACK")
|
||||
}
|
||||
|
||||
override fun onRootPathPermissionGranted(requestCode: Int, root: DocumentFile) {
|
||||
Log.d(TAG, "STORAGE ACCESS CALLBACK")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun selectFolder(call: PluginCall) {
|
||||
mainActivity.storage.folderPickerCallback = object : FolderPickerCallback {
|
||||
override fun onFolderSelected(requestCode: Int, folder: DocumentFile) {
|
||||
Log.d(TAG, "ON FOLDER SELECTED ${folder.uri} ${folder.name}")
|
||||
|
||||
var absolutePath = folder.getAbsolutePath(activity)
|
||||
var storageId = folder.getStorageId(activity)
|
||||
var storageType = folder.getStorageType(activity)
|
||||
var simplePath = folder.getSimplePath(activity)
|
||||
var basePath = folder.getBasePath(activity)
|
||||
|
||||
var jsobj = JSObject()
|
||||
jsobj.put("uri", folder.uri)
|
||||
jsobj.put("absolutePath", absolutePath)
|
||||
jsobj.put("storageId", storageId)
|
||||
jsobj.put("storageType", storageType)
|
||||
jsobj.put("simplePath", simplePath)
|
||||
jsobj.put("basePath", basePath)
|
||||
call.resolve(jsobj)
|
||||
}
|
||||
|
||||
override fun onStorageAccessDenied(requestCode: Int, folder: DocumentFile?, storageType: StorageType) {
|
||||
Log.e(TAG, "STORAGE ACCESS DENIED")
|
||||
var jsobj = JSObject()
|
||||
jsobj.put("error", "Access Denied")
|
||||
call.resolve(jsobj)
|
||||
}
|
||||
|
||||
override fun onStoragePermissionDenied(requestCode: Int) {
|
||||
Log.d(TAG, "STORAGE PERMISSION DENIED $requestCode")
|
||||
var jsobj = JSObject()
|
||||
jsobj.put("error", "Permission Denied")
|
||||
call.resolve(jsobj)
|
||||
}
|
||||
}
|
||||
mainActivity.storage.openFolderPicker(6)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
@PluginMethod
|
||||
fun requestStoragePermission(call: PluginCall) {
|
||||
Log.d(TAG, "Request Storage Permissions")
|
||||
mainActivity.storageHelper.requestStorageAccess()
|
||||
call.resolve()
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun checkStoragePermission(call: PluginCall) {
|
||||
var res = false
|
||||
|
||||
if (Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.P) {
|
||||
res = SimpleStorage.hasStoragePermission(context)
|
||||
Log.d(TAG, "Check Storage Access $res")
|
||||
} else {
|
||||
Log.d(TAG, "Has permission on Android 10 or up")
|
||||
res = true
|
||||
}
|
||||
|
||||
var jsobj = JSObject()
|
||||
jsobj.put("value", res)
|
||||
call.resolve(jsobj)
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun checkFolderPermissions(call: PluginCall) {
|
||||
var folderUrl = call.data.getString("folderUrl", "").toString()
|
||||
Log.d(TAG, "Check Folder Permissions for $folderUrl")
|
||||
|
||||
var hasAccess = SimpleStorage.hasStorageAccess(context,folderUrl,true)
|
||||
|
||||
var jsobj = JSObject()
|
||||
jsobj.put("value", hasAccess)
|
||||
call.resolve(jsobj)
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun searchFolder(call: PluginCall) {
|
||||
var folderUrl = call.data.getString("folderUrl", "").toString()
|
||||
Log.d(TAG, "Searching folder $folderUrl")
|
||||
|
||||
var df: DocumentFile = DocumentFileCompat.fromUri(context, Uri.parse(folderUrl))!!
|
||||
Log.d(TAG, "Folder as DF ${df.isDirectory} | ${df.getSimplePath(context)} | ${df.getBasePath(context)} | ${df.name}")
|
||||
|
||||
var mediaFolders = mutableListOf<MediaFolder>()
|
||||
var foldersFound = df.search(false, DocumentFileType.FOLDER)
|
||||
|
||||
foldersFound.forEach {
|
||||
Log.d(TAG, "Iterating over Folder Found ${it.name} | ${it.getSimplePath(context)} | URI: ${it.uri}")
|
||||
var folderName = it.name ?: ""
|
||||
var mediaFiles = mutableListOf<MediaFile>()
|
||||
|
||||
var filesInFolder = it.search(false, DocumentFileType.FILE, arrayOf("audio/*", "image/*"))
|
||||
filesInFolder.forEach { it2 ->
|
||||
var mimeType = it2?.mimeType ?: ""
|
||||
var filename = it2?.name ?: ""
|
||||
var isAudio = mimeType.startsWith("audio")
|
||||
Log.d(TAG, "Found $mimeType file $filename in folder $folderName")
|
||||
var imageFile = MediaFile(it2.uri, filename, it2.getSimplePath(context), it2.length(), mimeType, isAudio)
|
||||
mediaFiles.add(imageFile)
|
||||
}
|
||||
if (mediaFiles.size > 0) {
|
||||
mediaFolders.add(MediaFolder(it.uri, folderName, it.getSimplePath(context), mediaFiles))
|
||||
}
|
||||
}
|
||||
|
||||
// Files in root dir
|
||||
var rootMediaFiles = mutableListOf<MediaFile>()
|
||||
var mediaFilesFound:List<DocumentFile> = df.search(false, DocumentFileType.FILE, arrayOf("audio/*", "image/*"))
|
||||
mediaFilesFound.forEach {
|
||||
Log.d(TAG, "Folder Root File Found ${it.name} | ${it.getSimplePath(context)} | URI: ${it.uri} | ${it.mimeType}")
|
||||
var mimeType = it?.mimeType ?: ""
|
||||
var filename = it?.name ?: ""
|
||||
var isAudio = mimeType.startsWith("audio")
|
||||
Log.d(TAG, "Found $mimeType file $filename in root folder")
|
||||
var imageFile = MediaFile(it.uri, filename, it.getSimplePath(context), it.length(), mimeType, isAudio)
|
||||
rootMediaFiles.add(imageFile)
|
||||
}
|
||||
|
||||
var jsobj = JSObject()
|
||||
jsobj.put("folders", mediaFolders.map{ it.toJSObject() })
|
||||
jsobj.put("files", rootMediaFiles.map{ it.toJSObject() })
|
||||
call.resolve(jsobj)
|
||||
}
|
||||
|
||||
|
||||
@PluginMethod
|
||||
fun delete(call: PluginCall) {
|
||||
var url = call.data.getString("url", "").toString()
|
||||
var coverUrl = call.data.getString("coverUrl", "").toString()
|
||||
var folderUrl = call.data.getString("folderUrl", "").toString()
|
||||
|
||||
if (folderUrl != "") {
|
||||
Log.d(TAG, "CALLED DELETE FOLDER: $folderUrl")
|
||||
var folder = DocumentFileCompat.fromUri(context, Uri.parse(folderUrl))
|
||||
var success = folder?.deleteRecursively(context)
|
||||
var jsobj = JSObject()
|
||||
jsobj.put("success", success)
|
||||
call.resolve()
|
||||
} else {
|
||||
// Older audiobooks did not store a folder url, use cover and audiobook url
|
||||
var abExists = checkUriExists(Uri.parse(url))
|
||||
if (abExists) {
|
||||
var abfile = DocumentFileCompat.fromUri(context, Uri.parse(url))
|
||||
abfile?.delete()
|
||||
}
|
||||
|
||||
var coverExists = checkUriExists(Uri.parse(coverUrl))
|
||||
if (coverExists) {
|
||||
var coverfile = DocumentFileCompat.fromUri(context, Uri.parse(coverUrl))
|
||||
coverfile?.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun checkUriExists(uri: Uri?): Boolean {
|
||||
if (uri == null) return false
|
||||
val resolver = context.contentResolver
|
||||
var cursor: Cursor? = null
|
||||
return try {
|
||||
cursor = resolver.query(uri, null, null, null, null)
|
||||
//cursor null: content Uri was invalid or some other error occurred
|
||||
//cursor.moveToFirst() false: Uri was ok but no entry found.
|
||||
(cursor != null && cursor.moveToFirst())
|
||||
} catch (t: Throwable) {
|
||||
false
|
||||
} finally {
|
||||
try {
|
||||
cursor?.close()
|
||||
} catch (t: Throwable) {
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user