diff --git a/app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt b/app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt index 5a78d329..d45b1e3c 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt @@ -20,6 +20,7 @@ package xyz.quaver.pupil.util.download import android.content.Context import android.content.ContextWrapper +import android.util.SparseArray import androidx.core.content.ContextCompat import androidx.preference.PreferenceManager import kotlinx.coroutines.* @@ -116,11 +117,9 @@ class Cache(context: Context) : ContextWrapper(context) { { xyz.quaver.hiyobi.getReader(galleryID) } ).map { CoroutineScope(Dispatchers.IO).async { - try { + kotlin.runCatching { it.invoke() - } catch (e: Exception) { - null - } + }.getOrNull() } }.awaitAll().filterNotNull() } else { @@ -140,8 +139,23 @@ class Cache(context: Context) : ContextWrapper(context) { } } - suspend fun getImage(galleryID: Int): File? { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + fun getImages(galleryID: Int): SparseArray? { + val regex = Regex("[0-9]+") + val gallery = getCachedGallery(galleryID) ?: return null + + return SparseArray().apply { + gallery.listFiles { file -> + file.nameWithoutExtension.matches(regex) + }?.forEach { + append(it.nameWithoutExtension.toInt(), it) + } + } + } + + fun putImage(galleryID: Int, index: Int, data: ByteArray) { + val cache = getCachedGallery(galleryID) ?: File(cacheDir, "imageCache/$galleryID") + + File(cache, index.toString()).writeBytes(data) } } \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/util/download/DownloadWorker.kt b/app/src/main/java/xyz/quaver/pupil/util/download/DownloadWorker.kt index c0b3b9f6..9bb90bf6 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/download/DownloadWorker.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/download/DownloadWorker.kt @@ -20,18 +20,27 @@ package xyz.quaver.pupil.util.download import android.content.Context import android.content.ContextWrapper +import android.content.SharedPreferences import androidx.preference.PreferenceManager -import kotlinx.coroutines.* +import com.crashlytics.android.Crashlytics +import io.fabric.sdk.android.Fabric +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.Channel -import okhttp3.OkHttpClient -import okhttp3.ResponseBody +import kotlinx.coroutines.launch +import okhttp3.* import okio.* -import java.util.concurrent.Executors +import xyz.quaver.hitomi.Reader +import xyz.quaver.hitomi.urlFromUrlFromHash +import xyz.quaver.hiyobi.createImgList +import java.io.FileInputStream +import java.io.IOException @UseExperimental(ExperimentalCoroutinesApi::class) -class DownloadWorker(context: Context) : ContextWrapper(context) { +class DownloadWorker private constructor(context: Context) : ContextWrapper(context) { - val preferences = PreferenceManager.getDefaultSharedPreferences(context) + val preferences : SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) //region ProgressListener interface ProgressListener { @@ -72,15 +81,28 @@ class DownloadWorker(context: Context) : ContextWrapper(context) { } //endregion - val queue = Channel() - val progress = mutableMapOf() - val worker = Executors.newCachedThreadPool().asCoroutineDispatcher() + //region Singleton + companion object { - val progressListener = object: ProgressListener { - override fun update(tag: Any?, bytesRead: Long, contentLength: Long, done: Boolean) { + @Volatile private var instance: DownloadWorker? = null - } + fun getInstance(context: Context) = + instance ?: synchronized(this) { + instance ?: DownloadWorker(context).also { instance = it } + } } + //endregion + + val queue = Channel() + /* VALUE + * 0 <= value < 100 -> Download in progress + * Float.POSITIVE_INFINITY -> Download completed + * Float.NaN -> Exception + */ + val progress = mutableMapOf() + val result = mutableMapOf() + val exception = mutableMapOf() + val client = OkHttpClient.Builder() .addNetworkInterceptor { chain -> val request = chain.request() @@ -97,21 +119,65 @@ class DownloadWorker(context: Context) : ContextWrapper(context) { .build() }.build() - init { - val maxThread = preferences.getInt("max_thread", 4) + val progressListener = object: ProgressListener { + override fun update(tag: Any?, bytesRead: Long, contentLength: Long, done: Boolean) { + if (tag !is String) + return + + if (progress[tag] != Float.POSITIVE_INFINITY) + progress[tag] = bytesRead / contentLength.toFloat() + } + } + init { CoroutineScope(Dispatchers.Unconfined).launch { while (!(queue.isEmpty && queue.isClosedForReceive)) { val lowQuality = preferences.getBoolean("low_quality", false) val galleryID = queue.receive() - launch(Dispatchers.IO) { - val reader = Cache(context).getReader(galleryID) ?: return@launch + launch(Dispatchers.IO) io@{ + val reader = Cache(context).getReader(galleryID) ?: return@io + val cache = Cache(context).getImages(galleryID) reader.galleryInfo.forEachIndexed { index, galleryInfo -> - when(reader.code) { - + val tag = "$galleryID-$index" + val url = when(reader.code) { + Reader.Code.HITOMI -> + urlFromUrlFromHash(galleryID, galleryInfo, if (lowQuality) "webp" else null) + Reader.Code.HIYOBI -> + createImgList(galleryID, reader, lowQuality)[index].path + else -> "" //Shouldn't be called anyways } + + //Cache exists :P + cache?.get(index)?.let { + result[tag] = FileInputStream(it).readBytes() + progress[tag] = Float.POSITIVE_INFINITY + + return@io + } + + val request = Request.Builder() + .url(url) + .tag(tag) + .build() + + client.newCall(request).enqueue(object: Callback { + override fun onFailure(call: Call, e: IOException) { + if (Fabric.isInitialized()) + Crashlytics.logException(e) + + progress[tag] = Float.NaN + exception[tag] = e + } + + override fun onResponse(call: Call, response: Response) { + response.use { + result[tag] = (it.body?: return).bytes() + progress[tag] = Float.POSITIVE_INFINITY + } + } + }) } } }