working
This commit is contained in:
@@ -99,7 +99,7 @@ dependencies {
|
||||
implementation ("xyz.quaver:libpupil:1.3") {
|
||||
exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-serialization-core-jvm'
|
||||
}
|
||||
implementation "xyz.quaver:documentfilex:0.2.11-alpha6"
|
||||
implementation "xyz.quaver:documentfilex:0.2.11"
|
||||
testImplementation 'junit:junit:4.13'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation 'androidx.test:rules:1.3.0'
|
||||
|
||||
1
app/proguard-rules.pro
vendored
1
app/proguard-rules.pro
vendored
@@ -21,7 +21,6 @@
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
-dontobfuscate
|
||||
-dontoptimize
|
||||
|
||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||
-keep class * extends com.bumptech.glide.module.AppGlideModule {
|
||||
|
||||
@@ -23,7 +23,6 @@ import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.ContextCompat
|
||||
@@ -74,7 +73,10 @@ class Pupil : Application() {
|
||||
override fun onCreate() {
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
||||
val userID = Preferences["user_id", UUID.randomUUID().toString(), true]
|
||||
val userID = Preferences["user_id", ""].let { userID ->
|
||||
if (userID.isEmpty()) UUID.randomUUID().toString().also { Preferences["user_id"] = it }
|
||||
else userID
|
||||
}
|
||||
|
||||
FirebaseCrashlytics.getInstance().setUserId(userID)
|
||||
|
||||
|
||||
@@ -94,14 +94,24 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
||||
}
|
||||
|
||||
if (progress == max) {
|
||||
val downloadManager = DownloadFolderManager.getInstance(context)
|
||||
|
||||
if (completeFlag.get(galleryID, false)) {
|
||||
with(view.galleryblock_progress_complete) {
|
||||
setImageResource(R.drawable.ic_progressbar)
|
||||
setImageResource(
|
||||
if (downloadManager.getDownloadFolder(galleryID) != null)
|
||||
R.drawable.ic_progressbar
|
||||
else R.drawable.ic_progressbar_cache
|
||||
)
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
} else {
|
||||
with(view.galleryblock_progress_complete) {
|
||||
setImageDrawable(AnimatedVectorDrawableCompat.create(context, R.drawable.ic_progressbar_complete).apply {
|
||||
setImageDrawable(AnimatedVectorDrawableCompat.create(context,
|
||||
if (downloadManager.getDownloadFolder(galleryID) != null)
|
||||
R.drawable.ic_progressbar_complete
|
||||
else R.drawable.ic_progressbar_complete_cache
|
||||
).apply {
|
||||
this?.start()
|
||||
})
|
||||
visibility = View.VISIBLE
|
||||
|
||||
@@ -43,6 +43,7 @@ import xyz.quaver.pupil.interceptors
|
||||
import xyz.quaver.pupil.ui.ReaderActivity
|
||||
import xyz.quaver.pupil.util.downloader.Cache
|
||||
import xyz.quaver.pupil.util.downloader.DownloadFolderManager
|
||||
import xyz.quaver.pupil.util.ellipsize
|
||||
import xyz.quaver.pupil.util.requestBuilders
|
||||
import xyz.quaver.pupil.util.startForegroundServiceCompat
|
||||
import java.io.IOException
|
||||
@@ -208,8 +209,12 @@ class DownloadService : Service() {
|
||||
kotlin.runCatching {
|
||||
Cache.getInstance(this@DownloadService, galleryID).putImage(index, "$index.$ext", image)
|
||||
}.onSuccess {
|
||||
notify(galleryID)
|
||||
progress[galleryID]?.set(index, Float.POSITIVE_INFINITY)
|
||||
notify(galleryID)
|
||||
|
||||
if (isCompleted(galleryID))
|
||||
if (DownloadFolderManager.getInstance(this@DownloadService).getDownloadFolder(galleryID) != null)
|
||||
Cache.getInstance(this@DownloadService, galleryID).moveToDownload()
|
||||
}.onFailure {
|
||||
Log.i("PUPILD", "$galleryID-$index DLERR-RETRYING $it ${it.message}")
|
||||
|
||||
@@ -240,12 +245,12 @@ class DownloadService : Service() {
|
||||
|
||||
fun cancel(galleryID: Int) {
|
||||
client.dispatcher().queuedCalls().filter {
|
||||
(it.request().tag() as Tag).galleryID == galleryID
|
||||
(it.request().tag() as? Tag)?.galleryID == galleryID
|
||||
}.forEach {
|
||||
it.cancel()
|
||||
}
|
||||
client.dispatcher().runningCalls().filter {
|
||||
(it.request().tag() as Tag).galleryID == galleryID
|
||||
(it.request().tag() as? Tag)?.galleryID == galleryID
|
||||
}.forEach {
|
||||
it.cancel()
|
||||
}
|
||||
@@ -257,11 +262,11 @@ class DownloadService : Service() {
|
||||
|
||||
fun delete(galleryID: Int) = CoroutineScope(Dispatchers.IO).launch {
|
||||
cancel(galleryID)
|
||||
Cache.delete(galleryID)
|
||||
DownloadFolderManager.getInstance(this@DownloadService).deleteDownloadFolder(galleryID)
|
||||
Cache.delete(galleryID)
|
||||
}
|
||||
|
||||
fun download(galleryID: Int): Job = CoroutineScope(Dispatchers.IO).launch {
|
||||
fun download(galleryID: Int, priority: Boolean = false): Job = CoroutineScope(Dispatchers.IO).launch {
|
||||
if (progress.indexOfKey(galleryID) >= 0)
|
||||
cancel(galleryID)
|
||||
|
||||
@@ -271,6 +276,8 @@ class DownloadService : Service() {
|
||||
|
||||
val reader = cache.getReader()
|
||||
|
||||
Log.i("PUPILD", "READER")
|
||||
|
||||
// Gallery doesn't exist
|
||||
if (reader == null) {
|
||||
delete(galleryID)
|
||||
@@ -278,6 +285,8 @@ class DownloadService : Service() {
|
||||
return@launch
|
||||
}
|
||||
|
||||
Log.i("PUPILD", "READER OK")
|
||||
|
||||
if (progress.indexOfKey(galleryID) < 0)
|
||||
progress.put(galleryID, mutableListOf())
|
||||
|
||||
@@ -285,19 +294,39 @@ class DownloadService : Service() {
|
||||
progress[galleryID]?.add(if (it != null) Float.POSITIVE_INFINITY else 0F)
|
||||
}
|
||||
|
||||
notification[galleryID]?.setContentTitle(reader.galleryInfo.title)
|
||||
Log.i("PUPILD", "LOADED")
|
||||
|
||||
notification[galleryID]?.setContentTitle(reader.galleryInfo.title?.ellipsize(30))
|
||||
notify(galleryID)
|
||||
|
||||
Log.i("PUPILD", "NOTIFY")
|
||||
|
||||
val queued = mutableSetOf<Int>()
|
||||
|
||||
if (priority) {
|
||||
client.dispatcher().queuedCalls().forEach {
|
||||
val queuedID = (it.request().tag() as? Tag)?.galleryID ?: return@forEach
|
||||
|
||||
if (queued.add(queuedID))
|
||||
cancel(queuedID)
|
||||
}
|
||||
}
|
||||
|
||||
reader.requestBuilders.filterIndexed { index, _ -> !progress[galleryID]!![index].isInfinite() }.forEachIndexed { index, it ->
|
||||
val request = it.tag(Tag(galleryID, index)).build()
|
||||
client.newCall(request).enqueue(callback)
|
||||
}
|
||||
|
||||
queued.forEach { download(it) }
|
||||
|
||||
Log.i("PUPILD", "OK")
|
||||
}
|
||||
//endregion
|
||||
|
||||
companion object {
|
||||
const val KEY_COMMAND = "COMMAND" // String
|
||||
const val KEY_ID = "ID" // Int
|
||||
const val KEY_PRIORITY = "PRIORITY" // Boolean
|
||||
|
||||
const val COMMAND_DOWNLOAD = "DOWNLOAD"
|
||||
const val COMMAND_CANCEL = "CANCEL"
|
||||
@@ -307,9 +336,10 @@ class DownloadService : Service() {
|
||||
context.startForegroundServiceCompat(Intent(context, DownloadService::class.java).apply(extras))
|
||||
}
|
||||
|
||||
fun download(context: Context, galleryID: Int) {
|
||||
fun download(context: Context, galleryID: Int, priority: Boolean = false) {
|
||||
command(context) {
|
||||
putExtra(KEY_COMMAND, COMMAND_DOWNLOAD)
|
||||
putExtra(KEY_PRIORITY, priority)
|
||||
putExtra(KEY_ID, galleryID)
|
||||
}
|
||||
}
|
||||
@@ -331,7 +361,9 @@ class DownloadService : Service() {
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
when (intent?.getStringExtra(KEY_COMMAND)) {
|
||||
COMMAND_DOWNLOAD -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0) download(it) }
|
||||
COMMAND_DOWNLOAD -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0)
|
||||
download(it, intent.getBooleanExtra(KEY_PRIORITY, false))
|
||||
}
|
||||
COMMAND_CANCEL -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0) cancel(it) else cancel() }
|
||||
COMMAND_DELETE -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0) delete(it) }
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.*
|
||||
import android.text.style.AlignmentSpan
|
||||
@@ -448,6 +447,7 @@ class MainActivity : AppCompatActivity() {
|
||||
DownloadService.cancel(this@MainActivity, galleryID)
|
||||
}
|
||||
else {
|
||||
downloadFolderManager.addDownloadFolder(galleryID)
|
||||
DownloadService.download(this@MainActivity, galleryID)
|
||||
}
|
||||
}
|
||||
@@ -771,7 +771,7 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
R.id.main_menu_sort_newest -> {
|
||||
sortMode = MainActivity.SortMode.NEWEST
|
||||
sortMode = SortMode.NEWEST
|
||||
it.isChecked = true
|
||||
|
||||
runOnUiThread {
|
||||
@@ -1006,7 +1006,7 @@ class MainActivity : AppCompatActivity() {
|
||||
totalItems = it.size
|
||||
}
|
||||
}
|
||||
else -> doSearch("$defaultQuery $query", sortMode == MainActivity.SortMode.POPULAR).also {
|
||||
else -> doSearch("$defaultQuery $query", sortMode == SortMode.POPULAR).also {
|
||||
totalItems = it.size
|
||||
}
|
||||
}
|
||||
@@ -1027,13 +1027,7 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
Mode.DOWNLOAD -> {
|
||||
val downloads = getDownloadDirectory(this@MainActivity).listFiles()?.filter { file ->
|
||||
file.isDirectory && file.name.toIntOrNull() != null
|
||||
}?.sortedByDescending {
|
||||
it.lastModified()
|
||||
}?.map {
|
||||
it.name.toInt()
|
||||
} ?: emptyList()
|
||||
val downloads = downloadFolderManager.downloadFolderMap.keys.toList()
|
||||
|
||||
when {
|
||||
query.isEmpty() -> downloads.also {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
package xyz.quaver.pupil.ui
|
||||
|
||||
import android.app.DownloadManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
@@ -91,8 +92,6 @@ class ReaderActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private var deleteOnExit = true
|
||||
|
||||
private val timer = Timer()
|
||||
private var autoTimer: Timer? = null
|
||||
|
||||
@@ -244,12 +243,13 @@ class ReaderActivity : AppCompatActivity() {
|
||||
timer.cancel()
|
||||
(reader_recyclerview?.adapter as? ReaderAdapter)?.timer?.cancel()
|
||||
|
||||
if (deleteOnExit) {
|
||||
if (!DownloadFolderManager.getInstance(this).isDownloading(galleryID)) {
|
||||
downloader?.cancel(galleryID)
|
||||
DownloadFolderManager.getInstance(this).deleteDownloadFolder(galleryID)
|
||||
}
|
||||
|
||||
unbindService(conn)
|
||||
if (downloader != null)
|
||||
unbindService(conn)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
@@ -285,7 +285,7 @@ class ReaderActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun initDownloader() {
|
||||
DownloadService.download(this, galleryID)
|
||||
DownloadService.download(this, galleryID, true)
|
||||
bindService(Intent(this, DownloadService::class.java), conn, BIND_AUTO_CREATE)
|
||||
|
||||
timer.schedule(1000, 1000) {
|
||||
@@ -379,12 +379,14 @@ class ReaderActivity : AppCompatActivity() {
|
||||
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("cache_disable", false))
|
||||
Toast.makeText(context, R.string.settings_download_when_cache_disable_warning, Toast.LENGTH_SHORT).show()
|
||||
else {
|
||||
if (deleteOnExit) {
|
||||
deleteOnExit = false
|
||||
cache.moveToDownload()
|
||||
animateDownloadFAB(true)
|
||||
} else {
|
||||
val downloadManager = DownloadFolderManager.getInstance(this@ReaderActivity)
|
||||
|
||||
if (downloadManager.isDownloading(galleryID)) {
|
||||
downloadManager.deleteDownloadFolder(galleryID)
|
||||
animateDownloadFAB(false)
|
||||
} else {
|
||||
downloadManager.addDownloadFolder(galleryID)
|
||||
animateDownloadFAB(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import net.rdrei.android.dirchooser.DirectoryChooserActivity
|
||||
import net.rdrei.android.dirchooser.DirectoryChooserConfig
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.util.*
|
||||
import xyz.quaver.pupil.util.downloader.DownloadFolderManager
|
||||
import java.io.File
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
@@ -114,10 +115,10 @@ class DownloadLocationDialog(val activity: Activity) : AlertDialog(activity) {
|
||||
})
|
||||
|
||||
externalFilesDirs.indexOfFirst {
|
||||
it.canonicalPath == getDownloadDirectory(context).canonicalPath
|
||||
it.canonicalPath == DownloadFolderManager.getInstance(context).downloadFolder.canonicalPath
|
||||
}.let { index ->
|
||||
if (index < 0)
|
||||
buttons.first().first.isChecked = true
|
||||
buttons.last().first.isChecked = true
|
||||
else
|
||||
buttons[index].first.isChecked = true
|
||||
}
|
||||
|
||||
@@ -209,6 +209,9 @@ class SettingsFragment :
|
||||
"proxy" -> {
|
||||
summary = context?.let { getProxyInfo().type.name }
|
||||
}
|
||||
"download_folder" -> {
|
||||
summary = FileX(context, Preferences.get<String>("download_folder")).canonicalPath
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,6 +224,11 @@ class SettingsFragment :
|
||||
initPreferences()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
Preferences.unregisterOnSharedPreferenceChangeListener(this)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun initPreferences() {
|
||||
for (i in 0 until preferenceScreen.preferenceCount) {
|
||||
|
||||
@@ -274,13 +282,7 @@ class SettingsFragment :
|
||||
onPreferenceClickListener = this@SettingsFragment
|
||||
}
|
||||
"download_folder" -> {
|
||||
setSummaryProvider {
|
||||
val uri: String = Preferences[it.key]
|
||||
|
||||
kotlin.runCatching {
|
||||
FileX(context, uri).canonicalPath
|
||||
}.getOrElse { "" }
|
||||
}
|
||||
summary = FileX(context, Preferences.get<String>("download_folder")).canonicalPath
|
||||
|
||||
onPreferenceClickListener = this@SettingsFragment
|
||||
}
|
||||
|
||||
@@ -25,15 +25,6 @@ lateinit var preferences: SharedPreferences
|
||||
|
||||
object Preferences: SharedPreferences by preferences {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val putMap = mapOf<KClass<out Any>, (String, Any) -> Unit>(
|
||||
String::class to { k, v -> edit().putString(k, v as String).apply() },
|
||||
Int::class to { k, v -> edit().putBoolean(k, v as Boolean).apply() },
|
||||
Long::class to { k, v -> edit().putLong(k, v as Long).apply() },
|
||||
Boolean::class to { k, v -> edit().putBoolean(k, v as Boolean).apply() },
|
||||
Set::class to { k, v -> edit().putStringSet(k, v as Set<String>).apply() }
|
||||
)
|
||||
|
||||
val defMap = mapOf(
|
||||
String::class to "",
|
||||
Int::class to -1,
|
||||
@@ -42,12 +33,14 @@ object Preferences: SharedPreferences by preferences {
|
||||
Set::class to emptySet<Any>()
|
||||
)
|
||||
|
||||
inline operator fun <reified T: Any> set(key: String, value: T) {
|
||||
putMap[T::class]?.invoke(key, value)
|
||||
}
|
||||
operator fun set(key: String, value: String) = edit().putString(key, value).apply()
|
||||
operator fun set(key: String, value: Int) = edit().putInt(key, value).apply()
|
||||
operator fun set(key: String, value: Long) = edit().putLong(key, value).apply()
|
||||
operator fun set(key: String, value: Boolean) = edit().putBoolean(key, value).apply()
|
||||
operator fun set(key: String, value: Set<String>) = edit().putStringSet(key, value).apply()
|
||||
|
||||
inline operator fun <reified T: Any> get(key: String, defaultVal: T = defMap[T::class] as T, setIfNull: Boolean = false): T =
|
||||
(all[key] as? T) ?: defaultVal.also { if (setIfNull) set(key, defaultVal) }
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline operator fun <reified T: Any> get(key: String, defaultVal: T = defMap[T::class] as T): T = (all[key] as? T) ?: defaultVal
|
||||
|
||||
fun remove(key: String) {
|
||||
edit().remove(key).apply()
|
||||
|
||||
@@ -22,9 +22,10 @@ import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.util.Log
|
||||
import android.util.SparseArray
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
@@ -34,12 +35,12 @@ import xyz.quaver.Code
|
||||
import xyz.quaver.hitomi.GalleryBlock
|
||||
import xyz.quaver.hitomi.Reader
|
||||
import xyz.quaver.io.FileX
|
||||
import xyz.quaver.io.util.*
|
||||
import xyz.quaver.io.util.getChild
|
||||
import xyz.quaver.io.util.readBytes
|
||||
import xyz.quaver.io.util.readText
|
||||
import xyz.quaver.io.util.writeBytes
|
||||
import xyz.quaver.pupil.client
|
||||
import xyz.quaver.pupil.util.Preferences
|
||||
import xyz.quaver.pupil.util.formatDownloadFolder
|
||||
import kotlin.io.deleteRecursively
|
||||
import kotlin.io.writeText
|
||||
|
||||
@Serializable
|
||||
data class Metadata(
|
||||
@@ -53,25 +54,23 @@ data class Metadata(
|
||||
class Cache private constructor(context: Context, val galleryID: Int) : ContextWrapper(context) {
|
||||
|
||||
companion object {
|
||||
private val mutex = Mutex()
|
||||
private val instances = SparseArray<Cache>()
|
||||
|
||||
fun getInstance(context: Context, galleryID: Int) =
|
||||
instances[galleryID] ?: runBlocking { mutex.withLock {
|
||||
instances[galleryID] ?: synchronized(this) {
|
||||
instances[galleryID] ?: Cache(context, galleryID).also { instances.put(galleryID, it) }
|
||||
} }
|
||||
}
|
||||
|
||||
fun delete(galleryID: Int) { runBlocking { mutex.withLock {
|
||||
instances[galleryID]?.galleryFolder?.deleteRecursively()
|
||||
fun delete(galleryID: Int) {
|
||||
instances[galleryID]?.cacheFolder?.deleteRecursively()
|
||||
instances.delete(galleryID)
|
||||
} } }
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
galleryFolder.mkdirs()
|
||||
cacheFolder.mkdirs()
|
||||
}
|
||||
|
||||
private val mutex = Mutex()
|
||||
var metadata = kotlin.runCatching {
|
||||
findFile(".metadata")?.readText()?.let {
|
||||
Json.decodeFromString<Metadata>(it)
|
||||
@@ -82,11 +81,10 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
||||
get() = DownloadFolderManager.getInstance(this).getDownloadFolder(galleryID)
|
||||
|
||||
val cacheFolder: FileX
|
||||
get() = FileX(this, cacheDir, "imageCache/$galleryID")
|
||||
|
||||
val galleryFolder: FileX
|
||||
get() = DownloadFolderManager.getInstance(this).getDownloadFolder(galleryID)
|
||||
?: FileX(this, cacheDir, "imageCache/$galleryID")
|
||||
get() = FileX(this, cacheDir, "imageCache/$galleryID").also {
|
||||
if (!it.exists())
|
||||
it.mkdirs()
|
||||
}
|
||||
|
||||
fun findFile(fileName: String): FileX? =
|
||||
cacheFolder.getChild(fileName).let {
|
||||
@@ -96,18 +94,19 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
||||
} }
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
suspend fun setMetadata(change: (Metadata) -> Unit) { mutex.withLock {
|
||||
fun setMetadata(change: (Metadata) -> Unit) {
|
||||
change.invoke(metadata)
|
||||
|
||||
val file = galleryFolder.getChild(".metadata")
|
||||
val file = cacheFolder.getChild(".metadata")
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
kotlin.runCatching {
|
||||
kotlin.runCatching {
|
||||
if (!file.exists()) {
|
||||
Log.i("PUPILD", "$file")
|
||||
file.createNewFile()
|
||||
file.writeText(Json.encodeToString(metadata))
|
||||
}
|
||||
file.writeText(Json.encodeToString(metadata))
|
||||
}
|
||||
} }
|
||||
}
|
||||
|
||||
suspend fun getGalleryBlock(): GalleryBlock? {
|
||||
val sources = listOf(
|
||||
@@ -129,7 +128,7 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
||||
}
|
||||
|
||||
galleryBlock?.also {
|
||||
launch { setMetadata { metadata -> metadata.galleryBlock = it } }
|
||||
setMetadata { metadata -> metadata.galleryBlock = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,7 +144,7 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
||||
kotlin.runCatching {
|
||||
client.newCall(request).execute().body()?.use { it.bytes() }
|
||||
}.getOrNull()?.also { kotlin.run {
|
||||
galleryFolder.getChild(".thumbnail").writeBytes(it)
|
||||
cacheFolder.getChild(".thumbnail").writeBytes(it)
|
||||
} }
|
||||
} }
|
||||
|
||||
@@ -178,12 +177,12 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
||||
}
|
||||
|
||||
reader?.also {
|
||||
launch { setMetadata { metadata ->
|
||||
setMetadata { metadata ->
|
||||
metadata.reader = it
|
||||
|
||||
if (metadata.imageList == null)
|
||||
metadata.imageList = MutableList(reader.galleryInfo.files.size) { null }
|
||||
} }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,8 +191,8 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
||||
metadata.imageList?.get(index)?.let { findFile(it) }
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
suspend fun putImage(index: Int, fileName: String, data: ByteArray) {
|
||||
val file = galleryFolder.getChild(fileName)
|
||||
fun putImage(index: Int, fileName: String, data: ByteArray) {
|
||||
val file = cacheFolder.getChild(fileName)
|
||||
|
||||
file.createNewFile()
|
||||
file.writeBytes(data)
|
||||
@@ -202,14 +201,12 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
fun moveToDownload() = CoroutineScope(Dispatchers.IO).launch {
|
||||
if (downloadFolder == null)
|
||||
DownloadFolderManager.getInstance(this@Cache).addDownloadFolder(galleryID, this@Cache.formatDownloadFolder())
|
||||
val downloadFolder = downloadFolder ?: return@launch
|
||||
Log.i("PUPILD", "MOVING $galleryID")
|
||||
|
||||
metadata.imageList?.forEach { imageName ->
|
||||
imageName ?: return@forEach
|
||||
|
||||
Log.i("PUPIL", downloadFolder?.uri.toString())
|
||||
val target = downloadFolder!!.getChild(imageName)
|
||||
val target = downloadFolder.getChild(imageName)
|
||||
val source = cacheFolder.getChild(imageName)
|
||||
|
||||
if (!source.exists())
|
||||
@@ -221,14 +218,13 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
||||
}
|
||||
}
|
||||
|
||||
Log.i("PUPIL", downloadFolder?.uri.toString())
|
||||
val cacheMetadata = cacheFolder.getChild(".metadata")
|
||||
val downloadMetadata = downloadFolder!!.getChild(".metadata")
|
||||
val downloadMetadata = downloadFolder.getChild(".metadata")
|
||||
|
||||
if (cacheMetadata.exists()) {
|
||||
kotlin.runCatching {
|
||||
downloadMetadata.createNewFile()
|
||||
cacheMetadata.readBytes()?.let { downloadMetadata.writeBytes(it) }
|
||||
downloadMetadata.writeText(Json.encodeToString(metadata))
|
||||
cacheMetadata.delete()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,23 +20,17 @@ package xyz.quaver.pupil.util.downloader
|
||||
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.webkit.URLUtil
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Call
|
||||
import xyz.quaver.io.FileX
|
||||
import xyz.quaver.io.util.getChild
|
||||
import xyz.quaver.io.util.readText
|
||||
import xyz.quaver.io.util.*
|
||||
import xyz.quaver.pupil.client
|
||||
import xyz.quaver.pupil.services.DownloadService
|
||||
import xyz.quaver.pupil.util.Preferences
|
||||
import xyz.quaver.pupil.util.formatDownloadFolder
|
||||
|
||||
class DownloadFolderManager private constructor(context: Context) : ContextWrapper(context) {
|
||||
|
||||
@@ -61,15 +55,24 @@ class DownloadFolderManager private constructor(context: Context) : ContextWrapp
|
||||
}
|
||||
}.invoke()
|
||||
|
||||
private val downloadFolderMapMutex = Mutex()
|
||||
private val downloadFolderMap: MutableMap<Int, String> = runBlocking { downloadFolderMapMutex.withLock {
|
||||
kotlin.runCatching {
|
||||
downloadFolder.getChild(".download").readText()?.let {
|
||||
Json.decodeFromString<MutableMap<Int, String>>(it)
|
||||
}
|
||||
}.getOrNull() ?: mutableMapOf()
|
||||
} }
|
||||
val downloadFolderMap: MutableMap<Int, String> = {
|
||||
val file = downloadFolder.getChild(".download")
|
||||
|
||||
val data = if (file.exists())
|
||||
kotlin.runCatching {
|
||||
file.readText()?.let { Json.decodeFromString<MutableMap<Int, String>>(it) }
|
||||
}.onFailure { file.delete() }.getOrNull()
|
||||
else
|
||||
null
|
||||
|
||||
data ?: {
|
||||
file.createNewFile()
|
||||
file.writeText("{}")
|
||||
mutableMapOf<Int, String>()
|
||||
}.invoke()
|
||||
}.invoke()
|
||||
|
||||
@Synchronized
|
||||
fun isDownloading(galleryID: Int): Boolean {
|
||||
val isThisGallery: (Call) -> Boolean = { (it.request().tag() as? DownloadService.Tag)?.galleryID == galleryID }
|
||||
|
||||
@@ -77,13 +80,18 @@ class DownloadFolderManager private constructor(context: Context) : ContextWrapp
|
||||
&& client.dispatcher().let { it.queuedCalls().any(isThisGallery) || it.runningCalls().any(isThisGallery) }
|
||||
}
|
||||
|
||||
fun getDownloadFolder(galleryID: Int): FileX? = runBlocking { downloadFolderMapMutex.withLock {
|
||||
@Synchronized
|
||||
fun getDownloadFolder(galleryID: Int): FileX? =
|
||||
downloadFolderMap[galleryID]?.let { downloadFolder.getChild(it) }
|
||||
} }
|
||||
|
||||
fun addDownloadFolder(galleryID: Int, name: String) { runBlocking { downloadFolderMapMutex.withLock {
|
||||
@Synchronized
|
||||
fun addDownloadFolder(galleryID: Int) {
|
||||
if (downloadFolderMap.containsKey(galleryID))
|
||||
return@withLock
|
||||
return
|
||||
|
||||
val name = runBlocking {
|
||||
Cache.getInstance(this@DownloadFolderManager, galleryID).getGalleryBlock()
|
||||
}?.formatDownloadFolder() ?: return
|
||||
|
||||
val folder = downloadFolder.getChild(name)
|
||||
|
||||
@@ -92,29 +100,21 @@ class DownloadFolderManager private constructor(context: Context) : ContextWrapp
|
||||
|
||||
downloadFolderMap[galleryID] = name
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch { downloadFolderMapMutex.withLock {
|
||||
downloadFolder.getChild(".download").let {
|
||||
it.createNewFile()
|
||||
it.writeText(Json.encodeToString(downloadFolderMap))
|
||||
}
|
||||
} }
|
||||
} } }
|
||||
downloadFolder.getChild(".download").writeText(Json.encodeToString(downloadFolderMap))
|
||||
}
|
||||
|
||||
fun deleteDownloadFolder(galleryID: Int) { runBlocking { downloadFolderMapMutex.withLock {
|
||||
@Synchronized
|
||||
fun deleteDownloadFolder(galleryID: Int) {
|
||||
if (!downloadFolderMap.containsKey(galleryID))
|
||||
return@withLock
|
||||
return
|
||||
|
||||
downloadFolderMap[galleryID]?.let {
|
||||
if (downloadFolder.getChild(it).delete()) {
|
||||
kotlin.runCatching {
|
||||
downloadFolder.getChild(it).delete()
|
||||
downloadFolderMap.remove(galleryID)
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch { downloadFolderMapMutex.withLock {
|
||||
downloadFolder.getChild(".download").let {
|
||||
it.createNewFile()
|
||||
it.writeText(Json.encodeToString(downloadFolderMap))
|
||||
}
|
||||
} }
|
||||
downloadFolder.getChild(".download").writeText(Json.encodeToString(downloadFolderMap))
|
||||
}
|
||||
}
|
||||
} } }
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import kotlinx.coroutines.sync.withLock
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import xyz.quaver.Code
|
||||
import xyz.quaver.hitomi.GalleryBlock
|
||||
import xyz.quaver.hitomi.Reader
|
||||
import xyz.quaver.hitomi.getReferer
|
||||
import xyz.quaver.hitomi.imageUrlFromImage
|
||||
@@ -87,15 +88,15 @@ fun OkHttpClient.Builder.proxyInfo(proxyInfo: ProxyInfo) = this.apply {
|
||||
}
|
||||
}
|
||||
|
||||
val formatMap = mapOf<String, (Cache) -> (String)>(
|
||||
"-id-" to { runBlocking { it.getGalleryBlock()?.id.toString() } },
|
||||
"-title-" to { runBlocking { it.getGalleryBlock()?.title.toString() } },
|
||||
val formatMap = mapOf<String, GalleryBlock.() -> (String)>(
|
||||
"-id-" to { id.toString() },
|
||||
"-title-" to { title },
|
||||
// TODO
|
||||
)
|
||||
/**
|
||||
* Formats download folder name with given Metadata
|
||||
*/
|
||||
fun Cache.formatDownloadFolder(): String =
|
||||
fun GalleryBlock.formatDownloadFolder(): String =
|
||||
Preferences["download_folder_format", "-id-"].let {
|
||||
formatMap.entries.fold(it) { str, (k, v) ->
|
||||
str.replace(k, v.invoke(this), true)
|
||||
@@ -131,4 +132,10 @@ val Reader.requestBuilders: List<Request.Builder>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun String.ellipsize(n: Int): String =
|
||||
if (this.length > n)
|
||||
this.slice(0 until n) + "…"
|
||||
else
|
||||
this
|
||||
22
app/src/main/res/drawable/ic_progressbar_cache.xml
Normal file
22
app/src/main/res/drawable/ic_progressbar_cache.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:name="vector"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tintMode="multiply">
|
||||
<path
|
||||
android:name="path_1"
|
||||
android:pathData="M 0 12 L 24 12"
|
||||
android:fillColor="#000"
|
||||
android:strokeColor="#80d8ff"
|
||||
android:strokeWidth="24"/>
|
||||
<path
|
||||
android:name="path"
|
||||
android:pathData="M 0 12 L 24 12"
|
||||
android:fillColor="#000"
|
||||
android:strokeColor="#0091ea"
|
||||
android:strokeWidth="24"/>
|
||||
</vector>
|
||||
38
app/src/main/res/drawable/ic_progressbar_complete_cache.xml
Normal file
38
app/src/main/res/drawable/ic_progressbar_complete_cache.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
tools:ignore="NewApi">
|
||||
<aapt:attr name="android:drawable">
|
||||
<vector
|
||||
android:name="vector"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tintMode="multiply">
|
||||
<path
|
||||
android:name="path_1"
|
||||
android:pathData="M 0 12 L 24 12"
|
||||
android:fillColor="#000"
|
||||
android:strokeColor="#80d8ff"
|
||||
android:strokeWidth="24"/>
|
||||
<path
|
||||
android:name="path"
|
||||
android:pathData="M 0 12 L 24 12"
|
||||
android:fillColor="#000"
|
||||
android:strokeColor="#0091ea"
|
||||
android:strokeWidth="24"/>
|
||||
</vector>
|
||||
</aapt:attr>
|
||||
<target android:name="path">
|
||||
<aapt:attr name="android:animation">
|
||||
<objectAnimator
|
||||
android:propertyName="trimPathEnd"
|
||||
android:duration="1000"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:valueType="floatType"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
</animated-vector>
|
||||
Reference in New Issue
Block a user