mirror of https://git.sr.ht/~oppen/ariane
move uri handling to dedicated class
This commit is contained in:
parent
199de8b3b0
commit
08842fd316
|
@ -0,0 +1,73 @@
|
|||
package oppen.ariane.io.gemini
|
||||
|
||||
import android.net.Uri
|
||||
import oppen.ariane.Ariane
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
class AddressTests {
|
||||
|
||||
@Test
|
||||
fun uriApiTests(){
|
||||
var uri = Uri.parse(Ariane.DEFAULT_HOME_CAPSULE)
|
||||
|
||||
assertThat(uri.toString()).isEqualTo("gemini://gemini.circumlunar.space/~oppen/index.gmi")
|
||||
assertThat(uri.isAbsolute).isTrue()
|
||||
assertThat(uri.isHierarchical).isTrue()
|
||||
assertThat(uri.isRelative).isFalse()
|
||||
|
||||
val lastSegment = uri.lastPathSegment
|
||||
assertThat(lastSegment).isEqualTo("index.gmi")
|
||||
|
||||
uri = Uri.parse(uri.pathSegments.joinToString("/"))
|
||||
assertThat(uri.toString()).isEqualTo("~oppen/index.gmi")
|
||||
assertThat(uri.isRelative).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun arianeUriTests(){
|
||||
val builder = AddressBuilder()
|
||||
var uri = builder.request("gemini://gemini.circumlunar.space/~oppen/index.gmi").uri()
|
||||
|
||||
assertThat(uri.toString()).isEqualTo("gemini://gemini.circumlunar.space/~oppen/index.gmi")
|
||||
assertThat(uri.path).isEqualTo("/~oppen/index.gmi")
|
||||
|
||||
uri = builder.request("/hello/index.gmi").uri()
|
||||
|
||||
assertThat(uri.toString()).isEqualTo("gemini://gemini.circumlunar.space/~oppen/hello/index.gmi")
|
||||
|
||||
uri = builder.request("/world/index.gmi").uri()
|
||||
assertThat(uri.toString()).isEqualTo("gemini://gemini.circumlunar.space/~oppen/hello/world/index.gmi")
|
||||
|
||||
uri = builder.request("./foo/index.gmi").uri()
|
||||
assertThat(uri.toString()).isEqualTo("gemini://gemini.circumlunar.space/~oppen/hello/world/foo/index.gmi")
|
||||
|
||||
uri = builder.request("bar/").uri()
|
||||
assertThat(uri.toString()).isEqualTo("gemini://gemini.circumlunar.space/~oppen/hello/world/foo/bar/index.gmi")
|
||||
|
||||
}
|
||||
|
||||
|
||||
//https://todo.sr.ht/~oppen/ariane/28
|
||||
@Test
|
||||
fun userTicketUriTests(){
|
||||
val builder = AddressBuilder()
|
||||
|
||||
builder.request("gemini://mycapsule.com")
|
||||
val relativeWithGmi = builder.request("art/index.gmi").uri()
|
||||
assertThat(relativeWithGmi.toString()).isEqualTo("gemini://mycapsule.com/art/index.gmi")
|
||||
|
||||
builder.request("gemini://mycapsule.com")
|
||||
val relativeWithoutGmi = builder.request("art/").uri()
|
||||
assertThat(relativeWithoutGmi.toString()).isEqualTo("gemini://mycapsule.com/art/index.gmi")
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun doubleSlashTest(){
|
||||
val builder = AddressBuilder()
|
||||
|
||||
val uri = builder.request("//mycapsule.com").uri()
|
||||
assertThat(uri.toString()).isEqualTo("gemini://mycapsule.com")
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package oppen
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.CountDownTimer
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
|
@ -28,6 +29,18 @@ fun String.toURI(): URI {
|
|||
return URI.create(this)
|
||||
}
|
||||
|
||||
fun URI.toUri(): Uri {
|
||||
return Uri.parse(this.toString())
|
||||
}
|
||||
|
||||
fun Uri.toURI(): URI {
|
||||
return URI.create(this.toString())
|
||||
}
|
||||
|
||||
fun Uri.isGemini(): Boolean{
|
||||
return this.toString().startsWith("gemini://")
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
fun String.endsWithImage(): Boolean{
|
||||
return this.toLowerCase().endsWith(".png") ||
|
||||
|
@ -36,6 +49,12 @@ fun String.endsWithImage(): Boolean{
|
|||
this.toLowerCase().endsWith(".gif")
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
fun String.isWeb(): Boolean{
|
||||
return this.toLowerCase().startsWith("https://") ||
|
||||
this.toLowerCase().startsWith("http://")
|
||||
}
|
||||
|
||||
fun delay(ms: Long, action: () -> Unit){
|
||||
object : CountDownTimer(ms, ms/2) {
|
||||
override fun onTick(millisUntilFinished: Long) {}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
package oppen.ariane.io.gemini
|
||||
|
||||
import android.net.Uri
|
||||
import java.net.URI
|
||||
|
||||
class AddressBuilder{
|
||||
|
||||
private var uri: Uri = Uri.EMPTY
|
||||
|
||||
fun set(uri: Uri){
|
||||
this.uri = uri
|
||||
}
|
||||
|
||||
fun request(link: URI): AddressBuilder {
|
||||
return request(link.toString())
|
||||
}
|
||||
|
||||
fun request(link: String): AddressBuilder {
|
||||
val linkUri = Uri.parse(link)
|
||||
when {
|
||||
linkUri.isAbsolute -> uri = linkUri
|
||||
linkUri.toString().startsWith("//") -> uri = Uri.parse("gemini:$link")
|
||||
else -> {
|
||||
val currentAddress = uri.toString()
|
||||
|
||||
val directoryPath = when {
|
||||
currentAddress.lastIndexOf("/") > 8 -> {
|
||||
currentAddress.substring(0, currentAddress.lastIndexOf("/")+1)
|
||||
}
|
||||
else -> {
|
||||
"${uri.scheme}://${uri.host}/"
|
||||
}
|
||||
}
|
||||
|
||||
var cleanedLink = when {
|
||||
link.startsWith("./") -> {
|
||||
link.substring(2)
|
||||
}
|
||||
link.startsWith("/") -> {
|
||||
link.substring(1)
|
||||
}
|
||||
else -> {
|
||||
link
|
||||
}
|
||||
}
|
||||
|
||||
if(link.endsWith("/")) cleanedLink += "index.gmi"
|
||||
|
||||
uri = Uri.parse("$directoryPath$cleanedLink")
|
||||
}
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
fun uri(): Uri{
|
||||
return uri
|
||||
}
|
||||
}
|
|
@ -6,11 +6,12 @@ import androidx.preference.PreferenceManager
|
|||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import oppen.ariane.io.GemState
|
||||
import oppen.isGemini
|
||||
import oppen.toURI
|
||||
import oppen.toUri
|
||||
import java.io.*
|
||||
import java.net.ConnectException
|
||||
import java.net.URI
|
||||
import java.security.SecureRandom
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.net.ssl.*
|
||||
|
||||
const val GEMINI_SCHEME = "gemini"
|
||||
|
@ -27,6 +28,8 @@ class GeminiDatasource(
|
|||
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
private var last: URI? = null
|
||||
|
||||
private val addressBuilder = AddressBuilder()
|
||||
|
||||
override fun request(uri: URI, onUpdate: (state: GemState) -> Unit) {
|
||||
|
||||
//Any inputted uri starting with a colon is an app-specific command, eg. :prefs :settings
|
||||
|
@ -37,69 +40,42 @@ class GeminiDatasource(
|
|||
|
||||
when (uri.scheme) {
|
||||
GEMINI_SCHEME -> {
|
||||
addressBuilder.set(uri.toUri())
|
||||
val cached = RuntimeCache.get(uri)
|
||||
if (cached != null) {
|
||||
last = uri
|
||||
onUpdate(
|
||||
GemState.ResponseGemtext(
|
||||
uri,
|
||||
cached.first,
|
||||
cached.second
|
||||
)
|
||||
)
|
||||
return
|
||||
} else {
|
||||
onUpdate(GemState.Requesting(uri))
|
||||
when {
|
||||
cached != null -> {
|
||||
last = uri
|
||||
onUpdate(GemState.ResponseGemtext(uri, cached.first, cached.second))
|
||||
return
|
||||
}
|
||||
else -> {
|
||||
onUpdate(GemState.Requesting(uri))
|
||||
|
||||
GlobalScope.launch {
|
||||
geminiRequest(uri, onUpdate)
|
||||
GlobalScope.launch {
|
||||
geminiRequest(uri, onUpdate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val address = uri.toString()
|
||||
val parsedUri = when {
|
||||
address.startsWith("//") -> {
|
||||
//just missing protocol
|
||||
URI.create("gemini:$address")
|
||||
}
|
||||
address.startsWith("/") -> {
|
||||
//internal navigation/relative link
|
||||
val internalNav = "gemini://${last?.host}$address"
|
||||
URI.create(internalNav)
|
||||
}
|
||||
address.startsWith("./") -> {
|
||||
//internal navigation/relative link - with dot
|
||||
val internalNav = "gemini://${last?.host}${address.substring(1)}"
|
||||
URI.create(internalNav)
|
||||
}
|
||||
!address.contains("://") -> {
|
||||
//looks like a relative link
|
||||
val lastAddress = last.toString()
|
||||
val relAddress = "${lastAddress.substring(
|
||||
0,
|
||||
lastAddress.lastIndexOf("/") + 1
|
||||
)}$address"
|
||||
URI.create(relAddress)
|
||||
}
|
||||
else -> {
|
||||
onUpdate(GemState.NotGeminiRequest(uri))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val cached = RuntimeCache.get(parsedUri)
|
||||
if(cached != null){
|
||||
last = parsedUri
|
||||
onUpdate(
|
||||
GemState.ResponseGemtext(
|
||||
parsedUri,
|
||||
cached.first,
|
||||
cached.second
|
||||
val parsedUri = addressBuilder.request(uri).uri()
|
||||
if(parsedUri.isGemini()){
|
||||
val cached = RuntimeCache.get(parsedUri)
|
||||
if(cached != null){
|
||||
last = parsedUri.toURI()
|
||||
onUpdate(
|
||||
GemState.ResponseGemtext(
|
||||
parsedUri.toURI(),
|
||||
cached.first,
|
||||
cached.second
|
||||
)
|
||||
)
|
||||
)
|
||||
}else{
|
||||
request(parsedUri.toURI(), onUpdate)
|
||||
}
|
||||
}else{
|
||||
request(parsedUri, onUpdate)
|
||||
onUpdate(GemState.NotGeminiRequest(uri))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package oppen.ariane.io.gemini
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.collection.LruCache
|
||||
import java.net.URI
|
||||
|
||||
|
@ -15,6 +16,7 @@ object RuntimeCache {
|
|||
}
|
||||
|
||||
fun get(uri: URI): Pair<GeminiResponse.Header, List<String>>? = lruCache[uri.toString()]
|
||||
fun get(uri: Uri): Pair<GeminiResponse.Header, List<String>>? = lruCache[uri.toString()]
|
||||
|
||||
fun clear() = lruCache.evictAll()
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.view.View
|
|||
import androidx.appcompat.widget.PopupMenu
|
||||
import oppen.ariane.R
|
||||
import oppen.endsWithImage
|
||||
import oppen.isWeb
|
||||
import java.net.URI
|
||||
|
||||
object LinkPopup {
|
||||
|
@ -15,8 +16,10 @@ object LinkPopup {
|
|||
val popup = PopupMenu(view.context, view)
|
||||
val inflater: MenuInflater = popup.menuInflater
|
||||
|
||||
val uriStr = uri.toString()
|
||||
|
||||
when {
|
||||
uri.toString().endsWithImage() -> inflater.inflate(R.menu.image_link_menu, popup.menu)
|
||||
uriStr.endsWithImage() && !uriStr.isWeb() -> inflater.inflate(R.menu.image_link_menu, popup.menu)
|
||||
else -> inflater.inflate(R.menu.link_menu, popup.menu)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue