Bug fix
Thin mode added Cancel all downloads added
This commit is contained in:
@@ -49,13 +49,12 @@ import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.types.Tag
|
||||
import xyz.quaver.pupil.util.Histories
|
||||
import xyz.quaver.pupil.util.download.Cache
|
||||
import xyz.quaver.pupil.util.download.DownloadWorker
|
||||
import xyz.quaver.pupil.util.wordCapitalize
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.concurrent.schedule
|
||||
|
||||
class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryBlock>) : RecyclerSwipeAdapter<RecyclerView.ViewHolder>(), SwipeAdapterInterface {
|
||||
class GalleryBlockAdapter(private val context: Context, private val galleries: List<GalleryBlock>) : RecyclerSwipeAdapter<RecyclerView.ViewHolder>(), SwipeAdapterInterface {
|
||||
|
||||
enum class ViewType {
|
||||
NEXT,
|
||||
@@ -68,11 +67,12 @@ class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryB
|
||||
|
||||
val timer = Timer()
|
||||
|
||||
var isThin = false
|
||||
|
||||
inner class GalleryViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||
var timerTask: TimerTask? = null
|
||||
|
||||
private fun updateProgress(context: Context, galleryID: Int) {
|
||||
val cache = Cache(context).getCachedGallery(galleryID)
|
||||
val reader = Cache(context).getReaderOrNull(galleryID)
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
@@ -84,9 +84,7 @@ class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryB
|
||||
|
||||
with(view.galleryblock_progressbar) {
|
||||
|
||||
progress = cache.listFiles()?.count { file ->
|
||||
Regex("^[0-9]+.+\$").matches(file.name)
|
||||
} ?: 0
|
||||
progress = Cache(context).getImages(galleryID)?.size ?: 0
|
||||
|
||||
if (visibility == View.GONE) {
|
||||
visibility = View.VISIBLE
|
||||
@@ -126,6 +124,10 @@ class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryB
|
||||
val artists = galleryBlock.artists
|
||||
val series = galleryBlock.series
|
||||
|
||||
if (isThin)
|
||||
galleryblock_thumbnail.layoutParams.width = context.resources.getDimensionPixelSize(
|
||||
R.dimen.galleryblock_thumbnail_thin
|
||||
)
|
||||
galleryblock_thumbnail.setImageDrawable(CircularProgressDrawable(context).also {
|
||||
it.start()
|
||||
})
|
||||
@@ -138,16 +140,18 @@ class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryB
|
||||
null
|
||||
}
|
||||
|
||||
glide
|
||||
.load(thumbnail)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.error(R.drawable.image_broken_variant)
|
||||
.apply {
|
||||
if (BuildConfig.CENSOR)
|
||||
override(5, 8)
|
||||
}
|
||||
.into(galleryblock_thumbnail)
|
||||
galleryblock_thumbnail.post {
|
||||
glide
|
||||
.load(thumbnail)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.error(R.drawable.image_broken_variant)
|
||||
.apply {
|
||||
if (BuildConfig.CENSOR)
|
||||
override(5, 8)
|
||||
}
|
||||
.into(galleryblock_thumbnail)
|
||||
}
|
||||
}
|
||||
|
||||
//Check cache
|
||||
@@ -264,6 +268,14 @@ class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryB
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Make some views invisible to make it thinner
|
||||
if (isThin) {
|
||||
galleryblock_language.visibility = View.GONE
|
||||
galleryblock_type.visibility = View.GONE
|
||||
galleryblock_tag_group.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -341,10 +353,10 @@ class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryB
|
||||
mItemManger.closeAllExcept(layout)
|
||||
|
||||
holder.view.galleryblock_download.text =
|
||||
if (DownloadWorker.getInstance(holder.view.context).progress.indexOfKey(gallery.id) < 0)
|
||||
holder.view.context.getString(R.string.main_download)
|
||||
else
|
||||
if (Cache(context).isDownloading(gallery.id))
|
||||
holder.view.context.getString(android.R.string.cancel)
|
||||
else
|
||||
holder.view.context.getString(R.string.main_download)
|
||||
}
|
||||
|
||||
override fun onClose(layout: SwipeLayout?) {}
|
||||
|
||||
@@ -330,6 +330,13 @@ class MainActivity : AppCompatActivity() {
|
||||
true
|
||||
}
|
||||
|
||||
with(main_fab_cancel) {
|
||||
setImageResource(R.drawable.cancel)
|
||||
setOnClickListener {
|
||||
DownloadWorker.getInstance(context).stop()
|
||||
}
|
||||
}
|
||||
|
||||
with(main_fab_jump) {
|
||||
setImageResource(R.drawable.ic_jump)
|
||||
setOnClickListener {
|
||||
@@ -408,7 +415,7 @@ class MainActivity : AppCompatActivity() {
|
||||
if (!completeFlag.get(galleryID, false)) {
|
||||
val worker = DownloadWorker.getInstance(context)
|
||||
|
||||
if (worker.progress.indexOfKey(galleryID) >= 0) //download in progress
|
||||
if (Cache(context).isDownloading(galleryID)) //download in progress
|
||||
worker.cancel(galleryID)
|
||||
else {
|
||||
Cache(context).setDownloading(galleryID, true)
|
||||
@@ -724,6 +731,15 @@ class MainActivity : AppCompatActivity() {
|
||||
setOnMenuItemClickListener {
|
||||
when(it.itemId) {
|
||||
R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), REQUEST_SETTINGS)
|
||||
R.id.main_menu_thin -> {
|
||||
main_recyclerview.apply {
|
||||
(adapter as GalleryBlockAdapter).apply {
|
||||
isThin = !isThin
|
||||
}
|
||||
|
||||
adapter = adapter // Force to redraw
|
||||
}
|
||||
}
|
||||
R.id.main_menu_sort_newest -> {
|
||||
sortMode = SortMode.NEWEST
|
||||
it.isChecked = true
|
||||
|
||||
@@ -362,7 +362,7 @@ class ReaderActivity : AppCompatActivity() {
|
||||
window.attributes = this
|
||||
}
|
||||
|
||||
reader_recyclerview.adapter?.notifyDataSetChanged() // Force to redraw
|
||||
reader_recyclerview.adapter = reader_recyclerview.adapter // Force to redraw
|
||||
}
|
||||
|
||||
private fun scrollMode(isScroll: Boolean) {
|
||||
@@ -388,7 +388,7 @@ class ReaderActivity : AppCompatActivity() {
|
||||
if (worker.progress[galleryID]?.all { !it.isFinite() } == true) // If download is finished, stop animating
|
||||
post {
|
||||
setImageResource(R.drawable.ic_download)
|
||||
labelText = getString(R.string.reader_fab_download)
|
||||
labelText = getString(R.string.reader_fab_download_cancel)
|
||||
}
|
||||
else // Or continue animate
|
||||
post {
|
||||
|
||||
@@ -21,21 +21,19 @@ 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.preference.PreferenceManager
|
||||
import com.crashlytics.android.Crashlytics
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.io.InputStream
|
||||
import xyz.quaver.Code
|
||||
import xyz.quaver.hitomi.GalleryBlock
|
||||
import xyz.quaver.hitomi.Reader
|
||||
import xyz.quaver.proxy
|
||||
import xyz.quaver.pupil.util.copyRecursively
|
||||
import xyz.quaver.pupil.util.getCachedGallery
|
||||
import xyz.quaver.pupil.util.getDownloadDirectory
|
||||
import xyz.quaver.pupil.util.isParentOf
|
||||
import xyz.quaver.pupil.util.json
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
@@ -247,15 +245,27 @@ class Cache(context: Context) : ContextWrapper(context) {
|
||||
}
|
||||
}
|
||||
|
||||
fun moveToDownload(galleryID: Int) {
|
||||
fun moveToDownload(galleryID: Int) = CoroutineScope(Dispatchers.IO).launch {
|
||||
val cache = getCachedGallery(galleryID).also {
|
||||
if (!it.exists())
|
||||
return
|
||||
return@launch
|
||||
}
|
||||
val download = File(getDownloadDirectory(this), galleryID.toString())
|
||||
val download = File(getDownloadDirectory(this@Cache), galleryID.toString())
|
||||
|
||||
cache.copyRecursively(download, true) { _, _ -> OnErrorAction.SKIP }
|
||||
if (download.isParentOf(cache))
|
||||
return@launch
|
||||
|
||||
Log.i("PUPILD", "MOVING ${cache.canonicalPath} --> ${download.canonicalPath}")
|
||||
|
||||
cache.copyRecursively(download, true) { file, err ->
|
||||
Log.i("PUPILD", "MOVING ERROR ${file.canonicalPath} ${err.message}")
|
||||
OnErrorAction.SKIP
|
||||
}
|
||||
Log.i("PUPILD", "MOVED ${cache.canonicalPath}")
|
||||
|
||||
Log.i("PUPILD", "DELETING ${cache.canonicalPath}")
|
||||
cache.deleteRecursively()
|
||||
Log.i("PUPILD", "DELETED ${cache.canonicalPath}")
|
||||
}
|
||||
|
||||
fun isDownloading(galleryID: Int) = getCachedMetadata(galleryID)?.isDownloading == true
|
||||
|
||||
@@ -149,7 +149,6 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
||||
|
||||
private val loop = loop()
|
||||
private val worker = SparseArray<Job?>()
|
||||
val clients = SparseArray<OkHttpClient>()
|
||||
|
||||
val interceptor = Interceptor { chain ->
|
||||
val request = chain.request()
|
||||
@@ -159,7 +158,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
||||
.body(ProgressResponseBody(request.tag(), response.body(), progressListener))
|
||||
.build()
|
||||
}
|
||||
fun buildClient() =
|
||||
val client =
|
||||
OkHttpClient.Builder()
|
||||
.addInterceptor(interceptor)
|
||||
.connectTimeout(0, TimeUnit.SECONDS)
|
||||
@@ -172,17 +171,14 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
||||
queue.clear()
|
||||
|
||||
loop.cancel()
|
||||
for (i in 0..worker.size()) {
|
||||
for (i in 0 until worker.size()) {
|
||||
val galleryID = worker.keyAt(i)
|
||||
|
||||
Cache(this@DownloadWorker).setDownloading(galleryID, false)
|
||||
worker[galleryID]?.cancel()
|
||||
}
|
||||
|
||||
for (i in 0 until clients.size()) {
|
||||
clients.valueAt(i).dispatcher().cancelAll()
|
||||
}
|
||||
clients.clear()
|
||||
client.dispatcher().cancelAll()
|
||||
|
||||
progress.clear()
|
||||
exception.clear()
|
||||
@@ -194,17 +190,19 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
||||
queue.remove(galleryID)
|
||||
worker[galleryID]?.cancel()
|
||||
|
||||
clients[galleryID]?.dispatcher()?.cancelAll()
|
||||
clients.remove(galleryID)
|
||||
client.dispatcher().queuedCalls().filter {
|
||||
((it.request().tag() as Pair<*, *>).first as Int) == galleryID
|
||||
}.forEach {
|
||||
it.cancel()
|
||||
}
|
||||
|
||||
progress.remove(galleryID)
|
||||
exception.remove(galleryID)
|
||||
notification.remove(galleryID)
|
||||
notificationManager.cancel(galleryID)
|
||||
|
||||
if (progress.indexOfKey(galleryID) >= 0) {
|
||||
if (progress.indexOfKey(galleryID) >= 0)
|
||||
Cache(this@DownloadWorker).setDownloading(galleryID, false)
|
||||
}
|
||||
}
|
||||
|
||||
fun isCompleted(galleryID: Int) = progress[galleryID]?.all { !it.isFinite() } == true
|
||||
@@ -236,10 +234,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
||||
tag(galleryID to index)
|
||||
}.build()
|
||||
|
||||
if (clients.get(galleryID) == null)
|
||||
clients.put(galleryID, buildClient())
|
||||
|
||||
clients[galleryID]?.newCall(request)?.enqueue(callback)
|
||||
client.newCall(request).enqueue(callback)
|
||||
}
|
||||
|
||||
private fun download(galleryID: Int) = CoroutineScope(Dispatchers.IO).launch {
|
||||
@@ -294,8 +289,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
||||
notify(galleryID)
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
if (isCompleted(galleryID) && clients.indexOfKey(galleryID) >= 0) {
|
||||
clients.remove(galleryID)
|
||||
if (isCompleted(galleryID)) {
|
||||
with(Cache(this@DownloadWorker)) {
|
||||
if (isDownloading(galleryID)) {
|
||||
moveToDownload(galleryID)
|
||||
@@ -320,8 +314,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
||||
notify(galleryID)
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
if (isCompleted(galleryID) && clients.indexOfKey(galleryID) >= 0) {
|
||||
clients.remove(galleryID)
|
||||
if (isCompleted(galleryID)) {
|
||||
with(Cache(this@DownloadWorker)) {
|
||||
if (isDownloading(galleryID)) {
|
||||
moveToDownload(galleryID)
|
||||
@@ -340,8 +333,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
||||
notify(galleryID)
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
if (isCompleted(galleryID) && clients.indexOfKey(galleryID) >= 0) {
|
||||
clients.remove(galleryID)
|
||||
if (isCompleted(galleryID)) {
|
||||
with(Cache(this@DownloadWorker)) {
|
||||
if (isDownloading(galleryID)) {
|
||||
moveToDownload(galleryID)
|
||||
@@ -418,7 +410,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
||||
|
||||
val galleryID = queue.peek() ?: continue
|
||||
|
||||
if (clients.indexOfKey(galleryID) >= 0) // Gallery already downloading!
|
||||
if (progress.indexOfKey(galleryID) >= 0) // Gallery already downloading!
|
||||
continue
|
||||
|
||||
if (notification[galleryID] == null)
|
||||
@@ -427,10 +419,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
||||
if (Cache(this@DownloadWorker).isDownloading(galleryID))
|
||||
notificationManager.notify(galleryID, notification[galleryID].build())
|
||||
|
||||
if (clients.size() >= preferences.getInt("max_download", 4))
|
||||
continue
|
||||
|
||||
Log.i("PUPILD", "QUEUED $galleryID #${clients.size()+1}")
|
||||
Log.i("PUPILD", "QUEUED $galleryID")
|
||||
|
||||
worker.put(galleryID, download(galleryID))
|
||||
queue.poll()
|
||||
|
||||
@@ -26,7 +26,6 @@ import android.os.storage.StorageManager
|
||||
import android.provider.DocumentsContract
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import kotlinx.io.IOException
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.lang.reflect.Array
|
||||
@@ -214,64 +213,5 @@ fun Uri.toFile(context: Context): File? {
|
||||
return File(context.getExternalFilesDir(null)?.canonicalPath?.substringBeforeLast("/Android/data") ?: return null, folderName)
|
||||
}
|
||||
|
||||
fun File.copyRecursively(
|
||||
target: File,
|
||||
overwrite: Boolean = false,
|
||||
onError: (File, IOException) -> OnErrorAction = { _, exception -> throw exception }
|
||||
): Boolean {
|
||||
if (!exists()) {
|
||||
return onError(this, NoSuchFileException(file = this, reason = "The source file doesn't exist.")) !=
|
||||
OnErrorAction.TERMINATE
|
||||
}
|
||||
try {
|
||||
// We cannot break for loop from inside a lambda, so we have to use an exception here
|
||||
for (src in walkTopDown().onFail { f, e -> if (onError(f, e) == OnErrorAction.TERMINATE) throw IOException("Walk failed") }) {
|
||||
if (!src.exists()) {
|
||||
if (onError(src, NoSuchFileException(file = src, reason = "The source file doesn't exist.")) ==
|
||||
OnErrorAction.TERMINATE)
|
||||
return false
|
||||
} else {
|
||||
val relPath = src.toRelativeString(this)
|
||||
val dstFile = File(target, relPath)
|
||||
if (dstFile.exists() && !(src.isDirectory && dstFile.isDirectory)) {
|
||||
val stillExists = if (!overwrite) true else {
|
||||
if (dstFile.isDirectory)
|
||||
!dstFile.deleteRecursively()
|
||||
else
|
||||
!dstFile.delete()
|
||||
}
|
||||
|
||||
if (stillExists) {
|
||||
if (onError(dstFile, FileAlreadyExistsException(file = src,
|
||||
other = dstFile,
|
||||
reason = "The destination file already exists.")) == OnErrorAction.TERMINATE)
|
||||
return false
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (src.isDirectory) {
|
||||
dstFile.mkdirs()
|
||||
} else {
|
||||
val length = try {
|
||||
src.copyTo(dstFile, overwrite).length()
|
||||
} catch (e: IOException) {
|
||||
if (onError(src, e) == OnErrorAction.TERMINATE)
|
||||
return false
|
||||
else
|
||||
-1
|
||||
}
|
||||
|
||||
if (length != src.length()) {
|
||||
if (onError(src, IOException("Source file wasn't copied completely, length of destination file differs.")) == OnErrorAction.TERMINATE)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
} catch (e: IOException) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
fun File.isParentOf(another: File) =
|
||||
another.absolutePath.startsWith(this.absolutePath)
|
||||
Reference in New Issue
Block a user