Skip to content

Adding JVM support for Firebase Storage #44

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
4 changes: 3 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -82,6 +82,7 @@ val jar by tasks.getting(Jar::class) {
it.path.startsWith("${projectDir.path}${File.separator}build${File.separator}jar")
}.map { zipTree(it) }
})
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}

val sourceSets = project.the<SourceSetContainer>()
@@ -157,8 +158,9 @@ dependencies {
aar(libs.google.firebase.database)
aar(libs.google.firebase.config)
aar(libs.google.firebase.installations)
aar(libs.google.firebase.storage)
// extracted aar dependencies
// exclude lifecycle libs due to https://github.com/GitLiveApp/firebase-java-sdk/pull/15 - remove the exclude once the dependencies in the aars are updated to the required version
// exclude lifecycle libs due to https://github.com/GitLiveApp/firebase-java-sdk/pull/15 - remove the exclude once the dependencies in the aars are updated to the required version
api(fileTree(mapOf("dir" to "build/jar", "include" to listOf("*.jar"), "exclude" to listOf("lifecycle-*"))))
// polyfill dependencies
implementation(libs.kotlinx.coroutines.core)
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ google-firebase-database = { module = "com.google.firebase:firebase-database" }
google-firebase-firestore = { module = "com.google.firebase:firebase-firestore" }
google-firebase-functions = { module = "com.google.firebase:firebase-functions" }
google-firebase-installations = { module = "com.google.firebase:firebase-installations" }
google-firebase-storage = { module = "com.google.firebase:firebase-storage" }
io-grpc-okhttp = { module = "io.grpc:grpc-okhttp", version.ref = "io-grpc" }
io-grpc-protobuf-lite = { module = "io.grpc:grpc-protobuf-lite", version.ref = "io-grpc" }
io-grpc-stub = { module = "io.grpc:grpc-stub", version.ref = "io-grpc" }
1 change: 1 addition & 0 deletions src/main/java/android/content/pm/PackageManager.java
Original file line number Diff line number Diff line change
@@ -39,6 +39,7 @@ public ServiceInfo getServiceInfo(ComponentName component, int flags) throws Nam
data.put("com.google.firebase.components:com.google.firebase.functions.FunctionsRegistrar", "com.google.firebase.components.ComponentRegistrar");
data.put("com.google.firebase.components:com.google.firebase.installations.FirebaseInstallationsRegistrar", "com.google.firebase.components.ComponentRegistrar");
data.put("com.google.firebase.components:com.google.firebase.iid.Registrar", "com.google.firebase.components.ComponentRegistrar");
data.put("com.google.firebase.components:com.google.firebase.storage.StorageRegistrar", "com.google.firebase.components.ComponentRegistrar");
return new ServiceInfo(data);
}
throw new IllegalArgumentException(component.cls);
4 changes: 4 additions & 0 deletions src/main/java/android/net/ConnectivityManager.kt
Original file line number Diff line number Diff line change
@@ -21,6 +21,10 @@ class ConnectivityManager private constructor() {
connected.removeEventListener(networkCallback)
}

fun getActiveNetworkInfo(): NetworkInfo {
return NetworkInfo()
}

