Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/storage-sample/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ dependencies {
implementation(Libs.dataStore)
implementation(Libs.hiltAndroid)
implementation(Libs.activityKtx)
implementation(Libs.slf4jAndroid)
kapt(Libs.hiltCompiler)
kapt(Libs.glideCompiler)

Expand Down
4 changes: 4 additions & 0 deletions buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,8 @@ object Libs {

// Datastore
val dataStore by lazy { "androidx.datastore:datastore-preferences:${Versions.dataStore}" }

// SLF4J
val slf4jApi by lazy { "org.slf4j:slf4j-api:${Versions.slf4j}" }
val slf4jAndroid by lazy { "uk.uuid.slf4j:slf4j-android:${Versions.slf4jAndroid}" }
}
4 changes: 4 additions & 0 deletions buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,8 @@ object Versions {

// DataStore
const val dataStore = "1.1.1"

// slf4j
const val slf4j = "2.0.17"
const val slf4jAndroid = "2.0.17-0"
}
8 changes: 8 additions & 0 deletions docs/markdown/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,14 @@ Retrieves the storage quota currently in use, in bytes. It is entirely depends o
val quotaUsed = omhStorageClient.getStorageUsage()
```

### Resolve path of a file or folder

Retrieves the ID of the file/folder at specified path on the cloud storage (which the user has access to).

```kotlin
val fileOrFolderId = omhStorageClient.resolvePath("path/to/the/fileorfolder")
```

---

For a more in depth view on the available methods, access the [Reference API](https://openmobilehub.github.io/android-omh-storage/api/packages/core/com.openmobilehub.android.storage.core/-omh-storage-client).
Expand Down
4 changes: 4 additions & 0 deletions packages/core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ dependencies {
// Play services
implementation(Libs.googlePlayBase)

// Logging
implementation(Libs.slf4jApi)

// Test
testImplementation(Libs.junit)
androidTestImplementation(Libs.androidJunit)
testImplementation(Libs.mockk)
testImplementation(Libs.coroutineTesting)
testImplementation(Libs.slf4jAndroid)
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import com.openmobilehub.android.storage.core.model.OmhPermissionRole
import com.openmobilehub.android.storage.core.model.OmhStorageEntity
import com.openmobilehub.android.storage.core.model.OmhStorageException
import com.openmobilehub.android.storage.core.model.OmhStorageMetadata
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.ByteArrayOutputStream
import java.io.File

Expand All @@ -47,6 +49,8 @@ abstract class OmhStorageClient protected constructor(
protected val authClient: OmhAuthClient
) {

protected val logger: Logger = LoggerFactory.getLogger(javaClass)

interface Builder {

fun build(authClient: OmhAuthClient): OmhStorageClient
Expand Down Expand Up @@ -276,6 +280,28 @@ abstract class OmhStorageClient protected constructor(
fileId: String,
): String?

/**
* This method tries resolve the storage entity at specified path.
*
* Example usage:
*
* ```
* val path = "/path/to/file.txt"
* val entity = storageClient.resolvePath(path)
* if (entity != null) {
* Log.d("StorageClient", "Entity ${path} resolves to: ${entity.id}")
* // Do something else with the entity
* } else {
* Log.w("StorageClient", "Entity ${path} not found")
* }
* ```
*
* @param path The path of the storage entity
*
* @return [OmhStorageEntity] or null if not found
*/
abstract suspend fun resolvePath(path: String): OmhStorageEntity?
Comment thread
TranceLove marked this conversation as resolved.

/**
* This method provides an escape hatch to access the provider native SDK. This allows developers
* to use the underlying provider's API directly, should they need to access a feature of the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,15 @@ fun String.fromRFC3339StringToDate(): Date? {
null
}
}

fun String.splitPathToParts(): List<String> {
return if (!contains('/')) {
listOf(this)
} else {
if (endsWith("/")) {
substringBeforeLast("/").split("/")
} else {
split("/")
}.filter { it.isNotBlank() && it.isNotEmpty() }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,13 @@ class StringExtensionsTest {
// Assert
assertNull(result)
}

@Test
fun `test splitPathToParts`() {
assertEquals(listOf("abc"), "abc".splitPathToParts())
assertEquals(listOf(""), "".splitPathToParts())
assertEquals(listOf(" "), " ".splitPathToParts())
assertEquals(listOf("a", "b", "c"), "/a/b/c".splitPathToParts())
assertEquals(listOf("a", "b", "c"), "/a/b/c/".splitPathToParts())
}
}
4 changes: 4 additions & 0 deletions packages/plugin-dropbox/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@ dependencies {
implementation(Libs.androidxAnnotation)
implementation(Libs.coroutinesCore)

// slf4j
implementation(Libs.slf4jApi)

// Test dependencies
testImplementation(kotlin("test"))
testImplementation(Libs.junit)
testImplementation(Libs.mockk)
testImplementation(Libs.coroutineTesting)
testImplementation(Libs.slf4jAndroid)
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.openmobilehub.android.storage.plugin.dropbox

import android.webkit.MimeTypeMap
import androidx.annotation.VisibleForTesting
import com.dropbox.core.DbxException
import com.dropbox.core.v2.DbxClientV2
import com.openmobilehub.android.auth.core.OmhAuthClient
import com.openmobilehub.android.storage.core.OmhStorageClient
Expand All @@ -28,6 +29,7 @@ import com.openmobilehub.android.storage.core.model.OmhPermissionRole
import com.openmobilehub.android.storage.core.model.OmhStorageEntity
import com.openmobilehub.android.storage.core.model.OmhStorageException
import com.openmobilehub.android.storage.core.model.OmhStorageMetadata
import com.openmobilehub.android.storage.plugin.dropbox.data.mapper.ExceptionMapper
import com.openmobilehub.android.storage.plugin.dropbox.data.mapper.MetadataToOmhStorageEntity
import com.openmobilehub.android.storage.plugin.dropbox.data.repository.DropboxFileRepository
import com.openmobilehub.android.storage.plugin.dropbox.data.service.DropboxApiClient
Expand Down Expand Up @@ -179,6 +181,27 @@ internal class DropboxOmhStorageClient @VisibleForTesting internal constructor(
return null
}

override suspend fun resolvePath(path: String): OmhStorageEntity? {
val startTime = System.currentTimeMillis()
try {
val retval = repository.resolvePath(path)
if (logger.isDebugEnabled && retval != null) {
logger.debug("resolvePath: \"$path\" -> $retval")
} else {
logger.debug("resolvePath: \"$path\" not found")
}
return retval
} catch (e: DbxException) {
logger.error("resolvePath failed: $path", e)
throw ExceptionMapper.toOmhApiException(e)
} finally {
val endTime = System.currentTimeMillis()
if (logger.isDebugEnabled) {
logger.debug("resolvePath took ${endTime - startTime} ms")
}
}
}

override fun getProviderSdk(): DbxClientV2 = repository.apiService.apiClient.dropboxApiService

override suspend fun getStorageUsage(): Long {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,4 +450,11 @@ internal class DropboxFileRepository(
private fun isFolder(fileId: String): FolderMetadata? {
return apiService.getFile(fileId) as? FolderMetadata
}

fun resolvePath(path: String): OmhStorageEntity? {
val nodeId = apiService.queryNodeIdHaving(path)
return nodeId?.let {
metadataToOmhStorageEntity(apiService.getFile(it))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.dropbox.core.v2.async.LaunchResultBase
import com.dropbox.core.v2.files.CreateFolderResult
import com.dropbox.core.v2.files.DeleteResult
import com.dropbox.core.v2.files.FileMetadata
import com.dropbox.core.v2.files.FolderMetadata
import com.dropbox.core.v2.files.ListFolderResult
import com.dropbox.core.v2.files.ListRevisionsMode
import com.dropbox.core.v2.files.ListRevisionsResult
Expand Down Expand Up @@ -232,4 +233,9 @@ internal class DropboxApiService(internal val apiClient: DropboxApiClient) {
fun getSpaceUsage(): SpaceUsage {
return apiClient.dropboxApiService.users().spaceUsage
}

fun queryNodeIdHaving(path: String): String? {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a suggestion: would it be possible to simplify queryNodeIdHaving() by using the Dropbox API’s getMetadata() method directly?

Since apiClient.dropboxApiService.files().getMetadata(path) already returns a FileMetadata or FolderMetadata (depending on the path), you might be able to replace the manual traversal logic with something like:

fun queryNodeIdHaving(path: String): String? {
    return try {
        val metadata = apiClient.dropboxApiService.files().getMetadata(path)
        when (metadata) {
            is FolderMetadata -> metadata.id
            is FileMetadata -> metadata.id
            else -> null
        }
    } catch (e: Exception) {
        null
    }
}

This would still handle files and folders, and gracefully return null if the path doesn’t exist or isn’t accessible. Curious if there’s a specific reason for the manual traversal — maybe to validate intermediate folders?

Let me know what you think!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch 👍 let's see how this change would work.

val node: Metadata = apiClient.dropboxApiService.files().getMetadata(path) ?: return null
return (node as? FolderMetadata)?.id ?: (node as FileMetadata).id
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import com.openmobilehub.android.storage.plugin.dropbox.data.service.DropboxApiS
import com.openmobilehub.android.storage.plugin.dropbox.testdoubles.TEST_EMAIL_MESSAGE
import com.openmobilehub.android.storage.plugin.dropbox.testdoubles.TEST_FILE_EXTENSION
import com.openmobilehub.android.storage.plugin.dropbox.testdoubles.TEST_FILE_ID
import com.openmobilehub.android.storage.plugin.dropbox.testdoubles.TEST_FILE_MODIFIED_TIME
import com.openmobilehub.android.storage.plugin.dropbox.testdoubles.TEST_FILE_NAME
import com.openmobilehub.android.storage.plugin.dropbox.testdoubles.TEST_FILE_PARENT_ID
import com.openmobilehub.android.storage.plugin.dropbox.testdoubles.TEST_FILE_PATH
Expand All @@ -78,6 +79,7 @@ import com.openmobilehub.android.storage.plugin.dropbox.testdoubles.setUpMock
import com.openmobilehub.android.storage.plugin.dropbox.testdoubles.setUpMockForPersonalAccount
import com.openmobilehub.android.storage.plugin.dropbox.testdoubles.setupMockForOtherAccount
import com.openmobilehub.android.storage.plugin.dropbox.testdoubles.setupMockForTeamAccount
import com.openmobilehub.android.storage.plugin.dropbox.testdoubles.testFileJpg
import com.openmobilehub.android.storage.plugin.dropbox.testdoubles.testInvitedOmhPermission
import com.openmobilehub.android.storage.plugin.dropbox.testdoubles.testOmhFolder
import com.openmobilehub.android.storage.plugin.dropbox.testdoubles.testOmhGroupPermission
Expand Down Expand Up @@ -1087,4 +1089,40 @@ class DropboxFileRepositoryTest {
repository.getStorageQuota()
repository.getStorageUsage()
}

@Test
fun `test resolve path of non-existent file`() {
every { apiService.queryNodeIdHaving(any()) } returns null
assertNull(repository.resolvePath("/foo/bar"))

verify {
apiService.queryNodeIdHaving("/foo/bar")
}
}

@Test
fun `test resolve path of an existing file`() {
every { apiService.queryNodeIdHaving("/RSX/1/2/3/testfile.jpg") } returns
testFileJpg().id
every { apiService.getFile("id of file /RSX/1/2/3/testfile.jpg") } returns
testFileJpg()

every { metadataToOmhStorageEntity(testFileJpg()) } returns OmhStorageEntity.OmhFile(
id = "id of file /RSX/1/2/3/testfile.jpg",
name = "testfile.jpg",
createdTime = TEST_FILE_MODIFIED_TIME,
modifiedTime = TEST_FILE_MODIFIED_TIME,
parentId = "id of folder /RSX/1/2/3",
mimeType = "image/jpeg",
extension = "jpg",
size = 12345
)

val result = repository.resolvePath("/RSX/1/2/3/testfile.jpg")
assertNotNull(result)
assertEquals("id of file /RSX/1/2/3/testfile.jpg", result?.id)

verify { apiService.queryNodeIdHaving("/RSX/1/2/3/testfile.jpg") }
verify { apiService.getFile("id of file /RSX/1/2/3/testfile.jpg") }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import java.util.Date
const val TEST_FILE_ID = "123"
const val TEST_FILE_NAME = "fileName.txt"
val TEST_FILE_CREATED_TIME = null
val TEST_FILE_MODIFIED_TIME = TEST_FIRST_JUNE_2024_RFC_3339.fromRFC3339StringToDate()
val TEST_FILE_MODIFIED_TIME = TEST_FIRST_JUNE_2024_RFC_3339.fromRFC3339StringToDate()!!
const val TEST_FILE_PARENT_ID = "parentId"
const val TEST_FILE_MIME_TYPE = "test/mime-type"
const val TEST_FILE_EXTENSION = "txt"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.openmobilehub.android.storage.plugin.dropbox.testdoubles

import com.dropbox.core.v2.files.FileMetadata
import com.dropbox.core.v2.files.FolderMetadata
import com.dropbox.core.v2.files.ListFolderResult
import io.mockk.every
import io.mockk.mockk

val testQueryRootFolder = mockk<ListFolderResult>(relaxed = true).also { result ->
every { result.entries } returns listOf(testFolderRsx())
}

val testQueryFolderRsx = mockk<ListFolderResult>(relaxed = true).also { result ->
every { result.entries } returns listOf(testFolder1())
}

val testQueryFolder1 = mockk<ListFolderResult>(relaxed = true).also { result ->
every { result.entries } returns listOf(testFolder2())
}

val testQueryFolder2 = mockk<ListFolderResult>(relaxed = true).also { result ->
every { result.entries } returns listOf(testFolder3())
}

val testQueryFolder3 = mockk<ListFolderResult>(relaxed = true).also { result ->
every { result.entries } returns listOf(testFileJpg())
}

fun testFolderRsx(): FolderMetadata = mockk<FolderMetadata>().also { folder ->
every { folder.id } returns "id of folder /RSX"
every { folder.name } returns "RSX"
every { folder.parentSharedFolderId } returns ""
}

fun testFolder1(): FolderMetadata = mockk<FolderMetadata>().also { folder ->
every { folder.id } returns "id of folder /RSX/1"
every { folder.name } returns "1"
every { folder.parentSharedFolderId } returns "id of folder /RSX"
}

fun testFolder2(): FolderMetadata = mockk<FolderMetadata>().also { folder ->
every { folder.id } returns "id of folder /RSX/1/2"
every { folder.name } returns "2"
every { folder.parentSharedFolderId } returns "id of folder /RSX/1"
}

fun testFolder3(): FolderMetadata = mockk<FolderMetadata>().also { folder ->
every { folder.id } returns "id of folder /RSX/1/2/3"
every { folder.name } returns "3"
every { folder.parentSharedFolderId } returns "id of folder /RSX/1/2"
}

fun testFileJpg(): FileMetadata = FileMetadata(
"testfile.jpg",
"id of file /RSX/1/2/3/testfile.jpg",
TEST_FILE_MODIFIED_TIME,
TEST_FILE_MODIFIED_TIME,
"000000000000000000000",
12345L
)
4 changes: 4 additions & 0 deletions packages/plugin-googledrive-gms/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ dependencies {
// Omh Auth
api(Libs.omhGoogleGmsAuthLibrary)

// slf4j
implementation(Libs.slf4jApi)

// GMS
implementation(Libs.googlePlayServicesAuth)
implementation(Libs.googleJacksonClient)
Expand All @@ -52,4 +55,5 @@ dependencies {
testImplementation(Libs.junit)
testImplementation(Libs.mockk)
testImplementation(Libs.coroutineTesting)
testImplementation(Libs.slf4jAndroid)
}
Loading
Loading