feat(blossom): route image fetches through local Blossom cache
Adds support for the local-blossom-cache spec (https://github.com/hzrd149/blossom/blob/master/implementations/local-blossom-cache.md): when http://127.0.0.1:24242 responds 2xx to HEAD /, image and video fetches are routed through it with xs= upstream hints so it can proxy on miss. Toggle defaults ON per account; disable from Media Servers settings. Covers both blossom:// URIs and plain http(s) URLs that carry an imeta sha256, by rewriting the latter to a synthetic blossom:<sha>?xs=<host> URI before handing it to Coil/ExoPlayer.
This commit is contained in:
@@ -65,18 +65,38 @@ data class BlossomUri(
|
||||
append(sha256)
|
||||
append('.')
|
||||
append(extension)
|
||||
val params =
|
||||
buildList {
|
||||
servers.forEach { add("xs=${percentEncodeQueryValue(it)}") }
|
||||
authors.forEach { add("as=$it") }
|
||||
this@BlossomUri.size?.let { add("sz=$it") }
|
||||
}
|
||||
if (params.isNotEmpty()) {
|
||||
append('?')
|
||||
append(params.joinToString("&"))
|
||||
}
|
||||
appendQueryString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an HTTP URL pointing at a local Blossom cache that proxies
|
||||
* upstream using the same `xs`/`as`/`sz` hints as the canonical URI.
|
||||
*
|
||||
* @param base the cache base URL, e.g. `http://127.0.0.1:24242`.
|
||||
*/
|
||||
fun toLocalCacheUrl(base: String): String =
|
||||
buildString {
|
||||
append(base.removeSuffix("/"))
|
||||
append('/')
|
||||
append(sha256)
|
||||
append('.')
|
||||
append(extension)
|
||||
appendQueryString()
|
||||
}
|
||||
|
||||
private fun StringBuilder.appendQueryString() {
|
||||
val params =
|
||||
buildList {
|
||||
servers.forEach { add("xs=${percentEncodeQueryValue(it)}") }
|
||||
authors.forEach { add("as=$it") }
|
||||
this@BlossomUri.size?.let { add("sz=$it") }
|
||||
}
|
||||
if (params.isNotEmpty()) {
|
||||
append('?')
|
||||
append(params.joinToString("&"))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SCHEME = "blossom:"
|
||||
|
||||
|
||||
@@ -151,4 +151,76 @@ class BlossomUriTest {
|
||||
assertEquals(listOf(server1, server2), result.servers)
|
||||
assertEquals(size, result.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun toLocalCacheUrlMinimal() {
|
||||
val uri =
|
||||
BlossomUri(
|
||||
sha256 = sha256,
|
||||
extension = "jpg",
|
||||
servers = emptyList(),
|
||||
authors = emptyList(),
|
||||
size = null,
|
||||
)
|
||||
assertEquals(
|
||||
"http://127.0.0.1:24242/$sha256.jpg",
|
||||
uri.toLocalCacheUrl("http://127.0.0.1:24242"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun toLocalCacheUrlIncludesHints() {
|
||||
val uri =
|
||||
BlossomUri(
|
||||
sha256 = sha256,
|
||||
extension = "mp4",
|
||||
servers = listOf("https://cdn.example.com", "https://backup.example.com"),
|
||||
authors = listOf(authorPubkey),
|
||||
size = 1048576L,
|
||||
)
|
||||
// BlossomUri.percentEncodeQueryValue lets `:` and `/` through unencoded
|
||||
// since they're safe in a query value as long as `&`/`=`/`#` are absent.
|
||||
assertEquals(
|
||||
"http://127.0.0.1:24242/$sha256.mp4" +
|
||||
"?xs=https://cdn.example.com" +
|
||||
"&xs=https://backup.example.com" +
|
||||
"&as=$authorPubkey" +
|
||||
"&sz=1048576",
|
||||
uri.toLocalCacheUrl("http://127.0.0.1:24242"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun toLocalCacheUrlEncodesAmpersandInServer() {
|
||||
val uri =
|
||||
BlossomUri(
|
||||
sha256 = sha256,
|
||||
extension = "jpg",
|
||||
servers = listOf("https://x.example.com/path?a=1&b=2"),
|
||||
authors = emptyList(),
|
||||
size = null,
|
||||
)
|
||||
// & and = inside the server URL must be encoded so they don't break the query.
|
||||
assertEquals(
|
||||
"http://127.0.0.1:24242/$sha256.jpg" +
|
||||
"?xs=https://x.example.com/path?a%3D1%26b%3D2",
|
||||
uri.toLocalCacheUrl("http://127.0.0.1:24242"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun toLocalCacheUrlStripsTrailingSlash() {
|
||||
val uri =
|
||||
BlossomUri(
|
||||
sha256 = sha256,
|
||||
extension = "jpg",
|
||||
servers = emptyList(),
|
||||
authors = emptyList(),
|
||||
size = null,
|
||||
)
|
||||
assertEquals(
|
||||
"http://127.0.0.1:24242/$sha256.jpg",
|
||||
uri.toLocalCacheUrl("http://127.0.0.1:24242/"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user