open class NetworkCallback : ValueEventListener {
override fun onDataChange(data: DataSnapshot) {
when (data.getValue(Boolean::class.java)) {
9 changes: 9 additions & 0 deletions src/main/java/android/net/NetworkInfo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package android.net

class NetworkInfo {
var type: Int = 1 // ConnectivityManager.TYPE_WIFI
val isConnectedOrConnecting: Boolean = true
val isConnected: Boolean = true
val isSuspended: Boolean = false
val isAvailable: Boolean = true
}
2,039 changes: 2,016 additions & 23 deletions src/main/java/android/net/Uri.kt

Large diffs are not rendered by default.

147 changes: 147 additions & 0 deletions src/main/java/android/net/UriCodec.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package android.net

import java.net.URISyntaxException
import java.nio.ByteBuffer
import java.nio.charset.Charset
import java.nio.charset.CharsetDecoder
import java.nio.charset.CodingErrorAction

object UriCodec {
private const val INVALID_INPUT_CHARACTER = ''

private fun hexCharToValue(c: Char): Int {
return if (c in '0'..'9') {
c.code - 48
} else if (c in 'a'..'f') {
10 + c.code - 97
} else {
if (c in 'A'..'F') 10 + c.code - 65 else -1
}
}

@JvmStatic
private fun unexpectedCharacterException(
uri: String,
name: String?,
unexpected: Char,
index: Int
): URISyntaxException {
val nameString = if (name == null) "" else " in [$name]"
return URISyntaxException(uri, "Unexpected character$nameString: $unexpected", index)
}

@Throws(URISyntaxException::class)
private fun getNextCharacter(uri: String, index: Int, end: Int, name: String?): Char {
if (index >= end) {
val nameString = if (name == null) "" else " in [$name]"
throw URISyntaxException(uri, "Unexpected end of string$nameString", index)
} else {
return uri[index]
}
}

@JvmStatic
fun decode(s: String, convertPlus: Boolean, charset: Charset, throwOnFailure: Boolean): String {
val builder = StringBuilder(s.length)
appendDecoded(builder, s, convertPlus, charset, throwOnFailure)
return builder.toString()
}

private fun appendDecoded(
builder: StringBuilder,
s: String,
convertPlus: Boolean,
charset: Charset,
throwOnFailure: Boolean
) {
val decoder = charset.newDecoder().onMalformedInput(CodingErrorAction.REPLACE).replaceWith("")
.onUnmappableCharacter(CodingErrorAction.REPORT)
val byteBuffer = ByteBuffer.allocate(s.length)
var i = 0

while (i < s.length) {
var c = s[i]
++i
when (c) {
'%' -> {
var hexValue: Byte = 0

var j = 0
while (j < 2) {
try {
c = getNextCharacter(s, i, s.length, null as String?)
} catch (e: URISyntaxException) {
if (throwOnFailure) {
throw IllegalArgumentException(e)
}

flushDecodingByteAccumulator(builder, decoder, byteBuffer, throwOnFailure)
builder.append('')
return
}

++i
val newDigit = hexCharToValue(c)
if (newDigit < 0) {
if (throwOnFailure) {
throw IllegalArgumentException(
unexpectedCharacterException(
s,
null as String?,
c,
i - 1
)
)
}

flushDecodingByteAccumulator(builder, decoder, byteBuffer, throwOnFailure)
builder.append('')
break
}

hexValue = (hexValue * 16 + newDigit).toByte()
++j
}

byteBuffer.put(hexValue)
}

'+' -> {
flushDecodingByteAccumulator(builder, decoder, byteBuffer, throwOnFailure)
builder.append((if (convertPlus) ' ' else '+'))
}

else -> {
flushDecodingByteAccumulator(builder, decoder, byteBuffer, throwOnFailure)
builder.append(c)
}
}
}

flushDecodingByteAccumulator(builder, decoder, byteBuffer, throwOnFailure)
}

private fun flushDecodingByteAccumulator(
builder: StringBuilder,
decoder: CharsetDecoder,
byteBuffer: ByteBuffer,
throwOnFailure: Boolean
) {
if (byteBuffer.position() != 0) {
byteBuffer.flip()

try {
builder.append(decoder.decode(byteBuffer))
} catch (e: CharacterCodingException) {
if (throwOnFailure) {
throw IllegalArgumentException(e)
}

builder.append('')
} finally {
byteBuffer.flip()
byteBuffer.limit(byteBuffer.capacity())
}
}
}
}
1 change: 1 addition & 0 deletions src/test/kotlin/FirebaseTest.kt
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ abstract class FirebaseTest {
.setProjectId("my-firebase-project")
.setApplicationId("1:27992087142:android:ce3b6448250083d1")
.setApiKey("AIzaSyADUe90ULnQDuGShD9W23RDP0xmeDc6Mvw")
.setStorageBucket("fir-kotlin-sdk.appspot.com")
.build()

return Firebase.initialize(Application(), options)
42 changes: 42 additions & 0 deletions src/test/kotlin/FirestoreStorageTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import android.net.Uri
import com.google.firebase.Firebase
import com.google.firebase.storage.internal.Slashes
import com.google.firebase.storage.storage
import org.junit.Assert
import org.junit.Test

class FirestoreStorageTest : FirebaseTest() {

@Test
fun test_parsing_storage_uri() {
val input = "gs://edifikana-stage.appspot.com"

val normalized = Slashes.normalizeSlashes(input.substring(5))
val fullUri = Slashes.preserveSlashEncode(normalized)
val parsedUri = Uri.parse("gs://$fullUri")

Assert.assertEquals("gs://edifikana-stage.appspot.com", parsedUri.toString())
}

@Test
fun test_loading_default_storage_client() {
Firebase.storage(app)
}

@Test
fun test_getting_root_reference() {
val storage = Firebase.storage(app)
val reference = storage.reference
Assert.assertNotNull(reference)
}

@Test
fun test_getting_child_reference() {
val storage = Firebase.storage(app)
val reference = storage.reference
val downloadRef = reference.child("mountains.jpg")
val downloadUrl = downloadRef.downloadUrl

Assert.assertNotNull(downloadUrl)
}
}
26 changes: 26 additions & 0 deletions src/test/kotlin/fakes/FakeFirebasePlatform.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package fakes

import com.google.firebase.FirebasePlatform
import java.io.File

/**
* Fake used to store firebase data during testing. The [storage] is made purposefully public to allow for direct
* access and modification if needed.
*/
class FakeFirebasePlatform(
val storage: MutableMap<String, String> = mutableMapOf(),
databaseFolderPath: String = "./build/database/"
) : FirebasePlatform() {

private val databaseFolder = File(databaseFolderPath)

override fun store(key: String, value: String) { storage[key] = value }

override fun retrieve(key: String) = storage[key]

override fun clear(key: String) { storage.remove(key) }

override fun log(msg: String) = println(msg)

override fun getDatabasePath(name: String) = File(databaseFolder, name)
}