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.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
import android.os.CountDownTimer
|
import android.os.CountDownTimer
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
@ -28,6 +29,18 @@ fun String.toURI(): URI {
|
||||||
return URI.create(this)
|
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")
|
@SuppressLint("DefaultLocale")
|
||||||
fun String.endsWithImage(): Boolean{
|
fun String.endsWithImage(): Boolean{
|
||||||
return this.toLowerCase().endsWith(".png") ||
|
return this.toLowerCase().endsWith(".png") ||
|
||||||
|
@ -36,6 +49,12 @@ fun String.endsWithImage(): Boolean{
|
||||||
this.toLowerCase().endsWith(".gif")
|
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){
|
fun delay(ms: Long, action: () -> Unit){
|
||||||
object : CountDownTimer(ms, ms/2) {
|
object : CountDownTimer(ms, ms/2) {
|
||||||
override fun onTick(millisUntilFinished: Long) {}
|
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.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import oppen.ariane.io.GemState
|
import oppen.ariane.io.GemState
|
||||||
|
import oppen.isGemini
|
||||||
|
import oppen.toURI
|
||||||
|
import oppen.toUri
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.net.ConnectException
|
import java.net.ConnectException
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.security.SecureRandom
|
|
||||||
import java.security.cert.X509Certificate
|
|
||||||
import javax.net.ssl.*
|
import javax.net.ssl.*
|
||||||
|
|
||||||
const val GEMINI_SCHEME = "gemini"
|
const val GEMINI_SCHEME = "gemini"
|
||||||
|
@ -27,6 +28,8 @@ class GeminiDatasource(
|
||||||
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
private var last: URI? = null
|
private var last: URI? = null
|
||||||
|
|
||||||
|
private val addressBuilder = AddressBuilder()
|
||||||
|
|
||||||
override fun request(uri: URI, onUpdate: (state: GemState) -> Unit) {
|
override fun request(uri: URI, onUpdate: (state: GemState) -> Unit) {
|
||||||
|
|
||||||
//Any inputted uri starting with a colon is an app-specific command, eg. :prefs :settings
|
//Any inputted uri starting with a colon is an app-specific command, eg. :prefs :settings
|
||||||
|
@ -37,18 +40,15 @@ class GeminiDatasource(
|
||||||
|
|
||||||
when (uri.scheme) {
|
when (uri.scheme) {
|
||||||
GEMINI_SCHEME -> {
|
GEMINI_SCHEME -> {
|
||||||
|
addressBuilder.set(uri.toUri())
|
||||||
val cached = RuntimeCache.get(uri)
|
val cached = RuntimeCache.get(uri)
|
||||||
if (cached != null) {
|
when {
|
||||||
|
cached != null -> {
|
||||||
last = uri
|
last = uri
|
||||||
onUpdate(
|
onUpdate(GemState.ResponseGemtext(uri, cached.first, cached.second))
|
||||||
GemState.ResponseGemtext(
|
|
||||||
uri,
|
|
||||||
cached.first,
|
|
||||||
cached.second
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
} else {
|
}
|
||||||
|
else -> {
|
||||||
onUpdate(GemState.Requesting(uri))
|
onUpdate(GemState.Requesting(uri))
|
||||||
|
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
|
@ -56,50 +56,26 @@ class GeminiDatasource(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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 -> {
|
else -> {
|
||||||
onUpdate(GemState.NotGeminiRequest(uri))
|
val parsedUri = addressBuilder.request(uri).uri()
|
||||||
return
|
if(parsedUri.isGemini()){
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val cached = RuntimeCache.get(parsedUri)
|
val cached = RuntimeCache.get(parsedUri)
|
||||||
if(cached != null){
|
if(cached != null){
|
||||||
last = parsedUri
|
last = parsedUri.toURI()
|
||||||
onUpdate(
|
onUpdate(
|
||||||
GemState.ResponseGemtext(
|
GemState.ResponseGemtext(
|
||||||
parsedUri,
|
parsedUri.toURI(),
|
||||||
cached.first,
|
cached.first,
|
||||||
cached.second
|
cached.second
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}else{
|
}else{
|
||||||
request(parsedUri, onUpdate)
|
request(parsedUri.toURI(), onUpdate)
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
onUpdate(GemState.NotGeminiRequest(uri))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package oppen.ariane.io.gemini
|
package oppen.ariane.io.gemini
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import androidx.collection.LruCache
|
import androidx.collection.LruCache
|
||||||
import java.net.URI
|
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 get(uri: Uri): Pair<GeminiResponse.Header, List<String>>? = lruCache[uri.toString()]
|
||||||
|
|
||||||
fun clear() = lruCache.evictAll()
|
fun clear() = lruCache.evictAll()
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.view.View
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import oppen.ariane.R
|
import oppen.ariane.R
|
||||||
import oppen.endsWithImage
|
import oppen.endsWithImage
|
||||||
|
import oppen.isWeb
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
object LinkPopup {
|
object LinkPopup {
|
||||||
|
@ -15,8 +16,10 @@ object LinkPopup {
|
||||||
val popup = PopupMenu(view.context, view)
|
val popup = PopupMenu(view.context, view)
|
||||||
val inflater: MenuInflater = popup.menuInflater
|
val inflater: MenuInflater = popup.menuInflater
|
||||||
|
|
||||||
|
val uriStr = uri.toString()
|
||||||
|
|
||||||
when {
|
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)
|
else -> inflater.inflate(R.menu.link_menu, popup.menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue