From a6b203195001b45b9bb3ccbb82373aec5b1e656b Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Wed, 30 Jun 2021 17:44:48 -0400 Subject: [PATCH] more efficient way to search subdirs --- app/build.gradle | 1 + .../instagrabber/activities/CameraActivity.kt | 2 +- .../awais/instagrabber/utils/DownloadUtils.kt | 82 +++++++++++++------ .../viewmodels/DirectThreadViewModel.kt | 2 +- .../viewmodels/ImageEditViewModel.java | 2 +- 5 files changed, 62 insertions(+), 27 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6de63314..f184bcc6 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -180,6 +180,7 @@ dependencies { implementation "androidx.constraintlayout:constraintlayout:2.0.4" implementation "androidx.preference:preference:1.1.1" implementation 'androidx.palette:palette:1.0.0' + implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'com.google.guava:guava:27.0.1-android' diff --git a/app/src/main/java/awais/instagrabber/activities/CameraActivity.kt b/app/src/main/java/awais/instagrabber/activities/CameraActivity.kt index 1eca69d8..1ea124b6 100644 --- a/app/src/main/java/awais/instagrabber/activities/CameraActivity.kt +++ b/app/src/main/java/awais/instagrabber/activities/CameraActivity.kt @@ -52,7 +52,7 @@ class CameraActivity : BaseLanguageActivity() { setContentView(binding.root) Utils.transparentStatusBar(this, true, false) displayManager = getSystemService(DISPLAY_SERVICE) as DisplayManager - outputDirectory = DownloadUtils.getCameraDir() + outputDirectory = DownloadUtils.cameraDir cameraExecutor = Executors.newSingleThreadExecutor() displayManager.registerDisplayListener(displayListener, null) binding.viewFinder.post { diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.kt b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.kt index c272b2fd..e2491caa 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.kt +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.kt @@ -36,6 +36,7 @@ object DownloadUtils { private const val DIR_RECORDINGS = "Sent Recordings" private const val DIR_TEMP = "Temp" private const val DIR_BACKUPS = "Backups" + private val dirMap: MutableMap = mutableMapOf() private var root: DocumentFile? = null @JvmStatic @Throws(ReselectDocumentTreeException::class) @@ -48,18 +49,15 @@ object DownloadUtils { } val uri = Uri.parse(barinstaDirUri) if (!barinstaDirUri!!.startsWith("content://com.android.externalstorage.documents")) { - // reselect the folder in selector view throw ReselectDocumentTreeException(uri) } val existingPermissions = context.contentResolver.persistedUriPermissions if (existingPermissions.isEmpty()) { - // reselect the folder in selector view throw ReselectDocumentTreeException(uri) } val anyMatch = existingPermissions.stream() .anyMatch { uriPermission: UriPermission -> uriPermission.uri == uri } if (!anyMatch) { - // reselect the folder in selector view throw ReselectDocumentTreeException(uri) } root = DocumentFile.fromTreeUri(context, uri) @@ -68,24 +66,59 @@ object DownloadUtils { throw ReselectDocumentTreeException(uri) } Utils.settingsHelper.putString(PreferenceKeys.PREF_BARINSTA_DIR_URI, uri.toString()) + // set up directories + val dirKeys = listOf(DIR_DOWNLOADS, DIR_CAMERA, DIR_EDIT, DIR_RECORDINGS, DIR_TEMP, DIR_BACKUPS) + dirMap.putAll(checkSubDirs(context, root, dirKeys, true)) } fun destroy() { root = null + dirMap.clear() } - fun getDownloadDir(vararg dirs: String?): DocumentFile? { + fun checkSubDirs(context: Context, + parent: DocumentFile?, + queries: List, + create: Boolean): Map { + // first we'll find existing ones + val result: MutableMap = mutableMapOf() + if (parent == null) return result.toMap() + val parentUri = parent.uri + val docId = DocumentsContract.getTreeDocumentId(parentUri) + val docUri = DocumentsContract.buildChildDocumentsUriUsingTree(parentUri, docId) + val docCursor = context.contentResolver.query( + docUri, arrayOf( + DocumentsContract.Document.COLUMN_DISPLAY_NAME, + DocumentsContract.Document.COLUMN_DOCUMENT_ID, + DocumentsContract.Document.COLUMN_MIME_TYPE + ), null, null, null + ) + if (docCursor == null) return result.toMap() + while (docCursor.moveToNext()) { + if (!DocumentsContract.Document.MIME_TYPE_DIR.equals(docCursor.getString(2)) || !queries.contains(docCursor.getString(0))) + continue + val userFolderUri = DocumentsContract.buildDocumentUriUsingTree(parentUri, docCursor.getString(1)) + val dir = DocumentFile.fromTreeUri(context, userFolderUri) + Log.d("austin_debug", userFolderUri.toString() + " " + dir!!.uri.toString()) + result.put(docCursor.getString(0), dir) + } + docCursor.close() + // next we'll create inexistent ones + if (create) { + for (k in queries) { + if (result.get(k) == null) { + result.put(k, parent.createDirectory(k)) + } + } + } + return result.toMap() + } + + fun getDownloadDir(dir: String): DocumentFile? { if (root == null) { return null } - var subDir = root - for (dir in dirs) { - if (subDir == null || isEmpty(dir)) continue - val subDirFile = subDir.findFile(dir!!) - val exists = subDirFile != null && subDirFile.exists() - subDir = if (exists) subDirFile else subDir.createDirectory(dir) - } - return subDir + return dirMap.get(dir) } @JvmStatic @@ -93,23 +126,24 @@ object DownloadUtils { get() = getDownloadDir(DIR_DOWNLOADS) @JvmStatic - fun getCameraDir(): DocumentFile? { - return getDownloadDir(DIR_CAMERA) + val cameraDir: DocumentFile? + get() = getDownloadDir(DIR_CAMERA) + + @JvmStatic + fun getImageEditDir(sessionId: String?, context: Context): DocumentFile? { + val editRoot = getDownloadDir(DIR_EDIT) + if (sessionId == null) return editRoot + val result = checkSubDirs(context, editRoot, listOf(sessionId), true) + return result.get(sessionId) } @JvmStatic - fun getImageEditDir(sessionId: String?): DocumentFile? { - return getDownloadDir(DIR_EDIT, sessionId) - } - - fun getRecordingsDir(): DocumentFile? { - return getDownloadDir(DIR_RECORDINGS) - } + val recordingsDir: DocumentFile? + get() = getDownloadDir(DIR_RECORDINGS) @JvmStatic - fun getBackupsDir(): DocumentFile? { - return getDownloadDir(DIR_BACKUPS) - } + val backupsDir: DocumentFile? + get() = getDownloadDir(DIR_BACKUPS) // @Nullable // private static DocumentFile getDownloadDir(@NonNull final Context context, @Nullable final String username) { diff --git a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.kt b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.kt index 200f4deb..479ce01b 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.kt +++ b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.kt @@ -34,7 +34,7 @@ class DirectThreadViewModel( // private static final String ERROR_INVALID_THREAD = "Invalid thread"; private val contentResolver: ContentResolver = application.contentResolver - private val recordingsDir: DocumentFile? = DownloadUtils.getRecordingsDir() + private val recordingsDir: DocumentFile? = DownloadUtils.recordingsDir private var voiceRecorder: VoiceRecorder? = null private lateinit var threadManager: ThreadManager diff --git a/app/src/main/java/awais/instagrabber/viewmodels/ImageEditViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/ImageEditViewModel.java index ebbfc2f6..11c8f15a 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/ImageEditViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/ImageEditViewModel.java @@ -59,7 +59,7 @@ public class ImageEditViewModel extends AndroidViewModel { public ImageEditViewModel(final Application application) { super(application); sessionId = LocalDateTime.now().format(SIMPLE_DATE_FORMAT); - outputDir = DownloadUtils.getImageEditDir(sessionId); + outputDir = DownloadUtils.getImageEditDir(sessionId, application); destinationFile = outputDir.createFile(MIME_TYPE, RESULT + ".jpg"); destinationUri = destinationFile.getUri(); cropDestinationUri = outputDir.createFile(MIME_TYPE, CROP + ".jpg").getUri();