Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f249c07e7 | ||
|
|
5fd35b492c | ||
|
|
9bddf95013 |
@@ -20,7 +20,7 @@ android {
|
|||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 43
|
versionCode 43
|
||||||
versionName "4.7-beta1"
|
versionName "4.7"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":43,"versionName":"4.7-beta1","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
|
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":43,"versionName":"4.7","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
package xyz.quaver.pupil.adapters
|
package xyz.quaver.pupil.adapters
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -122,10 +123,23 @@ class ReaderAdapter(private val context: Context,
|
|||||||
val progress = DownloadWorker.getInstance(context).progress[galleryID]?.get(position)
|
val progress = DownloadWorker.getInstance(context).progress[galleryID]?.get(position)
|
||||||
|
|
||||||
if (progress?.isInfinite() == true && images != null) {
|
if (progress?.isInfinite() == true && images != null) {
|
||||||
|
holder.view.reader_item_progressbar.visibility = View.INVISIBLE
|
||||||
|
holder.view.container.apply {
|
||||||
|
val options = BitmapFactory.Options().apply {
|
||||||
|
inJustDecodeBounds = true
|
||||||
|
}
|
||||||
|
|
||||||
|
BitmapFactory.decodeFile(images.canonicalPath, options)
|
||||||
|
|
||||||
|
maxWidth = options.outWidth
|
||||||
|
maxHeight = options.outHeight
|
||||||
|
}
|
||||||
|
|
||||||
glide
|
glide
|
||||||
.load(images)
|
.load(images)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
.skipMemoryCache(true)
|
.skipMemoryCache(true)
|
||||||
|
.dontTransform()
|
||||||
.error(R.drawable.image_broken_variant)
|
.error(R.drawable.image_broken_variant)
|
||||||
.apply {
|
.apply {
|
||||||
if (BuildConfig.CENSOR)
|
if (BuildConfig.CENSOR)
|
||||||
@@ -133,6 +147,12 @@ class ReaderAdapter(private val context: Context,
|
|||||||
}
|
}
|
||||||
.into(holder.view.image)
|
.into(holder.view.image)
|
||||||
} else {
|
} else {
|
||||||
|
holder.view.reader_item_progressbar.visibility = View.VISIBLE
|
||||||
|
holder.view.container.apply {
|
||||||
|
maxWidth = Integer.MAX_VALUE
|
||||||
|
maxHeight = Integer.MAX_VALUE
|
||||||
|
}
|
||||||
|
|
||||||
if (progress?.isNaN() == true) {
|
if (progress?.isNaN() == true) {
|
||||||
if (Fabric.isInitialized())
|
if (Fabric.isInitialized())
|
||||||
Crashlytics.logException(DownloadWorker.getInstance(context).exception[galleryID]?.get(position))
|
Crashlytics.logException(DownloadWorker.getInstance(context).exception[galleryID]?.get(position))
|
||||||
|
|||||||
@@ -961,7 +961,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
Mode.DOWNLOAD -> {
|
Mode.DOWNLOAD -> {
|
||||||
val downloads = getDownloadDirectory(this@MainActivity).listFiles()?.filter { file ->
|
val downloads = getDownloadDirectory(this@MainActivity).listFiles()?.filter { file ->
|
||||||
file.isDirectory && (file.name.toIntOrNull() != null) && File(file, ".metadata").exists()
|
file.isDirectory && file.name.toIntOrNull() != null
|
||||||
|
}?.sortedByDescending {
|
||||||
|
it.lastModified()
|
||||||
}?.map {
|
}?.map {
|
||||||
it.name.toInt()
|
it.name.toInt()
|
||||||
} ?: emptyList()
|
} ?: emptyList()
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ package xyz.quaver.pupil.util.download
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
|
import android.util.SparseArray
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.crashlytics.android.Crashlytics
|
import com.crashlytics.android.Crashlytics
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -38,9 +39,25 @@ import xyz.quaver.pupil.util.json
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
import java.util.concurrent.locks.Lock
|
||||||
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
|
|
||||||
class Cache(context: Context) : ContextWrapper(context) {
|
class Cache(context: Context) : ContextWrapper(context) {
|
||||||
|
|
||||||
|
private val locks = SparseArray<Lock>()
|
||||||
|
private fun lock(galleryID: Int) {
|
||||||
|
synchronized(locks) {
|
||||||
|
if (locks.indexOfKey(galleryID) < 0)
|
||||||
|
locks.put(galleryID, ReentrantLock())
|
||||||
|
}
|
||||||
|
|
||||||
|
locks[galleryID].lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unlock(galleryID: Int) {
|
||||||
|
locks[galleryID]?.unlock()
|
||||||
|
}
|
||||||
|
|
||||||
private val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
private val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
|
||||||
// Search in this order
|
// Search in this order
|
||||||
@@ -217,13 +234,16 @@ class Cache(context: Context) : ContextWrapper(context) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun putImage(galleryID: Int, index: Int, ext: String, data: InputStream) {
|
fun putImage(galleryID: Int, index: Int, ext: String, data: InputStream) {
|
||||||
val cache = File(getCachedGallery(galleryID), "%05d.$ext".format(index)).also {
|
val cache = File(getCachedGallery(galleryID), "%05d.$ext".format(index)).also {
|
||||||
if (!it.exists())
|
if (!it.exists())
|
||||||
it.createNewFile()
|
it.createNewFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
data.copyTo(FileOutputStream(cache))
|
data.use {
|
||||||
|
it.copyTo(FileOutputStream(cache))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun moveToDownload(galleryID: Int) {
|
fun moveToDownload(galleryID: Int) {
|
||||||
@@ -233,7 +253,7 @@ class Cache(context: Context) : ContextWrapper(context) {
|
|||||||
}
|
}
|
||||||
val download = File(getDownloadDirectory(this), galleryID.toString())
|
val download = File(getDownloadDirectory(this), galleryID.toString())
|
||||||
|
|
||||||
cache.copyRecursively(download, true)
|
cache.copyRecursively(download, true) { _, _ -> OnErrorAction.SKIP }
|
||||||
cache.deleteRecursively()
|
cache.deleteRecursively()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,13 +37,14 @@ import okio.*
|
|||||||
import xyz.quaver.Code
|
import xyz.quaver.Code
|
||||||
import xyz.quaver.hitomi.Reader
|
import xyz.quaver.hitomi.Reader
|
||||||
import xyz.quaver.hitomi.getReferer
|
import xyz.quaver.hitomi.getReferer
|
||||||
import xyz.quaver.hitomi.urlFromUrlFromHash
|
import xyz.quaver.hitomi.imageUrlFromImage
|
||||||
import xyz.quaver.hiyobi.cookie
|
import xyz.quaver.hiyobi.cookie
|
||||||
import xyz.quaver.hiyobi.createImgList
|
import xyz.quaver.hiyobi.createImgList
|
||||||
import xyz.quaver.hiyobi.user_agent
|
import xyz.quaver.hiyobi.user_agent
|
||||||
import xyz.quaver.proxy
|
import xyz.quaver.proxy
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.ui.ReaderActivity
|
import xyz.quaver.pupil.ui.ReaderActivity
|
||||||
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.LinkedBlockingQueue
|
import java.util.concurrent.LinkedBlockingQueue
|
||||||
@@ -215,10 +216,10 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
|||||||
when (reader.code) {
|
when (reader.code) {
|
||||||
Code.HITOMI -> {
|
Code.HITOMI -> {
|
||||||
url(
|
url(
|
||||||
urlFromUrlFromHash(
|
imageUrlFromImage(
|
||||||
galleryID,
|
galleryID,
|
||||||
reader.galleryInfo.files[index],
|
reader.galleryInfo.files[index],
|
||||||
if (lowQuality) "webp" else null
|
lowQuality
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
addHeader("Referer", getReferer(galleryID))
|
addHeader("Referer", getReferer(galleryID))
|
||||||
@@ -256,7 +257,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
|||||||
val cache = Cache(this@DownloadWorker).getImages(galleryID)
|
val cache = Cache(this@DownloadWorker).getImages(galleryID)
|
||||||
|
|
||||||
progress.put(galleryID, reader.galleryInfo.files.indices.map { index ->
|
progress.put(galleryID, reader.galleryInfo.files.indices.map { index ->
|
||||||
if (cache?.getOrNull(index) != null)
|
if (cache?.firstOrNull { it?.nameWithoutExtension?.toIntOrNull() == index } != null)
|
||||||
Float.POSITIVE_INFINITY
|
Float.POSITIVE_INFINITY
|
||||||
else
|
else
|
||||||
0F
|
0F
|
||||||
@@ -308,9 +309,9 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
|||||||
override fun onResponse(call: Call, response: Response) {
|
override fun onResponse(call: Call, response: Response) {
|
||||||
Log.i("PUPILD", "OK ${call.request().tag()}")
|
Log.i("PUPILD", "OK ${call.request().tag()}")
|
||||||
|
|
||||||
try {
|
val ext = call.request().url().encodedPath().split('.').last()
|
||||||
val ext = call.request().url().encodedPath().split('.').last()
|
|
||||||
|
|
||||||
|
try {
|
||||||
response.body().use {
|
response.body().use {
|
||||||
Cache(this@DownloadWorker).putImage(galleryID, i, ext, it.byteStream())
|
Cache(this@DownloadWorker).putImage(galleryID, i, ext, it.byteStream())
|
||||||
}
|
}
|
||||||
@@ -332,6 +333,26 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
|||||||
|
|
||||||
Log.i("PUPILD", "SUCCESS ${call.request().tag()}")
|
Log.i("PUPILD", "SUCCESS ${call.request().tag()}")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
||||||
|
progress[galleryID]?.set(i, Float.NaN)
|
||||||
|
exception[galleryID]?.set(i, e)
|
||||||
|
|
||||||
|
notify(galleryID)
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
if (isCompleted(galleryID) && clients.indexOfKey(galleryID) >= 0) {
|
||||||
|
clients.remove(galleryID)
|
||||||
|
with(Cache(this@DownloadWorker)) {
|
||||||
|
if (isDownloading(galleryID)) {
|
||||||
|
moveToDownload(galleryID)
|
||||||
|
setDownloading(galleryID, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File(Cache(this@DownloadWorker).getCachedGallery(galleryID), "%05d.$ext".format(i)).delete()
|
||||||
|
|
||||||
Log.i("PUPILD", "FAIL ON OK ${call.request().tag()} (${e.message})")
|
Log.i("PUPILD", "FAIL ON OK ${call.request().tag()} (${e.message})")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -350,13 +371,17 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
|||||||
val max = progress[galleryID]?.size ?: 0
|
val max = progress[galleryID]?.size ?: 0
|
||||||
val progress = progress[galleryID]?.count { !it.isFinite() } ?: 0
|
val progress = progress[galleryID]?.count { !it.isFinite() } ?: 0
|
||||||
|
|
||||||
if (isCompleted(galleryID))
|
Log.i("PUPILD", "NOTIFY $galleryID ${isCompleted(galleryID)} $progress/$max")
|
||||||
|
|
||||||
|
if (isCompleted(galleryID)) {
|
||||||
notification[galleryID]
|
notification[galleryID]
|
||||||
?.setContentText(getString(R.string.reader_notification_complete))
|
?.setContentText(getString(R.string.reader_notification_complete))
|
||||||
?.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
?.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||||
?.setProgress(0, 0, false)
|
?.setProgress(0, 0, false)
|
||||||
?.setOngoing(false)
|
?.setOngoing(false)
|
||||||
else
|
|
||||||
|
notificationManager.cancel(galleryID)
|
||||||
|
} else
|
||||||
notification[galleryID]
|
notification[galleryID]
|
||||||
?.setProgress(max, progress, false)
|
?.setProgress(max, progress, false)
|
||||||
?.setContentText("$progress/$max")
|
?.setContentText("$progress/$max")
|
||||||
@@ -388,18 +413,27 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
|||||||
|
|
||||||
private fun loop() = CoroutineScope(Dispatchers.Default).launch {
|
private fun loop() = CoroutineScope(Dispatchers.Default).launch {
|
||||||
while (true) {
|
while (true) {
|
||||||
if (queue.isEmpty() || clients.size() >= preferences.getInt("max_download", 4))
|
if (queue.isEmpty())
|
||||||
continue
|
continue
|
||||||
|
|
||||||
val galleryID = queue.poll() ?: continue
|
val galleryID = queue.peek() ?: continue
|
||||||
|
|
||||||
if (clients.indexOfKey(galleryID) >= 0) // Gallery already downloading!
|
if (clients.indexOfKey(galleryID) >= 0) // Gallery already downloading!
|
||||||
continue
|
continue
|
||||||
|
|
||||||
initNotification(galleryID)
|
if (notification[galleryID] == null)
|
||||||
|
initNotification(galleryID)
|
||||||
|
|
||||||
if (Cache(this@DownloadWorker).isDownloading(galleryID))
|
if (Cache(this@DownloadWorker).isDownloading(galleryID))
|
||||||
notificationManager.notify(galleryID, notification[galleryID].build())
|
notificationManager.notify(galleryID, notification[galleryID].build())
|
||||||
|
|
||||||
|
if (clients.size() >= preferences.getInt("max_download", 4))
|
||||||
|
continue
|
||||||
|
|
||||||
|
Log.i("PUPILD", "QUEUED $galleryID #${clients.size()+1}")
|
||||||
|
|
||||||
worker.put(galleryID, download(galleryID))
|
worker.put(galleryID, download(galleryID))
|
||||||
|
queue.poll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,8 @@
|
|||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
android:background="@drawable/reader_item_boundary">
|
android:background="@drawable/reader_item_boundary">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|||||||
Reference in New Issue
Block a user