This commit is contained in:
tom5079
2020-09-02 00:13:25 +09:00
parent 7704c96955
commit 9583897ada
16 changed files with 237 additions and 140 deletions

View File

@@ -99,7 +99,7 @@ dependencies {
implementation ("xyz.quaver:libpupil:1.3") { implementation ("xyz.quaver:libpupil:1.3") {
exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-serialization-core-jvm' 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' testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test:rules:1.3.0' androidTestImplementation 'androidx.test:rules:1.3.0'

View File

@@ -21,7 +21,6 @@
#-renamesourcefileattribute SourceFile #-renamesourcefileattribute SourceFile
-dontobfuscate -dontobfuscate
-dontoptimize
-keep public class * implements com.bumptech.glide.module.GlideModule -keep public class * implements com.bumptech.glide.module.GlideModule
-keep class * extends com.bumptech.glide.module.AppGlideModule { -keep class * extends com.bumptech.glide.module.AppGlideModule {

View File

@@ -23,7 +23,6 @@ import android.app.Notification
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.content.Intent
import android.os.Build import android.os.Build
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@@ -74,7 +73,10 @@ class Pupil : Application() {
override fun onCreate() { override fun onCreate() {
preferences = PreferenceManager.getDefaultSharedPreferences(this) 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) FirebaseCrashlytics.getInstance().setUserId(userID)

View File

@@ -94,14 +94,24 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
} }
if (progress == max) { if (progress == max) {
val downloadManager = DownloadFolderManager.getInstance(context)
if (completeFlag.get(galleryID, false)) { if (completeFlag.get(galleryID, false)) {
with(view.galleryblock_progress_complete) { 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 visibility = View.VISIBLE
} }
} else { } else {
with(view.galleryblock_progress_complete) { 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() this?.start()
}) })
visibility = View.VISIBLE visibility = View.VISIBLE

View File

@@ -43,6 +43,7 @@ import xyz.quaver.pupil.interceptors
import xyz.quaver.pupil.ui.ReaderActivity import xyz.quaver.pupil.ui.ReaderActivity
import xyz.quaver.pupil.util.downloader.Cache import xyz.quaver.pupil.util.downloader.Cache
import xyz.quaver.pupil.util.downloader.DownloadFolderManager import xyz.quaver.pupil.util.downloader.DownloadFolderManager
import xyz.quaver.pupil.util.ellipsize
import xyz.quaver.pupil.util.requestBuilders import xyz.quaver.pupil.util.requestBuilders
import xyz.quaver.pupil.util.startForegroundServiceCompat import xyz.quaver.pupil.util.startForegroundServiceCompat
import java.io.IOException import java.io.IOException
@@ -208,8 +209,12 @@ class DownloadService : Service() {
kotlin.runCatching { kotlin.runCatching {
Cache.getInstance(this@DownloadService, galleryID).putImage(index, "$index.$ext", image) Cache.getInstance(this@DownloadService, galleryID).putImage(index, "$index.$ext", image)
}.onSuccess { }.onSuccess {
notify(galleryID)
progress[galleryID]?.set(index, Float.POSITIVE_INFINITY) 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 { }.onFailure {
Log.i("PUPILD", "$galleryID-$index DLERR-RETRYING $it ${it.message}") Log.i("PUPILD", "$galleryID-$index DLERR-RETRYING $it ${it.message}")
@@ -240,12 +245,12 @@ class DownloadService : Service() {
fun cancel(galleryID: Int) { fun cancel(galleryID: Int) {
client.dispatcher().queuedCalls().filter { client.dispatcher().queuedCalls().filter {
(it.request().tag() as Tag).galleryID == galleryID (it.request().tag() as? Tag)?.galleryID == galleryID
}.forEach { }.forEach {
it.cancel() it.cancel()
} }
client.dispatcher().runningCalls().filter { client.dispatcher().runningCalls().filter {
(it.request().tag() as Tag).galleryID == galleryID (it.request().tag() as? Tag)?.galleryID == galleryID
}.forEach { }.forEach {
it.cancel() it.cancel()
} }
@@ -257,11 +262,11 @@ class DownloadService : Service() {
fun delete(galleryID: Int) = CoroutineScope(Dispatchers.IO).launch { fun delete(galleryID: Int) = CoroutineScope(Dispatchers.IO).launch {
cancel(galleryID) cancel(galleryID)
Cache.delete(galleryID)
DownloadFolderManager.getInstance(this@DownloadService).deleteDownloadFolder(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) if (progress.indexOfKey(galleryID) >= 0)
cancel(galleryID) cancel(galleryID)
@@ -271,6 +276,8 @@ class DownloadService : Service() {
val reader = cache.getReader() val reader = cache.getReader()
Log.i("PUPILD", "READER")
// Gallery doesn't exist // Gallery doesn't exist
if (reader == null) { if (reader == null) {
delete(galleryID) delete(galleryID)
@@ -278,6 +285,8 @@ class DownloadService : Service() {
return@launch return@launch
} }
Log.i("PUPILD", "READER OK")
if (progress.indexOfKey(galleryID) < 0) if (progress.indexOfKey(galleryID) < 0)
progress.put(galleryID, mutableListOf()) progress.put(galleryID, mutableListOf())
@@ -285,19 +294,39 @@ class DownloadService : Service() {
progress[galleryID]?.add(if (it != null) Float.POSITIVE_INFINITY else 0F) 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) 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 -> reader.requestBuilders.filterIndexed { index, _ -> !progress[galleryID]!![index].isInfinite() }.forEachIndexed { index, it ->
val request = it.tag(Tag(galleryID, index)).build() val request = it.tag(Tag(galleryID, index)).build()
client.newCall(request).enqueue(callback) client.newCall(request).enqueue(callback)
} }
queued.forEach { download(it) }
Log.i("PUPILD", "OK")
} }
//endregion //endregion
companion object { companion object {
const val KEY_COMMAND = "COMMAND" // String const val KEY_COMMAND = "COMMAND" // String
const val KEY_ID = "ID" // Int const val KEY_ID = "ID" // Int
const val KEY_PRIORITY = "PRIORITY" // Boolean
const val COMMAND_DOWNLOAD = "DOWNLOAD" const val COMMAND_DOWNLOAD = "DOWNLOAD"
const val COMMAND_CANCEL = "CANCEL" const val COMMAND_CANCEL = "CANCEL"
@@ -307,9 +336,10 @@ class DownloadService : Service() {
context.startForegroundServiceCompat(Intent(context, DownloadService::class.java).apply(extras)) 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) { command(context) {
putExtra(KEY_COMMAND, COMMAND_DOWNLOAD) putExtra(KEY_COMMAND, COMMAND_DOWNLOAD)
putExtra(KEY_PRIORITY, priority)
putExtra(KEY_ID, galleryID) putExtra(KEY_ID, galleryID)
} }
} }
@@ -331,7 +361,9 @@ class DownloadService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.getStringExtra(KEY_COMMAND)) { 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_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) } COMMAND_DELETE -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0) delete(it) }
} }

View File

@@ -23,7 +23,6 @@ import android.app.Activity
import android.content.Intent import android.content.Intent
import android.graphics.drawable.Animatable import android.graphics.drawable.Animatable
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.* import android.text.*
import android.text.style.AlignmentSpan import android.text.style.AlignmentSpan
@@ -448,6 +447,7 @@ class MainActivity : AppCompatActivity() {
DownloadService.cancel(this@MainActivity, galleryID) DownloadService.cancel(this@MainActivity, galleryID)
} }
else { else {
downloadFolderManager.addDownloadFolder(galleryID)
DownloadService.download(this@MainActivity, galleryID) DownloadService.download(this@MainActivity, galleryID)
} }
} }
@@ -771,7 +771,7 @@ class MainActivity : AppCompatActivity() {
} }
} }
R.id.main_menu_sort_newest -> { R.id.main_menu_sort_newest -> {
sortMode = MainActivity.SortMode.NEWEST sortMode = SortMode.NEWEST
it.isChecked = true it.isChecked = true
runOnUiThread { runOnUiThread {
@@ -1006,7 +1006,7 @@ class MainActivity : AppCompatActivity() {
totalItems = it.size totalItems = it.size
} }
} }
else -> doSearch("$defaultQuery $query", sortMode == MainActivity.SortMode.POPULAR).also { else -> doSearch("$defaultQuery $query", sortMode == SortMode.POPULAR).also {
totalItems = it.size totalItems = it.size
} }
} }
@@ -1027,13 +1027,7 @@ class MainActivity : AppCompatActivity() {
} }
} }
Mode.DOWNLOAD -> { Mode.DOWNLOAD -> {
val downloads = getDownloadDirectory(this@MainActivity).listFiles()?.filter { file -> val downloads = downloadFolderManager.downloadFolderMap.keys.toList()
file.isDirectory && file.name.toIntOrNull() != null
}?.sortedByDescending {
it.lastModified()
}?.map {
it.name.toInt()
} ?: emptyList()
when { when {
query.isEmpty() -> downloads.also { query.isEmpty() -> downloads.also {

View File

@@ -18,6 +18,7 @@
package xyz.quaver.pupil.ui package xyz.quaver.pupil.ui
import android.app.DownloadManager
import android.content.ComponentName import android.content.ComponentName
import android.content.Intent import android.content.Intent
import android.content.ServiceConnection import android.content.ServiceConnection
@@ -91,8 +92,6 @@ class ReaderActivity : AppCompatActivity() {
} }
} }
private var deleteOnExit = true
private val timer = Timer() private val timer = Timer()
private var autoTimer: Timer? = null private var autoTimer: Timer? = null
@@ -244,12 +243,13 @@ class ReaderActivity : AppCompatActivity() {
timer.cancel() timer.cancel()
(reader_recyclerview?.adapter as? ReaderAdapter)?.timer?.cancel() (reader_recyclerview?.adapter as? ReaderAdapter)?.timer?.cancel()
if (deleteOnExit) { if (!DownloadFolderManager.getInstance(this).isDownloading(galleryID)) {
downloader?.cancel(galleryID) downloader?.cancel(galleryID)
DownloadFolderManager.getInstance(this).deleteDownloadFolder(galleryID) DownloadFolderManager.getInstance(this).deleteDownloadFolder(galleryID)
} }
unbindService(conn) if (downloader != null)
unbindService(conn)
} }
override fun onBackPressed() { override fun onBackPressed() {
@@ -285,7 +285,7 @@ class ReaderActivity : AppCompatActivity() {
} }
private fun initDownloader() { private fun initDownloader() {
DownloadService.download(this, galleryID) DownloadService.download(this, galleryID, true)
bindService(Intent(this, DownloadService::class.java), conn, BIND_AUTO_CREATE) bindService(Intent(this, DownloadService::class.java), conn, BIND_AUTO_CREATE)
timer.schedule(1000, 1000) { timer.schedule(1000, 1000) {
@@ -379,12 +379,14 @@ class ReaderActivity : AppCompatActivity() {
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("cache_disable", false)) if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("cache_disable", false))
Toast.makeText(context, R.string.settings_download_when_cache_disable_warning, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.settings_download_when_cache_disable_warning, Toast.LENGTH_SHORT).show()
else { else {
if (deleteOnExit) { val downloadManager = DownloadFolderManager.getInstance(this@ReaderActivity)
deleteOnExit = false
cache.moveToDownload() if (downloadManager.isDownloading(galleryID)) {
animateDownloadFAB(true) downloadManager.deleteDownloadFolder(galleryID)
} else {
animateDownloadFAB(false) animateDownloadFAB(false)
} else {
downloadManager.addDownloadFolder(galleryID)
animateDownloadFAB(true)
} }
} }
} }

View File

@@ -35,6 +35,7 @@ import net.rdrei.android.dirchooser.DirectoryChooserActivity
import net.rdrei.android.dirchooser.DirectoryChooserConfig import net.rdrei.android.dirchooser.DirectoryChooserConfig
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.util.* import xyz.quaver.pupil.util.*
import xyz.quaver.pupil.util.downloader.DownloadFolderManager
import java.io.File import java.io.File
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
@@ -114,10 +115,10 @@ class DownloadLocationDialog(val activity: Activity) : AlertDialog(activity) {
}) })
externalFilesDirs.indexOfFirst { externalFilesDirs.indexOfFirst {
it.canonicalPath == getDownloadDirectory(context).canonicalPath it.canonicalPath == DownloadFolderManager.getInstance(context).downloadFolder.canonicalPath
}.let { index -> }.let { index ->
if (index < 0) if (index < 0)
buttons.first().first.isChecked = true buttons.last().first.isChecked = true
else else
buttons[index].first.isChecked = true buttons[index].first.isChecked = true
} }

View File

@@ -209,6 +209,9 @@ class SettingsFragment :
"proxy" -> { "proxy" -> {
summary = context?.let { getProxyInfo().type.name } summary = context?.let { getProxyInfo().type.name }
} }
"download_folder" -> {
summary = FileX(context, Preferences.get<String>("download_folder")).canonicalPath
}
} }
} }
} }
@@ -221,6 +224,11 @@ class SettingsFragment :
initPreferences() initPreferences()
} }
override fun onDestroy() {
Preferences.unregisterOnSharedPreferenceChangeListener(this)
super.onDestroy()
}
private fun initPreferences() { private fun initPreferences() {
for (i in 0 until preferenceScreen.preferenceCount) { for (i in 0 until preferenceScreen.preferenceCount) {
@@ -274,13 +282,7 @@ class SettingsFragment :
onPreferenceClickListener = this@SettingsFragment onPreferenceClickListener = this@SettingsFragment
} }
"download_folder" -> { "download_folder" -> {
setSummaryProvider { summary = FileX(context, Preferences.get<String>("download_folder")).canonicalPath
val uri: String = Preferences[it.key]
kotlin.runCatching {
FileX(context, uri).canonicalPath
}.getOrElse { "" }
}
onPreferenceClickListener = this@SettingsFragment onPreferenceClickListener = this@SettingsFragment
} }

View File

@@ -25,15 +25,6 @@ lateinit var preferences: SharedPreferences
object Preferences: SharedPreferences by preferences { 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( val defMap = mapOf(
String::class to "", String::class to "",
Int::class to -1, Int::class to -1,
@@ -42,12 +33,14 @@ object Preferences: SharedPreferences by preferences {
Set::class to emptySet<Any>() Set::class to emptySet<Any>()
) )
inline operator fun <reified T: Any> set(key: String, value: T) { operator fun set(key: String, value: String) = edit().putString(key, value).apply()
putMap[T::class]?.invoke(key, value) 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 = @Suppress("UNCHECKED_CAST")
(all[key] as? T) ?: defaultVal.also { if (setIfNull) set(key, defaultVal) } 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) { fun remove(key: String) {
edit().remove(key).apply() edit().remove(key).apply()

View File

@@ -22,9 +22,10 @@ import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import android.util.Log import android.util.Log
import android.util.SparseArray import android.util.SparseArray
import kotlinx.coroutines.* import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
@@ -34,12 +35,12 @@ import xyz.quaver.Code
import xyz.quaver.hitomi.GalleryBlock import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.Reader import xyz.quaver.hitomi.Reader
import xyz.quaver.io.FileX 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.client
import xyz.quaver.pupil.util.Preferences import xyz.quaver.pupil.util.Preferences
import xyz.quaver.pupil.util.formatDownloadFolder
import kotlin.io.deleteRecursively
import kotlin.io.writeText
@Serializable @Serializable
data class Metadata( data class Metadata(
@@ -53,25 +54,23 @@ data class Metadata(
class Cache private constructor(context: Context, val galleryID: Int) : ContextWrapper(context) { class Cache private constructor(context: Context, val galleryID: Int) : ContextWrapper(context) {
companion object { companion object {
private val mutex = Mutex()
private val instances = SparseArray<Cache>() private val instances = SparseArray<Cache>()
fun getInstance(context: Context, galleryID: Int) = 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) } instances[galleryID] ?: Cache(context, galleryID).also { instances.put(galleryID, it) }
} } }
fun delete(galleryID: Int) { runBlocking { mutex.withLock { fun delete(galleryID: Int) {
instances[galleryID]?.galleryFolder?.deleteRecursively() instances[galleryID]?.cacheFolder?.deleteRecursively()
instances.delete(galleryID) instances.delete(galleryID)
} } } }
} }
init { init {
galleryFolder.mkdirs() cacheFolder.mkdirs()
} }
private val mutex = Mutex()
var metadata = kotlin.runCatching { var metadata = kotlin.runCatching {
findFile(".metadata")?.readText()?.let { findFile(".metadata")?.readText()?.let {
Json.decodeFromString<Metadata>(it) Json.decodeFromString<Metadata>(it)
@@ -82,11 +81,10 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
get() = DownloadFolderManager.getInstance(this).getDownloadFolder(galleryID) get() = DownloadFolderManager.getInstance(this).getDownloadFolder(galleryID)
val cacheFolder: FileX val cacheFolder: FileX
get() = FileX(this, cacheDir, "imageCache/$galleryID") get() = FileX(this, cacheDir, "imageCache/$galleryID").also {
if (!it.exists())
val galleryFolder: FileX it.mkdirs()
get() = DownloadFolderManager.getInstance(this).getDownloadFolder(galleryID) }
?: FileX(this, cacheDir, "imageCache/$galleryID")
fun findFile(fileName: String): FileX? = fun findFile(fileName: String): FileX? =
cacheFolder.getChild(fileName).let { cacheFolder.getChild(fileName).let {
@@ -96,18 +94,19 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
} } } }
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
suspend fun setMetadata(change: (Metadata) -> Unit) { mutex.withLock { fun setMetadata(change: (Metadata) -> Unit) {
change.invoke(metadata) 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.createNewFile()
file.writeText(Json.encodeToString(metadata))
} }
file.writeText(Json.encodeToString(metadata))
} }
} } }
suspend fun getGalleryBlock(): GalleryBlock? { suspend fun getGalleryBlock(): GalleryBlock? {
val sources = listOf( val sources = listOf(
@@ -129,7 +128,7 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
} }
galleryBlock?.also { 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 { kotlin.runCatching {
client.newCall(request).execute().body()?.use { it.bytes() } client.newCall(request).execute().body()?.use { it.bytes() }
}.getOrNull()?.also { kotlin.run { }.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 { reader?.also {
launch { setMetadata { metadata -> setMetadata { metadata ->
metadata.reader = it metadata.reader = it
if (metadata.imageList == null) if (metadata.imageList == null)
metadata.imageList = MutableList(reader.galleryInfo.files.size) { 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) } metadata.imageList?.get(index)?.let { findFile(it) }
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
suspend fun putImage(index: Int, fileName: String, data: ByteArray) { fun putImage(index: Int, fileName: String, data: ByteArray) {
val file = galleryFolder.getChild(fileName) val file = cacheFolder.getChild(fileName)
file.createNewFile() file.createNewFile()
file.writeBytes(data) file.writeBytes(data)
@@ -202,14 +201,12 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
fun moveToDownload() = CoroutineScope(Dispatchers.IO).launch { fun moveToDownload() = CoroutineScope(Dispatchers.IO).launch {
if (downloadFolder == null) val downloadFolder = downloadFolder ?: return@launch
DownloadFolderManager.getInstance(this@Cache).addDownloadFolder(galleryID, this@Cache.formatDownloadFolder()) Log.i("PUPILD", "MOVING $galleryID")
metadata.imageList?.forEach { imageName -> metadata.imageList?.forEach { imageName ->
imageName ?: return@forEach imageName ?: return@forEach
val target = downloadFolder.getChild(imageName)
Log.i("PUPIL", downloadFolder?.uri.toString())
val target = downloadFolder!!.getChild(imageName)
val source = cacheFolder.getChild(imageName) val source = cacheFolder.getChild(imageName)
if (!source.exists()) 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 cacheMetadata = cacheFolder.getChild(".metadata")
val downloadMetadata = downloadFolder!!.getChild(".metadata") val downloadMetadata = downloadFolder.getChild(".metadata")
if (cacheMetadata.exists()) { if (cacheMetadata.exists()) {
kotlin.runCatching { kotlin.runCatching {
downloadMetadata.createNewFile() downloadMetadata.createNewFile()
cacheMetadata.readBytes()?.let { downloadMetadata.writeBytes(it) } downloadMetadata.writeText(Json.encodeToString(metadata))
cacheMetadata.delete() cacheMetadata.delete()
} }
} }

View File

@@ -20,23 +20,17 @@ package xyz.quaver.pupil.util.downloader
import android.content.Context import android.content.Context
import android.content.ContextWrapper 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.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Call import okhttp3.Call
import xyz.quaver.io.FileX import xyz.quaver.io.FileX
import xyz.quaver.io.util.getChild import xyz.quaver.io.util.*
import xyz.quaver.io.util.readText
import xyz.quaver.pupil.client import xyz.quaver.pupil.client
import xyz.quaver.pupil.services.DownloadService import xyz.quaver.pupil.services.DownloadService
import xyz.quaver.pupil.util.Preferences import xyz.quaver.pupil.util.Preferences
import xyz.quaver.pupil.util.formatDownloadFolder
class DownloadFolderManager private constructor(context: Context) : ContextWrapper(context) { class DownloadFolderManager private constructor(context: Context) : ContextWrapper(context) {
@@ -61,15 +55,24 @@ class DownloadFolderManager private constructor(context: Context) : ContextWrapp
} }
}.invoke() }.invoke()
private val downloadFolderMapMutex = Mutex() val downloadFolderMap: MutableMap<Int, String> = {
private val downloadFolderMap: MutableMap<Int, String> = runBlocking { downloadFolderMapMutex.withLock { val file = downloadFolder.getChild(".download")
kotlin.runCatching {
downloadFolder.getChild(".download").readText()?.let {
Json.decodeFromString<MutableMap<Int, String>>(it)
}
}.getOrNull() ?: mutableMapOf()
} }
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 { fun isDownloading(galleryID: Int): Boolean {
val isThisGallery: (Call) -> Boolean = { (it.request().tag() as? DownloadService.Tag)?.galleryID == galleryID } 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) } && 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) } downloadFolderMap[galleryID]?.let { downloadFolder.getChild(it) }
} }
fun addDownloadFolder(galleryID: Int, name: String) { runBlocking { downloadFolderMapMutex.withLock { @Synchronized
fun addDownloadFolder(galleryID: Int) {
if (downloadFolderMap.containsKey(galleryID)) if (downloadFolderMap.containsKey(galleryID))
return@withLock return
val name = runBlocking {
Cache.getInstance(this@DownloadFolderManager, galleryID).getGalleryBlock()
}?.formatDownloadFolder() ?: return
val folder = downloadFolder.getChild(name) val folder = downloadFolder.getChild(name)
@@ -92,29 +100,21 @@ class DownloadFolderManager private constructor(context: Context) : ContextWrapp
downloadFolderMap[galleryID] = name downloadFolderMap[galleryID] = name
CoroutineScope(Dispatchers.IO).launch { downloadFolderMapMutex.withLock { downloadFolder.getChild(".download").writeText(Json.encodeToString(downloadFolderMap))
downloadFolder.getChild(".download").let { }
it.createNewFile()
it.writeText(Json.encodeToString(downloadFolderMap))
}
} }
} } }
fun deleteDownloadFolder(galleryID: Int) { runBlocking { downloadFolderMapMutex.withLock { @Synchronized
fun deleteDownloadFolder(galleryID: Int) {
if (!downloadFolderMap.containsKey(galleryID)) if (!downloadFolderMap.containsKey(galleryID))
return@withLock return
downloadFolderMap[galleryID]?.let { downloadFolderMap[galleryID]?.let {
if (downloadFolder.getChild(it).delete()) { kotlin.runCatching {
downloadFolder.getChild(it).delete()
downloadFolderMap.remove(galleryID) downloadFolderMap.remove(galleryID)
CoroutineScope(Dispatchers.IO).launch { downloadFolderMapMutex.withLock { downloadFolder.getChild(".download").writeText(Json.encodeToString(downloadFolderMap))
downloadFolder.getChild(".download").let {
it.createNewFile()
it.writeText(Json.encodeToString(downloadFolderMap))
}
} }
} }
} }
} } } }
} }

View File

@@ -30,6 +30,7 @@ import kotlinx.coroutines.sync.withLock
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import xyz.quaver.Code import xyz.quaver.Code
import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.Reader import xyz.quaver.hitomi.Reader
import xyz.quaver.hitomi.getReferer import xyz.quaver.hitomi.getReferer
import xyz.quaver.hitomi.imageUrlFromImage import xyz.quaver.hitomi.imageUrlFromImage
@@ -87,15 +88,15 @@ fun OkHttpClient.Builder.proxyInfo(proxyInfo: ProxyInfo) = this.apply {
} }
} }
val formatMap = mapOf<String, (Cache) -> (String)>( val formatMap = mapOf<String, GalleryBlock.() -> (String)>(
"-id-" to { runBlocking { it.getGalleryBlock()?.id.toString() } }, "-id-" to { id.toString() },
"-title-" to { runBlocking { it.getGalleryBlock()?.title.toString() } }, "-title-" to { title },
// TODO // TODO
) )
/** /**
* Formats download folder name with given Metadata * Formats download folder name with given Metadata
*/ */
fun Cache.formatDownloadFolder(): String = fun GalleryBlock.formatDownloadFolder(): String =
Preferences["download_folder_format", "-id-"].let { Preferences["download_folder_format", "-id-"].let {
formatMap.entries.fold(it) { str, (k, v) -> formatMap.entries.fold(it) { str, (k, v) ->
str.replace(k, v.invoke(this), true) 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

View 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>

View 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>

View File

@@ -22,7 +22,6 @@ allprojects {
repositories { repositories {
google() google()
jcenter() jcenter()
mavenLocal()
maven { url "https://jitpack.io" } maven { url "https://jitpack.io" }
maven { url 'https://guardian.github.com/maven/repo-releases' } maven { url 'https://guardian.github.com/maven/repo-releases' }
} }