Shows Image

This commit is contained in:
tom5079
2021-12-01 17:18:19 +09:00
parent 70452ba7a6
commit 6c13a624a9
10 changed files with 326 additions and 41 deletions

View File

@@ -0,0 +1,106 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2021 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.util
import android.content.Context
import com.google.firebase.crashlytics.FirebaseCrashlytics
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.util.*
import io.ktor.utils.io.*
import io.ktor.utils.io.core.*
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
import org.kodein.di.instance
import xyz.quaver.hitomi.sha256
import java.io.File
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
import kotlin.text.toByteArray
class NetworkCache(context: Context) : DIAware {
override val di by closestDI(context)
private val client: HttpClient by instance()
private val cacheDir = context.cacheDir
private val _channels = ConcurrentHashMap<String, Channel<Float>>()
val channels = _channels as Map<String, Channel<Float>>
private val requests = mutableMapOf<String, Job>()
private val networkScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher())
@OptIn(ExperimentalCoroutinesApi::class)
suspend fun load(requestBuilder: HttpRequestBuilder.() -> Unit): File = coroutineScope {
val request = HttpRequestBuilder().apply(requestBuilder)
val url = request.url.buildString()
val hash = sha256(url.toByteArray()).joinToString("") { "%02x".format(it) }
val file = File(cacheDir, "$hash.${url.takeLastWhile { it != '.' }}")
val progressChannel = if (_channels[url]?.isClosedForSend == false)
_channels[url]!!
else
Channel<Float>(1, BufferOverflow.DROP_OLDEST).also { _channels[url] = it }
if (file.exists())
progressChannel.close()
else
requests[url] = networkScope.launch {
kotlin.runCatching {
file.createNewFile()
client.request<HttpStatement>(request).execute { httpResponse ->
val responseChannel: ByteReadChannel = httpResponse.receive()
val contentLength = httpResponse.contentLength() ?: -1
var readBytes = 0f
file.outputStream().use { outputStream ->
while (!responseChannel.isClosedForRead) {
val packet = responseChannel.readRemaining(DEFAULT_BUFFER_SIZE.toLong())
while (!packet.isEmpty) {
val bytes = packet.readBytes()
outputStream.write(bytes)
readBytes += bytes.size
progressChannel.trySend(readBytes / contentLength)
}
}
}
progressChannel.close()
}
}.onFailure {
file.delete()
FirebaseCrashlytics.getInstance().recordException(it)
progressChannel.close(it)
}
}
return@coroutineScope file
}
}

View File

@@ -19,17 +19,33 @@
package xyz.quaver.pupil.util
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Resources
import android.graphics.BitmapFactory
import android.graphics.BitmapRegionDecoder
import android.view.MenuItem
import android.view.View
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.toAndroidRect
import androidx.lifecycle.MutableLiveData
import kotlinx.serialization.json.*
import org.kodein.di.DIAware
import org.kodein.di.DirectDIAware
import org.kodein.di.direct
import org.kodein.di.instance
import xyz.quaver.graphics.subsampledimage.ImageSource
import xyz.quaver.graphics.subsampledimage.newBitmapRegionDecoder
import xyz.quaver.io.FileX
import xyz.quaver.io.util.inputStream
import xyz.quaver.pupil.db.AppDatabase
import xyz.quaver.pupil.sources.ItemInfo
import xyz.quaver.pupil.sources.SourceEntries
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.util.*
@@ -136,4 +152,20 @@ fun View.hide() {
fun View.show() {
visibility = View.VISIBLE
}
class FileXImageSource(file: FileX): ImageSource {
private val decoder = newBitmapRegionDecoder(file.inputStream()!!)
override val imageSize by lazy { Size(decoder.width.toFloat(), decoder.height.toFloat()) }
override fun decodeRegion(region: Rect, sampleSize: Int): ImageBitmap =
decoder.decodeRegion(region.toAndroidRect(), BitmapFactory.Options().apply {
inSampleSize = sampleSize
}).asImageBitmap()
}
@Composable
fun rememberFileXImageSource(file: FileX) = remember {
FileXImageSource(file)
}