Finished integrating new downloader

This commit is contained in:
Pupil
2020-02-03 11:54:29 +09:00
parent 874606bff9
commit d30c51bb3a
8 changed files with 211 additions and 116 deletions

View File

@@ -51,7 +51,6 @@ class Pupil : MultiDexApplication() {
val preference = PreferenceManager.getDefaultSharedPreferences(this)
histories = Histories(File(ContextCompat.getDataDir(this), "histories.json"))
downloads = Histories(File(ContextCompat.getDataDir(this), "downloads.json"))
favorites = Histories(File(ContextCompat.getDataDir(this), "favorites.json"))
try {
@@ -64,7 +63,7 @@ class Pupil : MultiDexApplication() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val channel = NotificationChannel("download", getString(R.string.channel_download), NotificationManager.IMPORTANCE_LOW).apply {
val channel = NotificationChannel("download", getString(R.string.channel_download), NotificationManager.IMPORTANCE_MIN).apply {
description = getString(R.string.channel_download_description)
enableLights(false)
enableVibration(false)

View File

@@ -21,6 +21,7 @@ package xyz.quaver.pupil.adapters
import android.content.Context
import android.graphics.drawable.Drawable
import android.util.Base64
import android.util.Log
import android.util.SparseBooleanArray
import android.view.LayoutInflater
import android.view.View
@@ -29,6 +30,7 @@ import android.widget.LinearLayout
import androidx.cardview.widget.CardView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.bumptech.glide.Glide
@@ -65,9 +67,54 @@ class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryB
private val glide = Glide.with(context)
private lateinit var favorites: Histories
val timer = Timer()
inner class GalleryViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
var timerTask: TimerTask? = null
fun updateProgress(context: Context, galleryID: Int) = CoroutineScope(Dispatchers.Main).launch {
val cache = Cache(context).getCachedGallery(galleryID)
val reader = Cache(context).getReaderOrNull(galleryID)
Log.i("PUPILD", "$galleryID ${if (reader == null) null else "%d/%d".format(Cache(context).getImages(galleryID)?.count { it != null }, reader.galleryInfo.size)}")
if (reader == null) {
view.galleryblock_progressbar.visibility = View.GONE
view.galleryblock_progress_complete.visibility = View.GONE
return@launch
}
with(view.galleryblock_progressbar) {
progress = cache?.listFiles { file ->
file.nameWithoutExtension.toIntOrNull() != null
}?.size ?: 0
if (visibility == View.GONE) {
visibility = View.VISIBLE
max = reader.galleryInfo.size
}
if (progress == max) {
if (completeFlag.get(galleryID, false)) {
with(view.galleryblock_progress_complete) {
setImageResource(R.drawable.ic_progressbar)
visibility = View.VISIBLE
}
} else {
with(view.galleryblock_progress_complete) {
setImageDrawable(AnimatedVectorDrawableCompat.create(context, R.drawable.ic_progressbar_complete).apply {
this?.start()
})
visibility = View.VISIBLE
}
completeFlag.put(galleryID, true)
}
} else
view.galleryblock_progress_complete.visibility = View.INVISIBLE
}
}
fun bind(galleryBlock: GalleryBlock) {
with(view) {
val resources = context.resources
@@ -80,6 +127,10 @@ class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryB
val artists = galleryBlock.artists
val series = galleryBlock.series
galleryblock_thumbnail.setImageDrawable(CircularProgressDrawable(context).also {
it.start()
})
CoroutineScope(Dispatchers.Main).launch {
val thumbnail = Base64.decode(Cache(context).getThumbnail(galleryBlock.id), Base64.DEFAULT)
@@ -114,47 +165,8 @@ class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryB
galleryblock_progressbar.visibility = View.GONE
if (timerTask == null)
timerTask = Timer().schedule(0, 1000) {
CoroutineScope(Dispatchers.Main).launch {
val _cache = Cache(context).getCachedGallery(galleryBlock.id)
val _reader = Cache(context).getReaderOrNull(galleryBlock.id)
if (_reader == null) {
view.galleryblock_progressbar.visibility = View.GONE
view.galleryblock_progress_complete.visibility = View.GONE
return@launch
}
with(view.galleryblock_progressbar) {
progress = _cache?.listFiles { file ->
file.nameWithoutExtension.toIntOrNull() != null
}?.size ?: 0
if (visibility == View.GONE) {
visibility = View.VISIBLE
max = _reader.galleryInfo.size
}
if (progress == max) {
if (completeFlag.get(galleryBlock.id, false)) {
with(view.galleryblock_progress_complete) {
setImageResource(R.drawable.ic_progressbar)
visibility = View.VISIBLE
}
} else {
with(view.galleryblock_progress_complete) {
setImageDrawable(AnimatedVectorDrawableCompat.create(context, R.drawable.ic_progressbar_complete).apply {
this?.start()
})
visibility = View.VISIBLE
}
completeFlag.put(galleryBlock.id, true)
}
} else
view.galleryblock_progress_complete.visibility = View.INVISIBLE
}
}
timerTask = timer.schedule(0, 1000) {
updateProgress(context, galleryBlock.id)
}
galleryblock_title.text = galleryBlock.title
@@ -343,8 +355,10 @@ class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryB
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
super.onViewDetachedFromWindow(holder)
if (holder is GalleryViewHolder)
if (holder is GalleryViewHolder) {
holder.timerTask?.cancel()
holder.timerTask = null
}
}
override fun getItemCount() =

View File

@@ -47,6 +47,7 @@ class ReaderAdapter(private val context: Context,
var reader: Reader? = null
private val glide = Glide.with(context)
val timer = Timer()
var onItemClickListener : ((Int) -> (Unit))? = null
@@ -81,51 +82,56 @@ class ReaderAdapter(private val context: Context,
onItemClickListener?.invoke(position)
}
holder.view.container.setOnClickListener {
onItemClickListener?.invoke(position)
}
(holder.view.container.layoutParams as ConstraintLayout.LayoutParams)
.dimensionRatio = "${reader!!.galleryInfo[position].width}:${reader!!.galleryInfo[position].height}"
holder.view.reader_item_progressbar.progress = DownloadWorker.getInstance(context).progress[galleryID]?.get(position)?.roundToInt() ?: 0
holder.view.reader_index.text = (position+1).toString()
val progress = DownloadWorker.getInstance(context).progress[galleryID]?.get(position)
if (progress?.isFinite() == false) {
when {
progress.isInfinite() -> {
var image = Cache(context).getImages(galleryID)
while (image?.get(position) == null)
image = Cache(context).getImages(galleryID)
glide
.load(image[position])
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.error(R.drawable.image_broken_variant)
.apply {
if (BuildConfig.CENSOR)
override(5, 8)
}
.into(holder.view.image)
}
progress.isNaN() -> {
glide
.load(R.drawable.image_broken_variant)
.into(holder.view.image)
Snackbar
.make(
holder.view,
DownloadWorker.getInstance(context).exception[galleryID]!![position]?.message
?: context.getText(R.string.default_error_msg),
Snackbar.LENGTH_INDEFINITE
)
.show()
val images = Cache(context).getImages(galleryID)
if (images?.get(position) != null) {
glide
.load(images[position])
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.error(R.drawable.image_broken_variant)
.apply {
if (BuildConfig.CENSOR)
override(5, 8)
}
.into(holder.view.image)
} else {
val progress = DownloadWorker.getInstance(context).progress[galleryID]?.get(position)
if (progress?.isNaN() == true) {
glide
.load(R.drawable.image_broken_variant)
.into(holder.view.image)
Snackbar
.make(
holder.view,
DownloadWorker.getInstance(context).exception[galleryID]!![position]?.message
?: context.getText(R.string.default_error_msg),
Snackbar.LENGTH_INDEFINITE
)
.show()
return
}
} else {
holder.view.reader_item_progressbar.progress =
if (progress?.isInfinite() == true)
100
else
progress?.roundToInt() ?: 0
holder.view.image.setImageDrawable(null)
Timer().schedule(1000) {
timer.schedule(1000) {
CoroutineScope(Dispatchers.Main).launch {
notifyItemChanged(position)
}

View File

@@ -110,7 +110,6 @@ class MainActivity : AppCompatActivity() {
private var currentPage = 0
private lateinit var histories: Histories
private lateinit var downloads: Histories
private lateinit var favorites: Histories
override fun onCreate(savedInstanceState: Bundle?) {
@@ -148,7 +147,6 @@ class MainActivity : AppCompatActivity() {
with(application as Pupil) {
this@MainActivity.histories = histories
this@MainActivity.downloads = downloads
this@MainActivity.favorites = favorites
}
@@ -174,6 +172,12 @@ class MainActivity : AppCompatActivity() {
}
}
override fun onDestroy() {
super.onDestroy()
(main_recyclerview.adapter as GalleryBlockAdapter).timer.cancel()
}
override fun onResume() {
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
@@ -405,7 +409,6 @@ class MainActivity : AppCompatActivity() {
if (worker.progress.indexOfKey(galleryID) >= 0) //download in progress
worker.cancel(galleryID)
else {
Cache(context).moveToDownload(galleryID)
Cache(context).setDownloading(galleryID, true)
if (!worker.queue.contains(galleryID))
@@ -429,7 +432,6 @@ class MainActivity : AppCompatActivity() {
cache = Cache(context).getCachedGallery(galleryID)
}
downloads.remove(galleryID)
histories.remove(galleryID)
if (this@MainActivity.mode != Mode.SEARCH)
@@ -963,8 +965,14 @@ class MainActivity : AppCompatActivity() {
}
}
Mode.DOWNLOAD -> {
val downloads = getDownloadDirectory(this@MainActivity).listFiles { file ->
file.isDirectory and (file.name.toIntOrNull() != null) and File(file, ".metadata").exists()
}?.map {
it.name.toInt()
}?: listOf()
when {
query.isEmpty() -> downloads.toList().apply {
query.isEmpty() -> downloads.apply {
totalItems = size
}
else -> {

View File

@@ -38,9 +38,6 @@ import io.fabric.sdk.android.Fabric
import kotlinx.android.synthetic.main.activity_reader.*
import kotlinx.android.synthetic.main.activity_reader.view.*
import kotlinx.android.synthetic.main.dialog_numberpicker.view.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.ImplicitReflectionSerializer
import xyz.quaver.hitomi.Reader
import xyz.quaver.pupil.Pupil
@@ -199,6 +196,7 @@ class ReaderActivity : AppCompatActivity() {
super.onDestroy()
timer.cancel()
(reader_recyclerview.adapter as ReaderAdapter).timer.cancel()
if (!Cache(this).isDownloading(galleryID))
DownloadWorker.getInstance(this@ReaderActivity).cancel(galleryID)
@@ -327,10 +325,6 @@ class ReaderActivity : AppCompatActivity() {
animateDownloadFAB(false)
} else {
CoroutineScope(Dispatchers.IO).launch {
Cache(context).moveToDownload(galleryID)
}
Cache(context).setDownloading(galleryID, true)
animateDownloadFAB(true)
}

View File

@@ -21,8 +21,6 @@ package xyz.quaver.pupil.util.download
import android.content.Context
import android.content.ContextWrapper
import android.util.Base64
import android.util.Log
import android.util.SparseArray
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import kotlinx.coroutines.*
@@ -175,15 +173,13 @@ class Cache(context: Context) : ContextWrapper(context) {
}
}
fun getImages(galleryID: Int): SparseArray<File>? {
fun getImages(galleryID: Int): List<File?>? {
val gallery = getCachedGallery(galleryID) ?: return null
val reader = getReaderOrNull(galleryID) ?: return null
val images = gallery.listFiles() ?: return null
return SparseArray<File>().apply {
gallery.listFiles { file ->
file.nameWithoutExtension.toIntOrNull() != null
}?.forEach {
put(it.nameWithoutExtension.toInt(), it)
}
return reader.galleryInfo.indices.map { index ->
images.firstOrNull { file -> file.nameWithoutExtension.toIntOrNull() == index }
}
}
@@ -209,7 +205,7 @@ class Cache(context: Context) : ContextWrapper(context) {
val cache = getCachedGallery(galleryID)
if (cache != null) {
val download = getDownloadDirectory(this)
val download = File(getDownloadDirectory(this), galleryID.toString())
if (!download.isParentOf(cache)) {
cache.copyRecursively(download, true)
@@ -222,7 +218,6 @@ class Cache(context: Context) : ContextWrapper(context) {
fun isDownloading(galleryID: Int) = getCachedMetadata(galleryID)?.isDownloading == true
fun setDownloading(galleryID: Int, isDownloading: Boolean) {
Log.i("PUPILD", "$galleryID $isDownloading")
setCachedMetadata(galleryID, Metadata(getCachedMetadata(galleryID), isDownloading = isDownloading))
}

View File

@@ -18,10 +18,15 @@
package xyz.quaver.pupil.util.download
import android.app.PendingIntent
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.SharedPreferences
import android.util.SparseArray
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.TaskStackBuilder
import androidx.preference.PreferenceManager
import com.crashlytics.android.Crashlytics
import io.fabric.sdk.android.Fabric
@@ -34,6 +39,8 @@ import xyz.quaver.hitomi.urlFromUrlFromHash
import xyz.quaver.hiyobi.cookie
import xyz.quaver.hiyobi.createImgList
import xyz.quaver.hiyobi.user_agent
import xyz.quaver.pupil.R
import xyz.quaver.pupil.ui.ReaderActivity
import java.io.IOException
import java.util.concurrent.Executors
import java.util.concurrent.LinkedBlockingQueue
@@ -66,7 +73,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
private var bufferedSource : BufferedSource? = null
override fun contentLength() = responseBody.contentLength()
override fun contentType() = responseBody.contentType()
override fun contentType() = responseBody.contentType() ?: null
override fun source(): BufferedSource {
if (bufferedSource == null)
@@ -104,6 +111,8 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
}
//endregion
val notificationManager = NotificationManagerCompat.from(context)
val queue = LinkedBlockingQueue<Int>()
/*
@@ -131,6 +140,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
* null -> Download in progress / Loading
*/
val exception = SparseArray<MutableList<Throwable?>?>()
val notification = SparseArray<NotificationCompat.Builder>()
private val loop = loop()
private val worker = SparseArray<Job?>()
@@ -169,6 +179,8 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
progress.clear()
exception.clear()
notification.clear()
notificationManager.cancelAll()
nRunners = 0
@@ -187,15 +199,19 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
it.cancel()
}
if (progress.indexOfKey(galleryID) >= 0) {
progress.remove(galleryID)
exception.remove(galleryID)
progress.remove(galleryID)
exception.remove(galleryID)
notification.remove(galleryID)
notificationManager.cancel(galleryID)
if (progress.indexOfKey(galleryID) >= 0) {
Cache(this@DownloadWorker).setDownloading(galleryID, false)
nRunners--
}
}
fun isCompleted(galleryID: Int) = progress[galleryID]?.all { !it.isFinite() } == true
private fun queueDownload(galleryID: Int, reader: Reader, index: Int, callback: Callback) {
val cache = Cache(this@DownloadWorker).getImages(galleryID)
val lowQuality = preferences.getBoolean("low_quality", false)
@@ -204,6 +220,18 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
cache?.get(index)?.let {
progress[galleryID]?.set(index, Float.POSITIVE_INFINITY)
notify(galleryID)
if (isCompleted(galleryID)) {
with(Cache(this@DownloadWorker)) {
if (isDownloading(galleryID)) {
moveToDownload(galleryID)
setDownloading(galleryID, false)
}
}
nRunners--
}
return
}
@@ -250,6 +278,9 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
progress.put(galleryID, reader.galleryInfo.map { 0F }.toMutableList())
exception.put(galleryID, reader.galleryInfo.map { null }.toMutableList())
notification[galleryID].setContentTitle(reader.title)
notify(galleryID)
for (i in reader.galleryInfo.indices) {
val callback = object : Callback {
override fun onFailure(call: Call, e: IOException) {
@@ -259,11 +290,14 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
progress[galleryID]?.set(i, Float.NaN)
exception[galleryID]?.set(i, e)
if (progress[galleryID]?.all { !it.isFinite() } == true) {
progress.remove(galleryID)
exception.remove(galleryID)
notify(galleryID)
Cache(this@DownloadWorker).setDownloading(galleryID, false)
if (isCompleted(galleryID)) {
val cache = Cache(this@DownloadWorker)
if (cache.isDownloading(galleryID)) {
cache.moveToDownload(galleryID)
cache.setDownloading(galleryID, false)
}
nRunners--
}
}
@@ -278,11 +312,14 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
progress[galleryID]?.set(i, Float.POSITIVE_INFINITY)
}
if (progress[galleryID]?.all { !it.isFinite() } == true) {
progress.remove(galleryID)
exception.remove(galleryID)
notify(galleryID)
Cache(this@DownloadWorker).setDownloading(galleryID, false)
if (isCompleted(galleryID)) {
val cache = Cache(this@DownloadWorker)
if (cache.isDownloading(galleryID)) {
cache.moveToDownload(galleryID)
cache.setDownloading(galleryID, false)
}
nRunners--
}
}
@@ -292,6 +329,43 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
}
}
private fun notify(galleryID: Int) {
val max = progress[galleryID]?.size ?: 0
val progress = progress[galleryID]?.count { !it.isFinite() } ?: 0
if (isCompleted(galleryID))
notification[galleryID]
.setContentText(getString(R.string.reader_notification_complete))
.setProgress(0, 0, false)
else
notification[galleryID]
.setProgress(max, progress, false)
.setContentText("$progress/$max")
if (Cache(this).isDownloading(galleryID))
notificationManager.notify(galleryID, notification[galleryID].build())
else
notificationManager.cancel(galleryID)
}
private fun initNotification(galleryID: Int) {
val intent = Intent(this, ReaderActivity::class.java).apply {
putExtra("galleryID", galleryID)
}
val pendingIntent = TaskStackBuilder.create(this).run {
addNextIntentWithParentStack(intent)
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
}
notification.put(galleryID, NotificationCompat.Builder(this, "download").apply {
setContentTitle(getString(R.string.reader_loading))
setContentText(getString(R.string.reader_notification_text))
setSmallIcon(android.R.drawable.stat_sys_download) // had to use this because old android doesn't support VectorDrawable on Notification :P
setContentIntent(pendingIntent)
setProgress(0, 0, true)
})
}
private fun loop() = CoroutineScope(Dispatchers.Default).launch {
while (true) {
if (queue.isEmpty() || nRunners > preferences.getInt("max_download", 4))
@@ -299,6 +373,12 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
val galleryID = queue.poll() ?: continue
if (progress.indexOfKey(galleryID) >= 0) // Gallery already downloading!
continue
initNotification(galleryID)
if (Cache(this@DownloadWorker).isDownloading(galleryID))
notificationManager.notify(galleryID, notification[galleryID].build())
worker.put(galleryID, download(galleryID))
nRunners++
}

View File

@@ -27,8 +27,7 @@
<item android:id="@+id/reader_type"
android:title=""
app:showAsAction="ifRoom"
android:visible="false"/>
app:showAsAction="ifRoom"/>
<item android:id="@+id/reader_menu_page_indicator"
android:title="@string/page_indicator_placeholder"